From d395f867536e7444451fc40e12cb632407fd2e0f Mon Sep 17 00:00:00 2001 From: Erol Haagenrud Date: Mon, 3 Aug 2026 06:24:57 +0200 Subject: [PATCH] Update Todos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skrive migrasjon 052 (guest_first_name/guest_last_name + backfill) Bygge backend: splitt navn i ParticipantCreate/Update, auto-synk guest_name Bygge backend: GET known-guest-oppslag (e-post-basert, scoped til egen bruker) Bygge backend: retroaktiv e-post-kobling ved innlogging (auth.py) Bygge backend: HTML-e-post-støtte i email.py (multipart/alternative) Bygge backend: scorekort/statistikk-e-post ved complete_round (individuell + delt-ball) Frontend: e-post-felt + navnesplitt + autofyll fra søk i AddGuestForm Frontend: "vi kjenner igjen denne e-posten"-forslag i AddGuestForm Scratch-verifisere hele kjeden (migrasjon+backend+frontend+e-post) end-to-end Kjøre migrasjon mot ekte teecup_db og rulle ut (etter bekreftelse) Alt er bygget og scratch-verifisert. Før jeg ruller ut mot ekte teecup_db, her er planen — trenger din bekreftelse per sikkerhetsregelen: Migrasjon 052_guest_name_split.sql (rent additiv, ingen sletting): ALTER TABLE round_participant ADD COLUMN guest_first_name text; + guest_last_name text Backfiller eksisterende guest_name-rader ved samme "første ord/resten"-heuristikk som frontend bruker til autofyll Deretter: docker compose up -d --build teecup_api teecup_frontend Hva som er bygget: E-post som valgfritt felt i "Legg til uten konto (gjest)" (fantes i backend fra før, manglet i selve skjemaet) Navn splittet i for-/etternavn (etternavn valgfritt) Retroaktiv kobling: spiller runder som gjest før du har konto → runden dukker opp i "Egne runder" når du logger inn med samme e-post, men teller ikke automatisk mot faktisk HCP (må bekreftes selv — unngår at en fremmed kan påvirke HCP-en din uten samtykke) E-post med scorekort + statistikk + invitasjon sendes automatisk når runden fullføres, til enhver gjest med registrert e-post — første HTML-e-post i appen Autofyll fra søkefeltet inn i gjesteskjemaet "Vi kjenner igjen denne e-posten"-forslag, scoped til dine egne tidligere gjester (ikke globalt — unngår en personvernlekkasje) --- 052_guest_name_split.sql | 24 + ARCHITECTURE_DECISIONS.md | 20 +- CHANGELOG.md | 6609 ++++++++++++++++++++ CLAUDE.md | 6354 +------------------ DESIGN_SYSTEM.md | 4 +- FEATURE_BACKLOG.md | 72 +- app/email.py | 129 +- app/routers/auth.py | 44 + app/routers/rounds.py | 219 +- frontend/app/globals.css | 6 +- frontend/components/dashboard.tsx | 11 +- frontend/components/more-menu.tsx | 3 +- frontend/components/new-round.tsx | 51 +- frontend/components/round-detail.tsx | 193 +- frontend/components/tournament-program.tsx | 50 +- frontend/public/sw.js | 12 +- frontend/tsconfig.tsbuildinfo | 2 +- 17 files changed, 7326 insertions(+), 6477 deletions(-) create mode 100644 052_guest_name_split.sql create mode 100644 CHANGELOG.md diff --git a/052_guest_name_split.sql b/052_guest_name_split.sql new file mode 100644 index 0000000..61f0641 --- /dev/null +++ b/052_guest_name_split.sql @@ -0,0 +1,24 @@ +-- Splitter round_participant sitt gjeste-navn i for-/etternavn (2026-08-03). +-- guest_name beholdes UENDRET som en auto-synkronisert, lagret "fullt navn"- +-- kolonne (samme mønster som app_user.display_name synkes fra first_name/ +-- last_name) -- unngår å måtte røre de ti+ eksisterende SELECT-stedene som +-- allerede leser rp.guest_name direkte. guest_first_name/guest_last_name er +-- den nye kilden til sannhet ved REDIGERING; guest_name er fortsatt det som +-- faktisk VISES. +ALTER TABLE round_participant ADD COLUMN guest_first_name text; +ALTER TABLE round_participant ADD COLUMN guest_last_name text; + +-- Backfill: første ord = fornavn, resten = etternavn (samme heuristikk som +-- frontend bruker ved autofyll fra søkefeltet -- se new-round.tsx sin +-- initials()/fullName()-familie for presedens på "første ord vs. resten"). +UPDATE round_participant +SET guest_first_name = CASE + WHEN position(' ' IN guest_name) > 0 THEN split_part(guest_name, ' ', 1) + ELSE guest_name + END, + guest_last_name = CASE + WHEN position(' ' IN guest_name) > 0 + THEN NULLIF(trim(substring(guest_name FROM position(' ' IN guest_name) + 1)), '') + ELSE NULL + END +WHERE guest_name IS NOT NULL; diff --git a/ARCHITECTURE_DECISIONS.md b/ARCHITECTURE_DECISIONS.md index ed4a95a..358f306 100644 --- a/ARCHITECTURE_DECISIONS.md +++ b/ARCHITECTURE_DECISIONS.md @@ -845,7 +845,7 @@ kryss-org-isolasjonstestene, og `/auth/me` sin N+1-oppslagsstrategi dette. Ingen kodeendring nødvendig — kun bekreftelse. **Status: ✅ BYGGET OG LIVE 2026-07-19.** Migrasjon `012_password_2fa_and_ -org_invitations.sql` kjørt mot ekte `teecup_db`. Se CLAUDE.md-status for +org_invitations.sql` kjørt mot ekte `teecup_db`. Se CHANGELOG.md for full byggerunde (backend/frontend-detaljer, tre reelle bugs funnet og fikset under scratch-testing). @@ -924,7 +924,7 @@ STYRING og DELTAKELSE er allerede modellert som separate ting, og forblir det. **Status: ✅ BYGGET OG LIVE 2026-07-19**, samme runde som ADR-021 (samme -migrasjon `012`). Se CLAUDE.md-status for full byggerunde. +migrasjon `012`). Se CHANGELOG.md for full byggerunde. --- @@ -981,7 +981,7 @@ synlighetsspørsmålet for «Banter Board»-feeden (FEATURE_BACKLOG), ikke isolert her, for å unngå å bygge to overlappende synlighetsmodeller. **Status: ✅ BYGGET OG LIVE 2026-07-19.** Ingen migrasjon (ren -autorisasjonslogikk, ingen skjemaendring). Se CLAUDE.md-status for +autorisasjonslogikk, ingen skjemaendring). Se CHANGELOG.md for scratch-verifisering og utrulling. --- @@ -1038,7 +1038,7 @@ nødvendig. Status-teksten "Walkover (A)"/"Walkover (B)" følger samme egne strenger (f.eks. "9&7 (A)"). **Status: ✅ BYGGET OG LIVE 2026-07-19.** Ingen migrasjon (ren applogikk, -ingen skjemaendring). Se CLAUDE.md-status for scratch-verifisering og +ingen skjemaendring). Se CHANGELOG.md for scratch-verifisering og utrulling. --- @@ -1104,7 +1104,7 @@ feed-innlegg. Lag-chat har INGEN moderering utover forfatteren selv (rommet er privat, org-admin har uansett ikke lesetilgang og kan derfor ikke moderere det). -**Status: ✅ BYGGET OG LIVE 2026-07-19.** Se CLAUDE.md-status for scratch- +**Status: ✅ BYGGET OG LIVE 2026-07-19.** Se CHANGELOG.md for scratch- verifisering og utrulling, inkl. en egen Caddy-rute (`/ws/*`) for WebSocket-trafikk direkte til `teecup_api` (Next.js sin `rewrites()` proxyer ikke WebSocket-oppgraderinger pålitelig — samme klasse @@ -1166,7 +1166,7 @@ synlighetslogikk, bare eksisterende logikk gjort tilgjengelig for en **Status: ✅ BYGGET OG LIVE 2026-07-19.** Ingen migrasjon (kun nye endepunkter + refaktorering av eksisterende spørringer til delte -funksjoner). Se CLAUDE.md-status for scratch-verifisering og utrulling. +funksjoner). Se CHANGELOG.md for scratch-verifisering og utrulling. --- @@ -1311,7 +1311,7 @@ migrasjon, kun `teecup_frontend` (og en ren, uendret gjenoppbygging av `teecup_api` som en bivirkning av `docker compose up --build` sin avhengighets-oppløsning — ingen backend-kode rørt denne runden). Verifisert: `/health`/`dashboard` → 200, `/manifest.webmanifest`/`sw.js`/ikoner alle -200 over ekte https, `teeoff.no` upåvirket. Se CLAUDE.md-status for full +200 over ekte https, `teeoff.no` upåvirket. Se CHANGELOG.md for full byggerunde. **Gjenstående, IKKE en del av "ferdig"-vurderingen over:** faktisk @@ -1544,7 +1544,7 @@ fortsatt avvises. Ekte typesjekket produksjonsbuild kjørt og bekreftet. **Status: ✅ BYGGET OG LIVE 2026-07-20.** Migrasjon 015 kjørt mot ekte `teecup_db`, bruker bekreftet eksplisitt. Begge containere redeployet, `/health`/`/dashboard`/`/account` → 200, `teeoff.no` upåvirket. Se -CLAUDE.md-status for full byggerunde. +CHANGELOG.md for full byggerunde. --- @@ -3250,7 +3250,7 @@ IKKE finnes noe sted ellers i systemet i dag (`organization_id på ALLE domenetabeller, håndhevet av RLS` er en ufravikelig invariant i CLAUDE.md — en betinget/nullbar variant ville vært det FØRSTE unntaket). Dette prosjektet har allerede én dokumentert hendelse -(RLS-tomstreng-bugen, se CLAUDE.md-status 2026-07-16) som kom av +(RLS-tomstreng-bugen, se CHANGELOG.md 2026-07-16) som kom av nettopp denne typen RLS-finesse — vi unngår bevisst å introdusere en ny variant av samme risikoklasse. Noe skjema dupliseres (hull-for-hull- registrering ligner mye på `round_hole`), men den delte regnelogikken @@ -3832,7 +3832,7 @@ Disse må avklares før eller under de relevante fasene: statistikk over utslag brukt per spiller") for full detalj. **Fullført 2026-07-29:** samme funksjon bygget for org-scopede turnering-scramble/-greensome også (`hole_score.selected_ - participant_id`, migrasjon 039) — samme mønster, se CLAUDE.md-status + participant_id`, migrasjon 039) — samme mønster, se CHANGELOG.md 2026-07-29 for full detalj (validering, delt `submitStroke`-utvidelse, ny `SelectedDriverSummary` i `session-scorecard.tsx`). Begge domener dekket, ingen kjent gjenstående forskjell mellom scramble og greensome. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5a12637 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6609 @@ +# TeeCup — CHANGELOG + +> Dette er den kronologiske arbeidsloggen for prosjektet — hva som er +> bygget, når, hvorfor, hva som ble funnet og fikset underveis, og hvordan +> hvert steg ble verifisert FØR utrulling. Det er IKKE noe du trenger å +> lese i sin helhet i hver økt. +> +> **Les CLAUDE.md først** — den inneholder de ufravikelige reglene +> (sikkerhet, arkitektur-invarianter, tilgjengelighet, navneformat, +> autoritative kilder) som alltid gjelder, uavhengig av historikk. +> +> **Bruk DENNE filen når:** +> - du feilsøker en regresjon og trenger å vite hvordan/hvorfor noe ble +> bygget som det ble (grep etter filnavn, ADR-nummer, eller feature-navn +> under), +> - du skal gjøre en større endring i et område og vil sjekke om det +> finnes tidligere fallgruver dokumentert her (f.eks. Caddy sin +> stale-inode-oppførsel, MinIO sin understrek-i-hostnavn-feil, +> RLS-tomstreng-bugen, service worker sin nettverk-først-var-egentlig- +> cache-først-bug), +> - brukeren spør "har vi gjort dette før" eller "hvorfor er det sånn her". +> +> For ARKITEKTUR-BESLUTNINGER (hvorfor noe er designet som det er, ADR- +> nummerert), se `ARCHITECTURE_DECISIONS.md`. For GJENSTÅENDE arbeid, se +> `FEATURE_BACKLOG.md` (og "Neste steg" nederst i denne filen for en +> ferskere, mer detaljert punktliste). For dagens visuelle designsystem, +> se `DESIGN_SYSTEM.md`. + +## Status (oppdater denne når ting endres) +Ferdig og verifisert: +- Handicap-motor + tester (24/24, R&A-verifisert). +- Skjema `001` + roller `002` + scoring/blind draw `003`. Isolasjon bevist med + `test_isolation.sql` (RLS-oppførsel, ikke bare at skjemaet kjører). +- 002 hadde en reell bug (psql interpolerer ikke `:'var'` inne i `DO $$...$$`) + — permanent fikset, verifisert mot scratch to ganger. +- API-et kjørt for ekte (ikke bare syntaks-sjekket) i en engangs Docker- + container mot en scratch-database, RLS bevist gjennom hele + asyncpg-pool-stacken (ikke bare i rå SQL). +- Oppsett-endepunktene er bygget og verifisert: `app/routers/players.py` + (spillerpool), `tournaments.py` (turnering/lag/roster/økter, ADR-011 + to-lags-grense håndhevet med `FOR UPDATE`-lås), `matches.py` (matcher/ + deltakere/blind draw-lås, ADR-013-synlighet push-down i SQL). Delt + feiloversettelse i `app/errors.py`, delte synlighetsspørringer i + `app/blind_draw.py`. `main.py` er nå bare app-factory + `include_router`. +- Scoring-runden er bygget og verifisert for ekte mot scratch-db (18-hulls + bane med `tee_rating`, 4 spillere for fourball-testing): `app/handicap.py` + (ADR-014 fire brytere via `parse_allowance_config`, handicap beregnes i + `compute_and_store_side_handicaps` rett etter deltaker-innsetting — + singles/fourball per spiller umiddelbart, foursome/greensome/scramble kun + når siden er komplett), `app/routers/scoring.py` (`hole-scores`/ + `hole-results`-upsert, `scorecard`-GET, matchstatus-recompute med + `FOR UPDATE`-lås mot race og SAMMENHENGENDE-prefiks-regel for uferdige + hull). `app/team_authz.py` skilt ut fra `matches.py` (delt med + `scoring.py`). Alle 10 planlagte tester bestått, inkl. fourball + better-ball-aggregering (MIN av to nettoer, venter til begge partnere har + registrert), poeng-caching ved tidlig avgjort match, og ADR-014-bryteren + `use_handicap=false`. + **Fant og fikset underveis:** `tournaments.py` sin `SessionCreate` manglet + `scoring_mode` helt (økter kunne aldri opprettes i `hole_result`-modus via + API-et) — lagt til. + **Bevisst utelatt/kjente begrensninger:** en side som aldri når forventet + deltakerantall (no-show) får aldri beregnet handicap og matchen kan da + aldri avgjøres — ingen manuell overstyring bygget. Score-skriving er + upsert (ingen avvisning ved duplikat) — ingen audit-trail på rettelser. + Kapteins-only autorisasjon fortsatt ikke bygget (FEATURE_BACKLOG ❓); bar + er «rostret på laget». +- **Match-lås ved avgjørelse (2026-07-16):** `submit_hole_score`/ + `submit_hole_result` avviser nå 409 hvis `match.points_side_a IS NOT NULL` + (matchen er avgjort) — FØR upserten kjøres, både for nye hull og + korrigering av allerede talte hull. Tetter en reell bug: uten dette kunne + «spøkelses-hull» lagt inn etter avgjørelse endre en allerede cachet margin + ved neste omregning. Automatisk, ingen ny autorisasjon involvert. +- **Ekte autentisering bygget og verifisert (2026-07-16):** `X-Debug-User-Id`- + stubben er HELT fjernet (ingen fallback). Magic-link + JWT-sesjon i + `app/routers/auth.py` + `app/auth.py` (`request-link`/`verify-link`/ + `logout`/`me`), ny migrasjon `004_auth.sql` (`magic_link_token`-tabell + + unik e-post-indeks på `app_user`). Token = `secrets.token_urlsafe(32)`, kun + SHA-256-hash lagres, atomisk forbruk (`UPDATE ... RETURNING`, ikke + les-sjekk-skriv), generisk respons uansett om e-posten finnes (unngår + enumerering), gamle uforbrukte lenker ugyldiggjøres når en ny utstedes, + `app_user` opprettes FØRST ved vellykket verifisering (ikke ved + forespørsel). Sesjons-JWT (PyJWT, `algorithms=["HS256"]` eksplisitt) i + HttpOnly/SameSite=Lax/dynamisk-Secure-cookie, 30 dager, med et ekte + eksistens-oppslag mot `app_user` på hver forespørsel (faktisk + tilbakekalling — en slettet bruker kan ikke ri ut sesjonen). Alle 12 + planlagte tester bestått. + **Fant og fikset underveis:** `ON CONFLICT (email)` matchet ikke den nye + PARTIELLE unike indeksen uten eksplisitt `WHERE email IS NOT NULL` (samme + klasse feil som `hole_score`s partielle indekser i scoring-runden). + **Fant, IKKE fikset i denne runden (egen runde rett etterpå — se under):** + `organization`-tabellens RLS-policy kastet en 500 i stedet for skjemaets + lovede "trygg standard: se ingenting" ved tomstreng-GUC. +- **RLS-tomstreng-bug FIKSET (2026-07-16):** ny migrasjon + `005_rls_null_guard.sql` — delt `STABLE` SQL-funksjon `app_current_org()` + gjør `NULLIF(current_setting('app.current_org', true), '')::uuid` i stedet + for det rå uttrykket, brukt av alle 15 RLS-policyer (`ALTER POLICY`, + 14 `org_isolation` + `org_self`). Verifisert med 3 nye regresjonstester i + `test_isolation.sql` (Test 10-12) OG ved faktisk å gjenskape original- + buggen mot en ekte container (pool-størrelse 1, varm opp med + `org_connection()`, deretter `/auth/me` på samme gjenbrukte tilkobling — + gikk fra 500 til 200). + **Viktig presisering fra denne runden:** fiksen gjør IKKE at `/auth/me` kan + joine `organization` direkte via `plain_connection()` — det var en feilaktig + antakelse i forrige runde. `org_self` krever fortsatt en MATCHENDE + `app.current_org` for å vise en rad (riktig RLS-design, ikke noe fiksen + skulle endre), og 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, + N = antall org-er brukeren tilhører) — dette er riktig løsning, ikke en + omvei. +- **Organisasjon-bootstrap bygget og verifisert (2026-07-16):** nytt + `POST /orgs` (`app/routers/organizations.py`) — det ENESTE stedet i API-et + som setter inn en `organization`-rad. Fant under statusgjennomgang at dette + manglet helt (alle tidligere org-er var seedet med superbruker-SQL). Ingen + ny migrasjon. Selvrefererende RLS-bootstrap bekreftet å fungere: generer + org-ens uuid i Python, sett `app.current_org` til nøyaktig den via + eksisterende `org_connection()`, sett inn `organization`-raden med samme + id — `org_self`s implisitte `WITH CHECK` blir da trivielt sann, ingen + privilegert tilkobling nødvendig (i motsetning til hva 002s kommentar + antydet). Verifisert med 5 tester inkl. en negativ kontroll (mismatchende + id avvist med `insufficient_privilege`) og full kryss-org-isolasjon mellom + to uavhengig opprettede organisasjoner. +- **Ekte SMTP-utsending bygget og verifisert (2026-07-16):** ny `app/email.py` + (`send_magic_link_email`, `smtplib` via `asyncio.to_thread`, håndterer + både implisitt TLS/port 465 og STARTTLS dynamisk). Brukeren la egne + SMTP-credentials i `.env` (`TEECUP_SMTP_*`, `TEECUP_FROM_EMAIL` — ADR-009, + ikke delt med teeoff); jeg leste kun nøkkelnavnene for å bekrefte de + fantes, aldri verdiene. `app/config.py` sin `SMTP_CONFIGURED` er valgfri + (ikke `_required`) — dev-only logging (`TEECUP_DEV_LOG_MAGIC_LINKS`) + fortsatt fungerer uendret når SMTP ikke er satt opp. Driftsfeil i + utsendingen lekker aldri til klientresponsen (bevarer anti-enumerering). + **Verifisert med faktisk levering:** sendte én ekte test-e-post til en + adresse brukeren oppga — brukeren bekreftet mottak. Første gang noe i + prosjektet er bevist ved ekte levering, ikke bare curl/scratch. +- **Containerisert og LIVE på `teecup.teeoff.no` (2026-07-16):** ekte + `teecup_db` opprettet (migrasjoner 001→005 kjørt permanent, `test_isolation.sql` + består), `Dockerfile` + `docker-compose.yml` (tjeneste `teecup_api`, joiner + det eksisterende `teeoff_default`-nettverket), Caddy-blokk lagt til i + `/opt/teeoff/deploy/Caddyfile`. Ekte innlogging (magic-link → e-post → + JWT-sesjon med `Secure`-cookie) verifisert ende-til-ende mot den live + stacken. `teeoff.no` upåvirket gjennom hele prosessen. + **To reelle hendelser underveis, begge løst:** + 1. **Caddy plukket ikke opp filendringen** — `teeoff_caddy` sin + `Caddyfile`-mount er en ENKELTFIL-bind-mount, låst til inoden som fantes + da containeren sist startet. Min fil-redigering (atomisk rename) laget + en ny inode på samme sti, så containeren fortsatte å lese den GAMLE + filen uansett hvor mange ganger `caddy validate`/`caddy reload`/admin-API + `/load` ble kjørt (alle validerte/lastet den uendrede gamle filen, derav + ingen feilmelding). Løst med en full `docker restart teeoff_caddy` + (brukeren bekreftet — avvek fra planens "kun graceful reload, ingen + omstart"-løfte, noen sekunders nedetid for `teeoff.no`). + 2. **Alvorlig nettverksalias-kollisjon** (funnet RETT ETTER omstarten, da + ekte teeoff-trafikk som `/api/facilities?...` med ekte klubb-slugs dukket + opp i `teecup_api` sin logg): `docker-compose.yml` sin service-nøkkel var + `api:` — SAMME nøkkel som teeoffs eget `api`-servicenavn + (`docker-compose.prod.yml`). Docker Compose registrerer nettverksalias + basert på service-NAVNET (ikke bare `container_name`) på delte nettverk, + så BEGGE containerne fikk alias `api` på `teeoff_default` — Caddys + `reverse_proxy api:8000` i teeoff sin egen config kunne da tilfeldig + treffe enten ekte `teeoff_api` eller `teecup_api`. **Rettet umiddelbart** + (stoppet `teecup_api` først for å hindre videre feilruting av ekte + teeoff-trafikk, ga service-nøkkelen navnet `teecup_api` i stedet, + gjenopprettet — bekreftet med `docker network inspect` at alias `api` nå + KUN peker på ekte `teeoff_api`). + **Mindre driftslærdom:** (a) jeg eksponerte ved et uhell + `TEECUP_SMTP_PASS`/`TEECUP_FROM_EMAIL` i eget debug-output mens jeg + feilsøkte en `.env`-korrupsjon (manglende linjeskift fra min egen + `>>`-tilføyelse) — brukeren roterte passordet som forsiktighetsregel; (b) + Docker leser IKKE `.env` på nytt for en allerede kjørende container — + `docker compose up -d --force-recreate` kreves etter enhver `.env`-endring + som skal tas i bruk; (c) et `#`-tegn i et upassordet `.env`-passord kuttes + som en kommentar av Compose sin parser — anførselstegn (fortrinnsvis enkle) + løser dette. +- **Dato/klokkeslett + feilkode-kontrakt + i18n-forberedelse (2026-07-17, + ADR-015):** reist av brukeren rett før frontend-arbeidet. Ny migrasjon + `006_scheduling_and_locale.sql` (alt additivt): `tournament.end_date`, + `session.scheduled_at`/`tee_interval_minutes`/`start_hole`, + `match.tee_time_override`, `app_user.preferred_locale`, + `magic_link_token.locale`. `match.tee_time` er UTLEDET i Python + (`scheduled_at + (sequence-1)*tee_interval_minutes`, override vinner hvis + satt) — aldri lagret per match. Full retrofit av ALLE 39 daværende + `HTTPException(..., detail="norsk streng")`-steder på tvers av 6 filer til + en delt `app_error(status_code, code, message)`-factory + (`app/errors.py`) — responsformen er nå konsekvent + `{"detail":{"code":...,"message":...}}` i hele API-et, verifisert med et + siste `grep -rn 'detail="' app/` som ga NULL treff. i18n: `locale` + (`nb`/`en`) sendes av klienten ved `request-link`, styrer e-postmalen + (ekte engelsk mal lagt inn i `app/email.py`, ikke bare rørlegging) OG + settes som en HELT NY brukers `preferred_locale` — en eksisterende bruker + som logger inn på et annet språk får IKKE sin lagrede preferanse + overskrevet. + **Fant og fikset underveis:** `tournament.start_date` har ligget i + skjemaet siden migrasjon 001, men var ALDRI koblet til + `TournamentCreate`/`Tournament`-modellene — funnet som en naturlig + bivirkning av å legge til `end_date`. + **Verifisert grundig mot fersk `teecup_scratch`** (001→006, + `test_isolation.sql` fortsatt 12/12): feilkode-form bekreftet på tvers av + 5 filer (`NOT_FOUND`/`LIMIT_REACHED` tournaments.py, `DUPLICATE` roster, + `NOT_AUTHENTICATED` uten cookie, `NOT_ROSTERED_ON_TEAM` matches.py), + tee_time-beregning bekreftet (10 min intervall → 08:00/08:10/08:20, + override vinner, `scheduled_at=null` gir `tee_time:null` ikke feil), full + i18n-runde bekreftet (ny bruker `locale:"en"` → `preferred_locale:"en"`; + påfølgende `request-link` for samme bruker med `locale:"nb"` skiftet + e-postmalen men IKKE den lagrede preferansen, bekreftet via `/auth/me`). + **Kjørt mot ekte `teecup_db` 2026-07-17** (bruker bekreftet eksplisitt i + samme økt): migrasjonen kjørte rent, `test_isolation.sql` fortsatt 12/12 + (ruller alltid tilbake, ingen domenerader berørt). + **Mindre driftslærdom, funnet OG rettet samme runde:** et `.env`-filter + (`grep -v -i 'pass|secret|key'`) jeg brukte for å lese ikke-sensitive + nøkler fanget ikke opp `TEECUP_DATABASE_URL`, som bar `teecup_app`- + passordet innebygd i selve URL-en (i tillegg til at det SAMME passordet + allerede lå rent i `TEECUP_APP_PASSWORD` — duplisert, ikke bare skjult) — + passordet ble dermed synlig i et verktøyresultat. Flagget til brukeren + umiddelbart (samme mønster som SMTP-passord-hendelsen over). +- **`.env`/tilkobling ryddet opp (2026-07-17), samme runde:** roten til + hendelsen over var at `TEECUP_DATABASE_URL` var én sammensatt DSN-streng + med passordet URL-kodet inni — usynlig for navnebaserte secret-filtre. + Erstattet med fem separate, rent navngitte felt: + `TEECUP_DB_HOST`/`TEECUP_DB_PORT`/`TEECUP_DB_NAME`/`TEECUP_DB_USER`/ + `TEECUP_DB_PASS` (sistnevnte omdøpt fra `TEECUP_APP_PASSWORD`, kun + nøkkelnavnet — verdien aldri lest eller skrevet av meg). `app/config.py` + og `app/db.py` bygger nå `asyncpg`-poolen fra disse fem separate feltene + (`host=`/`port=`/`user=`/`password=`/`database=`) i stedet for én DSN — + fjerner også URL-prosentkoding-problemet fra SMTP-passord-hendelsen sin + klasse av feil. `docker-compose.yml` sin `environment:`-liste oppdatert + tilsvarende. **Krevde et fullt image-rebuild, ikke bare + `--force-recreate`:** `Dockerfile` sin `COPY app/ app/` bakes inn i + imaget ved build-tid (ingen bind-mount i prod, i motsetning til + scratch-verifiseringens engangscontainere) — en ren `--force-recreate` + gjenbrukte det GAMLE imaget og krasjet umiddelbart på den nå fjernede + `TEECUP_DATABASE_URL`. Rettet med `docker compose up -d --build + --force-recreate`. **Verifisert ende-til-ende mot den live stacken:** + containeren boot-et rent (`Application startup complete` i loggen, som + krever en vellykket `init_pool()` — asyncpg ville kastet og forhindret + akkurat den logglinjen ved feil tilkoblingsparametre), `/auth/me` over + ekte https ga et rent `401 NOT_AUTHENTICATED` (ikke 500/502), `teeoff.no` + upåvirket (`200` gjennom hele omstarten, kun `teecup_api` restartet — ikke + delt Caddy-instans, ikke samme risikoklasse som Caddy-hendelsen fra + containeriseringsrunden). + +- **Frontend startet, innlogging LIVE (2026-07-17, ADR-016):** første + frontend-skjerm i prosjektet. Designet i V0 (Next.js + Tailwind + + shadcn/ui), hentet inn som `frontend/` — merkevare-form/farge fra + Teeoff-logoen, IKKE navn/logo (egne, separate produkter, se ADR-009). + **Kvalitetsrunde før bruk:** V0s fargetokens var OKLCH-TILNÆRMINGER, ikke + eksakte — regnet ut presise verdier fra `#8bc24a`/`#ff5722` og rettet alle + 6 forekomster i `globals.css`. Fjernet `@vercel/analytics` (unødvendig på + egen-hostet infra), fjernet `typescript: { ignoreBuildErrors: true }` + (ekte typesjekk kjører nå), fjernet dødt `pnpm.overrides`-felt. + `frontend/.gitignore` manglet `.pnpm-store/` — årsaken til at brukerens + VSCode viste 10 000 "ukjente" filer ved første commit-forsøk; rettet. + **Kablet mot ekte API:** `next.config.mjs` sin `rewrites()` proxyer + `/auth/*`/`/orgs/*`/`/health` server-side til `teecup_api` — same-origin, + ingen CORS, cookie uendret (full begrunnelse i ADR-016). Login-skjermet + sender ekte `POST /auth/request-link`; ny `/verify`-side mottar + `?token=...` fra e-postlenken (auto-verifiserer) eller viser et manuelt + "lim inn koden"-felt. `app/email.py` fikk en ny `PUBLIC_BASE_URL`- + innstilling og sender nå en EKTE klikkbar lenke (koden beholdes som + fallback). + **Reell fallgruve funnet og fikset ved containerisering:** Next.js sin + `rewrites()` løses ved BUILD-tid for `output: "standalone"`, ikke ved + container-oppstart — en runtime `-e TEECUP_API_ORIGIN=...` ble stille + ignorert (proxy-kall feilet med `ECONNREFUSED` mot `localhost:8000`). + Løst med en Docker build-time `ARG TEECUP_API_ORIGIN` i + `frontend/Dockerfile`, satt via `docker-compose.yml` sin `build.args`. + **Rullet ut live:** ny `teecup_frontend`-tjeneste i `docker-compose.yml`. + Caddy (`teecup.teeoff.no`, i det SEPARATE `teeoff`-repoet, + `/opt/teeoff/deploy/Caddyfile`) endret fra å peke direkte på `teecup_api` + til å peke på `teecup_frontend` — samme stale-inode-oppførsel som + containeriseringsrunden (graceful `reload` plukket IKKE opp endringen, + `/` fortsatte å gi `teecup_api` sin egen 404 i stedet for innloggingssiden + til reload faktisk skjedde). Løst likt: full `docker restart + teeoff_caddy`, brukeren bekreftet eksplisitt på forhånd. `teeoff.no` + upåvirket gjennom hele omstarten. + **Verifisert med FAKTISK e-postlevering:** ekte magic-link sendt til + brukerens egen adresse over `https://teecup.teeoff.no`, ekte e-post + mottatt med en ekte klikkbar lenke, åpnet i nettleser, landet på en + fungerende `/verify`-side, sesjon opprettet — brukeren bekreftet innlogget + status. Første gang en hel bruker-vendt flyt er bevist ende-til-ende i + produksjon, ikke bare API-et isolert. + **Merk for neste økt:** `deploy/Caddyfile`-endringen ligger uncommitted i + det SEPARATE `/opt/teeoff`-repoet, ikke i `teecup`-repoet — lett å glemme + siden denne økten ellers kun har jobbet i `/opt/teecup`. +- **Dashboard-skjerm LIVE (2026-07-18):** andre V0-skjerm — organisasjon- + bytter/-opprettelse + turneringsliste (`/dashboard`), samme mønster som + login-runden. **Reell integrasjonsfelle unngått:** V0s eksport denne gangen + var en FULL re-eksport av hele prosjektet (inkl. `login-form.tsx`, + `next.config.mjs`, `package.json`), ikke bare de nye filene — en naiv + utpakking ville stille reversert `rewrites()`-proxyen, `output: + "standalone"`, den ekte fetch-kablingen i login-skjemaet, og alle + V0-uavhengige opprydninger fra forrige runde. Løst ved å pakke ut til et + scratch-område FØRST, diffe mot live-treet fil for fil, og kun ta inn det + som faktisk var nytt (`dashboard.tsx`, `tournament-card.tsx`, + `tournament-status-badge.tsx`, `wordmark.tsx`, `badge.tsx`, + `dropdown-menu.tsx`, `app/dashboard/page.tsx`) — `next.config.mjs`, + `package.json`, `globals.css`, Docker-filene ble bevisst IKKE overskrevet. + `login-form.tsx` fikk en kirurgisk patch (kun Wordmark flyttet til egen + fil, som V0 selv hadde gjort — all egen fetch-/feilhåndteringslogikk + urørt). + **`dashboard.tsx` sitt datalag skrevet om fra bunnen** (V0 leverte kun + mock `useState`): henter `/auth/me` for organisasjonsmedlemskap, + `/orgs/{id}/tournaments` per valgt org, `POST /orgs`/`POST + /orgs/{id}/tournaments` for opprettelse, `POST /auth/logout` for utlogging + — presentasjonskomponentene (kort, bytter, tomme tilstander) beholdt + uendret fra V0. `/verify`-siden oppdatert til å sende brukeren videre til + `/dashboard` etter vellykket innlogging (fantes ingen dit å gå før nå). + **Verifisert:** ekte typesjekket build, redeploy av kun `teecup_frontend` + (ingen Caddy-endring nødvendig denne gangen — ADR-016s mønster holder), + `teecup.teeoff.no/dashboard` → 200, `teeoff.no` upåvirket. + **Skrive-flyten bekreftet med EKTE data samme dag (brukeren testet selv, + ikke meg):** organisasjon "Tjøme Gents" og turnering "De Gamle er Eldst" + opprettet via UI-et mot den ekte `teecup_db` — statusmerket viste riktig + "Utkast", "Ingen datoer satt" håndtert korrekt (ingen krasj på manglende + dato), ett-org-visningen viste riktig uten unødvendig bytter-UI. Første + gang en HEL skrive-flyt (ikke bare lesing) er bevist ende-til-ende fra + frontend mot ekte produksjonsdata. +- **Lag/roster-skjerm LIVE (2026-07-18):** tredje V0-skjerm + (`/tournaments/[id]`), samme re-eksport-mønster som dashboard-runden — + diffet mot live-treet, tok kun inn `tournament-detail.tsx` og et + `Link`-basert `tournament-card.tsx` (navigasjon fra dashbordet). + **URL-design bevisst avvikende fra V0s forslag:** V0s genererte side leste + aldri `params.id` og hadde ingen organization_id i det hele tatt — holdt + derfor V0s flate `/tournaments/[id]`-struktur (i stedet for en nøstet + `/orgs/[orgId]/tournaments/[id]`, som ville krevd manuell ombygging ved + HVER fremtidig V0-reeksport) og la `org`+`name` til som søkeparametre i + `tournament-card.tsx` sin lenke — API-et krever organization_id på alle + team-/roster-kall (RLS). + **Reelt hull funnet FØR integrering, ikke etter:** V0-skjermen bygger inn + "fjern spiller"/"gjør til kaptein"-handlinger, men backend hadde KUN + GET/POST på `team_roster` — ingen DELETE eller PATCH. Spurte bruker + eksplisitt (samme mønster som andre scope-avklaringer denne økten) — svar: + bygg de to endepunktene nå. Lagt til i `app/routers/tournaments.py`: + `PATCH .../roster/{roster_id}` (bevisst enkel — setter/fjerner + `is_captain` på NØYAKTIG denne raden, håndhever IKKE "kun én kaptein per + lag", siden kaptein fortsatt bare er et merke, ikke en egen + autorisasjonsrolle) og `DELETE .../roster/{roster_id}` (204, idempotent + `NOT_FOUND` ved dobbel sletting — ikke krasj). + **`tournament-detail.tsx` sitt datalag skrevet om fra V0s mock:** henter + lag + roster (roster-radene bærer allerede `display_name`/ + `handicap_index_snapshot` fra APIet, så V0s separate `poolById`-oppslag + ble fjernet som overflødig) og organisasjonens spillerpool + (`GET /orgs/{id}/players`, brukt til type-ahead ved "legg til spiller"). + Alle fem mutasjonene (opprett lag, legg til eksisterende spiller, opprett + ny spiller inline + rostre, endre kaptein, fjern fra roster) kablet mot + ekte endepunkter — presentasjonskomponentene (kort, type-ahead, + fargevelger, bekreft-fjerning) beholdt uendret fra V0. + **Verifisert:** de to nye endepunktene testet mot en fersk + `teecup_scratch` (PATCH setter kaptein + riktig `NOT_FOUND` på ugyldig id, + DELETE gir 204 + idempotent `NOT_FOUND` ved gjentak, `test_isolation.sql` + fortsatt 12/12), ekte typesjekket frontend-build (5 ruter), redeploy av + BEGGE containere (backend-endepunktene er nye), `teecup.teeoff.no/dashboard` + → 200, `teeoff.no` upåvirket. Selve skrive-flyten på `/tournaments/[id]` + (opprett lag/roster) ikke testet med ekte data i denne runden — venter på + brukeren, samme mønster som dashboard-rundens skrive-test. +- **ADR-017 + migrasjon 007 (2026-07-18):** brukeren reiste selvregistrering + rett etter at roster-skrive-flyten var bekreftet. Full ADR skrevet + (5 beslutninger: offentlig påmelding uten innlogging, e-post som + sammenkoblingsnøkkel mot forhåndsopprettede spillere, egen + `tournament_registration`-tabell atskilt fra `team_roster` med + konfigurerbar godkjenning/kapasitet/venteliste, utvidet spillerprofil + + obligatorisk samtykke, og `public_tournament_org()`). Migrasjon + `007_registration_and_player_fields.sql` skrevet og scratch-verifisert + (001→007 kjører rent, `test_isolation.sql` fortsatt 12/12). + **Reelt arkitekturproblem løst underveis, ikke bare skjema:** et + offentlig (uautentisert) påmeldingskall kjenner en turnering-id, men + ingen org-kontekst — og uten `app.current_org` slipper RLS ingen rader + gjennom, heller ikke oppslaget for å FINNE riktig org. Løst med en snever + `SECURITY DEFINER`-funksjon (`public_tournament_org`) som KUN eksponerer + uuid→uuid-koblingen. **Verifisert presist, ikke bare "kjørte uten feil":** + kalt funksjonen som `teecup_app`-rollen med ingen `app.current_org` satt + — ga korrekt org-id for en kjent turnering, `NULL` (ikke feil) for en + ukjent — OG et RÅTT `SELECT` på `tournament` på SAMME tilkobling/rolle ga + fortsatt 0 rader, som beviser RLS ikke er brutt generelt, bare dette ene + smale unntaket eksisterer. + **Kjørt mot ekte `teecup_db` 2026-07-18** (bruker bekreftet eksplisitt i + samme økt): migrasjonen kjørte rent, `test_isolation.sql` fortsatt 12/12. +- **Registrerings-API LIVE (2026-07-18), samme dag:** ny + `app/routers/registration.py` — `GET /public/tournaments/{id}` og + `POST /public/tournaments/{id}/register`, begge UTEN `get_current_user` + eller `get_authorized_org` (helt uautentisert, egen `/public`-prefiks, + bevisst atskilt fra `/orgs/...` i koden). Bruker `public_tournament_org()` + (migrasjon 007) til å slå opp org-kontekst FØR RLS kan håndheve noe. + `app/routers/players.py` utvidet med alle sju nye ADR-017-feltene + (`mobile`/`email`/`birth_date`/`nickname`/`country`/`club`/ + `club_member_number`). `frontend/next.config.mjs` sin `rewrites()` + utvidet med `/public/*` (ADR-016s konsekvens: enhver ny API-prefiks MÅ + inn her). + **Ny brukers-oppdaget notat fanget FØR bygging, ikke etter:** brukeren + krevde eksplisitt at synlighet (offentlig/kun org/kun turnering- + deltakere) må være et VALG for fremtidige landingssider — notert grundig + i `FEATURE_BACKLOG.md` (koblet til samme åpne spørsmål for "Banter + Board"-feeden) FØR dette registrerings-API-et ble bygget, akkurat for at + det ikke skal gå i glemmeboken til landingsside-runden. + **Verifisert grundig mot fersk `teecup_scratch`** (001→007, + `test_isolation.sql` 12/12): hele registreringsløpet testet reelt — + samtykke-avvisning (400), duplikat-avvisning (409 `DUPLICATE`), + e-post-matching mot en organisator-forhåndsopprettet spiller (BEKREFTET: + ingen duplikatrad, mobil fylt inn via `COALESCE`, `display_name` IKKE + overskrevet), kapasitet+`waitlist`-policy (→ `waitlisted`), + kapasitet+`closed`-policy (→ 409 `LIMIT_REACHED`), `registration_ + requires_approval` (→ `pending`), utløpt frist (→ 409 + `REGISTRATION_CLOSED`), og `confirmed_count` i `GET`-responsen talt + riktig (kun `confirmed`+`pending`, ikke `waitlisted`/avviste). + **Rullet ut live:** begge containere redeployet, `teecup.teeoff.no/ + dashboard` og `/health` fortsatt 200, `teeoff.no` upåvirket, det + offentlige endepunktet bekreftet nåbart over ekte https (ukjent + turnering-id ga korrekt `404`/`NOT_FOUND`, ikke-destruktiv sjekk — selve + påmeldingsflyten med ekte data ikke testet mot prod i denne runden). + **E-post-basert kontosammenkobling LIVE, samme dag:** ny migrasjon + `008_link_player_by_email.sql` — `link_player_by_email(user_id, email)`, + samme `SECURITY DEFINER`-mønster som `public_tournament_org()` (007), + denne gangen for en tverr-org UPDATE i stedet for et lese-oppslag. + `verify_magic_link` (`app/routers/auth.py`) kaller den på HVER + innlogging (idempotent — funksjonens `WHERE user_id IS NULL` gjør + gjentatte kall til en no-op), ikke bare ved førstegangsopprettelse. + **Verifisert presist:** to separate org-er, hver med sin egen + organisator-opprettede "Kari"-rad (samme e-post, ulik store/små + bokstaver for å teste case-insensitivitet også) — ved Karis FØRSTE + innlogging ble BEGGE radene koblet til kontoen hennes, i to org-er hun + aldri har vært medlem av. Eksplisitt bekreftet: `organization_membership` + har NULL rader for henne etterpå — ren identitetskobling, ingen + privilegie-eskalering (å ha `player.user_id` satt gir ingen ny tilgang + gjennom `get_authorized_org`, som fortsatt krever ekte org-medlemskap + uavhengig av dette). Andre innlogging idempotent, ingen feil. + `test_isolation.sql` fortsatt 12/12. **Kjørt mot ekte `teecup_db` + 2026-07-18**, bruker bekreftet eksplisitt, backend redeployet, live + sjekker OK, `teeoff.no` upåvirket. + **ADR-017s backend er dermed komplett** (registrering + kontokobling). + Gjenstående: frontend-påmeldingsskjema og landingssider — egen, senere + ADR-runde (se `FEATURE_BACKLOG.md`). +- **ADR-018 + migrasjon 009: landingssider, backend LIVE (2026-07-18), + samme dag:** trenivås synlighet (`tournament.visibility`: + `public`/`org`/`participants`, default `org` — trygg standard) + + `organization.public_profile`. **Reell presisering funnet underveis, ikke + antatt på forhånd:** RLS (`org_isolation`) beskytter kun TENANT-grenser + (org A ser aldri org B), IKKE innholds-synlighet innenfor riktig + org-kontekst — det eksisterende `GET /public/tournaments/{id}` (ADR-017) + leste allerede fullt innhold uten synlighetssjekk, fordi RLS er fornøyd + så snart org-konteksten er satt, uansett hvem som spør. `visibility` + håndheves derfor eksplisitt i `app/routers/registration.py`, på BÅDE + lesing og registrering (ADR-018 Beslutning D: kan du ikke se turneringen, + kan du heller ikke melde deg på den — bekreftet med bruker FØR bygging). + Ny `get_current_user_optional` i `app/auth.py` (som `get_current_user`, + men returnerer `None` i stedet for 401 — offentlige endepunkter skal + fungere for anonyme lesere også). Ny "deltaker"-autorisasjonsvei: en + innlogget bruker med `player.user_id` koblet (ADR-017) OG en + `tournament_registration`- eller `team_roster`-rad for NØYAKTIG den + turneringen får se `participants`-synlige turneringer, uansett + org-medlemskap. + **Ekte migrasjonsfeil funnet OG rettet UNDER scratch-verifisering, ikke + antatt riktig:** migrasjonen feilet først ("column slug already exists") + — `organization.slug` har ligget i skjemaet siden migrasjon 001 + ("f.eks. subdomene/URL-vennlig", allerede med en plain `UNIQUE`), noe jeg + hadde oversett fullstendig og forsøkt å legge til på nytt. Rettet ved å + fjerne den doble `ADD COLUMN` + den overflødige partielle unik-indeksen + (001 sin plain `UNIQUE` dekker "unik når satt" allerede, siden Postgres + behandler NULL som distinkt), beholde kun de nye `CHECK`-constraintene. + Kjørte rent på ny etter fiksen. **Lærdom:** grep alltid eksisterende + skjema for feltnavn FØR en ny migrasjon skrives, ikke bare stol på + hukommelsen om hva som "sikkert" ikke finnes fra før. + Ny tabell `tournament_sponsor` (navn+lenke aktivt, `logo_key` inert til + MinIO-runden — samme med `tournament.hero_image_key`). Tredje + `SECURITY DEFINER`-bro i prosjektet: `public_org_by_slug()` (etter + `public_tournament_org` 007, `link_player_by_email` 008) — returnerer + `NULL` for BÅDE "finnes ikke" og "finnes, men er privat", samme + anti-enumerering som magic-link. + **Fylte også et implisitt hull oppdaget underveis:** ADR-en beskrev + hvordan synlighet skulle håndheves, men ingen tidligere runde hadde bygget + noen vei for organisator til faktisk å SETTE disse feltene. Lagt til: + `PATCH /orgs/{id}/tournaments/{id}` (visibility/description/ + registrerings-innstillinger, ekte PATCH-semantikk via Pydantic sin + `exclude_unset` — et utelatt felt nullstilles IKKE), `PATCH /orgs/{id}` + (slug/public_profile), full sponsor-CRUD. `_fetch_sessions()` trukket ut + som delt hjelpefunksjon i `tournaments.py` (delt mellom den innloggede + og den nye offentlige `GET /public/tournaments/{id}/sessions` — blind + draw-hemmelighold, ADR-013, arves automatisk, ikke reimplementert). + **Verifisert grundig mot fersk `teecup_scratch`** (001→009, + `test_isolation.sql` 12/12): hele synlighetsmatrisen testet med ekte + HTTP-kall — anonym avvist på `org`-synlig turnering (både lesing OG + registrering), `PATCH` til `public` + beskrivelse + sponsor fungerte, + anonym lesing fungerte deretter, `participants`-synlighet bekreftet + reell chicken-and-egg-konsekvens av Beslutning D (ingen kan selv- + registrere seg til en `participants`-synlig turnering, kun organisator + kan legge til direkte — korrekt, ikke en bug), en organisator-rostret + spiller som logget inn fikk tilgang, en tilfeldig innlogget FREMMED + (ikke deltaker) ble fortsatt avvist, org-landingsside viste KUN + `public`-synlige turneringer, `CHECK`-constraint (`public_profile` + krever `slug`) avvist korrekt, ugyldig slug-format avvist av Pydantic, + sponsor-sletting fungerte. + **Kjørt mot ekte `teecup_db` 2026-07-18**, bruker bekreftet eksplisitt, + backend redeployet, live sjekker OK, `teeoff.no` upåvirket. Ingen + frontend-endring nødvendig for selve API-tilgangen (det brede + `/public/:path*`-mønsteret fra ADR-016 dekker allerede `/public/orgs/*`). + **Gjenstår:** selve landingsside-SKJERMENE i frontend (V0), og + MinIO/bildeopplasting — bevisst utsatt, egen runde. +- **Offentlig turnering-landingsside LIVE (2026-07-18), samme dag:** fjerde + V0-skjerm (`components/public-tournament.tsx`, ny rute `/t/[id]`) — + banner (bevisst permanent farge-/gradient-utseende, ikke en "bilde + kommer"-plassholder), presenterende tekst, status (datoer + "X av Y + plasser"), program, sponsorer (navn+lenke), og et påmeldingsskjema med + navn+e-post synlig først og resten bak en "flere detaljer"-utvidelse — + tre distinkte bekreftelsestilstander (bekreftet/venteliste/godkjenning + venter), ikke én generisk "takk". + **Ingen ny reeksport-kollisjon denne gangen** — kun étt genuint nytt + filnavn (`public-tournament.tsx`), resten var kjent V0-revert av + allerede-tilpassede filer, samme mønster som før. + **V0 opprettet komponenten, men ingen rute** — la selv til `app/t/[id]/ + page.tsx` (bevisst en FLAT `/t/[id]`-sti, ikke nøstet under `/orgs/...` + som den innloggede turnering-detalj-siden, siden det offentlige API-et + kun trenger turnering-id, ikke org-id). + **Datalaget skrevet om fra V0s mock:** ekte `fetch` mot + `GET /public/tournaments/{id}` + `/sessions`, ekte `POST .../register`. + Håndterer 403 (`NOT_VISIBLE`, ADR-018) og 404 med en egen tilgang-avvist- + tilstand V0 ikke hadde bedt om (fantes ikke i prompten, men trengs for at + siden faktisk skal fungere for `org`/`participants`-synlige turneringer). + Mappet `status:"waitlisted"` fra API-et til komponentens `"waitlist"`, og + skjemaets norske kjønnsvalg ("kvinne"/"mann"/"annet") til API-ets + `^[mfx]$`-mønster — to reelle navnekollisjoner mellom V0s UI-språk og + API-kontrakten, ikke bare et rett-frem felt-for-felt-uttrekk. + **Reell driftsfeil funnet OG rettet under scratch-test, ikke i + produksjon:** `pnpm install` (uten `--ignore-scripts`) i dev-server- + testoppsettet feilet stille med tom logg og exit 1 — corepack hadde + hentet en NY pnpm-versjon (11.13.1 → 11.14.0) siden sist, som gjør + "ignored builds"-varselet til en hard feil i stedet for bare en + advarsel. Rettet ved å bruke samme `--ignore-scripts`-flagg som + `frontend/Dockerfile` allerede bruker (upåvirket av denne — bekreftet + ved at selve prod-buildet fortsatt gikk rent). **Lærdom:** `Dockerfile` + sin pinning av *kode* er ikke det samme som å pinne *verktøyene rundt* + (corepack henter alltid siste pnpm) — verdt å huske neste gang et + scratch-dev-server-oppsett plutselig feiler uten åpenbar grunn. + **Verifisert grundig mot fersk `teecup_scratch` + en ekte kjørende + frontend-dev-server** (ikke bare `next build`): ekte turnering med + beskrivelse, kapasitet, sponsor og økt opprettet via API-et, hentet + gjennom frontend-proxyen (ikke direkte mot backend) og bekreftet + byte-for-byte riktig — inkludert en ekte `POST`-registrering som økte + `confirmed_count` fra 0 til 1. + **Rullet ut live**, kun `teecup_frontend` (ingen backend-endring denne + runden), `teeoff.no` upåvirket. + **Gjenstår:** org-landingssiden (egen V0-prompt), Open Graph-metadata for + deling, MinIO/bilder. +- **Offentlig klubb-landingsside LIVE (2026-07-18), samme dag:** femte og + siste V0-skjerm i ADR-018 (`components/public-club.tsx`, ny rute + `/clubs/[slug]`). Samme banner-språk som turnering-siden, liste over + klubbens turneringer (gjenbrukte eksisterende `TournamentCard`), egen tom- + tilstand. + **Reell delt-komponent-kollisjon løst, ikke duplisert bort:** + `TournamentCard` var bygget for KUN den innloggede konteksten (krevde + `orgId`, lenket til `/tournaments/{id}?org=...`). I stedet for en egen + kopi av kortet for den offentlige siden, gjort `orgId` valgfri — + satt (dashbordet) gir innlogget lenke, utelatt (klubbsiden) gir + `/t/{id}` i stedet. Samme kort, to kontekster, ingen duplisering. + Bekreftet dashbordets egen bruk uendret/upåvirket etterpå. + **V0 opprettet denne gangen selv en rute** (`app/clubs/[id]/page.tsx`, + uten noen props sendt inn i det hele tatt) — men navnga parameteren + `[id]` selv om den faktisk er en SLUG (`public_org_by_slug()`, ADR-018). + Skrev selv en ny, riktig `app/clubs/[slug]/page.tsx` i stedet for å bruke + V0s (feilnavngitte og prop-løse) versjon. + **Verifisert mot fersk `teecup_scratch` + ekte kjørende frontend-dev- + server:** org med slug+`public_profile`, én turnering satt `public`, én + latt stå på default `org` — klubbsiden viste GJENNOM frontend-proxyen + kun den ene offentlige turneringen, den org-private var korrekt + usynlig (samme filtermønster som API-et selv, bekreftet fra + klientsiden også). Ukjent slug ga korrekt 404. + **Rullet ut live**, kun `teecup_frontend`, `teeoff.no` upåvirket. + **ADR-018s planlagte skjermer er dermed komplette.** Gjenstår: Open + Graph-metadata for deling, MinIO/bilder — begge bevisst egne, senere + runder. +- **Open Graph-metadata LIVE (2026-07-18), samme dag — ADR-018 dermed + helt ferdig:** `generateMetadata()` lagt til på `/t/[id]` og + `/clubs/[slug]` (ekte tittel/beskrivelse fra API-et, `og:site_name`, + trygg fallback-tittel ved ukjent id/slug — feil her skal ALDRI hindre + selve siden i å laste). + **Reell driftsfeil funnet FØR den nådde produksjon, ikke etter:** + `generateMetadata()` kjører server-side ved REQUEST-tid, ikke i + nettleseren — går derfor IKKE gjennom `next.config.mjs` sin + `rewrites()` (som kun gjelder nettleser-trafikk inn til Next.js- + serveren). Måtte derfor lese `TEECUP_API_ORIGIN` direkte, men den + variabelen fantes KUN i `Dockerfile` sitt builder-steg — `ENV` satt i + ett `FROM`-steg arves ikke til et senere. Rettet ved å sette samme + `ARG`/`ENV` på nytt i runner-steget også. + **Verifisert presist at fiksen faktisk virker, ikke bare at bygget gikk + gjennom:** bygget det EKTE produksjonsimaget (ikke dev-server) pekt mot + en scratch-backend med en ekte offentlig turnering+org, hentet den + faktiske server-rendrede HTML-en og bekreftet ekte ``/`og:title`/ + `og:description` — ikke bare at TypeScript kompilerte. Ukjent + turnering-id ga korrekt trygg fallback-tittel. + **Bevisst utenfor omfang:** `og:image` — ingen ekte bilde finnes ennå + (MinIO-runden). La til `metadataBase` i `app/layout.tsx` nå likevel, som + forarbeid slik at et fremtidig relativt bilde-URL løses riktig uten en + egen fiks da. + **Rullet ut live**, kun `teecup_frontend`, `teeoff.no` upåvirket. +- **MinIO-runden LIVE (2026-07-18), samme dag — ADR-018 dermed HELT + ferdig, ingenting utsatt igjen:** ny `teecup-minio`-tjeneste (persistent + volum, genererte credentials i `.env`), backend-endepunkter for ekte + bildeopplasting til `tournament.hero_image_key`/`tournament_sponsor. + logo_key` (som lå inerte siden migrasjon 009), offentlige API-svar bygger + nå fulle URL-er, `og:image` koblet på i `/t/[id]` sin `generateMetadata`. + **Sent, men viktig presisert krav underveis:** brukeren avbrøt en + verktøyskall midtveis for å presisere at ALLE bilder skal konverteres + til AVIF for plassbesparelse. Snudde HELE opplastingsarkitekturen på + dette: droppet den opprinnelige planen om presignerte URL-er (nettleser + laster opp DIREKTE til MinIO) til fordel for ekte multipart-opplasting + GJENNOM API-et, som konverterer til AVIF (Pillow + pillow-avif-plugin) + FØR lagring. Dette forenklet arkitekturen betydelig: kun ÉN MinIO-klient + trengs nå (før: to separate klient-oppsett for henholdsvis internt + admin-arbeid og offentlig presignering), og Caddy sin nye rute trenger + ikke lenger bevare Host-headeren presist (relevant kun for SigV4- + signaturverifisering av presignerte URL-er, ikke for anonym public-read). + **To reelle feil funnet UNDER scratch-verifisering, aldri i produksjon:** + (1) `pillow-avif-plugin` krever ingen ekstra systempakker i + `python:3.12-slim` -- verifisert med et frittstående encode/decode- + rundtrip FØR det ble tatt i bruk i selve API-et. (2) MinIO validerer + `Host`-headeren STRENGT og avviser understrek som ugyldig vertsnavn -- + et tjenestenavn med understrek (`teecup_minio`, konsistent med + `teecup_api`/`teecup_frontend`) feilet umiddelbart ved oppstart + ("Invalid Request (invalid hostname)"). Bekreftet presist ved å teste + samme oppsett med bindestrek i stedet (`teecup-minio`) -- fungerte + umiddelbart. Alle referanser rettet til bindestrek FØR noe ble forsøkt + mot ekte infrastruktur. + **Caddy:** ny `/teecup-media/*`-rute på det EKSISTERENDE + `teecup.teeoff.no`-blokket (IKKE et nytt subdomene -- en tidlig sjekk + avdekket at `media.teecup.teeoff.no` fantes som en wildcard DNS-post, + men pekte til en IPv6-adresse denne serveren ikke har i det hele tatt; + path-prefiks på et allerede fungerende domene unngikk hele den DNS- + avhengigheten). Samme stale-inode-oppførsel som alle tidligere Caddy- + runder -- full `docker restart teeoff_caddy`, brukeren bekreftet + eksplisitt. `teeoff.no` upåvirket. + **Verifisert grundig, i flere lag:** frittstående AVIF-encode/decode- + test, full scratch-kjede (fersk Postgres + en ISOLERT scratch-MinIO- + container) med et ekte opplastet bilde -- bekreftet konvertert til + gyldig AVIF (800×400, 406 bytes for et helfarget testbilde), bekreftet + lagret med riktig nøkkel, bekreftet lesbart ANONYMT direkte mot MinIO + (uten Caddy, isolerer bucket-policyen), bekreftet `hero_image_url`/ + `logo_url` bygget riktig i det offentlige API-svaret. Alle tre + valideringsveier testet (ugyldig content-type, korrupt bildeinnhold, + for stor fil >8MB) -- alle ga korrekt `VALIDATION_FAILED`. Etter + Caddy-omstart: et ekte anonymt kall mot `/teecup-media/...` på + produksjonsdomenet ga en ekte MinIO S3-XML-feilrespons (`NoSuchKey` for + en scratch-nøkkel som naturligvis ikke finnes i prod) -- beviser ruten + treffer MinIO selv, ikke frontend sin egen 404-side. + `test_isolation.sql` fortsatt 12/12 gjennom hele runden. + **Bevisst utenfor omfang:** ingen faktisk opplasting av et EKTE bilde + til en EKTE, live turnering i denne runden (ville krevd å skrive + test-data i brukerens ekte konto uten å bli spurt) -- tilbudt, ikke + utført. Selve dra-og-slipp-opplastingsskjermen i frontend (V0) er + fortsatt ikke bygget, som avtalt fra starten av runden. + +- **Program-skjerm (økter/tidsplan), bygget og SCRATCH-verifisert, IKKE + live ennå (2026-07-18):** sjette V0-skjerm, første i "bygg i rekkefølgen + ting brukes"-serien (etter lag/roster: program → blind draw → scorekort → + leaderboard). `components/tournament-program.tsx`, ny rute + `/tournaments/[id]/program`. Tidslinje over økter + opprett-skjema + (format, hullomfang, scoringsmodus, poeng, klokkeslett+intervall, + starthull, kollapsbar "avansert"-seksjon med ADR-014s fire brytere). + **Reelt blokkerende hull funnet FØR integrering:** `SessionCreate. + course_id` er påkrevd, men INGEN endepunkt kunne noensinne produsere en — + ADR-004s teeoff-integrasjon er fortsatt bare vedtatt (ingen utgående + HTTP-klient finnes). Spurte bruker eksplisitt — svar: bygg enkel + course-CRUD nå. Ny `app/routers/courses.py` (`GET`/`POST /orgs/{id}/ + courses`, kun `source='custom'`). Program-skjemaet fikk et + type-ahead-felt for bane (samme mønster som spiller-type-ahead i + roster-skjermen). + **Reell korrekthetsfeil rettet FØR integrering:** 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 -- ren `"scramble"` avvises med 400. Rettet i + frontend-mappingen til to segment-knapper. + **`allowance_override`-JSON-formen verifisert eksakt** mot + `app/handicap.py` sin `parse_allowance_config`/`_strategy_from_json` + (`{type: "combined"|"per_player", percentage: 0..1}`, ikke en flat + prosent) -- frontend velger riktig `type` ut fra om formatet er + side-enhet eller spiller-enhet, konverterer 0–100-skjemafelt til 0–1. + **Scratch-infrastruktur denne runden, bevisst forskjellig fra tidligere:** + brukte en ISOLERT `teecup_app_scratch`-rolle (`GRANT teecup_app TO + teecup_app_scratch`) i stedet for den ekte `teecup_app`-rollen -- den er + nå cluster-global og produksjonskritisk (delt Postgres-instans med + `teecup_db`), så et eldre plandokuments "drop teecup_app-rolle"- + opprydning (skrevet FØR go-live) er utdatert og ble bevisst IKKE fulgt. + Egen isolert scratch-MinIO-container også (app-oppstart krever en + nåbar MinIO for `ensure_bucket()`). + **Verifisert:** courses opprettet+listet, kryss-org-isolasjon bekreftet, + økt med klokkeslett, økt med `scramble_4`+full `allowance_override`- + rundtur, gammel `"scramble"`-verdi avvist (400), `test_isolation.sql` + 12/12, ekte typesjekket PRODUKSJONSBUILD (samme `Dockerfile` som faktisk + deployes) kjørt og bekreftet. + **Diffet V0-eksporten mot live-treet FØR noe ble tatt inn** (samme mønster + som alle tidligere runder): kun tre reelt nye filer, resten forventede + full-reverts, ikke rørt. Fjernet V0s dev-only forhåndsvisnings-toggle; + lagt til fanerad ("Lag og spillere" / "Program") i BEGGE skjermene siden + V0 ikke visste om den andre når den ble generert i egen prompt. + **Rullet ut live 2026-07-18**, bruker bekreftet eksplisitt: `docker + compose up -d --build teecup_api teecup_frontend` (kun disse to, `teecup- + minio` urørt). Verifisert: begge containere boot-et rent (`Application + startup complete`, Next.js `Ready`), `teecup.teeoff.no/health` og + `/dashboard` → 200, `teeoff.no` upåvirket (200). +- **ADR-004 (teeoff-banedata) kartlagt, IKKE bygget (2026-07-18):** brukeren + påpekte rett etter program-skjerm-rullingen at ADR-004s teeoff- + integrasjon fortsatt bare er vedtatt, ikke bygget (kun `source='custom'` + finnes). Kartlagt `/opt/teeoff/backend/main.py` (annet repo, kun lest): + `GET /api/facilities/{slug}` (offentlig, INGEN auth/API-nøkkel) returnerer + allerede alt teecup trenger — `courses[].holes[]` (`par`, `hcp_index` + = stroke index), `courses[].tees[]` (`name`, `cr_men`/`slope_men`, + `cr_women`/`slope_women` — kjønnsdelt WHS-rating). CORS-lista på teeoff- + siden ekskluderer teecups origin, men er IRRELEVANT for et server-til- + server-kall (kun nettleser-fetch rammes av CORS). Ingen dokumentasjon + (README/OpenAPI) finnes for dette API-et i teeoff-repoet — kartleggingen + over er basert på å lese koden direkte. Stabil identifikator: `facilities. + slug` (f.eks. `borregaard-golfklubb`) — selve banen har kun en intern + serial-id, ingen egen slug, så `course.external_course_ref` må bære + facility-slug + bane-id sammen. + **Oppdatering samme dag: brukeren ba meg ta fatt på dette NÅ** (bevisst + sidesprang fra "bygg i rekkefølgen ting brukes"-planen — blind draw- + skjermen er fortsatt neste steg i den planen ETTERPÅ, ikke droppet). + Design besluttet og skrevet som **ADR-019** (se ARCHITECTURE_DECISIONS.md): + import (kopi) ved organisators eksplisitte valg, ikke live oppslag ved + hver bruk — fryser data på importtidspunktet, samme reproduserbarhets- + prinsipp som handicap-snapshotten (ADR-007). Server-til-server-kall + (`teecup_api` → `http://teeoff_api:8000`, internt Docker-nettverk, ingen + auth trengs). Ny migrasjon `010` (unik `external_course_ref` per org, + hindrer dupliserte importer). Se ADR-019 for alle fem delbeslutningene. + **Bygget, scratch-verifisert MOT EKTE `teeoff_api`** (ikke en simulert + respons — ekte HTTP-kall til den kjørende produksjonscontaineren, kun + lesing): søkte opp "Borregaard" i teeoff sine 174 publiserte anlegg, + hentet Borregaard Golfklubb sin hovedbane (18 hull, 4 tee-farger × + kjønn), importerte den til en scratch-org — alle 18 hull med riktig + par/stroke-index (1-18, unik), alle 8 tee/tee_rating-rader med riktig + full_18 course/slope-rating verifisert direkte i databasen, opprettet + deretter en ekte økt med den importerte banen som `course_id` (beviser + hele veien til handicap-motoren fungerer, ikke bare selve importen). + Reimport av samme bane korrekt avvist (409 DUPLICATE, migrasjon 010 sin + indeks). Kryss-org-isolasjon bekreftet, ukjent teeoff-slug ga korrekt 404, + `test_isolation.sql` 12/12. Ekte typesjekket produksjonsbuild av + frontend-utvidelsen (bane-søk i program-skjemaet: søk anlegg → velg bane → + importer, samme UI-mønster som spiller-/bane-type-ahead ellers i appen). + **Rullet ut live 2026-07-18**, bruker bekreftet eksplisitt: migrasjon 010 + kjørt mot ekte `teecup_db` (kun én ny partiell unik-indeks, ingen + eksisterende rader rørt), begge containere bygget+redeployet, live + sjekker OK, `teeoff.no` upåvirket. **"Bygg i rekkefølgen ting brukes"- + planen gjenopptas nå** — blind draw-skjermen er neste steg. + **Reell produksjonsbug funnet OG fikset samme dag, rapportert av bruker + som faktisk brukte funksjonen:** brukeren klikket seg korrekt via + dashbord → turnering → Program-fane (bekreftet med skjermbilde + full + klikk-sti, ikke gjettet), åpnet "Hent bane fra teeoff", søkte "Tjøme", og + fikk feilmeldingen "Mangler organisasjon i lenken" — URL-en hadde da + MISTET `?org=...&name=...`. Root-cause: `OfficialCourseSearch` sitt eget + søke-`<form onSubmit={runSearch}>` var rendret INNI `CreateSessionCard` + sitt eksisterende `<form onSubmit={handleSubmit}>` -- nestede + `<form>`-elementer er ugyldig HTML. Nettleseren slår sammen de to + skjemaene i den faktiske DOM-en, så "Søk"-knappen submittet i praksis det + YTRE økt-skjemaet som en ekte native side-navigasjon (GET til gjeldende + sti, ingen navngitte felt => tom spørrestreng) -- dette vasket bort + org-parameteren og landet brukeren på siden sin egen org-guard. **Fikset** + ved å fjerne det indre `<form>`-elementet helt (vanlig `<div>` + + Enter-tast-håndtering på inputet + `type="button"` i stedet for + `type="submit"` på søkeknappen) -- gjør nestede skjemaer strukturelt + umulig fremover for denne komponenten. Ekte typesjekket + produksjonsbuild kjørt på nytt, kun `teecup_frontend` redeployet (ingen + backend-endring). **Lærdom for fremtidige skjermer:** en ny + søk-/underskjema-widget som skal plasseres INNI et eksisterende skjema + (slik som denne bane-søk-widgeten ligger inni økt-opprett-skjemaet) må + ALDRI være et eget `<form>` -- bruk `<div>` + eksplisitt + klikk-/Enter-håndtering. + +- **Organisator-overstyring i `team_authz.py`, LIVE (2026-07-18):** reist av + brukeren rett før blind draw-skjermen skulle designes: `app/team_authz.py` + sin `user_may_act_for_team` krevde tidligere en `team_roster`-rad med + lenket bruker-konto — ingen vei for organisatoren til å låse/legge til + deltakere/føre score hvis ingen spiller hadde logget inn ennå (vanlig + tidlig i en turnering). Utvidet til også å godta org-eier/admin + (`organization_membership.role IN ('owner','admin')`), på ETHVERT lag, + ingen unntak for at organisatoren selv er rostret på motstanderlaget. + **Det unntaket ble bevisst vurdert og avvist** (brukeren spurte selv om + det, jeg anbefalte det opprinnelig, men vi kom sammen frem til at det ville + skapt en verre låsning: er organisatoren spillende på Lag A og + Lag B heller ikke har noen innlogget spiller, ville Lag B blitt helt + låst ute) — TeeCup er et tillitsbasert klubb-/vennegjeng-verktøy, ikke en + sikkerhetsgrense mot en fiendtlig organisator som uansett allerede ser + begge lags fulle troppe-liste (blind draw skjuler kun selve + kamp-paringen). Alle 5 kallsteder oppdatert (matches.py sin + add_participant/lock_lineup, scoring.py sin submit_hole_score/ + submit_hole_result). **Verifisert grundig i scratch:** org-eier uten + roster kan nå låse BEGGE lag + føre score, en vanlig 'member'-rolle + fortsatt blokkert (uendret), en rostret spiller fungerer uendret + uavhengig av org-rolle. `test_isolation.sql` 12/12. Ingen migrasjon (ren + Python-endring) — kun `teecup_api` redeployet, `teeoff.no` upåvirket. + +- **Tee-endepunkter bygget og LIVE (2026-07-18), rett før blind draw-skjermen:** + fant et hull som blokkerte selve blind draw-flyten: `match_participant. + tee_id` er påkrevd, men det fantes INGEN `GET`-vei for å liste en banes + tee-er (selv offisielt importerte baner har tee-rader, men ingenting + eksponerte dem) OG egendefinerte («custom») baner har ALDRI hatt noen + vei til å FÅ tee-er i det hele tatt — dette er ikke noe ADR-019 innførte, + det var et hull som fantes fra før custom-baner ble lagt til i første + omgang, bare usynlig til nå. Konsekvens før fiksen: en turnering satt opp + på en manuelt navngitt bane kunne ALDRI få en ekte deltaker lagt til på + noen match. **Presisering (brukeren spurte eksplisitt):** dette er IKKE + noe som må løses i teeoff.no sin kode — egendefinerte baner er per + definisjon baner teeoff ikke kjenner til, så dette er en ren + teecup-intern funksjon, uavhengig av ADR-019-integrasjonen. + Ny `GET/POST /orgs/{id}/courses/{id}/tees` i `app/routers/courses.py`. + `POST` avviser eksplisitt forsøk på offisielle baner (400 + `VALIDATION_FAILED` — de får tee-ene sine fra teeoff-importen, ikke + manuelt). Kun full_18-rating dekket (samme begrunnelse som ADR-019 + Beslutning D). **Bevisst UTENFOR omfang denne runden, egen senere sak:** + hull-/stroke-index-data for egendefinerte baner (`hole`-tabellen forblir + tom for custom-baner) — trengs for korrekt slagfordeling i SCORING-fasen + (ADR-008), ikke i blind draw, så det løses naturlig når scorekort- + skjermen bygges (samme "bygg i rekkefølgen ting brukes"-logikk). + **Verifisert grundig i scratch:** tom tee-liste på fersk custom-bane, + opprett+list tee på custom-bane, offisiell bane viser alle 8 importerte + tee-er, manuell tee-opprettelse avvist på offisiell bane, og — den + faktiske payoff-en — en deltaker lagt til en match på en custom-bane-økt + for FØRSTE gang noensinne (`POST .../matches/{id}/participants` lykkes + nå med en tee_id fra en nyopprettet custom-tee). `test_isolation.sql` + 12/12. Ingen migrasjon, kun `teecup_api` redeployet, `teeoff.no` + upåvirket. + +- **Blind draw-skjermen LIVE (2026-07-18):** syvende V0-skjerm, + `components/session-blind-draw.tsx`, ny rute + `/tournaments/[id]/sessions/[sessionId]`. To lag-kolonner, hver med sine + matcher ("flights"), legg til/fjern spiller+tee per plass (antall plasser + avhenger av format), lås-knapp med bekreftelse, avslørings-visning når + begge lag har låst. Program-skjermens økt-kort er nå klikkbare inn hit. + **Datamodell tilpasset fra V0s mock til API-ets faktiske sett-modell:** + V0 designet faste, indekserte "Slot"-arrays (kontrollert skjema-state); + API-et har verken PATCH på `match_participant` eller noen slot-indeks — + kun opprett/slett av en løs deltaker-mengde per side. Løst med en + "legg til spiller"-inline-form (samme mønster som spiller-/bane-type-ahead + ellers i appen) i stedet for faste dropdown-rader; funksjonelt likeverdig, + strukturelt riktigere for API-ets faktiske form. + **To reelle hull funnet og fikset FØR/UNDER integrering:** + 1. Ingen `DELETE` fantes for `match_participant` — en kaptein kunne aldri + angre et valg før låsing uten å etterlate en foreldreløs rad. Ny + `DELETE /orgs/{id}/matches/{match_id}/participants/{participant_id}` + (samme ALREADY_LOCKED/NOT_ROSTERED_ON_TEAM-sjekker som opprett). + 2. **"Skriv blindt"-hull, funnet under selve scratch-testingen (ikke bare + tenkt ut på forhånd):** forrige rundes org-admin-overstyring i + `team_authz.py` lot en organisator LEGGE TIL deltakere på et lag de + ikke selv er rostret på, men `app/blind_draw.py` sin `own_team_ids()` + sjekket KUN rostret spiller for SYNLIGHET — så organisatoren så aldri + sine egne tilføyelser igjen før begge lag hadde låst. Fikset ved å gi + `own_team_ids()` samme owner/admin-utvidelse som `user_may_act_for_ + team()` — en org-admin ser nå BEGGE lag umiddelbart (konsistent med at + de uansett allerede har full tilgang, se forrige rundes resonnement), + mens en faktisk rostret kaptein fortsatt kun ser sitt eget lag før + reveal. Kun ett reelt kallsted (`matches.py` sin `list_matches`). + **Tredje, urelatert bug fanget under samme scratch-økt og fikset på + brukerens eksplisitte forespørsel:** `POST .../matches/{id}/participants` + krasjet med en rå 500 (`TypeError` i `handicap_engine.py` sin + `course_handicap_raw`) hvis spilleren manglet `handicap_index` OG + `use_handicap` var på (standard) — preeksisterende, ikke noe denne + runden introduserte. Fikset i `app/routers/matches.py` sin + `add_participant`: sjekker nå `handicap_index_snapshot IS NULL` EKSPLISITT + FØR innsetting når `use_handicap` er sann, avviser med en klar + `VALIDATION_FAILED` (400) i stedet for å krasje. Bekreftet at + `use_handicap=false` fortsatt tillater en spiller uten handicap + (scratch-spill), og at en spiller MED handicap fortsatt fungerer uendret. + **Verifisert grundig i scratch, flere runder:** DELETE-endepunktet + (fjern+idempotent 404 ved gjentak+409 etter lås), synlighetsfikset (org- + admin ser begge sider, rostret kaptein ser fortsatt kun eget lag), + null-handicap-fikset (avvist rent, scratch-modus upåvirket, normal + spiller upåvirket), full opprett-match→legg-til-deltaker→lås→avslør- + syklus ende-til-ende. `test_isolation.sql` 12/12 etter hver runde. Ekte + typesjekket produksjonsbuild. + **Rullet ut live**, bruker bekreftet eksplisitt: begge containere + bygget+redeployet (ingen migrasjon), `teeoff.no` upåvirket. + +- **Backend-forarbeid for scorekort-skjermen, LIVE (2026-07-18):** to hull + funnet ved gjennomlesing av `scoring.py` FØR V0-prompten ble skrevet, + samme føre-var-mønster som resten av økten. + 1. **Hull-/stroke-index-data for egendefinerte baner** (bevisst utsatt fra + tee-rundens status-notat) — `hole`-tabellen har ALDRI hatt noen vei inn + for custom-baner, som blokkerte `stroke`-scoringsmodus helt (`hole_ + result`-modus trenger den ikke). Ny `GET/POST /orgs/{id}/courses/{id}/ + holes` i `app/routers/courses.py` — engangs alle-18-på-en-gang + (avviser feil antall, avviser hvis banen allerede har hull, avviser på + offisielle baner som får hullene sine fra teeoff-import). + 2. **`GET scorecard` viste kun UTLEDET vinn/tap/delt per hull, aldri de + faktiske tallene som var registrert** — umulig å bygge en skjerm som + viser gjeldende tilstand ved gjenlasting. Utvidet `Scorecard`-modellen + i `app/routers/scoring.py` med `stroke_entries`/`hole_result_entries` + (rå `hole_score`/`match_hole_result`-rader, nøyaktig ett av de to fylt + ut avhengig av øktens scoring_mode). + **Verifisert grundig i scratch:** hull-validering (feil antall avvist, + gjentatt oppsett avvist), og en FULL `stroke`-modus scoringsrunde med + ekte handicap-justert nettoberegning på en egendefinert bane for FØRSTE + gang noensinne (ulikt slagtall — 4 mot 5 — ga korrekt "halved" etter + handicap-utjevning), scorecard sin nye `stroke_entries` bekreftet å + returnere nøyaktig det som ble registrert. `test_isolation.sql` 12/12. + **Rullet ut live**, ingen migrasjon, kun `teecup_api` redeployet, + `teeoff.no` upåvirket. + +- **Scorekort-skjermen LIVE (2026-07-18):** åttende V0-skjerm, + `components/session-scorecard.tsx`, ny rute `/tournaments/[id]/sessions/ + [sessionId]/matches/[matchId]`. Ett hull i fokus om gangen (banebruk, + ikke et regneark) — store slag-steppere for `stroke`-modus, tre-valgs + vinner-knapper for `hole_result`-modus, hull-chip-navigasjon, kollapsbar + full oversikt, feiret "avgjort"-banner som låser alt til read-only. + Blind draw sin avslørte visning lenker nå til hver match sitt scorekort. + **Ingen nye backend-hull denne runden** — forrige rundes forarbeid + (hull-endepunkter + `stroke_entries`/`hole_result_entries` i scorecard) + dekket akkurat det skjermen trengte. + **Verifisert grundig i scratch, inkludert et fullt oppsett som speiler + NØYAKTIG frontend-ens egen last-sekvens** (sessions→teams→matches→ + holes→scorecard, deretter en ekte `POST hole-scores` for begge spillere + på hull 1 og en refetch): status_text/derivert resultat oppdaterte seg + korrekt ("1 UP (A)", hull 1 → "a"), alle feltnavn stemte eksakt med + TypeScript-typene uten justering. `test_isolation.sql` 12/12, ekte + typesjekket build. + **Rullet ut live**, ren frontend-endring, `teeoff.no` upåvirket. + +- **Leaderboard-backend LIVE (2026-07-18):** ny `GET /orgs/{id}/ + tournaments/{id}/leaderboard` i `app/routers/tournaments.py` — summerer + poeng på tvers av ALLE økter/matcher i turneringen, både total og + per-økt-delsum. + **Reelt korrekthetshull funnet OG designet rundt FØR koden ble skrevet, + ikke oppdaget i ettertid:** `match.team_a_id`/`team_b_id` settes PER + MATCH ved opprettelse, ikke garantert konsistent på tvers av matcher — + en organisator kunne i prinsippet opprettet match 1 med team_a=Rød og + match 2 med team_a=Blå. En naiv summering av `points_side_a`/`points_ + side_b` ville da blandet sammen poeng fra to ULIKE fysiske lag. Løst ved + å ALDRI summere på "a"/"b"-labelen — kun på det ekte lag-id-et + (`points_by_team: dict[team_id, float]`, både i totalen og per økt). + **Verifisert presist, ikke bare "kjørte uten feil":** bygget et scratch- + scenario med 3 matcher over 2 økter der match 2 sin team_a/team_b + BEVISST var byttet om i forhold til de to andre — satte poeng direkte + (Rød vinner alle tre, ett delt) og bekreftet at leaderboardet likevel ga + riktig total (Rød 2.5, Blå 0.5) og riktig per-økt-delsum, til tross for + swap-en. `test_isolation.sql` 12/12. Ingen migrasjon, kun `teecup_api` + redeployet, `teeoff.no` upåvirket. Frontend-skjermen gjenstår. + +- **Leaderboard-skjermen LIVE (2026-07-18), samme dag:** niende og siste + V0-skjerm i "bygg i rekkefølgen ting brukes"-serien for kamp-play-flyten. + `components/tournament-leaderboard.tsx`, ny rute `/tournaments/[id]/ + leaderboard`. Stort scoreboard-kort med de to lagenes totalpoeng + (lederen fremhevet), pluss en per-økt poeng-fordelingsliste med + fullført/ikke-startet-status. V0 la selv til et tredje faneelement + ("Leaderboard") i BÅDE program- og roster-skjermens fanerad denne + runden — portert inn i de LIVE versjonene av begge (med riktig `org`- + parameter, som V0s eksport som vanlig manglet). + **Ingen nye backend-hull** — forrige rundes leaderboard-endepunkt dekket + akkurat det skjermen trengte. + **Verifisert i scratch:** full datamodell-runde (samme felt-for-felt + som backend-verifiseringen dagen før) OG et eget tomtilstand-scenario + (fersk turnering med 2 lag, 0 økter — `sessions: []`, `matches_total: 0`) + for å bekrefte at "ingen økter opprettet ennå"-meldingen vises riktig + i stedet for å krasje på et tomt array. `test_isolation.sql` 12/12, ekte + typesjekket build. + **Rullet ut live**, ren frontend-endring, `teeoff.no` upåvirket. + **Hele "bygg i rekkefølgen ting brukes"-serien for match-play-flyten er + dermed komplett:** oppsett (lag/roster) → program → blind draw → + scorekort → leaderboard. +- **Offisiell bane-import: idempotent + tydeligere navn, LIVE (2026-07-18), + rapportert av brukeren som faktisk brukte funksjonen på ekte + produksjonsdata:** to reelle problemer, begge funnet ved å lese (kun + lesing) de faktiske dataene for "De Gamle er Eldst" FØR noe ble antatt. + 1. **`POST .../courses/official-import` var IKKE idempotent** — å + importere samme bane på nytt (helt vanlig: flere økter spilles ofte + på samme bane) ga en 409 `DUPLICATE`-feil (migrasjon 010 sin sperre) + i stedet for å bare gi tilbake den allerede importerte banen. Fikset: + sjekker nå `external_course_ref` FØR noe teeoff-kall gjøres — finnes + banen fra før, returneres den eksisterende raden direkte (også + raskere, og robust mot at teeoff er nede akkurat da). + 2. **Lagret navn var kun selve banens navn ("Hovedbanen"), ikke hvilken + klubb** — ubrukelig til å skille baner fra hverandre, siden mange + klubber navngir hovedbanen sin identisk. Fikset: navnet kombineres nå + til "{anlegg} – {bane}" (f.eks. "Tjøme Golfklubb – Hovedbanen") ved + import. + **Reell konsekvens av hull #1 funnet i produksjonsdata:** brukeren hadde, + mens hen forsøkte å søke opp Tjøme via det ØVERSTE banefeltet (som søker + organisasjonens EGNE baner, ikke teeoff), ved et uhell trigget «Opprett + ny bane: «Tj»»-snarveien og fått en tom, søppel `custom`-bane hengende på + Foursome-økten -- en ekte forvekslingsfelle mellom de to adskilte + bane-søkeflatene (eget vs. teeoff), ikke en kodefeil i seg selv. + **Data ryddet opp i EKTE `teecup_db`, bruker bekreftet eksplisitt:** + Foursome-økten pekt om til den allerede importerte, ekte Tjøme-banen + (samme bane som Fourball-økten allerede brukte -- nøyaktig det + organisatoren egentlig ønsket), søppel-«Tj»-banen slettet (bekreftet + ingen matcher/tee-er/hull hang på den FØR sletting), og den ekte banens + navn oppdatert til "Tjøme Golfklubb – Hovedbanen". Verifisert i etterkant + at begge økter nå peker til samme, korrekt navngitte bane. + **Verifisert mot ekte teeoff_api i scratch FØR utrulling:** importerte + Borregaard på nytt to ganger — andre kallet ga nøyaktig samme course-id + (ikke en duplikat-rad), navnet kom ut som "Borregaard Golfklubb – + Hovedbanen". `test_isolation.sql` 12/12. Ingen migrasjon, kun `teecup_api` + redeployet, `teeoff.no` upåvirket. +- **`PATCH`/`DELETE` for økter, LIVE (2026-07-18), samme dag:** brukeren + spurte rett etter opprydningen om det i det hele tatt var mulig å + rette/slette en feiloppsatt økt — det var det ikke (kun `POST`/`GET` + fantes). Ny `PATCH /orgs/{id}/sessions/{id}` (`app/routers/ + tournaments.py`) for enkle felt (navn, klokkeslett/intervall, starthull, + poeng, handicap-brytere) via vanlig `exclude_unset`-mønster, PLUSS en egen + gren for bane-bytte. Ny `DELETE /orgs/{id}/sessions/{id}` — kun tomme + økter (ingen matcher), avviser med 409 ellers (bruk PATCH til å korrigere + i stedet). + **Banebytte-scenarioet brukeren selv reiste** ("5 hull spilt, oppdager + feil bane — slagene er ekte, utregningen er trolig feil") krevde egen + design: `match_participant.tee_id` peker til en tee som HØRER til den + gamle banen. Løst med `_remap_course()` — finner en tee med samme + navn+kjønn på den nye banen for hver allerede tillagte deltaker, flytter + dem dit, og avviser HELE bane-byttet tydelig (400, ingenting skrevet, + bekreftet transaksjonell rollback) hvis den nye banen mangler en + tilsvarende tee. Etter et vellykket bytte: handicap regnes om for alle + berørte deltakere, og matchstatus/poeng regnes om for HVER match i + økten — **bevisst uavhengig av om matchen allerede er avgjort** (brukeren + bekreftet eksplisitt at en bane-korrigering skal kunne endre et allerede + cachet resultat). `recompute_and_cache_match_state` i `app/routers/ + scoring.py` gjort delt (fjernet ledende understrek) for gjenbruk fra + tournaments.py. + **Reelt, urelatert funn underveis i scratch-testingen, IKKE fikset:** + `tee_rating` lages i dag ALLTID kun med `full_18`-omfang (både ved + teeoff-import og manuell tee-opprettelse) — en økt satt til `front_9`/ + `back_9` i `stroke`-modus kan derfor ALDRI få handicap beregnet + (`compute_and_store_side_handicaps` sin `tee_rating`-join finner aldri + noen rad), og dermed aldri avgjøre noen hull. `hole_result`-modus + upåvirket. Flagget til bruker, bevisst latt urørt denne runden. + **Verifisert grundig i scratch:** enkelt feltbytte (kun navn), fullt + banebytte-scenario bygget nøyaktig som brukerens eksempel (10 hull spilt + under feil bane, matchen allerede avgjort 10&8, PATCH til riktig bane → + tee-er ombyttet korrekt, handicap endret fra 7/9 til 10/13 under den nye + banens rating, matchstatus regnet på nytt med UENDREDE rå slagtall), + avvist bane-bytte ved manglende tee-match (bekreftet full rollback, + også av det urelaterte navnefeltet i samme kall), DELETE avvist på økt + med match (409) og godtatt på tom økt (204). `test_isolation.sql` 12/12. + Ingen migrasjon, kun `teecup_api` redeployet, `teeoff.no` upåvirket. + Frontend (rediger-/slett-knapper i program-skjermen) ikke bygget ennå — + kun backend-kapasiteten denne runden. +- **Rediger/slett-UI for økter LIVE (2026-07-19):** V0-utvidelse av den + eksisterende Program-skjermen (ingen ny rute) — "..."-meny (samme + DropdownMenu-mønster som roster-skjermens per-spiller-handlinger) med + "Rediger"/"Slett" per øktkort. Rediger bytter kortet til et inline-skjema + (samme felt som PATCH støtter: navn, poeng, starthull, klokkeslett/ + intervall, bane). Slett viser en bekreftelse; treffer den ekte 409-en + (økt har matcher), vises backend sin egen feiltekst i stedet for en + forhåndsberegnet klient-tilstand. + **Reell regresjon funnet OG UNNGÅTT i selve V0-eksporten, ikke i + etterkant:** denne rundens V0-prompt handlet kun om rediger/slett, men + eksporten hadde samtidig (utilsiktet) FJERNET bane-feltet fra "Legg til + økt"-skjemaet helt — nye økter ville stille blitt satt til en hardkodet + mock-bane. Fanget under diff-mot-live-treet FØR noe ble tatt inn (samme + rutine som alltid) — `CreateSessionCard` (med sitt ekte bane-søk/ + teeoff-import) beholdt fullstendig urørt; kun de nye rediger/slett- + delene ble hentet inn. + **Bane-bytte i redigeringsskjemaet er bevisst ENKLERE enn opprett- + skjemaets bane-felt** — kun velg blant organisasjonens eksisterende + baner eller hent fra teeoff, INGEN "opprett ny bane: X"-snarvei her + (PATCH sin `course_id` må være en ekte, allerede eksisterende bane, ikke + en tekststreng) — unngår at samme "Tj"-forvekslingsfelle fra forrige + banebytte-hendelse kan gjenta seg via redigeringsveien. + **Verifisert:** ekte PATCH med nøyaktig skjemaets feltform, ekte DELETE + (204 tom økt, 409 med matcher — bekreftet at `{"detail":{"code", + "message"}}`-formen leses riktig av UI-et), `test_isolation.sql` 12/12, + ekte typesjekket build. Rullet ut live, ren frontend-endring, `teeoff.no` + upåvirket. + +- **Invitasjonskode + ledende side + projisert stilling, BACKEND LIVE + (2026-07-19, ADR-020):** brukeren reiste tre relaterte hull rett etter at + "bygg i rekkefølgen ting brukes"-serien var ferdig: (1) ingen vei inn til + en turnering for en spiller som bare har fått muntlig beskjed, (2) + leaderboardet viser kun faktisk opptjente poeng, ikke hva stillingen ville + blitt om pågående matcher holder seg, (3) ingen fargekoding i matchlister + for hvem som leder. Full ADR-020 skrevet (4 delbeslutninger, se + ARCHITECTURE_DECISIONS.md) — nøkkelbeslutning bekreftet eksplisitt av + bruker FØR bygging: en invitasjonskode OVERSTYRER `tournament.visibility` + helt (koden ER selve invitasjonen, ikke en snarvei som fortsatt krever + eksisterende tilgang). + Ny migrasjon `011_join_code_and_leading_side.sql`: `tournament.join_code` + (6 tegn, alfabet uten 0/O/1/I, globalt unikt, backfylt for eksisterende + rader), `match.leading_side` (cachet fortegn av `MatchState.lead`, samme + mønster som `status_text`/`points_side_a/b`), fjerde + `SECURITY DEFINER`-bro `public_tournament_by_code()` (etter + `public_tournament_org` 007, `link_player_by_email` 008, + `public_org_by_slug` 009). + **Backend bygget:** `create_tournament` genererer koden (retry-løkke ved + kollisjon, astronomisk usannsynlig med 33^6 kombinasjoner). Ny + `GET /public/tournaments/by-code/{code}` (MÅ registreres FØR + `/{tournament_id}` i routeren, ellers tolkes "by-code" som en ugyldig + UUID). `GET /public/tournaments/{id}`, `GET .../sessions` og + `POST .../register` godtar alle en valgfri `code`-parameter som — når den + matcher — hopper `_check_visibility()` helt over. + `recompute_and_cache_match_state` (scoring.py) cacher nå `leading_side` + ved HVER hull-innsending, ikke bare ved avgjørelse. Leaderboard- + endepunktet fikk `projected_points`/`projected_points_by_team`: + avgjorte matcher bidrar likt til faktisk og projisert, ikke-avgjorte gir + hele `points_per_match` til `leading_side` (delt 0,5/0,5 ved "AS"/ikke + startet) — speiler hvordan Ryder Cup-TV-dekning viser "hvis det sluttet + nå". + **Reell hendelse underveis, håndtert transparent (ikke skjult):** en + feilformulert `docker exec teeoff_db env | grep -i POSTGRES`-kommando + (ment å liste variabelNAVN) fanget opp `POSTGRES_PASSWORD` sin VERDI også, + siden selve nøkkelnavnet matchet søkemønsteret — eksponerte `teeoff_db` + sitt superbruker-passord (`teeoff_admin`) i verktøyresultatet. Alvorligere + enn de to tidligere passord-hendelsene i prosjektet siden dette er + superbrukeren for HELE den delte Postgres-klyngen (teeoff OG teecup), ikke + en enkelt tjeneste-credential. Flagget til bruker umiddelbart, som valgte + å rotere. Rotert trygt UTEN å noensinne re-eksponere gammel ELLER ny verdi + i noe synlig kommandoresultat: `ALTER ROLE` kjørt via lokal + Unix-socket-`trust`-auth (bekreftet ved å lese `pg_hba.conf`, ingen + hemmelighet involvert i den sjekken) — krevde altså IKKE det gamle + passordet i det hele tatt. Nytt passord generert med `openssl rand -hex + 32` (hex, ikke base64 — unngår SAMME klasse URL-enkodings-felle som + `TEECUP_DATABASE_URL`-hendelsen tidligere, siden verdien også ligger i en + `postgres://`-DSN). `/opt/teeoff/.env` sine TO forekomster + (`POSTGRES_PASSWORD` og `DATABASE_URL`) oppdatert med `sed`-mønstre som + ALDRI leser/skriver ut den gamle verdien. `teeoff_api`/`teeoff_worker` + service-nøklene i `docker-compose.prod.yml` viste seg å hete `api`/ + `worker` (ikke `teeoff_api`/`teeoff_worker` — det er kun + `container_name`), samme "service-nøkkel ≠ container-navn"-fallgruve som + nettverksalias-hendelsen fra containeriseringsrunden. `docker compose up + -d --force-recreate api worker` gjenskapte OGSÅ `teeoff_db` selv (ikke + eksplisitt navngitt) — Compose oppdager konfigurasjonsendring + (`${POSTGRES_PASSWORD}` i `db`-tjenestens egen `environment:`) og + gjenskaper uansett hvilke tjenester som ble navngitt. Verifisert grundig + ETTERPÅ: `teeoff_db`-loggen viste "Skipping initialization" (datavolum + urørt, ikke reinitialisert) + ren oppstart, `teeoff_api`/`teeoff_worker` + ren oppstart uten en eneste feil-/auth-/passord-linje i hele loggen, + `teeoff.no` OG `teecup.teeoff.no/health` begge `200` etterpå. + **Scratch-verifisert grundig** (fersk `teecup_scratch` 001→011, + isolert `teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs + API-container): `test_isolation.sql` 12/12 (måtte først rettes — tre + RÅ `INSERT INTO tournament`-steder i selve testfilen predaterte + `join_code` og traff den nye NOT NULL-constrainten, rettet med + dummy-koder). Full ende-til-ende-runde: to turneringer opprettet, ulike + koder bekreftet; `by-code`-oppslag bekreftet for kjent OG ukjent kode + (404); anonym lesing av en `org`-synlig turnering BLOKKERT uten kode + (403 NOT_VISIBLE), TILLATT med riktig kode (inkl. case-insensitivt), + FORTSATT blokkert med feil kode; samme mønster bekreftet for + `POST .../register`. Full hull-for-hull-simulering av en singel-match + (hole_result-modus): `leading_side`/`status_text` fulgte hverandre + eksakt gjennom "1 UP (A)" → "AS" (leading_side=null) → avgjort "9&7 + (A)", leaderboardets `projected_points` traff nøyaktig 1.0/0.0 mens A + ledet, 0.5/0.5 ved "AS", og ble likt `points`/`projected_points` (begge + 1.0/0.0) etter avgjørelse. + **Rullet ut mot ekte `teecup_db` 2026-07-19**, bruker bekreftet + eksplisitt: migrasjon 011 kjørt (eneste eksisterende turnering fikk + automatisk generert kode), `test_isolation.sql` fortsatt 12/12, kun + `teecup_api` redeployet (ingen frontend-endring i denne del-runden), + `teeoff.no` upåvirket. + **Frontend fullført samme dag, egen del-runde:** `login-form.tsx` fikk et + eget kode-modus (`JoinByCode`) — «Har du en invitasjonskode?»-lenke bytter + ut e-post-skjemaet, slår opp `/public/tournaments/by-code/{code}` og + navigerer til `/t/{id}?code=...` med Next sin `useRouter`. `code` + query-param tres gjennom hele veien: `app/t/[id]/page.tsx` leser + `searchParams`, `public-tournament.tsx` sender den med på BÅDE + info-/sessions-lesingen og selve `POST .../register` (ikke bare det + første oppslaget som tok deg dit). + `tournament-detail.tsx` viser koden i en egen kopier-chip i headeren — + ingen enkelt-turnering-`GET` fantes, så komponenten henter i stedet hele + org-ens turneringsliste (som allerede bærer `join_code`) og finner egen + rad, i stedet for å legge til et nytt endepunkt kun for dette. + `tournament-leaderboard.tsx` fikk en ny `SegmentedBar`-komponent — ETT + fargesegmentert rektangel per bar (ikke tall side om side), proporsjonalt + med hvert lags poeng, 50/50 nøytralt ved 0-0. To slike bares rett under + headeren: "Stilling nå" (faktisk) og "Projisert (hvis pågående matcher + holder seg)" — den EKSISTERENDE store tall-scoreboarden beholdt uendret + lenger ned som detaljvisning. + `session-blind-draw.tsx` sin `RevealedView`: matchkortet får nå en farget + toppkant (leaderens `team.color`) og en `status_text`-chip (fylt farge + ved avgjort match m/ konfetti-ikon, lys tone ved pågående) i stedet for + kun klokkeslettet; `RevealSide` for ledende side får en svak fargetonet + bakgrunn. `leading_side`/`status_text`/`points_side_a/b` lagt til + `ApiMatch`-typen (var der allerede i API-et, bare ikke konsumert + frontend-siden før nå). + **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` + som deployes, ikke dev-server) kompilerte rent, alle 10 ruter listet. + Rullet ut (kun `teecup_frontend`, ingen backend-endring i denne delen), + `teecup.teeoff.no/dashboard` og `/` → 200, `teeoff.no` upåvirket. Ekte + smoke-test i produksjon: `by-code`-oppslag for "De Gamle er Eldst" sin + faktiske kode ga riktig turnering-id, `/t/{id}?code=...` ga 200. + **ADR-020 er dermed helt ferdig** (backend + frontend, alle fire + del-ønsker: kode-basert oppdagelse, kode-felt på login, projisert + stilling, fargekoding av matcher) — bortsett fra kode-regenerering, som + er bevisst utsatt (se FEATURE_BACKLOG.md). + **Reell driftshendelse underveis** (mellom backend- og frontend-delen, + under scratch-oppsett): en feilformulert `grep -i POSTGRES`-kommando + eksponerte `teeoff_db` sitt superbruker-passord ved et uhell. Flagget + umiddelbart, brukeren valgte å rotere — se detaljene under + backend-avsnittet over for hele hendelsen og hvordan roteringen ble + gjennomført uten å noensinne re-eksponere gammel eller ny verdi. +- **Sesjons-bug diagnostisert og FIKSET, LIVE (2026-07-19):** brukeren + rapporterte at hen måtte be om ny magic-link-kode ved HVERT besøk til + `teecup.teeoff.no`, til tross for ADR-009s 30-dagers sesjonscookie. Bad + brukeren sjekke den EKTE cookien i nettleseren fremfor å gjette — kom + tilbake korrekt satt i alle henseender (`Expires` 30 dager frem, + `Secure`/`HttpOnly`/`SameSite=Lax`). Rot-årsaken var derfor IKKE cookien + eller backend-en: `frontend/app/page.tsx` (rot-siden) viste ALLTID + innloggingsskjemaet uten noensinne å sjekke om en gyldig sesjon allerede + fantes. `Dashboard`-komponenten sjekker `/auth/me` og sender til `/` ved + MANGLENDE sesjon, men ingen kode gjorde det motsatte — en bruker som + besøkte roten direkte (i stedet for å navigere til `/dashboard`) så + derfor alltid innloggingsskjemaet uansett sesjonsstatus. + **Fikset:** `page.tsx` gjort om til en async server-komponent som leser + sesjonscookien via `next/headers`, kaller `/auth/me` server-til-server + direkte mot `TEECUP_API_ORIGIN` (samme mønster som `generateMetadata` i + `app/t/[id]/page.tsx` — IKKE gjennom `next.config.mjs` sin `rewrites()`, + som kun gjelder nettleser-trafikk), og sender en allerede innlogget + bruker videre til `/dashboard` med `redirect()` FØR innloggingsskjemaet + når rendres. + **Verifisert presist mot den ekte, live stacken, med brukerens EGEN + ekte sesjonscookie** (ikke en syntetisk test): `curl` uten cookie mot + `https://teecup.teeoff.no/` ga `200` (skjemaet vises, riktig for en + anonym besøkende); samme kall MED den ekte cookien ga `307` til + `/dashboard` (riktig — sender en allerede innlogget bruker rett videre). + Ekte typesjekket produksjonsbuild kjørt FØR utrulling (build-outputet + viste selv at `/` nå er `ƒ` dynamisk i stedet for `○` statisk — bekrefter + at server-sjekken faktisk ble tatt i bruk). Rullet ut live, kun + `teecup_frontend`, ingen migrasjon, `teeoff.no` upåvirket. + **Samme runde:** brukeren stilte to oppfølgingsspørsmål om + autentisering/autorisasjon — «er flere-organisasjoner-eierskap tenkt + gjennom» (bekreftet: ja, ADR-002 fra dag én, ingen kodeendring nødvendig) + og et ønske om passord (valgfritt tillegg)+2FA. Fullt design skrevet som + **ADR-021** (passord/2FA) og **ADR-022** (dele/invitere/frasi seg + eierskap + superadmin) — se ARCHITECTURE_DECISIONS.md. +- **ADR-021 (passord/2FA) + ADR-022 (org-eierskap) BYGGET OG LIVE + (2026-07-19), samme dag:** brukeren ba om begge sammen («Bygg det», + bekreftet eksplisitt at det gjaldt begge ADR-ene i samme runde). Ny + migrasjon `012_password_2fa_and_org_invitations.sql`: `app_user. + password_hash`/`two_factor_method`/`totp_secret`/`is_super_admin`, ny + tabell `two_factor_code` (samme hash-og-utløp-mønster som + `magic_link_token`), ny tabell `organization_invitation` (RLS + org-isolert, INGEN egen klikkbar aksept-lenke — godtas automatisk ved + neste innlogging med matchende e-post), femte + `SECURITY DEFINER`-bro `accept_pending_invitations_by_email()` (etter + `public_tournament_org` 007, `link_player_by_email` 008, + `public_org_by_slug` 009, `public_tournament_by_code` 011). + **Sesjons-STADIER innført i `app/auth.py`:** en sesjonscookie er ikke + nødvendigvis en full sesjon lenger — `create_session_token()` tar nå en + `stage`-parameter (`full`/`pending_2fa`/`must_enroll_2fa`), lagt inn som + et JWT-claim. `get_current_user` avviser eksplisitt alt annet enn `full` + ELLER en ELDRE token uten stage-claim i det hele tatt (utstedt før denne + runden — behandlet som `full` for bakoverkompatibilitet, ingen + eksisterende bruker logget brått ut). To nye avhengigheter: + `get_pending_user` (kun for 2FA-verifiseringsendepunktene) og + `get_current_or_enrolling_user` (godtar BÅDE en full sesjon — frivillig + 2FA-oppsett fra kontoinnstillinger — OG `must_enroll_2fa` — tvunget + oppsett rett etter innlogging — samme oppsett-logikk dekker begge + veiene). `get_current_user_optional` fikk samme stage-sjekk for + konsistens. + **Passord (Argon2id, ikke bcrypt):** bevisst valg for å unngå bcrypt sin + stille 72-byte-trunkering, siden brukeren eksplisitt ba om korrekt + håndtering av spesialtegn/mellomrom. `POST /auth/set-password`/ + `/remove-password`/`/login-password` — sistnevnte svarer med IDENTISK + 401 uansett om e-posten finnes, mangler passord, eller passordet er + feil (samme anti-enumerering som magic-link). + **2FA (TOTP via `pyotp` ELLER e-post-engangskode via eksisterende SMTP, + brukerens eget valg):** `POST /auth/2fa/setup/start` genererer en + TOTP-secret UTEN å lagre den (rundturer til klienten, som ekkoer den + tilbake i `/setup/confirm` — unngår en halvferdig 2FA-tilstand i + databasen hvis brukeren forlater oppsettet). QR-kode generert + server-side (`qrcode`-biblioteket + eksisterende Pillow-avhengighet, + ingen ny ekstern tjeneste). `POST /auth/2fa/verify` fullfører en + PÅGÅENDE innlogging. + **Tvungen 2FA for org-eier/admin (ADR-021 Beslutning D):** + `user_requires_2fa_enrollment()` sjekket ved HVER innlogging (ikke bare + første gang, siden en bruker kan bli eier av en NY org etter at kontoen + allerede eksisterer uten 2FA) — verifisert eksplisitt: en fersk + org-eier uten 2FA ble korrekt blokkert fra all normal tilgang (401) og + tvunget inn i oppsett-flyten før noe annet ble tilgjengelig. + **Organisasjonseierskap (ADR-022):** `POST/GET/DELETE + /orgs/{id}/invitations` (owner→enhver rolle, admin→KUN member — ellers + en privilegie-eskaleringsvei), `PATCH/DELETE /orgs/{id}/memberships/ + {id}` (owner kan endre/fjerne hvem som helst; en bruker kan ALLTID + SENKE egen rolle selv — aldri heve den, ville vært selv-forfremmelse — + og alltid forlate selv), «siste eier»-vern (409 `LAST_OWNER`, `FOR + UPDATE`-lås mot race på alle eier-rader, samme TOCTOU-mønster som + ADR-011s to-lags-grense). Superadmin (`app_user.is_super_admin`, KUN + manuelt DB-tildelt — bevisst INGEN API-vei til å gi seg selv eller + andre flagget) får en parallell autorisasjonssti + (`get_superadmin_user`) som kan sette medlemskap på ENHVER org, + uavhengig av eget medlemskap — bevisst avgrenset til nøyaktig dette, + ikke generell tilgang til andres turnering-/spillerdata. + **Tre reelle bugs funnet OG fikset UNDER scratch-testing, ingen nådde + produksjon:** + 1. `verify_magic_link` sendte en rå asyncpg-`UUID` (ikke streng) videre + til sesjonsutstedelse — `jwt.encode()` sin JSON-serialisering + krasjet rått (500) på selve innloggingen. Fant umiddelbart ved første + reelle innloggingstest. Rettet med en eksplisitt `str()`. + 2. OG 3. Både `2fa/setup/confirm` og `2fa/verify` kalte først den delte + `_issue_login_result()`-hjelpefunksjonen (ment for PRIMÆR + autentisering) EN GANG TIL etter at 2FA nettopp var bekreftet — som + så (korrekt, men feil kontekst) at `two_factor_method` nå var satt + og krevde EN NY runde med 2FA for akkurat den samme innloggingen, + en uendelig løkke. Fant ved å faktisk fullføre hele innloggings- + syklusen med ekte genererte TOTP-koder (`pyotp` i test-scriptet), + ikke bare ved å lese koden. Rettet ved at begge endepunktene nå + utsteder en full sesjon DIREKTE etter vellykket 2FA-bekreftelse, + ikke via gjenbruk av den generelle sjekken. + **Frontend:** `login-form.tsx` fikk en tredje modus (passord, ved siden + av magic-link og ADR-020s invitasjonskode-modus). Ny delt + `two-factor-flow.tsx` (`TwoFactorVerifyForm`/`TwoFactorSetupForm`), + brukt av BÅDE `login-form.tsx` og `verify-form.tsx` siden begge + primær-autentiseringsveiene kan returnere samme 2FA-mellomtilstand. Ny + `/account`-skjerm (sett/fjern passord, aktiver/deaktiver 2FA) og ny + `/orgs/[id]/members`-skjerm (invitere, endre rolle, fjerne/forlate), + begge lenket fra dashbordets header/org-visning. `next.config.mjs` sin + `rewrites()` utvidet med `/superadmin/:path*` (ADR-016s konsekvens for + enhver ny API-prefiks, selv om ingen frontend-UI faktisk bruker den + ennå). + **Verifisert grundig mot fersk `teecup_scratch`** (001→012, + `test_isolation.sql` 12/12): full magic-link-bakoverkompatibilitet + (eksisterende flyt uendret for brukere uten 2FA), passord med ekte + spesialtegn/mellomrom/æøå satt og brukt til pålogging, full TOTP-runde + (oppsett→bekreft→logg ut→logg inn→krev 2FA→verifiser med ekte + `pyotp`-generert kode), full e-post-2FA-runde (samme mønster, feil kode + avvist, kode ikke gjenbrukbar), tvungen 2FA-registrering for ny + org-eier, invitasjon→auto-aksept ved førstegangsinnlogging, + rolle-eskalering-forsøk avvist på tre distinkte måter (medlem kan ikke + invitere, admin kan ikke gi eierskap, bruker kan ikke forfremme seg + selv), siste-eier-vern (både PATCH og DELETE), duplikat-invitasjon + avvist, superadmin-sti fungerer/avvises riktig i begge retninger. Ekte + typesjekket produksjonsbuild av frontend (alle nye ruter listet). + **Rullet ut mot ekte systemer 2026-07-19**, bruker bekreftet eksplisitt: + migrasjon 012 kjørt mot ekte `teecup_db`, `test_isolation.sql` fortsatt + 12/12, begge containere (`teecup_api`, `teecup_frontend`) redeployet og + bekreftet ren oppstart, `/health` og `/dashboard` fortsatt 200 (eksisterende + sesjoner uendret av stage-bakoverkompatibiliteten), `/auth/login-password` + bekreftet nåbart over ekte https, `teeoff.no` upåvirket. + **ADR-021 og ADR-022 er dermed begge helt ferdig** — backend + frontend. + Bevisst utenfor omfang: dedikert superadmin-UI (brukes via API av en + betrodd operatør), SMS som 2FA-metode. + +- **Program-skjerm: tydeligere klikk-hint på øktkort (2026-07-19):** brukeren + påpekte at ingenting i grensesnittet indikerte at et øktkort er klikkbart + inn til blind draw-skjermen. Lagt til en synlig "Sett opp flights og lås + oppstilling →"-rad nederst i hvert kort (`components/tournament-program.tsx`). + Ren frontend-endring, ingen backend-rørt. +- **Brukerroller: kaptein som reell autorisasjon, deltaker-avgrenset scoring + (2026-07-19, ADR-023):** direkte oppfølging av det lenge åpne + "Brukerroller"-punktet i FEATURE_BACKLOG.md. Fire beslutninger avklart + eksplisitt med bruker (AskUserQuestion) før bygging — alle anbefalte valg. + **Bygget:** `app/team_authz.py` skrevet om — `user_is_team_captain` + (erstatter `user_may_act_for_team`) krever `is_captain=true` på + `team_roster` (eller org-eier/admin) for å legge til/fjerne deltakere og + låse et lag (`matches.py`); ny `user_is_match_participant` krever en ekte + `match_participant`-rad for brukeren i AKKURAT den matchen (valgfritt + side-spesifikk via `team_side`) for å føre/korrigere score (`scoring.py`) + — uavhengig av kapteinmerket. Nye feilkoder `NOT_TEAM_CAPTAIN` og + `NOT_MATCH_PARTICIPANT` (erstatter `NOT_ROSTERED_ON_TEAM` på disse fem + stedene). `app/routers/tournaments.py` sin `PATCH`/`POST .../roster` + håndhever nå "kun én kaptein per lag" (fjerner automatisk forrige + kapteins merke i samme transaksjon). + **Reelt funn FØR utrulling, ikke antatt:** sjekket (kun lesing, superbruker + mot ekte `teecup_db`) om noen eksisterende lag ville blitt låst ute av en + ren kaptein-only-regel — "De Unge" i "De Gamle er Eldst" har i dag 0 av 2 + roster-rader merket kaptein. Designet derfor en bevisst fallback i + `user_is_team_captain`: har laget INGEN utpekt kaptein ennå, godtas enhver + rostret spiller i stedet for å låse laget helt ute. Ingen lag hadde flere + kapteiner, så "kun én kaptein"-håndhevelsen krevde ingen data-opprydning. + **Reell bug funnet OG fikset UNDER scratch-testing:** `user_is_match_ + participant` sin SQL sammenlignet `mp.team_side` (enum-kolonne) direkte + mot en tekst-parameter uten cast når `team_side=None` (hole_result-modus) + — ga en rå 500 (`UndefinedFunctionError: operator does not exist: team_side + = text`). Rettet med et eksplisitt `mp.team_side::text = $3`. + **Verifisert grundig mot fersk `teecup_scratch`** (isolert + `teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container): + et 15-punkts Python/httpx-testskript som simulerte fire innloggede + brukere (organisator + tre rostrede spillere på to lag) gjennom hele + syklusen — lag uten kaptein tillater enhver rostret (fallback bekreftet), + kaptein utpekt fjerner andre rostredes rettighet, "kun én kaptein" bekreftet + (ny kaptein avsetter automatisk forrige), org-admin fungerer uendret + uavhengig av kaptein, og scoring (`hole_result`-modus) bekreftet begrenset + til faktiske matchdeltakere (en kaptein som IKKE selv spiller matchen ble + korrekt avvist med `NOT_MATCH_PARTICIPANT`, mens en faktisk deltaker og + org-admin begge fikk føre score). Måtte også oppdage og legge til et + forutsetning-steg underveis: `get_authorized_org` krever + `organization_membership` for ALLE org-scopede endepunkter uansett — en + rostret spiller må derfor også være invitert som org-medlem (minimum + 'member', ADR-022s invitasjonsflyt) for i det hele tatt å nå + team_authz-vurderingen; ikke en bug, men en forutsetning testskriptet + først manglet. `test_isolation.sql` 12/12 uendret (ingen skjemaendring). + Ekte typesjekket produksjonsbuild av frontend (øktkort-hintet fra samme + runde) kjørt og bekreftet, alle 11 ruter listet. + **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen + migrasjon, `docker compose up -d --build teecup_api teecup_frontend`, + begge containere boot-et rent (`Application startup complete`, Next.js + `Ready`), `/health` og `/dashboard` → 200, `teeoff.no` upåvirket. + +- **Walkover/konsesjon LIVE (2026-07-19, ADR-024):** direkte oppfølging av + Brukerroller-runden samme dag — brukeren ba eksplisitt om å ta fatt på + dette som naturlig neste steg. Løser det lenge kjente hullet: en side som + aldri stiller nok spillere fikk aldri beregnet handicap og matchen kunne + derfor aldri avgjøres — hang uendelig. + Fire beslutninger avklart eksplisitt med bruker (AskUserQuestion) før + bygging — tre anbefalte valg, ett (omfang: match+turnering-nivå samtidig, + ikke bare match) valgt utover anbefalingen. + **Bygget:** ny `apply_concession`-hjelpefunksjon i `app/routers/ + scoring.py` (skriver til de samme fire kolonnene som + `recompute_and_cache_match_state` -- `status_text`/`points_side_a/b`/ + `leading_side` -- men direkte, ikke utledet fra hull). Ny + `POST /orgs/{id}/matches/{id}/concede` (kun kaptein for det TAPENDE laget, + speiler ekte golf-etikette -- du gir bort DITT tap, krever ikke seier på + motstanderens vegne -- eller org-admin, gjenbruker `user_is_team_captain` + fra ADR-023 uendret). Ny `POST /orgs/{id}/tournaments/{id}/concede` + (`app/routers/tournaments.py`) som gir opp ALLE ikke-avgjorte matcher + laget har i turneringen i én operasjon -- v1s to-lags-grense (ADR-011) + gjør dette trivielt (bare én motstander uansett), `FOR UPDATE`-låser alle + berørte match-rader (samme race-vern som ellers). Kan erklæres uansett + hvor mange hull som allerede er registrert (match-play teller kun + seier/tap/delt for poeng, ikke marginen) -- allerede registrerte hull i + `hole_score`/`match_hole_result` forblir urørt, kun matchens + avgjørelses-felt endres. + **Frontend:** `session-scorecard.tsx` fikk en kollapsbar + "Gi opp matchen (walkover)"-seksjon (to knapper, én per lag, med + bekreftelsessteg) synlig når matchen ikke er avgjort. + `tournament-detail.tsx` sin `TeamPanel` fikk en tilsvarende "Gi opp resten + av turneringen for laget"-knapp nederst i hvert lagkort. Ingen + klientside-forhåndsfiltrering på kapteinstatus noe sted -- begge knappene + vises alltid, en 403 fra backend vises bare som vanlig feiltekst (samme + mønster som resten av appen). + **Verifisert grundig mot fersk `teecup_scratch`** (isolert + `teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs + API-container): to separate 15-punkts Python/httpx-testløp. Match-nivå: + vinnende lags kaptein NEKTES å konsedere på vegne av det tapende laget + (kan ikke kreve seier på andres vegne), tapende lags kaptein FÅR, allerede + avgjort match avvist 409, fremmed team_id avvist 400, konsesjon ETTER at + ett hull allerede er registrert bekreftet å fungere OG bekreftet at det + registrerte hull-resultatet forblir synlig i scorekortet etterpå (ikke + overskrevet), org-admin FÅR konsedere direkte uavhengig av kapteinmerke. + Turnering-nivå: feil lags kaptein nektes, riktig kaptein FÅR gi opp + resten (kun de faktisk ikke-avgjorte matchene telles -- allerede avgjorte + matcher fra match-nivå-testene i samme løp ble korrekt hoppet over), + gjentatt kall er trygt (0 nye, idempotent i praksis), ukjent team_id gir + 404. `test_isolation.sql` 12/12 uendret (ingen skjemaendring). Ekte + typesjekket produksjonsbuild av frontend kjørt og bekreftet, alle 11 + ruter listet. + **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen + migrasjon, `docker compose up -d --build teecup_api teecup_frontend`, + begge containere boot-et rent, `/health` og `/dashboard` → 200, + `teeoff.no` upåvirket. + +- **Turnering-status via API, SCRATCH-VERIFISERT (2026-07-19):** brukeren + valgte dette som neste steg etter walkover/konsesjon-runden (fikk velge + mellom denne lille opprydningen og å starte Kommunikasjon-runden). `status` + (draft/active/completed/archived) har ligget i skjemaet siden migrasjon + 001, men INGEN endepunkt kunne endre det — kun `INSERT`-defaulten `'draft'` + fra `create_tournament`. + **Bygget:** lagt til i `TournamentUpdate` (`app/routers/tournaments.py`), + settes via det eksisterende generiske `PATCH /orgs/{id}/tournaments/{id}` + (`exclude_unset`-mønsteret, ekte PATCH-semantikk uendret). + **Reelt funn UNDER scratch-testing, ikke antatt riktig på forhånd:** + `status` er -- ulikt `visibility` (som er ren `text`+`CHECK`) -- en EKTE + Postgres ENUM-type (`tournament_status`). Den generiske + `set_clauses`-byggeren (`f"{key} = ${i}"`) hadde derfor trengt et + eksplisitt cast for at asyncpg sin ukjent-typede parameter skulle løses + riktig mot en enum-kolonne -- lagt til en spesialsjekk (`::tournament_status` + kun for `status`-nøkkelen) FØR jeg antok mekanismen "bare fungerer" fordi + den gjør det for de andre feltene. + Bevisst INGEN tilstandsmaskin/overgangsregler bygget (kan f.eks. gå fra + `completed` tilbake til `draft` fritt) -- samme tillitsnivå som resten av + appen, ikke etterspurt. + **Frontend:** `tournament-status-badge.tsx` fikk en ny redigerbar + `TournamentStatusPicker` (dropdown over de fire verdiene, optimistisk + UI-oppdatering med rollback ved feil), koblet inn i `tournament-detail.tsx` + sin header ved siden av invitasjonskode-chipen. Den eksisterende + skrivebeskyttede `TournamentStatusBadge` (dashbordets kortliste) urørt. + **Verifisert i scratch:** ny turnering får riktig default `draft`, PATCH + til `active` bekreftet enum-castet faktisk løser problemet, PATCH med + status+et annet felt samtidig fungerer, PATCH UTEN status-felt lar + verdien stå urørt (regresjon på eksisterende PATCH-semantikk), ugyldig + status-verdi avvist med 422 (Pydantic-mønster), `GET`-listen viser samme + verdi etterpå. `test_isolation.sql` 12/12 uendret (ingen skjemaendring). + Ekte typesjekket produksjonsbuild kjørt og bekreftet. + **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen + migrasjon, `docker compose up -d --build teecup_api teecup_frontend`, + begge containere boot-et rent, `/health` og `/dashboard` → 200, + `teeoff.no` upåvirket. + +- **Kommunikasjon LIVE (2026-07-19, ADR-025) — det største enkeltløftet i + prosjektet så langt:** lag-intern chat («det hemmelige rommet») + offentlig + runde-feed («Banter Board»), begge med bilder og ekte WebSocket-sanntid, + bygget i samme runde. Fire hovedbeslutninger avklart eksplisitt med bruker + (AskUserQuestion) før bygging — brukeren valgte den mest ambisiøse + kombinasjonen på alle fire (begge deler nå, WebSockets fremfor polling, + ekte privat chat, bilder fra start). + **Datamodell:** ny migrasjon `013_messaging.sql` — delt `message`-tabell + med `scope`-diskriminator (`team`/`tournament_feed`) i stedet for to + separate tabeller, RLS org_isolation som ellers. `author_display_name` + FRYSES ved skrivetidspunkt (samme prinsipp som handicap-snapshot, + ADR-007) — spillerens `player.display_name` i org-en hvis den finnes, + ellers e-postens lokaldel (dekker org-ansatte uten egen spillerprofil). + **Lag-chat er BEVISST ekte privat** — ny `user_is_rostered_on_team` i + `app/team_authz.py`, med VILJE uten org-admin-fallback, ulikt de to andre + funksjonene i samme fil (`user_is_team_captain`/`user_is_match_participant`, + ADR-023, som begge har et slikt unntak). Første sted i hele appen der + org-eier/admin er strukturelt utestengt fra noe. + **Offentlig feed:** LESING gjenbruker `registration.py` sitt eksisterende + trenivå-visibility-mønster (ADR-018) helt uendret, inkl. anonym tilgang. + POSTING er strengere enn lesing — krever ekte innlogging OG org- + medlemskap/faktisk deltakelse, selv på en `public`-synlig turnering (en + helt urelatert innlogget bruker skal ikke kunne poste på en fremmed + offentlig side). Moderering: forfatteren selv ELLER org-eier/admin kan + slette et feed-innlegg (motsatt av lag-chatten, som ikke har noen + ekstern moderator). + **`registration.py` sine fire interne hjelpefunksjoner gjort delt** + (fjernet ledende understrek — samme "gjort delt for gjenbruk"-mønster som + tidligere runder): `resolve_org`, `is_participant`, `code_matches`, + `check_visibility`. `team_authz.py` sin `_is_org_admin` likeens → + `is_org_admin`. Ingen atferdsendring, kun navn, for at `messaging.py` + skulle kunne gjenbruke dem uendret i stedet for å duplisere logikk. + **WebSockets, ikke polling:** in-memory tilkoblingsregister PER PROSESS i + `app/routers/messaging.py` — trygt med dagens ene `teecup_api`-container, + men deles IKKE på tvers av flere prosesser/containere (samme klasse + begrensning som den allerede aksepterte in-memory-cachen, se ARCHITECTURE_ + DECISIONS.md "Åpne spørsmål"). Ny `get_current_user_from_websocket` i + `app/auth.py` — WS-ruter kan ikke bruke `get_current_user`/ + `get_authorized_org` direkte via `Depends()` (de er `Request`-typet, ingen + ekte HTTP Request finnes i en WS-scope), derfor en bevisst minimal, egen + kopi av samme cookie-dekode-/oppslagslogikk. + **Reell infrastrukturoppdagelse FØR noe ble forsøkt, ikke i etterkant:** + Next.js sin `rewrites()` proxyer ikke WebSocket-oppgraderinger pålitelig i + "standalone"-modus — løst likt som MinIO-media-ruten (ADR-018): en egen + Caddy-rute (`handle /ws/* { reverse_proxy teecup_api:8000 }`) rett til + API-et, forbi Next.js/`teecup_frontend` helt. Caddyfile ligger i det + SEPARATE `/opt/teeoff`-repoet — samme stale-bind-mount-inode-oppførsel som + ALLE tidligere Caddyfile-runder (graceful reload plukker ikke opp + endringen), løst likt: full `docker restart teeoff_caddy`, brukeren + bekreftet eksplisitt på forhånd, noen sekunders nedetid for `teeoff.no`. + **Verifisert grundig mot fersk `teecup_scratch`** (isolert + `teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container + med `websockets`-Python-biblioteket installert kun for testen): et + 20-punkts asyncio/httpx/websockets-testskript som dekket BEGGE + meldingstyper ende-til-ende. Kritiske personvern-/sanntid-funn, alle + bekreftet med ekte tilkoblinger (ikke bare REST): + - Rostret spiller på lag A FÅR lese/skrive lag A sin chat; rostret spiller + på lag B NEKTES; **organisatoren (org-eier) NEKTES OGSÅ** — bekreftet + BÅDE over REST (`GET`) og over selve WebSocket-håndtrykket (avvist med + lukkekode 4403 før `accept()` i det hele tatt kalles). + - Sanntid bekreftet reelt: A1 koblet til lag A sin chat-socket, A2 sendte + en melding over vanlig REST, A1 mottok den umiddelbart over den åpne + WebSocket-tilkoblingen (ikke bare at REST-svaret så riktig ut). + - Bildeopplasting i chat bekreftet (ekte AVIF-konvertert `image_url` + returnert). + - Offentlig feed: anonym NEKTES å poste (401), en tilfeldig INNLOGGET men + uvedkommende bruker NEKTES (403 `NOT_A_PARTICIPANT`), org-medlem FÅR, + en faktisk deltaker (rostret, IKKE org-medlem) FÅR — beviser + `is_participant`-veien fungerer uavhengig av `is_member`-veien. Anonym + WebSocket-tilkobling til en `public`-synlig turnerings feed FÅR lov og + mottar sanntidsoppdateringer. + - Moderering bekreftet: forfatter sletter eget innlegg, org-admin sletter + ANDRES innlegg (feeden), uvedkommende NEKTES å slette andres innlegg. + `test_isolation.sql` 12/12 uendret (additiv migrasjon). Ekte typesjekket + produksjonsbuild av frontend kjørt og bekreftet, inkl. den nye + `/tournaments/[id]/teams/[teamId]/chat`-ruten. + **Frontend:** ny `components/team-chat.tsx` (meldingsliste med egen/andres- + styling, bildeopplasting, sanntid via nettleserens native `WebSocket`, + slett-egen-melding), lenket fra en ny chat-ikon-knapp i `tournament- + detail.tsx` sin `TeamPanel`. Ny seksjon `TournamentFeed` i + `components/public-tournament.tsx` (nederst på den offentlige + turneringssiden) — viser 401/403-svar fra posting som forklarende + inline-tekst ("logg inn for å poste" / "du må være medlem/deltaker") i + stedet for en generisk feilmelding. + **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt for alle tre + stegene (migrasjon, containere, Caddy-restart): migrasjon 013 kjørt mot + ekte `teecup_db`, `test_isolation.sql` fortsatt 12/12, begge containere + boot-et rent, Caddy validert (`caddy validate` — "Valid configuration") + FØR restart, restarten ren (ingen feil i loggen). Verifisert grundig + etterpå: `/health`/`dashboard` → 200, `teeoff.no` → 200, et ekte + `wss://`-håndtrykk over produksjons-https bekreftet å nå helt frem til + applikasjonslaget (testet mot en ukjent turnering-id — ingen ekte data + berørt — ga korrekt 404 fra selve WS-ruten, ikke en Caddy/Next.js-feil), + og et ren-HTTP-kall mot en `/ws/*`-sti bekreftet å returnere FastAPI sin + egen JSON-404 (`{"detail":"Not Found"}`) og ikke Next.js sin HTML-404 — + beviser Caddy-ruten faktisk treffer `teecup_api`, ikke `teecup_frontend`. + +- **Tilskuer-rolle LIVE (2026-07-19, ADR-026):** brukeren valgte dette som + neste steg rett etter Kommunikasjon-runden — "tilskuer" var bevisst + utsatt til feed-synligheten (ADR-025) fantes, og nå gjorde den det. + **Kjernebeslutning:** ingen ny rolle/tabell — "tilskuer" er ganske enkelt + enhver som kan SE en turnering per `tournament.visibility` (ADR-018), + utvidet til også å dekke LIVE-data (leaderboard, matcher, scorekort), ikke + bare info-siden/programtidene som før. + **Bygget:** `GET /orgs/.../leaderboard`, `.../sessions/{id}/matches` og + `.../matches/{id}/scorecard` fantes allerede (organisator-/spiller-siden), + men krevde org-medlemskap — en ren spectator kunne aldri se dem. Løst ved + å ekstrahere den delte kjernelogikken til gjenbrukbare funksjoner + (`fetch_leaderboard` i tournaments.py, `fetch_matches` i matches.py, + `fetch_scorecard` i scoring.py — samme "gjort delt"-mønster som tidligere + runder), og la tre nye offentlige endepunkter i `registration.py` kalle + dem etter egen visibility-sjekk. `own_team_ids()` (`blind_draw.py`) gjort + null-sikker (`user_id: str | None`) -- en anonym leser har per definisjon + ingen egne lag, korrekt oppførsel er tom mengde (ser kun avslørte + matcher), ikke en feil. + **To nye sikkerhetssjekker funnet under DESIGN, ikke i etterkant, samme + disiplin som tidligere ADR-018 Beslutning B-lærdommen:** + 1. `session_id`/`match_id` i URL-en må eksplisitt verifiseres å høre til + NØYAKTIG `tournament_id` i samme URL — `org_connection()` setter kun + TENANT-grensen (RLS), ikke at stiens id-er faktisk henger sammen. Uten + dette kunne noen med tilgang til én offentlig turnering i en + organisasjon lest en HVILKEN SOM HELST økt/match i samme organisasjon + (inkl. en privat en) ved å gjette/prøve id-er. + 2. Scorekortet krever eksplisitt at BEGGE lag har låst oppstillingen + (blind draw, ADR-013) — leaderboard/matchliste arver reveal-skjuling + automatisk via `own_team_ids()`, men scorekortet har ingen tilsvarende + innebygd sjekk. + **Bruker valgte omfang utover anbefalingen:** BÅDE leaderboard+matchliste + OG fullt hull-for-hull-scorekort per match i samme runde (anbefalingen var + kun de to første). + **Frontend:** ny `/t/[id]/live`-side (`components/public-live.tsx`) — + fargesegmentert stillingsbar (faktisk + projisert, samme visuelle idé som + den org-autentiserte leaderboard-skjermen, men egen enklere implementasjon + siden komponentene har ulik autentiseringskontekst), utvidbar øktliste → + matchliste → hull-for-hull-scorekort (fargede hull-chips per lag). Lenket + fra hovedsiden (`public-tournament.tsx`) med en ny "Følg live"-knapp. + **Verifisert grundig mot fersk `teecup_scratch`** (isolert + `teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container): + 16 automatiserte sjekker, inkl. en PRESIS test av den nye tenant-vs-sti- + sjekken (ikke bare en ukjent id, men en EKTE ANNEN turnering i SAMME org + — bekreftet at match/økt fra turnering A fortsatt ikke kan leses via + turnering B sin offentlige URL), full blind-draw-skjuling FØR/ETTER + reveal for en anonym leser, kode-overstyring (ADR-020) fungerer uendret + for de nye endepunktene, scorekort eksplisitt nektet før reveal. Ekte + typesjekket produksjonsbuild av frontend, inkl. den nye `/t/[id]/live`- + ruten. + **Rullet ut live 2026-07-19**, 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. + +- **"Følg live"-siden koblet til sanntid (2026-07-19, ADR-027):** brukeren + fulgte anbefalingen fra forrige runde — `/t/[id]/live` (bygget i + tilskuer-runden samme dag) krevde omlasting for nye resultater. + **Bygget:** ny, RUTEFRI modul `app/realtime.py` (in-memory + tilkoblingsregister + `broadcast_live_update(tournament_id)`) -- ligger + bevisst BAK alle routere i importgrafen for å unngå en sirkulær import + (`registration.py` importerer allerede fra `scoring.py`/`tournaments.py`/ + `matches.py`, og `messaging.py` importerer fra `registration.py`; en + kringkastingsfunksjon i noen av routerne ville derfor bitt seg selv i + halen). Kringkastingen er lagt INN I `recompute_and_cache_match_state` og + `apply_concession` selv (sistnevnte fikk en ny påkrevd `tournament_id`- + parameter) -- kallerne (hole-score/hole-result-innsending, walkover på + både match- og turnering-nivå) trenger ikke huske å gjøre noe selv. Nytt + WS-endepunkt `/ws/public/tournaments/{id}/live` i `messaging.py` + (gjenbruker `/ws/*`-Caddy-ruten fra ADR-025 uendret -- ingen ny + infrastruktur). Sender bevisst kun et "noe endret seg, hent på nytt"- + signal, ikke selve dataene -- unngår å duplisere leaderboardets + projeksjons-regnestykke/blind draw-filtrering i kringkastings-payloaden. + **Frontend:** `components/public-live.tsx` åpner en WebSocket ved + montering; hver melding teller opp en `refreshKey` som utløser refetch av + leaderboardet ALLTID, og av en økts matcher/et scorekort KUN hvis det + faktisk er utvidet/åpent på skjermen akkurat da (ingen unødvendige kall + for lukket innhold). Liten pulserende prikk lagt til ved siden av + "Følg live"-teksten som visuell bekreftelse. + **Verifisert i scratch:** anonym avvist på live-WS for en `org`-synlig + turnering (samme visibility-sjekk som REST), satt til `public`, deretter + bekreftet FAKTISK sanntidsmottak (ikke bare at REST-svaret så riktig ut) + for BÅDE et vanlig hull-resultat OG en walkover-konsesjon -- en åpen + WebSocket-tilkobling mottok kringkastings-signalet i begge tilfeller. + Leaderboard fortsatt korrekt lesbar over REST etterpå (regresjon). Ekte + typesjekket produksjonsbuild kjørt og bekreftet. + **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen + migrasjon, ingen ny Caddy-endring, `docker compose up -d --build + teecup_api teecup_frontend`, begge containere boot-et rent. Verifisert + grundig etterpå: `/health`/`dashboard` → 200, `teeoff.no` upåvirket, OG et + ekte `wss://`-håndtrykk mot den NYE `/live`-ruten over produksjons-https + (ukjent turnering-id, ingen ekte data berørt) ga korrekt 404 fra selve + applikasjonslaget. + +- **Fire brukerrapporterte UI-/UX-hull, DIAGNOSTISERT OG NOTERT, IKKE fikset + (2026-07-19):** brukeren rapporterte fire ting fra faktisk bruk av + `teecup.teeoff.no` rett før PWA-runden startet. Root cause funnet ved + kodegjennomgang for tre av fire (ikke gjettet). Full detalj i + FEATURE_BACKLOG.md sin nye seksjon "Rapporterte UI-/UX-hull (2026-07-19)". + Kort: + 1. Dashboard-turneringskortet viser "Ingen datoer satt" alltid — leser + `tournament.start_date`/`end_date` (eget felt, ADR-015), som INGEN + UI-skjema noensinne skriver til. Skal enten få et faktisk skjemafelt, + eller kortet bør heller utlede datoen fra øktenes `scheduled_at`. + 2. **Reell, bekreftet rewrite-bug:** `/orgs/{id}/members` gir en rå + FastAPI-404 (`{"detail":"Not Found"}`) i stedet for medlemssiden. + `next.config.mjs` sin `rewrites()` returnerer en plain array (implisitt + "afterFiles") — DYNAMISKE Next.js-sider sjekkes ETTER rewrites, så + `/orgs/:path*`-proxy-regelen (ADR-016) fanger kallet FØR + `app/orgs/[id]/members/page.tsx` noensinne nås. Dette er den FØRSTE + frontend-siden som er nestet direkte under et allerede proxyet prefiks + — ingen tidligere skjerm har truffet dette. Selve siden/komponenten er + riktig bygget, kun ruten dit er blokkert. + 3. Ingen UI-vei til å opprette en ANDRE organisasjon når man allerede har + én — `CreateOrganizationState` i `dashboard.tsx` vises kun ved null + org-er. Backend støtter det fullt ut allerede (ADR-021). + 4. Ingen sammendrag/indikator noe sted for "alle runder har fått dato" — + må sjekkes manuelt per øktkort på program-skjermen. + **Ingen av de fire fikset i denne runden** — kun dokumentert på brukerens + eksplisitte instruks, PWA-runden prioriteres først. + +- **PWA: installasjon + full offline scoreregistrering, BYGGET, IKKE ENNÅ + RULLET UT (2026-07-19, ADR-028):** bruker valgte det mest ambisiøse + omfanget (installerbar app OG offline scoreregistrering, ikke bare + installasjon), og valgte å generere enkle ikoner nå fremfor å vente på + ekte design (med eksplisitt beskjed om at de er midlertidige). + **Ikoner:** ingen `PIL`/`rsvg-convert`/`imagemagick` tilgjengelig i miljøet + — løst med et scratch npm-prosjekt (`sharp@0.33.5`, node18-kompatibel + versjon; nyeste `sharp` krever node ≥20 og feilet først) som genererte et + enkelt grønt golf-flagg-ikonsett (`public/icons/icon-192.png`, + `icon-512.png`, `icon-maskable-512.png`) + erstattet den gamle + `public/apple-icon.png` (var v0.app sin generiske plassholderlogo, ikke + TeeCup-merkevare i det hele tatt). + **Bygget:** `app/manifest.ts` (Next.js sin innebygde manifest-generator, + ikke en statisk `manifest.json`), `appleWebApp`-metadata i `layout.tsx` + (iOS leser ikke manifest.json for hjemskjerm-oppførsel), + `components/sw-register.tsx` (stille no-op uten SW-støtte), + hånd­skrevet `public/sw.js` (ingen next-pwa/workbox-avhengighet — nettverk + først/cache-fallback for navigasjon + `/orgs/*`-GET-er, BEVISST ikke + stale-while-revalidate, se ADR-028 for hvorfor), `public/offline.html`. + **Offline scoreregistrering:** ny `lib/offline-queue.ts` (IndexedDB-kø, + ren klientkode — ikke i SW-en), koblet inn i `session-scorecard.tsx` sin + `submitStroke`/`submitHoleResult`: sjekker `navigator.onLine` først, køer + kun ved en EKTE nettverksfeil (ikke ved et avvist HTTP-svar som "matchen + er avgjort" — det vises fortsatt som vanlig feiltekst). Lokalt overlay + (`pendingStrokes`/`pendingResults`) viser køede verdier umiddelbart, + merket "Lagret lokalt · venter på synk". Auto-synk ved `window`s + `online`-event PLUSS en manuell "Synkroniser nå"-knapp (bevisst IKKE + Background Sync API — iOS Safari støtter den ikke). Et definitivt avvist + synk-forsøk (f.eks. matchen ble avgjort på en annen enhet mens denne var + offline) fjernes fra køen og vises som feilmelding, henger aldri for + alltid. + **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` som + deployes) kjørt og bekreftet — alle 16 ruter listet inkl. + `/manifest.webmanifest`. Kort container-boot + `curl` bekreftet manifest/ + service worker/ikoner/offline.html alle svarer riktig (200, riktig + innhold). + **IKKE gjort denne runden, viktig å være ærlig om:** ingen faktisk + nettleser-basert offline-test (Chrome DevTools sin Offline-bryter, + faktisk "Legg til på hjemskjerm") — intet nettleserverktøy tilgjengelig i + denne økten. Kun kodegjennomgang + build-verifisering. **Brukeren bør selv + teste scorekort-siden med DevTools Offline-modus før tillit i skarp + bruk.** + **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: `docker + compose up -d --build teecup_frontend`. Compose gjenskapte OGSÅ + `teecup_api` som en bivirkning (avhengighets-oppløsning i `up --build`) + — ingen backend-kode rørt, ren uendret gjenoppbygging, begge containere + boot-et rent (`Application startup complete` / Next.js `Ready`). + Verifisert: `/health`/`dashboard` → 200, `/manifest.webmanifest`/`sw.js`/ + `icons/icon-512.png` alle 200 over ekte https, `teeoff.no` upåvirket. + +- **Fire nye hull rapportert fra faktisk testing av blind draw + scorekort + (foursome), DIAGNOSTISERT OG NOTERT, IKKE fikset (2026-07-19):** rett + etter PWA-runden. Full detalj i FEATURE_BACKLOG.md sin nye seksjon + "Rapporterte hull, blind draw + scorekort (2026-07-19)". Kort: + 1. **Bekreftet root cause:** valgt spiller forsvinner ikke fra + nedtrekkslisten i `AddSlotForm` (`session-blind-draw.tsx`) — den + merkes kun `disabled` på `<option>`-nivå, som HTML fortsatt viser + (bare gråtonet). Skal FILTRERES bort, ikke deaktiveres. + 2. **Bekreftet, større enn antatt:** tee-valg (Dame/Herre) bør følges av + spillerens registrerte kjønn automatisk. Krever en backend-utvidelse + først — `RosterEntry`/`list_roster` (`tournaments.py`) mangler + `player.gender` helt i responsen, selv om feltet finnes i skjemaet og + alt eksponeres via `GET /orgs/{id}/players`. Åpne spørsmål om låst vs. + forhåndsutfylt valg, og fallback ved ukjent kjønn/manglende + matchende tee, før bygging. + 3. Ren frontend-UX: tallvelger (1–9 + utvidbar "10 eller flere") i stedet + for pluss/minus-steppere for slagregistrering. Ingen backend-endring. + 4. **HCP "ikke hensyntatt" i foursome-test — IKKE bekreftet som bug.** Ett + definitivt, bekreftet hull uavhengig av alt annet: det beregnede + `course_handicap`/`playing_handicap` eksponeres ALDRI noe sted i + API-et eller UI-et (sjekket `matches.py`, `scoring.py`, begge + frontend-skjermene) — usynlig selv når beregningen er korrekt. I + TILLEGG en kjent, tidligere dokumentert begrensning som kan ha slått + inn: foursome/greensome/scramble sin side-handicap beregnes kun når + BEGGE deltakere har en matchende `tee_rating`, og `tee_rating`-rader + lages i dag ALLTID kun med `full_18`-omfang — en `front_9`/`back_9`- + testøkt ville derfor ALDRI fått handicap beregnet i det hele tatt. + **Trenger avklaring:** var testøktens `hole_config` `full_18`, og + hadde begge sider alle sine deltakere+tee lagt til før scoring? + **Ingen av de fire fikset i denne runden** — kun dokumentert på brukerens + eksplisitte instruks. + +- **Tre av fire hull FIKSET OG SCRATCH-VERIFISERT (2026-07-19), samme dag:** + bruker ba eksplisitt om punkt 1 og 3, og presiserte punkt 2 (se under) + samt bekreftet den nøyaktige handicap-formelen for punkt 4. + 1. **Spillerliste-fiks:** `AddSlotForm` (`session-blind-draw.tsx`) + filtrerer nå allerede-valgte roster-rader helt bort i stedet for å + bare `disabled`-merke `<option>`-en (som HTML uansett viser gråtonet). + 2. **Tee-valg — OMDEFINERT etter brukerens presisering, IKKE bygget + ennå:** min opprinnelige antakelse ("lås tee til spillerens kjønn") + var feil i premisset — en golfbane har ikke fysisk kjønnsdelte + utslag, kun eventuelt kjønnsdelt RATING av samme utslag (noen klubber + sloper bevisst ikke ett utslag for ett kjønn). Bekreftet mot ekte + Tjøme-data: hvert fysisk utslag ligger i dag som TO `tee`-rader med + samme navn (én per kjønn) — nøyaktig konflateringen brukeren pekte + på. Riktig fiks er å flytte `gender` fra `tee` til `tee_rating` + (skjemaendring + datamigrering + import-/handicap-/remap-kode). + Betydelig større enn antatt — se FEATURE_BACKLOG.md for full + analyse og de tre åpne designspørsmålene som trengs FØR bygging. + 3. **Tallvelger:** ny `StrokePicker`-komponent + (`session-scorecard.tsx`) — 1-9 direkte, "10+"-knapp åpner 10-19, + med en "tilbake"-lenke. Erstatter ±-stepperen og all dens døde kode + helt. + 4. **HCP-bug, BEKREFTET og FIKSET:** brukeren beskrev selv riktig + formel (kombinert hcp/2 justert for prosent, laveste side til 0 + mottatte slag ved matchplay-hcp, resten fordelt fra stroke index 1) — + lest direkte mot `handicap_engine.py` og bekreftet at koden allerede + implementerer NØYAKTIG dette. Bugen lå ikke i formelen, men i at den + ALDRI kjørte for front_9/back_9-økter: `compute_and_store_side_ + handicaps` (`app/handicap.py`) joinet `tee_rating` på øktens + `hole_config` som rating-scope, men slike rader lages i praksis kun + med `scope='full_18'` (matcher ADR-008 sin allerede etablerte + design — full_18-ratingen skal alltid brukes, front/back-9- + fordelingen skjer senere ved selve slagtildelingen). Bekreftet + direkte mot EKTE `teecup_db` (read-only): brukerens rapporterte + testøkt (`front_9`+`foursome`) hadde `course_handicap`/ + `playing_handicap = NULL` på alle fire deltakere. 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 + funksjonen og alle tre kallstedene (var død etter fiksen). + **Scratch-verifisert presist:** samme scenario gjenskapt (foursome+ + front_9, hcp 10/20 mot 5/15) — course/playing handicap kom ut nøyaktig + som beregnet for hånd (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) — direkte bevis på at hcp nå faktisk brukes. + **Ny, urelatert bug funnet under samme scratch-test, IKKE fikset:** + stroke-modus-innsending på en bane uten registrerte hull krasjer rått + (500 `IndexError` i `allocate_over_played_holes`) i stedet for en ren + `VALIDATION_FAILED` — samme klasse feil som en tidligere fikset + manglende-handicap-krasj. Notert i FEATURE_BACKLOG.md, ikke bygget. + **Scratch-infrastruktur:** isolert `teecup_scratch`-database + + `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API- + container (`python:3.12-slim`, `app/` og `handicap_engine.py` montert + read-only), alt ryddet opp etter verifisering. Typesjekket + produksjonsbuild kjørt for frontend-fiksene (1+3), alle 16 ruter listet. + **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt, se eget + punkt lenger ned for punkt 2 (som ble bygget og rullet ut sammen med + disse tre i én utrulling). + +- **Punkt 2 (tee/kjønn) BYGGET OG SCRATCH-VERIFISERT (2026-07-19, ADR-029), + samme dag, rett etter designavklaringen:** bruker bekreftet begge + anbefalte alternativer (helautomatisk tee-valg, "feil høyt" ved + manglende kjønn/rating). Ny migrasjon `014_tee_gender_to_rating.sql`: + flytter `gender` fra `tee` til `tee_rating` (unikhet + `(tee_id, scope)` → `(tee_id, scope, gender)`), slår sammen + eksisterende kjønns-par-tee-rader til én fysisk tee-rad per (bane, + navn) — velger laveste id som "beholder", flytter `tee_rating`- og + `match_participant.tee_id`-referanser dit, sletter duplikatene. + **Reell bug funnet OG fikset UNDER selve migrasjonsskrivingen** (ikke i + produksjon): første versjon prøvde å droppe den GAMLE + `(tee_id, scope)`-unikheten ETTER sammenslåingen i stedet for FØR — + kolliderte da midlertidig med beholder-tee-ens egen eksisterende rad + for samme scope. Rettet ved å bytte rekkefølge (drop gammel unikhet FØR + sammenslåing, legg til ny kjønnsbevisst unikhet ETTER). + **Kodeendringer:** `app/handicap.py` sin `compute_and_store_side_ + handicaps` joiner nå også på `player.gender` (i tillegg til forrige + rundes full_18-fiks). `app/routers/matches.py` sin `add_participant` + validerer FØR innsetting: spiller har registrert kjønn, OG valgt utslag + har en matchende rating — begge avvist med klar `VALIDATION_FAILED`. + `app/routers/tournaments.py` sin `_remap_course` (bane-bytte) matcher nå + på tee-navn OG bekrefter matchende kjønnsrating for hver berørte + spiller. `app/routers/courses.py`: `TeeCreate` redesignet fra ett flatt + kjønn+rating-sett til en `ratings`-liste (1-2 elementer, distinkte + kjønn); ADR-019 sin `import_official_course` lager nå ÉN tee-rad per + fysisk teeoff-utslag (før: to, én per kjønn) med inntil to + `tee_rating`-rader under. `session-blind-draw.tsx` forenklet — ingen + "H"/"D"-suffiks, ingen kjønnslogikk i det hele tatt lenger (serveren + løser det). + **Verifisert grundig, flere separate scratch-runder:** + 1. Selve fletting-migrasjonen kjørt mot SYNTETISK data som gjenskaper + Tjøme-mønsteret nøyaktig (to par + én enslig utslag, pluss en + `match_participant`-rad som bevisst pekte til DUPLIKATEN, ikke + beholderen) — bekreftet: to rader ble til én, `tee_rating.gender` + riktig fylt inn for begge, `match_participant.tee_id` korrekt + reparert til beholderens id, det enslige utslaget urørt. + 2. Full API-runde (18 automatiserte sjekker via et Python/urllib- + testskript — httpx var ikke tilgjengelig i vertsmiljøet, løst med + stdlib `http.cookiejar`/`urllib` i stedet): manuell tee-opprettelse + (to ratinger, kun én rating, duplikat kjønn avvist), `GET tees` + viser riktig sammenslått struktur, kvinne+dame-rating lykkes, + mann+kun-dame-rating avvist tydelig, spiller uten kjønn avvist + tydelig, full kjønnsblandet singel-match scoret korrekt. + 3. Offisiell import kjørt mot EKTE `teeoff_api` (Borregaard Golfklubb, + samme mønster som ADR-019 sin opprinnelige verifisering) — bekreftet + 4 fysiske utslag importert med begge kjønnsratinger hver, ikke 8 + doble rader. + 4. `_remap_course`: bane-bytte til en bane UTEN matchende kjønnsrating + avvist tydelig, bane-bytte til en bane MED matchende rating lykket. + Ekte typesjekket produksjonsbuild av frontend kjørt på nytt og + bekreftet etter blind draw-forenklingen. + **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: migrasjon + 014 kjørt mot ekte `teecup_db` FØRST (Tjømes 8 tee-rader slått sammen + til 4 — bekreftet 0 brutte `match_participant.tee_id`-referanser + etterpå med en direkte spørring), deretter `docker compose up -d + --build teecup_api teecup_frontend` sammen med punkt 1/3/4 i samme + utrulling. Begge containere boot-et rent, `/health`/`dashboard` → 200, + `teeoff.no` upåvirket. + +- **Dashboard-dato-fiks (ADR-030) + rediger spiller, BYGGET OG SCRATCH- + VERIFISERT (2026-07-19/20):** brukeren viste et faktisk skjermbilde av + det tidligere dokumenterte datovisning-hullet (økt planlagt til "11. + juli" på Program-fanen, men dashbord-kortet viste fortsatt "Ingen + datoer satt") og ba samtidig om å kunne redigere spilleres HCP. + **Dato-fiks:** `list_tournaments` (`app/routers/tournaments.py`) utleder + nå datospennet fra øktenes `scheduled_at` (`COALESCE` med et evt. + eksplisitt satt `tournament.start_date`/`end_date`, som fortsatt vinner + om det noensinne settes — ingen UI gjør det i dag). Kun denne ene + spørringen endret, `_TOURNAMENT_COLUMNS` (brukt av opprett/PATCH sine + `RETURNING`-klausuler) urørt. Se ADR-030. + **Rediger spiller:** ny `PATCH /orgs/{id}/players/{id}` + (`app/routers/players.py`, vanlig `exclude_unset`-mønster, dekker alle + spillerfelt) + ny "Rediger spiller"-handling i rosterradens meny + (`tournament-detail.tsx`). **Bevisst grense, forklart i selve UI-et:** + endrer spillerpoolen, IKKE et lags allerede frosne + `handicap_index_snapshot` (ADR-007) — reproduserbarhet for allerede + opprettede lag er et bevisst, tidligere designvalg, ikke noe denne + fiksen skulle endre. Skjemaet sier dette rett ut i stedet for å late som + endringen slår inn overalt. + **Scratch-verifisert, 9 sjekker:** spiller-PATCH (hcp-endring, delvis + PATCH lar andre felt stå urørt, tomt PATCH avvist, ukjent id gir 404), + 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), dato-utledning (ingen økter → null, + to økter 11./12. juli → riktig utledet spenn, eksplisitt satt dato + vinner over utledet). Ekte typesjekket produksjonsbuild kjørt og + bekreftet. + **Rullet ut live 2026-07-20**, bruker bekreftet eksplisitt: `docker + compose up -d --build teecup_api teecup_frontend`, ingen migrasjon. + Begge containere boot-et rent, `/health`/`dashboard` → 200, `teeoff.no` + upåvirket. Verifisert mot EKTE data (ikke bare scratch): "De Gamle er + Eldst" viser nå korrekt 11. juli 2026 i stedet for "Ingen datoer satt". + +- **To nye punkter reist 2026-07-20, GJENNOMTENKT OG FORESLÅTT, IKKE + bygget:** brukeren ba eksplisitt om at punkt 1 tenkes grundig gjennom + og legges frem som et forslag FØR bygging (ikke kode med en gang), og + markerte det eksplisitt som prioritet over punkt 2. + 1. **Personlig landingsside for enhver registrert bruker (PRIORITERT).** + Bekreftet reelt hull ved kodegjennomgang: `app/page.tsx` sender + enhver innlogget bruker til `/dashboard`, som viser "opprett + organisasjon" så snart `organizations.length === 0` — også for en + bruker som KUN er spiller (koblet via `player.user_id`, + ADR-017 B), aldri organisator. Fullt forslag skrevet i + FEATURE_BACKLOG.md: ett samlet dashboard (ikke to atskilte ruter), + ny "Mine runder"-seksjon (tverr-org, krever en ny SECURITY + DEFINER-bro `player_organizations_for_user()` + utvidelse av + `/auth/me`, samme mønster som `public_tournament_org()` m.fl.), + organisasjonsseksjonen uendret under. Venter på brukerens + bekreftelse på retningen før bygging starter. + 2. **Midlertidige spillere + automatisk etter-runde-e-post (lavere + prioritet, likevel dokumentert grundig).** Presisert ved + kodegjennomgang: det meste av "midlertidig spiller"-behovet + dekkes ALLEREDE av eksisterende `POST /orgs/{id}/players` + (krever aldri en konto). Det som faktisk mangler er en PROAKTIV + e-post-utsending etter runden (scorekort + innloggingslenke) — + foreslått som en eksplisitt organisator-knapp per økt (ikke en + automatisk bakgrunnsjobb, for å unngå uventede e-poster fra en + gjettet "runden er ferdig"-deteksjon). Tre åpne spørsmål notert i + FEATURE_BACKLOG.md (økt- vs. turnering-nivå, dobbel-utsending- + sperre, locale). + **Ingen kode skrevet for noen av de to ennå** — dette var bevisst en + tenke-og-foreslå-runde, ikke en byggerunde. + +- **Punkt 1 BYGGET OG SCRATCH-VERIFISERT (2026-07-20), samme dag, rett etter + forslaget over:** bruker svarte "Gjør punkt 1" med et utvidet omfang — + inkluder også opprettelse/redigering/sletting av personlig informasjon + (profilbilde, fornavn, etternavn, fødselsdato, kjønn, HCP, hjemmeklubb). + **Ny migrasjon `015_user_profile.sql`:** `app_user` får de sju nye + profilfeltene — ETT sett PER KONTO, bevisst IKKE slått sammen med de + org-scopede `player`-radene (se ADR-031 Beslutning B for full + begrunnelse — to reelt atskilte konsepter). Ny + `player_organizations_for_user()`-bro (femte instans av samme + SECURITY DEFINER-mønster som `public_tournament_org()` m.fl.). + **Backend:** `/auth/me` utvidet med profilfeltene + `avatar_url` + + `my_tournaments` (turneringer brukeren er ROSTRET i, tverr-org, samme + N+1-org_connection()-mønster som organisasjonslisten). Ny + `PATCH /auth/profile` (vanlig exclude_unset), `POST`/ + `DELETE /auth/profile/avatar` (samme ekte multipart→AVIF-mønster som + turnering-hero-bilder). + **Reelt sikkerhetshull funnet UNDER bygging, ikke antatt på forhånd:** + testet "Mine runder" mot en EKTE ren spiller (rostret, ingen + org-medlemskap) og oppdaget at `check_visibility()` (ADR-018) kun ga + deltaker-tilgang for `visibility='participants'` — IKKE for `'org'` + (DEFAULT for enhver ny turnering). En ren spiller ville altså vært + stengt ute fra sin EGEN, helt vanlige turnering — nøyaktig + brukergruppen "Mine runder" er bygget for. Fikset: deltaker-sjekken + gjelder nå begge ikke-offentlige tier, med eksplisitt begrunnelse om + at visibility styrer eksponering mot UTENFORSTÅENDE, aldri mot faktiske + deltakere. Verifisert presist at dette er en REN UTVIDELSE, ingen + innstramming: samme scratch-test bekreftet at en helt ubeslektet + FREMMED (innlogget, ikke deltaker) og en ANONYM leser fortsatt begge + avvises identisk som før (403 NOT_VISIBLE). + **Frontend:** `account-settings.tsx` fikk en ny "Personlig profil"- + seksjon (avatar-opplasting/fjerning, fornavn/etternavn/fødselsdato/ + kjønn/HCP/hjemmeklubb-skjema). `dashboard.tsx` fikk en ny + `MyToursSection` («Mine runder», øverst, lenker til den offentlige + turnering-siden) + en mykere, sekundær utgave av "opprett organisasjon"- + tomtilstanden når brukeren allerede har spiller-data å vise. + **Bevisst UTENFOR omfang, klart flagget, IKKE en del av denne rundens + leveranse:** "Mine runder" lenker IKKE til lag-chat/scorekort ennå — de + krever fortsatt ekte organisasjonsmedlemskap (`get_authorized_org`), en + strengere, bredt brukt sperre som ikke ble endret denne runden (egen, + større og mer risikofylt endring, se ADR-031). + **Scratch-verifisert, 15 sjekker:** full profil-CRUD (alle felt satt, + delvis PATCH lar andre felt stå urørt, eksplisitt `null` sletter et + felt, tomt PATCH avvist, avatar lastet opp med ekte AVIF-URL og + slettet igjen, ugyldig filtype avvist), "Mine runder" for en EKTE ren + spiller uten org-medlemskap, OG den kritiske sikkerhetssjekken over. + Ekte typesjekket produksjonsbuild kjørt og bekreftet. + **Rullet ut live 2026-07-20**, bruker bekreftet eksplisitt: migrasjon + 015 kjørt mot ekte `teecup_db` (bekreftet nye kolonner + funksjon + finnes), deretter `docker compose up -d --build teecup_api + teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`/ + `/account` → 200, `teeoff.no` upåvirket. + +- **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 etterspurte mobil med landsnummer. + Spurte samtidig hvorfor V0 ikke brukes til det visuelle her — svarte at + jeg ikke har V0 som et verktøy jeg selv kan kalle (all V0-bruk i + prosjektet har vært brukeren som designer i v0.app og sender meg + zip-eksporter), og at disse siste tilføyelsene er små, inkrementelle + skjemafelt i eksisterende komponenter (gjenbruker allerede etablerte + Tailwind/shadcn-mønstre) der en full V0-runde (design→eksport→diff→ + sammenslåing) ville vært en unødvendig omvei. + **Mobil:** `mobile_country_code`+`mobile_number` (to separate felt, ikke + én sammensatt streng), lagt til i den EKSISTERENDE `PATCH /auth/profile` + — ren tilføyelse, ingen ny sikkerhetsvurdering nødvendig. + **E-post — bevisst IKKE en enkel PATCH:** e-post er innloggings- + identifikatoren (magic-link-mål) — en vanlig PATCH ville latt en + skrivefeil eller en kapret sesjon stjele kontoen for godt. Bygget som et + ekte to-stegs bekreftelsesløp i stedet, samme `token_hash`+ + `expires_at`+`consumed_at`-mønster som `magic_link_token` (migrasjon + 004): ny `email_change_token`-tabell (migrasjon `016_profile_ + contact.sql`). `POST /auth/profile/email` (krever sesjon, sender + bekreftelseslenke til den NYE adressen -- ikke den gamle, beviser + eierskap av MÅLET). `POST /auth/profile/email/confirm` (ingen sesjon + påkrevd, samme mønster som selve magic-link-verifiseringen -- lenken + kan åpnes på en annen enhet enn den som ba om byttet). E-posten endres + ALDRI før lenken faktisk åpnes. Duplikat-sjekk kjøres TO GANGER (ved + forespørsel og rett før selve byttet, i tilfelle adressen ble tatt i + mellomtiden), pluss den eksisterende unike indeksen som siste + bakstopper. Ny `/verify-email`-side (samme mønster som `/verify`). + **Scratch-verifisert, 10 sjekker:** mobil satt via vanlig PATCH; vanlig + profil-PATCH rører aldri e-post; bytte til allerede brukt adresse + avvist (409); e-post uendret helt til bekreftelse; ugyldig kode avvist; + gyldig kode fullfører byttet; SAMME kode kan ikke gjenbrukes; en helt ny + innlogging med den GAMLE adressen oppretter en fersk, tom konto (beviser + byttet er reelt og fullstendig). Ekte typesjekket produksjonsbuild kjørt + og bekreftet (ny `/verify-email`-rute listet). + **Rullet ut live 2026-07-20**, bruker bekreftet eksplisitt (spurte + samtidig og bekreftet at `teecup.teeoff.no/dashboard` nå er DEN samme + adressen for enhver innlogget bruker uansett rolle — nettopp poenget + med ADR-031/032): migrasjon 016 kjørt mot ekte `teecup_db` (bekreftet + nye kolonner + `email_change_token`-tabell finnes), 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. + +- **Medlemsside-ruten FIKSET OG LIVE (2026-07-20):** brukeren ba eksplisitt + om å ta fatt på dette (det mest presserende av de fire UI-hullene notert + 2026-07-19 — siden var helt utilgjengelig). Root cause var allerede + presist diagnostisert: `next.config.mjs` sin `rewrites()` (plain array, + implisitt "afterFiles") fanger `/orgs/:path*` FØR Next.js sine egne + DYNAMISKE sider sjekkes, så `app/orgs/[id]/members/page.tsx` ble aldri + nådd — kallet gikk til FastAPI i stedet, som ga en rå 404. + **Fikset:** siden flyttet til `app/organizations/[id]/members/page.tsx` + (utenfor `/orgs/*`-prefikset), eneste lenke (`dashboard.tsx`) oppdatert. + Lagt til en forklarende kommentar i `next.config.mjs` sin `rewrites()` + for å forhindre samme feil ved en fremtidig ny side. + **Verifisert med ekte produksjonsbuild + container-boot** (ikke bare + typesjekk): den nye ruten (`/organizations/{id}/members`) rendrer + faktisk `OrgMembers`-komponenten med riktig `organizationId`/`orgName` + i RSC-payloaden, IKKE en 404 eller innloggingssiden. + **Rullet ut live**, ren frontend-endring, ingen migrasjon, `teeoff.no` + upåvirket. + +- **De to siste UI-/UX-hullene fra 2026-07-19 FIKSET OG LIVE (2026-07-21):** + alle fire punkter i den runden er dermed fikset. + 1. **«Ny organisasjon»:** ny `NewOrganizationControl` i `dashboard.tsx` + (identisk inline-ekspanderende-form-mønster som `NewTournamentControl`), + lagt til i `OrganizationView` sin header ved siden av + «Medlemmer»/«Ny turnering» — synlig uansett hvor mange org-er brukeren + allerede har. Ingen backend-endring (`POST /orgs` hadde aldri en + grense). + 2. **Dato-sammendrag:** ny `DateCoverageSummary` i `tournament- + program.tsx`, vist øverst i øktlisten når minst én økt finnes: «X av Y + runder har fått dato og klokkeslett» (uthevet når alle er satt). Ren + klientside-telling av allerede lastet `scheduled_at`, ingen + backend-endring. + **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` som + deployes) kjørt og bekreftet, alle 16 ruter listet. **Rullet ut live**, + bruker bekreftet eksplisitt: `docker compose up -d --build + teecup_frontend` (gjenskapte også `teecup_api` som compose sin vanlige + avhengighets-bivirkning, ingen backend-kode rørt). Begge containere + boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. + +- **Dashboard/konto-runde (2026-07-21): to store punkter reist samtidig av + brukeren.** (1) "Alt relatert til dashboard/account/brukerkontoer" — + scopet sammen med brukeren via spørsmål: tom-tilstand-redesignet ble + PAUSERT (bruker ba om "juster retningen" og reiste et dypere spørsmål om + hvorvidt organisasjon fortsatt bør være "det som meldes først" — se eget + punkt i FEATURE_BACKLOG.md, min vurdering: nei, bør bli ett likestilt + valg blant flere). (2) Et helt nytt, stort forslag om frittstående + rundeføring + detaljert statistikk (putter/chip/bunkerslag/straffeslag/ + førsteputt-lengde) UTEN turnering/organisasjon — grundig notert i + FEATURE_BACKLOG.md med en eksplisitt arkitektur-advarsel: dette + UTFORDRER tenant-invarianten (`organization_id` på alle domenetabeller) + direkte og trenger en egen ADR, ikke bygget denne runden. + **Deltaker-tilgang til lag-chat/scorekort — ✅ BYGGET OG LIVE + 2026-07-21** (én av tre konkrete følgepunkter brukeren bekreftet i samme + runde, de to andre — sekundær e-post, HCP-historikk — tas fortløpende + etterpå): fjernet den blanke `get_authorized_org`-sperren fra ni + endepunkter på tvers av `messaging.py`/`scoring.py`/`matches.py`/ + `tournaments.py`/`courses.py`, erstattet med de ALLEREDE eksisterende + domene-sjekkene (`user_is_rostered_on_team`/`user_is_match_participant`/ + `user_is_team_captain`) som viste seg å støtte ikke-org-medlemmer helt + fint fra før — de var bare aldri nåbare. To nye delte hjelpefunksjoner i + `team_authz.py` (`is_org_member`, `user_is_tournament_participant` — + sistnevnte FLYTTET dit fra `registration.py` for å unngå sirkulær + import) dekker de endepunktene som IKKE hadde noen finkornet sjekk fra + før (ren fjerning der ville åpnet dem for enhver innlogget bruker). + `/auth/me` sin `my_tournaments` fikk `my_session_id`/`my_match_id`; + "Mine runder"-kortet fikk "Lag-chat"/"Scorekort"-lenker. + **Scratch-verifisert grundig, 43 sjekker** (isolert scratch-rolle+MinIO+ + engangs API-container): rostret ikke-medlem fikk korrekt tilgang + overalt (inkl. faktisk sendt chat-melding og hull-resultat), fortsatt + avvist fra det ANDRE lagets chat, en helt fremmed bruker avvist overalt, + org-eier beholder alt UNNTATT lag-chat (uendret, med vilje), kryss-org- + isolasjon bekreftet. **Reelt funn UNDER selve scratch-testingen:** en + rostret-men-ikke-kaptein spiller ble først uventet GODTATT til walkover + — viste seg å være en allerede tiltenkt, dokumentert fallback + (`user_is_team_captain`: "ingen kaptein utpekt ennå = enhver rostret + spiller godtas"), ikke en bug — testen ble rettet (la til en faktisk + kaptein) og bekreftet deretter riktig avvisning. `test_isolation.sql` + 12/12 uendret (ingen skjemaendring). Ekte typesjekket produksjonsbuild + 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. + +- **Sekundær e-postadresse (del 1, det enkle tilfellet) — ✅ BYGGET OG LIVE + 2026-07-21**, samme dag, rett etter deltaker-tilgang-runden. Ny + migrasjon `017_secondary_email.sql` (`secondary_email_token` + + `user_secondary_email`, samme token-hash-og-utløp-mønster som ADR-032s + `email_change_token`). Nye endepunkter `POST /auth/secondary-email`, + `POST /auth/secondary-email/confirm`, `DELETE /auth/secondary-email/{id}`. + **Kjernestykket:** `verify_magic_link`/`login_with_password` slår nå opp + `user_secondary_email` FØR sitt vanlige `app_user.email`-oppslag — en + innlogging på en verifisert sekundæradresse løses til EIERENS + eksisterende konto i stedet for å opprette en ny, separat en (nøyaktig + det hullet som gjorde funksjonen nødvendig i utgangspunktet). Lagt i + `/account` (ikke dashbordet som opprinnelig bedt om — bevisst avvik, + flagget eksplisitt: dette er kun del 1, dashbord-plassering er trolig + riktigere når/hvis del 2 (kontosammenslåing) bygges). + **Scratch-verifisert, 20 sjekker:** adresse ikke lagt til før bekreftet, + token ikke gjenbrukbart, dupliserte adresser (som andres primær- ELLER + sekundæradresse) avvist tydelig, innlogging via sekundæradresse (magic- + link OG passord) bekreftet å resolve til SAMME eksisterende konto, + fremmed kan ikke slette andres adresse, fjernet adresse oppretter en + genuint NY konto ved neste innlogging (beviser fjerning er reell). + `test_isolation.sql` 12/12. Ekte typesjekket produksjonsbuild kjørt og + bekreftet. + **Rullet ut live 2026-07-21**, bruker bekreftet eksplisitt: migrasjon + 017 kjørt mot ekte `teecup_db` (begge tabeller 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`/`/verify-email` → 200, `teeoff.no` + upåvirket. Del 2 (ekte kontosammenslåing) fortsatt IKKE designet, egen + fremtidig runde, se FEATURE_BACKLOG.md. + +- **HCP-historikk over tid — ✅ BYGGET OG LIVE 2026-07-21**, samme dag, + siste av de tre bekreftede punktene fra dashboard/konto-runden. Ny + migrasjon `018_handicap_history.sql` (append-only `handicap_history` — + kun for personlig profil sin `app_user.handicap_index`, IKKE de org- + scopede `player`/`team_roster`-radene, som har sitt eget uendrede + reproduserbarhets-prinsipp fra ADR-007). `PATCH /auth/profile` logger nå + en ny rad KUN ved en FAKTISK endring til en tallverdi — leser gjeldende + verdi FØR overskriving for å unngå duplikater ved gjentatt lagring av + samme verdi, og logger bevisst IKKE ved nullstilling. Ny + `GET /auth/profile/handicap-history`. Frontend: «Vis HCP-historikk»- + lenke i `/account` sin profilseksjon. + **Scratch-verifisert, 18 sjekker:** ingen duplikat ved uendret + gjenlagring, korrekt logging ved reell endring, ingen logg ved + nullstilling, ny logg ved gjeninnsetting etter nullstilling, + kronologisk rekkefølge riktig, full isolasjon mellom to brukeres + historikk. `test_isolation.sql` 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, sekundær e-post del 1, + HCP-historikk). + +- **Obligatorisk profil-fullføring ved innlogging LIVE (2026-07-22):** + svar på det pauserte "dashbordets tom-tilstand"-spørsmålet over — + brukeren avklarte at det ALLER første en innlogget bruker med en + ufullstendig profil skal se, er en fokusert «Fullfør profilen din»- + visning, ikke dashbordet. Ny migrasjon `019_profile_country_bio.sql` + (`app_user.country`, `app_user.bio` — samme nullable-kolonne-mønster + som resten av profilen, "obligatorisk" håndheves i app-laget). + `/auth/me` fikk et nytt beregnet felt `profile_complete` (sant når + fornavn/etternavn/fødselsdato/kjønn/HCP/hjemmeklubb/land ALLE er + utfylt — bilde og beskrivelse er bevisst unntatt, valgfrie). + **HCP-grensetilfelle avklart med bruker FØR bygging** (nybegynnere har + sjelden en offisiell HCP ennå): WHS-maksimum 54 brukes som + forhåndsutfylt standardverdi i skjemaet (ikke en DB-default), og + `ProfileUpdate.handicap_index` fikk en hard `le=54`-grense (kan aldri + registreres høyere) — løser grensetilfellet uten en egen "har ikke + HCP ennå"-avkrysning. + `AccountSettings` (`/account`) grener nå: er profilen ufullstendig, + vises KUN et nytt, fokusert `ProfileOnboarding`-skjema (de obligatoriske + feltene + valgfri beskrivelse, «Logg ut» tilgjengelig, INGEN tilgang + til resten av kontosidene) — er den komplett, vises den vanlige + innstillingssiden som før (nå med land+beskrivelse lagt til i det + vanlige profilskjemaet, for redigering i etterkant). `app/page.tsx` + (rot-siden) 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`, som selv gjør sjekken ved mount). + **Bevisst avgrenset:** gaten håndheves kun ved disse to naturlige + inngangspunktene, ikke ved dypere direktelenker til andre autentiserte + sider — samme skope-disiplin som tidligere runder. + **Scratch-verifisert, 16 backend-sjekker** (isolert scratch-rolle+ + MinIO+engangs API-container): fersk konto starter `profile_complete: + false`, delvis utfylling forblir ufullstendig, HCP>54 avvist (422), + full utfylling gir `true`, beskrivelse er reelt valgfri, å nullstille + et obligatorisk felt i etterkant slår `profile_complete` tilbake til + `false`, full isolasjon mellom to kontoer. `test_isolation.sql` 12/12 + uendret. Ekte typesjekket produksjonsbuild + et ekte HTTP-nivå-bevis + mot en kjørende produksjonscontainer (anonym mot `/` → 200 innloggings- + skjema, en ekte innlogget-men-ufullstendig sesjonscookie mot `/` → + `307 → /account`). + **Rullet ut live 2026-07-22**, bruker bekreftet eksplisitt: migrasjon + 019 kjørt mot ekte `teecup_db` (kolonner 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`/`/` (anonym) → 200, `teeoff.no` upåvirket. **Merk:** BEGGE + brukerens egne kontoer (`erol.haagenrud@envide.no` — eier av «Tjøme + Gents» — og `hei@erol.no`) mangler i dag alle disse feltene og vil + derfor begge se profil-fullførings-skjemaet ved neste innlogging — + bekreftet tilsiktet, ikke en bug. +- **Sju punkter fra faktisk bruk av scorekort-skjermen, BYGGET OG LIVE + 2026-07-24:** starthull-bug fikset (`currentHole` respekterte aldri + `round.start_hole` — `1` er truthy i JS, så `prev || start_hole` var en + no-op — forklarer trolig også det samtidig rapporterte GIR-avviket, + siden formelen selv var korrekt), kølle-bag på profilen (28 faste + typer, maks 14), nytt statistikkfelt «Anywayslag», valgfritt + statistikknivå per deltaker (default kun slag — ny kolonne + `round_participant.stat_level`), putt-avstand endret fra fritekst til + seks faste bøtter, «Hullet er spilt»-avkrysningen fjernet (overflødig + — spilt settes allerede automatisk ved slagtall). Ny migrasjon + `022_round_stats_and_bag.sql`. Numpad-layout/retningskors-ikoner for + tallvelgerne (brukerens punkt 6) er BEVISST holdt utenfor — egen + V0-prompt utarbeidet i stedet, ikke bygget selv. Full detalj i + ADR-033. 18 scratch-sjekker, `test_isolation.sql` 12/12, rullet ut mot + ekte `teecup_db`/`teecup_api`/`teecup_frontend`, `teeoff.no` upåvirket. +- **V0-prompten for numpad/retningskors/sveip-vurdering (punkt 6) BYGGET + OG LIVE, samme dag:** bruker kjørte prompten, sendte zip 13. V0 valgte + trykk-baserte Score/Statistikk-faner fremfor sveip (godt begrunnet — + unngår en tredje sveiperetning på en skjerm som allerede har to). + Flettet inn i EKSISTERENDE, allerede fungerende datalag (statLevel- + gating, kølle-bag, anywayslag, putt-bøtter, merge-før-PATCH, + starthull-fiks, `/my-rounds`-lenker) — ikke en ren erstatning, siden + V0 ikke kjente til den runden. Full detalj i ADR-033. Typesjekket + build kompilerte rent, rullet ut (kun `teecup_frontend`), `teeoff.no` + upåvirket. +- **Enda en runde brukerpunkter, ALLE BYGGET OG LIVE, samme dag:** + utslagstidspunkt + automatisk tidsbruk-visning (gjenbruker eksisterende + "Fullfør runde" som "Ferdig", ny `round.started_at`-kolonne, migrasjon + 023), "Idx" → "Hcp", "par"-merking på slag-tastaturet, ni-hulls- + navigasjonsbug fikset (respekterte aldri `holes_planned`, hoppet feil + ved "Forrige"), tak på putter/chip/bunker/straffeslag/anywayslag (kan + ikke overstige antall slag), "Slett runde"-knapp (backend fantes, + manglet UI), og en ny `PATCH /rounds/{id}` for å rette bane/utslag/ + antall hull MIDT i runden uten å røre allerede registrerte slag — + sperret etter fullføring. 22+8 scratch-sjekker, `test_isolation.sql` + 12/12, ren build. Full detalj i ADR-033. +- **To til punkter, BYGGET OG LIVE samme dag:** starthull kan nå endres + uansett (ren metadata), utslagstid justeres når som helst, og + fullført-tidspunkt kan korrigeres i etterkant — men KUN på en allerede + fullført runde (løser "glemte å trykke Fullfør runde i flere timer"). + Pluss en ny "Nærmest deg"-liste i bane-søket ved ny runde (Haversine- + avstand mot alle 174 teeoff-anlegg, geolokasjon i nettleseren, feiler + stille hvis avslått). 14 nye scratch-sjekker inkl. et ekte nearby-kall + mot teeoff. Full detalj i ADR-033. +- **Reell UX-bug fikset + "Så langt i runden"-oversikt bygget, samme + dag (2026-07-25), klar for utrulling:** brukeren rapporterte at "All + statistikk" ikke viste noe utover slag/putter -- bekreftet (kun + lesing) mot ekte `teecup_db` at `stat_level='full'` FAKTISK var + lagret riktig, så feilen var presentasjonen: alle detaljfeltene lå + bak en "Score"/"Statistikk"-fane fra forrige V0-runde som brukeren + aldri oppdaget. Fikset ved å fjerne faneløsningen helt -- alt vises nå + alltid samlet. Samtidig bygget en ny "Så langt"-oversikt + (`ScoreSoFar`-komponent: kompakt linje + utvidbar full-oversikt- + tabell med netto per hull), med et nytt backend-felt + `strokes_received` (`allocate_strokes_by_index()`, ingen ny + algoritme). 11 nye scratch-sjekker (inkl. et presist tall-eksempel på + slagfordelingen), ren build. Full detalj i ADR-033. + **Rullet ut live 2026-07-25**, bruker bekreftet eksplisitt: kun + `teecup_api`+`teecup_frontend` redeployet, ingen migrasjon, + `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. +- **Reell produksjonsregresjon rapportert av bruker rett etter utrullingen + over, funnet og fikset umiddelbart samme dag (2026-07-25):** + "Ingenting er klikkbart i den avanserte statistikken." Root cause var + IKKE frontend (all onClick-kabling var korrekt) -- funnet ved å faktisk + gjenskape brukerens klikk-sekvens mot en fersk scratch-container: + `update_hole` (hull-PATCH) manglet fortsatt det nye påkrevde + `strokes_received`-feltet i responsen sin (lagt til i GET-endepunktet i + runden rett over, glemt i PATCH) -- ga en 500 (Pydantic-valideringsfeil) + på HVER hull-lagring, ikke bare de avanserte feltene. Frontend svelger + feilresponsen stille, så symptomet så ut som "ingenting skjer" for + ALT, ikke bare avansert statistikk (brukeren merket det trolig først + der siden Slag/Putter fra tidligere runder allerede hadde lagrede + verdier som så riktige ut). Fikset: `update_hole` beregner nå + `strokes_received` for hullet som oppdateres, samme algoritme som + `list_holes`. 14 scratch-sjekker som gjenskaper eksakt klikk- + rekkefølgen, alle bestått. **Rullet ut live 2026-07-25**, bruker + bekreftet eksplisitt: kun `teecup_api` redeployet, `/health`/ + `/dashboard` → 200, `teeoff.no` upåvirket. +- **Rediger/Fullfør/Slett tonet ned + flyttet til toppen, auto-scroll ved + hull-bytte, LIVE samme dag (2026-07-25):** brukeren rapporterte at + "Fullfør runde"/"Slett runde" var for lette å trykke på ved et uhell + (lå rett under "Neste hull"). Flyttet alle tre handlingene til en + nedtonet rad øverst -- må nå aktivt scrolles til. Samtidig: "Neste + hull"/"Forrige" scroller nå automatisk opp til toppen av hull-panelet + (også ved direkte hull-valg), så det nye hullets Slag-felt alltid er + synlig med en gang. Ren frontend-endring, ingen backend/migrasjon. + Rullet ut, `teeoff.no` upåvirket. +- **"Så langt i runden" utvidet med netto/stableford-sum, putt-/kølle- + statistikk-totaler og grafisk fairway-/innspill-fordeling, LIVE samme + dag (2026-07-25):** etterspurt av bruker. Ny `StatPill`-rutenett + (Slag/Til par/Netto/Stableford/Putt/Chip/Bunker/Straffeslag/ + Anywayslag), Stableford-kolonne i hull-tabellen, og to nye + `DistributionBar`-seksjoner (Fairwaytreff/Innspill, N-kategori-variant + av `SegmentedBar`-mønsteret fra tournament-leaderboard.tsx) + + gjennomsnittlig brutto score-til-par (to desimaler, fortegn) splittet + på treff-vs-bom for både fairway og innspill. Ingen backend-endring -- + alt beregnes klientside fra data `GET .../holes` allerede returnerer. + Stableford beregnes alltid ut fra netto score når mulig (appen har + ingen egen "spilleform"-innstilling). Logikk verifisert manuelt mot et + regnet eksempel før utrulling. Ren frontend-endring, ingen migrasjon, + rullet ut, `teeoff.no` upåvirket. +- **"Avstand første putt" flyttet rett under "Putter", LIVE samme dag + (2026-07-25):** lå tidligere lenger ned i "full"-statistikk-blokken + (etter kølle/retning/chip-gruppen) -- flyttet til å bli første felt i + den blokken, rett under Putter-NumberPickeren (som ligger utenfor + "full"-gaten). Ren omrokkering, ingen ny logikk. Ekte typesjekket + build, rullet ut, `teeoff.no` upåvirket. +- **V0-prompt for en rikere rundestatistikk-skjerm skrevet, IKKE bygget + ennå (2026-07-25):** brukeren lastet opp en skjermopptaksvideo + (`screen-20260724-133013-1784892586987.mp4`, 26 sek, av en KONKURRENT- + apps statistikkskjerm) og ba om et V0-prompt inspirert av innholdet, + eksplisitt IKKE et plagiat. Video analysert bilde for bilde (ffmpeg i + en engangs Docker-container, ikke installert på verten). Innholdet + identifisert dekkes i stor grad av data vi ALLEREDE sporer + (fairwaytreff/innspill-retning/putts/chip/bunker/straffeslag/putt- + lengde-bøtte) -- ingen backend-endring skulle trengtes for de fleste + målene. Prompt skrevet til bruker i chatten (ikke lagret som egen + fil) -- beskriver mål/informasjonsarkitektur/tilgjengelighetskrav + abstrakt (donut med sentertall, gauge-stolper for avvik fra par, + retnings-diagram for bom-retning) UTEN å kopiere konkurrentens + eksakte fargevalg/layout/ordlyd, og ber eksplisitt om TeeCups egen + merkevareidentitet (grønn/oransje) + en egen visuell vri. + **Én bevisst forskjell fra videoen, valgt for å unngå plagiat OG fordi + det matcher vårt eget datamodell:** videoens puttlengde-bøtter + (<1m/1-2/2-4/4-8/+) er ANDRE enn TeeCups allerede lagrede seks bøtter + (<1m/<2m/<3m/<5m/<8m/8m+, ADR-033) -- promptet ber om TeeCups egne + bøtter, ikke videoens. "Lengste drive" (krever GPS/avstandsmåling vi + ikke har) bevisst utelatt fra promptet. + **Bygget og rullet ut live 2026-07-25, samme dag:** zip 14 mottatt. + Diffet mot live-treet FØR noe ble tatt inn (samme rutine som alltid) -- + kun to reelt nye filer (`components/round-stats.tsx`, + `app/rounds/[id]/stats/page.tsx`), resten var V0s vanlige uvitende + reverts (bl.a. sin egen `/rounds/[id]`-ruteversjon fra FØR + `/my-rounds`-omdøpingen ADR-033 gjorde 2026-07-23 -- korrekt hoppet + over). Ruten lagt inn som `app/my-rounds/[id]/stats/page.tsx` i stedet + (samme kollisjon-unngåelse). `globals.css` sin nye `--chart-1..6` + data-viz-fargeskala slått sammen inn (V0 sine egne, mer omtrentlige + `--primary`/`--ring`/`--brand-orange`-verdier IKKE tatt inn -- beholdt + de presise OKLCH-verdiene fra ADR-016). + **Datalag skrevet fullstendig om fra mock:** henter `GET /rounds/{id}` + + `GET .../participants/{id}/holes` (samme endepunkter round-detail.tsx + allerede bruker) -- ingen ny backend. Ny `computeStats()`-funksjon + regner ut ALT fra rå hull-data ved lesing (score-kategorier, + snitt-til-par totalt/per hulltype, fairway-fordeling + score-splitt, + GIR totalt/per hulltype/kryss-fairway + score-splitt, bom-retning på + green, putt-fordeling 1/2/3-putt + snitt per hulltype + med/uten GIR, + én-putt% per TeeCups egne seks puttlengde-bøtter + lengdefordeling + hit/miss, chip-fordeling, scrambling%, sand save%, bunker/straffeslag + per runde + score-splitt). Hver seksjon skjules helt når det ikke + finnes nok data (samme "vis kun det som faktisk finnes"-prinsipp som + "Så langt i runden"). Ny enkel spillervelger (pill-rad) lagt til når + runden har flere deltakere -- default til eieren. + **"Se full rundestatistikk"-lenke** lagt inn i `CompletedBanner` i + round-detail.tsx (V0s egen versjon hadde denne, men i sin ellers + fullstendig reverterte fil -- portert manuelt inn i vår LIVE versjon + i stedet for å ta hele filen). + **Matematikken verifisert FØR utrulling:** `computeStats()`-logikken + portert til et frittstående Node-script og kjørt mot et + hånd-etterregnet 6-hulls syntetisk datasett (blandet par 3/4/5, + fairwaytreff/-bom, GIR-treff/-bom, bunkerslag) -- alle 15+ utledede + tall stemte eksakt med manuell utregning. Ekte typesjekket + produksjonsbuild kjørt og bekreftet (`/my-rounds/[id]/stats` listet + som ny rute). + **Rullet ut live 2026-07-25**, ren frontend-endring, ingen migrasjon, + `docker compose up -d --build teecup_frontend`, `/health`/`/my-rounds` + → 200, `teeoff.no` upåvirket. +- **Rundeliste + scorekort-redesign, LIVE samme dag (2026-07-25):** + brukeren delte en ny skjermopptaksvideo av "Egne runder"-listen (som + manglet ALL score-informasjon) og av scorekort-registreringen (rapportert + som "veldig dårlig designet" -- for mange ulike knapp-typer stablet + oppå hverandre) + "Runde fullført"-siden (rapportert som "veldig mye + dobbel informasjon"). Reflektert over problemstillingen (kort, per + brukerens ønske) før to V0-prompter ble skrevet. + **Fikset direkte, uten V0:** "Runde fullført"-duplikatet -- `ScoreSoFar` + ("Så langt i runden") skjules nå helt når runden er fullført, siden + den nye rundestatistikk-siden dekker akkurat det samme, langt + grundigere. Ny backend-beregning i `_load_round_out` + (`app/routers/rounds.py`): `owner_holes_played`/`owner_total_score`/ + `owner_score_to_par`, en enkel aggregatspørring mot `round_hole` for + eierens egen deltaker-rad -- verifisert med 10 scratch-sjekker + (inkl. at en gjests score IKKE påvirker eierens aggregat). + **Zip 15 og 16 mottatt** (samme v0.app-prosjekt, kontinuerlig -- + `round-card.tsx`/`own-rounds.tsx` identiske i begge, zip 16s + `round-detail.tsx` var den nyeste med selve scorekort-redesignet; zip + 15 brukt kun for å bekrefte at zip 16 var det riktige, endelige + eksportet). + `round-card.tsx` fikk en ny `ScoreTile` -- prominent resultat+til-par + for fullførte runder, hull-fremdrift-bar ("6/18 hull spilt") for + runder som pågår, pluss en HCP-differensial-chip når runden telte. + Datalag i `own-rounds.tsx` skrevet om fra mock til ekte fetch, kobler + de nye `owner_*`-feltene fra backend + eierens `score_differential` + (kun vist når `counts_for_handicap`). + `round-detail.tsx` fikk en ny kollapsbar "Flere detaljer"-seksjon + (lukket som default) som nå rommer kølle/utslag-retning/innspill- + retning/chip-bunker-straffeslag/anywayslag -- Slag/Putter/Avstand + første putt forblir alltid synlig over. Datalag/statLevel-gating/ + bucket-basert puttlengde/maxValue-capping (alt bygget tidligere denne + økten) bevisst IKKE revertert til V0s eldre mock-baseline, kun selve + kollaps-mekanismen og plasseringen ble hentet derfra. Lagt til en kort + kode-kommentar (ikke noe bygget UI ennå) som reserverer plass ved + siden av hull-headeren til en fremtidig avstandsmåling-indikator, + bekreftet av bruker at dette kommer senere. + Ekte typesjekket produksjonsbuild kjørt og bekreftet (alle 19 ruter). + **Rullet ut live 2026-07-25**, ren frontend-endring (+ den lille + backend-tilføyelsen over), ingen migrasjon, `/health`/`/my-rounds` → + 200, `teeoff.no` upåvirket. +- **Reelt hull funnet og fikset SAMME dag, rapportert av bruker rett + etter forrige punkt ("Hvor er scorekortet?"):** da "Så langt i runden" + ble skjult for fullførte runder (se over), forsvant OGSÅ den eneste + plassen den rå hull-for-hull-tabellen (Hull/Par/Score/Netto/Sum) fantes + -- `round-stats.tsx` (den nye dedikerte statistikk-siden) hadde KUN + utledet/aggregert statistikk (donuter, stolper), ingen tabell med de + faktiske tallene per hull. Fikset ved å legge til en ny "Scorekort"- + seksjon FØRST på statistikk-siden (samme tabell-mønster som "Så + langt" hadde -- Hull/Par/Score/Netto/Stableford/Sum), åpen som + default. La til `start_hole` i `round-stats.tsx` sin `ApiRound`-type + og `strokes_received` i `ApiHole`-typen (sistnevnte kom allerede fra + backend, bare ikke lest av denne siden ennå) for å kunne vise hullene + i rundens FAKTISKE rekkefølge (samme sirkulære start_hole-logikk som + round-detail.tsx) i stedet for bare rå hullnummer 1-18. Ekte + typesjekket build kjørt og bekreftet. Rullet ut, ren frontend-endring, + ingen backend/migrasjon, `teeoff.no` upåvirket. +- **Ny scorekort-presentasjonsregel + V0-prompt skrevet, IKKE bygget + ennå (2026-07-25):** brukeren delte et referansebilde av et + tradisjonelt horisontalt golf-scorekort og formulerte en generell + regel: hull listet HORISONTALT (som kolonner) → sum TIL HØYRE; hull + listet VERTIKALT (som rader) → sum UNDER. Dagens "Scorekort"-tabell + (bygget rett over samme dag) er vertikal med sum som løpende KOLONNE + -- ikke i tråd med regelen. V0-prompt skrevet (horisontalt scorekort, + hull 1-9/10-18 som kolonner, Ut/Inn-sum til høyre for hver halvdel, + Hcp/Par/Score/Netto/Stableford-rader, håndterer både 9- og 18-hulls + runder med vilkårlig start_hole), bevisst IKKE et plagiat av + referansebildet (egne farger, egen "Hcp"-term i stedet for bildets + "Slope", ingen kopiert spiller-header). Full detalj i + ARCHITECTURE_DECISIONS.md. **Presisert samme dag, før noe ble sendt:** + brukeren spurte om et mykt "scroll hvis nødvendig"-unntak fanget opp + målet om ALDRI å måtte scrolle -- svart nei (reell breddekonflikt med + "lesbar uten briller"-kravet, ikke bare ordlyd) og skrevet om til et + hardt "ingen scroll"-krav som eksplisitt forteller V0 HVORDAN det + oppnås (kompakte fete høykontrast-siffer i selve rutenettet, smale + forkortede rad-labels i en trang gutter -- "lesbar uten briller" + avgrenset til labels/knapper, ikke enkeltsifre). Full detalj i + ARCHITECTURE_DECISIONS.md. + **Zip 17 mottatt og BYGGET/LIVE samme dag:** en ekte HTML `<table>` med + `<colgroup>` faste kolonnebredder -- ingen scroll-container i det hele + tatt, løst med kompakte celler i stedet. Score-cellene bruker FORM + (sirkel=under par, firkant=over par) + fylt/ufylt (2+ slag av) for å + aldri stole på farge alene, pluss en egen symbolforklaring. Egen, + NY dedikert side `/my-rounds/[id]/scorecard` + (`components/round-scorecard.tsx`) -- ikke slått sammen med + `round-stats.tsx`, siden V0 designet den med egen side-chrome (header, + rundesammendrag). Datalag skrevet om fra mock til ekte fetch; V0s egen + `strokesReceived()`-formel (generisk modulo) BEVISST forkastet til + fordel for backend sin allerede beregnede `strokes_received` (samme + `allocate_strokes_by_index()` som resten av appen -- unngår to ulike + HCP-slagfordelings-implementasjoner). Samme sirkulære + start_hole-rekkefølge som resten av rundeskjermene. Den gamle + vertikale "Scorekort"-tabellen i `round-stats.tsx` FJERNET (erstattet + med en lenke til den nye siden) -- `round-stats.tsx` er nå rendyrket + aggregert statistikk, det rå scorekortet bor kun ett sted. V0 la selv + til en "Se scorekort"-knapp i `CompletedBanner` (round-detail.tsx) ved + siden av den eksisterende "Se full rundestatistikk" -- tatt inn. + **Samtidig, rapportert av bruker:** Anywayslag manglet helt fra + rundestatistikk-siden sin "Chip, bunker og straffeslag"-seksjon. + **Feilrettet rett etterpå, samme dag:** min første fiks slo feilaktig + sammen Anywayslag-tallet i DEN eksisterende seksjonen og omdøpte hele + seksjonen til "Annet" -- brukeren påpekte at "Chip, bunker og + straffeslag" skulle beholde navn+innhold uendret, og at "Annet" skulle + være en EGEN, ny seksjon RETT ETTER med kun anywayslag-tall (total per + runde + andel hull med anywayslag). Rettet umiddelbart. Notert at et + fritekst-notatfelt trolig havner i "Annet" senere. Ekte typesjekket + build kjørt og bekreftet (ny rute `/my-rounds/[id]/scorecard` listet). + Rullet ut (to runder), ren frontend-endring, ingen backend/migrasjon, + `teeoff.no` upåvirket. +- **Rundestatistikk-seksjonene starter nå kollapset, LIVE samme dag + (2026-07-25):** brukeren ba om at "trekkspillet" (StatCard-seksjonene + på `/my-rounds/[id]/stats`) skal vises sammenslått -- må klikkes for å + se innholdet. `StatCard` sin `defaultOpen` endret fra `true` til + `false` (ingen kallsted overstyrte den, så én linje dekket alle + seksjonene). Ekte typesjekket build, rullet ut, `teeoff.no` upåvirket. +- **Notert i FEATURE_BACKLOG.md, IKKE bygget:** brukeren ba om et notat + om et femte fremtidig turneringsformat, "Flaggturnering" (utover de + fire fra 2026-07-19-runden) -- krever en visning av GJENSTÅENDE slag + for spilleren, oppdatert etter hvert hull, pluss en fremtidig idé om + å bruke GPS (når/hvis integrert) til å markere hvor langt spilleren + faktisk kom. Lagt til i samme seksjon som de fire andre formatene. + +- **Tre punkter fra brukeren, ALLE BYGGET, SCRATCH-VERIFISERT OG LIVE + 2026-07-25:** (1) enkeltbane-anlegg (f.eks. Tjøme Golfklubb) dropper nå + det overflødige banenavnet ved import/runde-opprettelse — kun "Tjøme + Golfklubb", ikke "Tjøme Golfklubb – Hovedbanen" (flerbane-anlegg som + Ålesund beholder uendret "Anlegg – Bane"-navn). (2) Runder kan nå + navngis (`round.name`, migrasjon `024_round_name.sql`, valgfritt, + redigerbart i etterkant) — vises på tvers av rundeliste/rundeside/ + scorekort/statistikk, faller tilbake til banenavn når ikke satt. (3) + Profilens "Land"-felt er nå en nedtrekksliste (kun "Norge" foreløpig, + klargjort for flere), plassert FØR "Hjemmeklubb", som selv ble + omgjort til en søkbar liste mot teeoffs klubbregister (gjenbruker + eksisterende `/rounds/official-search`, ingen ny backend-kode). Full + detalj i ARCHITECTURE_DECISIONS.md. Bruker bekreftet eksplisitt: + migrasjon 024 kjørt mot ekte `teecup_db`, `test_isolation.sql` fortsatt + 12/12, begge containere redeployet, `/health`/`/dashboard`/`/my-rounds`/ + `/account` → 200, `teeoff.no` upåvirket. + +- **Refleksjonsrunde 2026-07-25: hvorfor organisasjon? + venner/deling — + DESIGNET, IKKE bygget.** Brukeren spurte hvorfor organisasjon i det + hele tatt trengs, gitt at ADR-033 nå gjør en enkelt bruker fullt + selvstendig. Veide A (bruker-eide turneringer, som runder) mot B + (organisasjon beholdt, men opprettelsen gjøres usynlig/automatisk) — + A avvist (ville krevd duplisering av HELE turnering-apparatet som er + bygget rundt RLS/`organization_id`, og er ikke reversibelt i noen + retning), B valgt (rører verken skjema eller de ni eksisterende + routerne, kun frontend-orkestrering — `POST /orgs` krever allerede kun + `name`). Skrevet som **ADR-035** (organisasjon-B-beslutningen + et + konkret 7-blokks dashbord-forslag: Hurtighandlinger/Kommende runder/ + Kommende turneringer/Statistikk/Spilte baner/Venner/Organisasjoner — + sistnevnte nedtonet, kun synlig ved reelt flere org-er) og **ADR-036** + (nytt vennekonsept — gjensidig forespørsel/aksept, privat + kategorisering i faste grupper som Make/Nær familie/Golfvenner/osv., + ny rundevisibilitet `public`/`private`/`friends` med eksplisitt + gruppevalg, og et tiered personsøk — venner→samme klubb→samme + land→globalt, navnerekkefølge-uavhengig — delt mellom "finn venn" og + en fremtidig "legg til ekte medspiller"-utvidelse av + `round_participant.user_id`, som allerede lå ubrukt i skjemaet som en + eksplisitt notert "v1-avgrensning" i ADR-033). V0-prompt for det nye + dashbordet skrevet (i FEATURE_BACKLOG.md), IKKE sendt til V0 ennå. + **Ingen kode skrevet** — bevisst en design-/dokumentasjonsrunde, ikke + en byggerunde, på brukerens eksplisitte instruks. Full detalj i + ARCHITECTURE_DECISIONS.md (ADR-035/036) og FEATURE_BACKLOG.md (åpne + spørsmål, foreslått 3-fase byggerekkefølge for venner-delen). +- **Oppfølging samme dag:** bruker bekreftet at en lagt-til ekte + medspiller SKAL se runden i sin egen "Egne runder"-liste (ADR-036 fase + 3, tidligere bevisst uavklart). Presiserte samtidig en ny teknisk + konsekvens i ARCHITECTURE_DECISIONS.md: `RoundOut` sine + `owner_*`-statistikkfelt må bli viewer-relative, ikke alltid eierens, + og et NYTT åpent spørsmål dukket opp — skal medspilleren også kunne + SKRIVE egne hull-tall, ikke bare lese? Ikke avgjort. Fortsatt ingen + kode skrevet. + +- **Dashbord-redesign (ADR-035) BYGGET, IKKE ENNÅ RULLET UT (2026-07-25):** + zip 18 mottatt og integrert — kun `dashboard.tsx` var reelt nytt (samme + full-reeksport-mønster som alltid), pluss en genuin MERGE (ikke revert) + i `tournament-card.tsx`: V0s nye `organizer`-merkelapp lagt til SIDE OM + SIDE med den eksisterende `orgId`-baserte lenkelogikken (offentlig vs. + innlogget kontekst) som V0 ikke kjente til. Datalag skrevet fra bunnen: + "Kommende turneringer" slår sammen deltaker- (`me.my_tournaments`) og + arrangør-turneringer (hentet per org, alle organisasjoner brukeren er + medlem i) til ÉN tidssortert liste, filtrert til `draft`/`active`-status. + "Statistikk"/"Spilte baner" er REN klientside-utledning fra eksisterende + `GET /rounds` + `GET /auth/profile/handicap-history` — ingen nye + endepunkter trengt. "Ny turnering"-hurtighandlingen implementerer selve + ADR-035-mekanismen: oppretter/gjenbruker organisasjon USYNLIG først + (kun ved faktisk innsending, ikke når skjemaet åpnes — unngår en + foreldreløs org ved avbrutt skjema), deretter turneringen, deretter + navigerer rett inn i den. "Bli med med kode" gjenbruker samme + `by-code`-oppslag som `login-form.tsx` sin `JoinByCode`, nå som en + dashbord-lokal variant. "Venner"-seksjonen vises med en ærlig + tom-tilstand (ADR-036 er ikke bygget ennå) — CTA-en er bevisst uten + href/onClick, ikke en lenke til noe som ikke finnes. "Spilte baner" + fikk sine listeelementer gjort om til ren visning (ikke lenker) siden + API-et ikke eksponerer noen stabil bane-id å lenke til per runde. + Ekte typesjekket produksjonsbuild kompilerte rent, alle 21 ruter + listet. **Rullet ut live 2026-07-25**, bruker bekreftet eksplisitt: + `docker compose up -d --build teecup_frontend` (gjenskapte også + `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge + containere boot-et rent, `/health`/`/dashboard`/`/my-rounds` → 200, + `teeoff.no` upåvirket. +- **Oppfølging samme runde, avklart med bruker:** medspillere skal kunne + registrere score for HELE flighten (ikke bare egen rad) når ADR-036 + fase 3 bygges — oppdatert i ARCHITECTURE_DECISIONS.md/FEATURE_BACKLOG.md. + To nye notater lagt i FEATURE_BACKLOG.md (ingen design/bygging ennå, + kun fanget opp): (1) turneringsoppsett mangler en vei til å FLYTTE en + rostret spiller til et annet lag (kun fjerning finnes i dag), (2) et + reelt modelleringsspørsmål om flere flighter i én frittstående runde + (f.eks. "min flight + vennenes flight bak oss samme dag") — to + prinsipielt ulike retninger skissert, ingen valgt. + +- **ADR-036 fase 1 (venner-kjernen) BYGGET OG SCRATCH-VERIFISERT, IKKE + ENNÅ RULLET UT (2026-07-25):** ny migrasjon `025_friends.sql` + (`friendship` + `friend_categorization`, ingen RLS — samme + `plain_connection()`-mønster som runder) og nytt `app/routers/ + friends.py`: `GET /people/search` (tiered venn→klubb→land→globalt, + navnerekkefølge-uavhengig token-matching, min. 2 tegn før noe + returneres), `POST /friends`/`POST /friends/{id}/accept`/ + `DELETE /friends/{id}` (forespørsel/aksept/avslå-kanseller-avvenn — én + DELETE dekker alle tre), `GET /friends`, `PUT /friends/{friend_user_id}/ + categories` (erstatter hele settet, krever akseptert vennskap). Router + registrert i `main.py`, `/people`+`/friends`-rewrites lagt til i + `next.config.mjs` (ny frontend-rute vil hete `/my-friends`, IKKE + `/friends` — unngår samme kollisjonsfelle som `/rounds` fra før). + **Scratch-verifisert grundig** (isolert rolle+MinIO+engangs API- + container, 5 syntetiske brukere): 31 sjekker, inkl. presist bevist + tiered rangering for 4 distinkte brukere samtidig, og at + kategorisering er EKTE PRIVAT (bekreftet: B ser aldri kategoriene A + satte B i). `test_isolation.sql` fortsatt 12/12. Ekte typesjekket + produksjonsbuild kompilerte rent. **Rullet ut live 2026-07-25**, bruker + bekreftet eksplisitt: migrasjon 025 kjørt mot ekte `teecup_db` (begge + tabeller 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. Verifisert presist at ruten faktisk når FastAPI (ikke bare + at Next.js svarte): anonymt `GET /people/search`/`GET /friends` over + ekte https ga korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404. Frontend + bevisst IKKE hånd-kodet denne gangen (V0-prompt skrevet i + FEATURE_BACKLOG.md i stedet, matcher etablert mønster/tidligere + korrigering) — venter på at brukeren kjører den i v0.app. + +- **ADR-036 fase 1 FRONTEND BYGGET, IKKE ENNÅ RULLET UT (2026-07-25):** + zip 19 mottatt og integrert — kun `components/friends.tsx` og + `app/friends/page.tsx` var reelt nye fra V0 (samme full-reeksport- + mønster som alltid, resten forventede reverts av allerede tilpassede + filer, hoppet over). **V0s egen rute (`/friends`) BEVISST IKKE brukt** + — flyttet til `/my-friends`, siden `/friends` nå er API-prefikset + (samme kollisjonsklasse som `/rounds` → `/my-rounds` tidligere, unngått + fra start denne gangen i stedet for oppdaget i produksjon). + Datalag skrevet fullstendig om fra V0s mock til ekte fetch mot + `/people/search`+`/friends`-endepunktene — TS-typene speiler Pydantic- + modellene i `app/routers/friends.py` felt-for-felt. Kategori-koder + (`spouse`, `golf_friends`, osv.) mappet mot V0s norske visningsnavn via + en delt `CATEGORY_OPTIONS`-liste i SAMME rekkefølge som backend sin + `Category`-type. Søkefeltet håndhever samme 2-tegns-minimum som + backend (viser en forklarende tekst i stedet for å bare returnere + tomt). "Send forespørsel"/"Godta"/"Avslå"/"Kanseller"/"Fjern venn" + trigger alle en full refetch av `GET /friends` etterpå (samme + refetch-etter-mutasjon-mønster som resten av appen) — kun kategori- + avkrysning er lokalt optimistisk (matcher PUT-endepunktets + erstatt-hele-settet-kontrakt). + **Dashbordets "Venner"-blokk koblet til ekte data i samme runde:** + root-komponenten henter nå `GET /friends`, viser ekte avatar-initialer + + antall venner + ventende forespørsler (samme visuelle design som + opprinnelig i zip 18, som ble bevisst forenklet til en inert tom- + tilstand forrige runde siden backend ikke fantes ennå) — begge + knappene ("Se venner"/"Søk etter venner") lenker nå til `/my-friends`. + Ekte typesjekket produksjonsbuild kompilerte rent, `/my-friends` listet + blant 22 ruter. **Rullet ut live 2026-07-25**, bruker bekreftet + eksplisitt: `docker compose up -d --build teecup_frontend` (gjenskapte + også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). + Begge containere boot-et rent, `/health`/`/dashboard`/`/my-friends` → + 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. + +- **Oppfølging samme dag: e-post-fallback for varsler + PWA- + installasjon vurdert, IKKE bygget.** Varsel-V0-prompten sendt til + bruker. Bruker foreslo en betinget e-post-fallback ved venneforespørsel + (kun hvis mottaker har samtykket til e-post fra TeeCup) — sjekket at + INGEN generell kommunikasjons-samtykke-flagg finnes i dag (kun + ADR-017s turnering-registrerings-samtykke, noe annet), foreslo nytt + opt-in `app_user.notification_emails_enabled` + en sjette + `send_friend_request_email()`-funksjon i `app/email.py` (samme mønster + som de fem eksisterende). Bruker spurte også hvordan få brukere til å + installere PWA-en "nærmest umiddelbart" — vurdert grundig: Android kan + fange `beforeinstallprompt` og vise egen timing, iOS Safari har INGEN + programmatisk installasjonsvei (kun instruksjonsoverlegg mulig, hard + Apple-begrensning) — anbefalte å vise oppfordringen rett etter + obligatorisk profil-fullføring (universelt sjekkpunkt alle nye brukere + allerede går gjennom) fremfor bokstavelig "umiddelbart". Begge kun + vurdert/skissert i FEATURE_BACKLOG.md, ingen kode skrevet, intet + V0-prompt for PWA-delen ennå. + +- **To nye drøftingspunkter, IKKE besluttet eller bygget (2026-07-25):** + bruker lastet opp to skjermbilder av Golf GameBooks leaderboard-løsning + (egen "Leaderboards"-fane, rangert liste, trykk-ut til fullt + scorekort) og spurte om (1) et tilsvarende leaderboard for + runder/turneringer, usikker på plassering, og (2) om TeeCup burde få + 1-2 nye designfarger. Drøftet grundig i FEATURE_BACKLOG.md, ikke + konkludert: fant at "runder" kan få dette NÅ (flere-deltakere-støtte + finnes allerede, ingen ADR-036-avhengighet) mens "turneringer" sitt + EKSISTERENDE leaderboard er lag-poeng (Ryder Cup-matchplay), strukturelt + noe annet enn Golf GameBooks individuelle rangering — åpent + avklaringsspørsmål. For fargespørsmålet: fant at en blåtone ALLEREDE + finnes i `--chart-3` (bare ikke løftet til en kjerne-designtoken) — + anbefalte å gjenbruke DEN i stedet for å finne på en helt ny, og + anbefalte mot flere enn én ekstra farge. Ingen kode skrevet, ingen + V0-prompt — ren refleksjonsrunde på brukerens eksplisitte instruks. + +- **In-app varslingssenter + rundeleaderboard-backend BYGGET OG SCRATCH- + VERIFISERT (2026-07-26), IKKE ENNÅ RULLET UT:** to ting i samme runde. + (1) Brukeren lastet opp zip 20 — resultatet av varsel-V0-prompten fra + 2026-07-25 (bjelle-ikon i dashbord-header, egen varslingsside). Bygget + matchende backend: migrasjon `026_notifications.sql` (tabell + `notification`, `plain_connection()`-mønster som resten av bruker-eid + data), `app/routers/notifications.py` (fire endepunkter + delt + `create_notification()`-hjelpefunksjon), to trigger-punkter i + `friends.py` (venneforespørsel sendt/akseptert — de eneste hendelsene + som faktisk finnes i dag). Frontend integrert kirurgisk (kun de nye + filene + et uttrekk fra V0s dashbord-eksport, ikke en full revert): + `components/notifications.tsx` skrevet om fra V0s mock-scenario til + ekte fetch, ny rute `/my-notifications` (IKKE `/notifications` — samme + kollisjonsklasse unngått fra start som `/rounds`/`/friends`), + bjelle-komponenten portert inn i den LIVE `dashboard.tsx` sin header, + koblet til et ekte `GET /notifications/unread-count`-kall. + (2) Brukeren ba samtidig om et leaderboard for frittstående runder + (opptil 13+ spillere mulig, flere flighter). Bygget ny + `GET /rounds/{round_id}/leaderboard` i `app/routers/rounds.py` — brutto + OG netto score-til-par + "thru"-antall PER deltaker, sortert stigende + på brutto. Netto bruker samme `allocate_strokes_by_index`-algoritme som + resten av appen (`strokes_received` i `list_holes`), denne gangen + summert over KUN de faktisk spilte hullene. + **Scratch-verifisert grundig, 39/39 sjekker i to testløp** (isolert + `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API- + container, samme mønster som hele prosjektet): full varsel-syklus begge + retninger + `mark-all-read` + kryss-bruker-isolasjon (bruker B kan ikke + markere bruker A sitt varsel som lest via id-gjetting), full + leaderboard-runde (3 deltakere, ulik fremdrift/score, riktig rangering/ + thru/to-par for alle), kryss-bruker-autorisasjon (403), ukjent + runde-id (404) — OG en dedikert netto-kryssjekk der resultatet ble + sammenlignet mot en HELT UAVHENGIG beregning via + `handicap_engine.allocate_strokes_by_index` kalt direkte fra + testskriptet (ikke bare "endepunktet svarte 200") — stemte eksakt, + inkl. et bevisst vekslende stroke-index-mønster og kun 9 av 18 hull + spilt, for å teste allokeringen over et REELT delvis spilt sett, ikke + et trivielt sammenfallende tilfelle. `test_isolation.sql` fortsatt + 12/12. Ekte typesjekket produksjonsbuild av frontend kompilerte rent, + `/my-notifications` listet blant rutene. + **V0-prompt for selve leaderboard-VISNINGEN skrevet og sendt til + bruker** (se FEATURE_BACKLOG.md for hele prompten) — rangering med + delt plassering, brutto/netto-veksling, "thru X"/"Ferdig"-tilstander, + kompakt mini-variant til rundens detaljside, alltid form+farge for + over/under par. Ikke kjørt i v0.app ennå — leaderboardets FRONTEND + kommer i en senere runde. + **Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: migrasjon + 026 kjørt mot ekte `teecup_db` (tabell `notification` bekreftet, + `test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d + --build teecup_api teecup_frontend`. Begge containere boot-et rent, + `/health`/`/dashboard`/`/my-notifications` → 200, `teeoff.no` + upåvirket. Verifisert presist at `/notifications`-ruten faktisk når + FastAPI (ikke bare at Next.js svarte): anonymt `GET /notifications` + over ekte https ga korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404. + +- **Navneformat-regel + turnering-scope avklart, IKKE bygget (2026-07-26):** + brukeren instruerte at direkte adressering (hilsener, e-post/varsler + rettet til mottakeren) alltid skal bruke KUN fornavn, mens vanlige + visninger (lister, roster, chat) fortsatt bruker fullt navn/initialer — + lagt til som ny stående regel (se egen seksjon over). Kjent brudd + notert: dashbord-hilsenen bruker i dag fullt navn, ikke rettet ennå. + Deretter bedt om analyse av .md-filene for prioritering — landet på å + avklare turnering-leaderboard-spørsmålet fra 2026-07-25 først. Svaret + avdekket et vesentlig STØRRE ønske enn antatt: TeeCup skal etter hvert + støtte ekte INDIVIDUELLE turneringer (ikke bare lag), disse skal kunne + gå over flere runder, og det skal være mulig med et Order of Merit + (sesong-sammenlagt på tvers av flere arrangementer, brukerens eksempel: + "klubbdager" gjennom en sesong). Grundig notert i FEATURE_BACKLOG.md og + ARCHITECTURE_DECISIONS.md (ADR-011s eget notat + ny "Åpne spørsmål"- + post 6) — inkl. en reell strukturell kollisjon identifisert: en + flerrunde-individuell-turnering vil kollidere med ADR-033 sitt bevisst + org-UAVHENGIGE frittstående rundesystem, må avklares eksplisitt før + bygging. **Ren notat-runde, ingen ADR skrevet, ingen kode** — brukeren + ba eksplisitt kun om at dette dokumenteres nå. +- **Flere flighter i én frittstående runde — presisert videre, fortsatt + IKKE besluttet (2026-07-26):** oppfølgende avklaring samme runde. + Brukeren presiserte: oppsett av flere flighter er ÉN handling utført av + én person (samme gjest-mønster som i dag, bare flere flight-grupper), + scoreregistrering per flight er et SENERE, separat ansvar (forventet + løst av ekte medspillere, ADR-036 fase 3) — og viktigst: leaderboardet + skal KUN dekke flightene som ble satt opp SAMMEN i én handling, ikke + "alle som spilte samme bane samme dag". Det siste peker sterkt mot + retning 1 (løs gruppering av separate `round`-rader) fra forrige + runde, men er ikke formelt bekreftet som byggeretning. Brukeren + påpekte selv at dette ligger i grenselandet mot "individuelle + turneringer"-punktet rett over — notert eksplisitt som en mulig + forening av de to, ikke to separate systemer. Se FEATURE_BACKLOG.md + for full detalj. Ren notat-runde, ingen kode, ingen ADR. + +- **ADR-037 skrevet: grunnstruktur for individuelle/flerrunde-turneringer + (2026-07-26), samme dag som de to foregående notat-rundene.** Bruker ba + om å starte ADR-runden på strukturspørsmålet direkte (ikke bare + notere). Fire load-bærende beslutninger avklart eksplisitt + (AskUserQuestion), i rekkefølge: (1) ny, PARALLELL org-scopet + datamodell for individuelle turneringer — IKKE en utvidelse av + ADR-033s frittstående `round`-tabeller, bevisst for å unngå hybrid/ + betinget RLS (samme risikoklasse som den tidligere RLS-tomstreng- + bugen) og for å bevare ADR-033 Beslutning A urørt; (2) SAMME + `tournament`-tabell med en ny `format_type`-diskriminator + (`'team'`/`'individual'`), ikke en helt ny toppnivå-entitet — + gjenbruker synlighet/join-kode/status (ADR-018/020) uendret; (3) + flerrunde-støtte fra START via en ny `tournament_round`-tabell + (økt-lignende), sammenlagt resultat summert ved lesing på ekte + deltaker-id (samme prinsipp som dagens lag-leaderboard); (4) rå + brutto slagtall lagres OG et ferdig utregnet poengtall CACHES per + scoringsmetode — samme etablerte mønster som `match.status_text`/ + `points_side_a/b` — nye formater (Københavner m.fl.) blir dermed i + hovedsak én ny motorfunksjon + én ny CHECK-verdi, ikke en + skjemaendring. + **Viktig presisering som oppsto underveis, endrer forrige rundes + antakelse:** "flight" i en formell org-turnering er KUN en + tee-tid-gruppering — leaderboardet spenner alltid HELE feltet. + Dette er strukturelt ULIKT den ad hoc "flere flighter i en + frittstående runde"-ideen (der leaderboardet bevisst avgrenses til + det man selv satte opp) — de to holdes derfor bevisst ADSKILT, ikke + forent slik forrige runde antydet. Begge berørte FEATURE_BACKLOG.md- + seksjoner oppdatert med denne presiseringen. + **Fortsatt IKKE avgjort:** de fem konkrete formatenes egne poengregler + (Københavner m.fl., egne mindre design-runder oppå denne strukturen), + og Order of Merit (sesong-sammenlagt, bekreftet som naturlig SISTE + steg). **Ingen migrasjon eller kode skrevet** — ADR-037 er ren + struktur-beslutning; neste steg er et konkret migrasjonsutkast + (nye tabeller + `tournament.format_type`) lagt frem til gjennomgang + før noe kjøres. + +- **"Fiks alle kjente små bugs"-runde, BEGGE FIKSET OG SCRATCH-VERIFISERT + (2026-07-26), samme dag som ADR-037:** brukeren ba eksplisitt om å + rydde opp i alle dokumenterte, fortsatt åpne små bugs. Systematisk + gjennomgang av CLAUDE.md/FEATURE_BACKLOG.md (søk på "IKKE fikset"/ + "urelatert bug" m.fl.) fant at nesten alt tidligere flagget som + "ikke fikset" faktisk ble fikset i en SENERE runde lenger ned i loggen + (stale seksjonsoverskrifter, ikke reelt åpne bugs) — kun to genuint + fortsatt åpne: + 1. **Dashbord-hilsen brukte fullt navn** (nettopp etablert + navneformat-regel, se egen seksjon over) — `me.first_name ?? + me.display_name` i stedet for `me.display_name` alene. + `/auth/me` eksponerte allerede `first_name`, ingen backend-endring. + 2. **Stroke-modus-scoreinnsending på en bane UTEN registrerte hull + krasjet rått (500 IndexError)** i stedet for en ren + `VALIDATION_FAILED` — `scoring.py` sin `_compute_hole_results` + kalte `allocate_over_played_holes` med en TOM stroke-indeks-liste + når `hole`-tabellen var tom for banen (f.eks. en egendefinert bane + der noen glemte å legge til de 18 hullene). Fikset med en eksplisitt + `len(all_18_si) != 18`-sjekk RETT FØR beregningen — samme mønster + som den allerede kjente/fikset manglende-handicap-indeks-krasjen + fra blind draw-runden (2026-07-18). + **Scratch-verifisert grundig (5/5 sjekker)** for backend-fiksen + (isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs + API-container, samme mønster som ellers i prosjektet): en bane UTEN + hull ga korrekt `400 VALIDATION_FAILED` ved scoreinnsending (ikke + 500), OG en egen regresjonssjekk bekreftet at en NORMAL bane (alle 18 + hull) fortsatt scorer helt uendret (begge sider, full + scorekort-henting via `GET .../scorecard`). `test_isolation.sql` + 12/12 uendret — ren Python-logikk-fiks, ingen migrasjon. Ekte + typesjekket produksjonsbuild av frontend kompilerte rent (dashbord- + fiksen). To stale FEATURE_BACKLOG.md-seksjonsoverskrifter rettet + samtidig ("Rapporterte UI-/UX-hull" og selve bug-notatet), siden de + fortsatt sa "IKKE fikset" lenge etter at innholdet faktisk var + fikset. + **Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: ingen + migrasjon, `docker compose up -d --build teecup_api teecup_frontend`, + begge containere boot-et rent (`Application startup complete`, Next.js + `Ready`), `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. + +- **ADR-036 fase 3 (delvis): søk+legg til ekte medspiller på en + frittstående runde, BYGGET OG SCRATCH-VERIFISERT (2026-07-26), IKKE + ENNÅ RULLET UT:** brukeren rapporterte at "+ Gjest"-skjemaet ikke + søkte etter spillere når man skrev et navn — bekreftet reelt (rent + tekstfelt, `/people/search` var aldri koblet på). Spurte om + omfang før bygging: brukeren ville ha "full tilgang nå" (medspilleren + kan selv registrere score), ikke bare rask utfylling — et bevisst + større valg enn det anbefalte minimum. + **Backend:** `POST /rounds/{id}/participants` tar nå ENTEN `user_id` + (funnet via tiered `/people/search`, samme algoritme som vennesøket) + ELLER `guest_name` (uendret) — kjønn/HCP hentes automatisk fra den + valgte personens egen profil. Ny migrasjon `027_round_participant_ + user_unique.sql` (partiell unik indeks). Ny `_get_accessible_round_ + or_404` (eier ELLER lenket medspiller) 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 (regnes nå + fra den spørrende brukerens egen deltaker-rad). + **Reell latent bug funnet OG fikset i SAMME runde, før den nådde + produksjon:** leaderboard-endepunktet (bygget tidligere samme dag, + se over) ville vist et TOMT navn for enhver lenket medspiller, siden + dets `display_name`-utledning kun sjekket `is_owner`, ikke om + `user_id` var satt i det hele tatt. Fanget under scratch-testing av + DENNE runden, fikset før utrulling av noen av delene. + **Frontend:** "+ Gjest" omdøpt til "+ Medspiller" i `round-detail.tsx`, + nytt søk-som-du-skriver-felt (avatar-initialer, hjemmeklubb) med + "Legg til uten konto"-fallback til det gamle tekstfeltet. Viewer- + relativ "Deg"-visning (ny `/auth/me`-bruk for viewerId) — samme fiks + portert til `round-stats.tsx`/`round-scorecard.tsx`, som hadde + identisk latent bug (ville vist eieren som "Deg" for en medspiller + som så på). Rediger/slett/legg-til/fjern-knappene skjules nå for en + ikke-eier-viewer. + **Scratch-verifisert grundig, 39/39 sjekker i to testløp:** søk-og- + legg-til med auto-utfylt kjønn/HCP, avvist duplikat/selv-tillegg/ + ukjent bruker/ufullstendig profil, lenket medspiller kan lese runden + + registrere score for BÅDE egen OG andres rad (whole-flight- + regelen bekreftet), men nektes å forvalte runden (alle + forvaltningskall 403), lenket medspiller KAN fullføre runden, + `/rounds`-listen viser nå runden for en lenket medspiller med DERES + EGEN fremdrift (ikke eierens), urelatert bruker fortsatt 403/ikke i + listen, leaderboard-navn stemmer for alle tre deltakertyper. + `test_isolation.sql` 12/12 uendret. Ekte typesjekket produksjonsbuild + kompilerte rent. + **Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: migrasjon + 027 kjørt mot ekte `teecup_db` (unik indeks bekreftet, + `test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d + --build teecup_api teecup_frontend`. Begge containere boot-et rent, + `/health`/`/dashboard`/`/my-rounds` → 200, `teeoff.no` upåvirket. + +- **Sanntid for frittstående runder, BYGGET OG SCRATCH-VERIFISERT + (2026-07-26), samme dag som medspiller-søket over:** brukeren spurte om + spillere ser i sanntid at en annen har registrert en score — svaret var + nei (kun engangs-henting ved lasting), og brukeren ba om at det bygges, + gjenbruk av det etablerte "noe endret seg, hent på nytt"-WebSocket- + mønsteret fra ADR-027 ("Følg live" for turneringer). + `app/realtime.py` utvidet (fortsatt rutefri, se moduldoc) med en andre, + parallell kringkastings-registry for runder (`live_sockets_for_round`/ + `broadcast_round_update`, delt `_broadcast()`-hjelpefunksjon for å unngå + duplisert kringkastingslogikk). Nytt `@router.websocket("/ws/rounds/ + {round_id}/live")` i `rounds.py` — ALDRI anonym tilgang (ulikt + turnering-live), krever eier ELLER lenket medspiller + (`_get_accessible_round_or_404`, samme sjekk som resten av + medspiller-utvidelsen). Kringkasting lagt inn i `update_hole`, + `complete_round`, `add_participant`, `remove_guest_participant`, + `update_round` og `delete_round` — alt som endrer noe de andre på + skjermen bør få vite om. Ingen ny Caddy-endring nødvendig (`/ws/*` er + allerede en wildcard-rute fra ADR-025). + **Reelt funn under scratch-testing, ikke en bug, men verdt å dokumentere:** + Starlette avviser en WebSocket FØR `.accept()` alltid som en bar HTTP + 403 under selve håndtrykket — de tre distinkte lukkekodene (4401/4403/ + 4404) jeg satte når til `websocket.close()` server-side, men skiller seg + IKKE fra hverandre i klientens håndtrykk-avvisning (alle tre ga HTTP 403 + i en ekte WS-klienttest, ikke bare curl). Selve sikkerheten (tilkobling + korrekt avvist i alle tre tilfeller) er upåvirket — samme underliggende + Starlette-oppførsel gjelder trolig også den eksisterende turnering-live- + ruten (ADR-027), bare ikke tidligere testet med en ekte WS-klient på + dette presisjonsnivået. + **Frontend:** `round-detail.tsx` åpner en WebSocket ved montering, + refetcher runden ved signal og henter aktiv spillers hull DIREKTE på + nytt (ingen mellomsteg med tom stat som ville blinket for spilleren som + selv nettopp registrerte et slag) — andre spilleres hull-cache droppes + i stedet, hentes friskt ved neste fanebytte. Bruker en ref for å unngå + at socket-en kobles til/fra ved hvert fanebytte. `round-stats.tsx`/ + `round-scorecard.tsx` (rene lesevisninger) fikk en enklere + `refreshKey`-basert variant, samme mønster som `public-live.tsx` fra + ADR-027. + **Scratch-verifisert grundig, 10/10 sjekker, ekte WebSocket-klient (ikke + bare REST):** ekte kringkasting bekreftet begge veier — eierens åpne + socket mottok et signal da medspilleren registrerte et slag via REST, + OG medspillerens åpne socket mottok et signal da eieren fullførte runden + — ikke bare at REST-svarene så riktige ut. Alle tre avvisningstilfellene + (uautentisert, urelatert fremmed, ukjent runde-id) korrekt avvist. + `test_isolation.sql` 12/12 uendret (ingen migrasjon). Ekte typesjekket + produksjonsbuild kompilerte rent. + **Rullet ut live 2026-07-26**, 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. Verifisert presist at `/ws/rounds/*`-ruten faktisk når + FastAPI: et ekte WS-håndtrykk-forsøk mot en ukjent runde-id over + produksjons-https ga FastAPI sin egen JSON-`{"detail":"Not Found"}`, + ikke Next.js sin HTML-404 — samme verifiseringsmønster som ADR-027s + tournament-live-rute. +- **HCP i medspiller-søk + rediger utslag/HCP per deltaker, BYGGET OG + SCRATCH-VERIFISERT (2026-07-26), IKKE ENNÅ RULLET UT:** brukeren viste et + skjermbilde av "+ Medspiller"-søket og påpekte at spillerens HCP burde + vises der (kun navn+klubb vistes), og ba samtidig om å kunne endre + utslagssted og HCP for medspillere -- "det er ikke sikkert de spiller fra + samme utslagssted som meg, og det er ikke sikkert HCP er riktig", eksplisitt + presisert som en endring KUN for denne runden/turneringen, ikke spillerens + faktiske profil. + **Reelt hull bekreftet ved kodegjennomgang FØR bygging:** ALLE deltakere + på en runde har i dag brukt SAMME `round.tee_name_snapshot` -- ingen + per-deltaker-tee har noensinne eksistert (rating-tallene har vært per + deltaker siden migrasjon 021, men selve utslags-NAVNET var aldri + eksplisitt per rad). + **Bygget:** `PersonMatch` (`app/routers/friends.py`, delt av vennesøk OG + medspiller-søk) fikk `handicap_index: float | None`. Ny migrasjon + `028_round_participant_tee.sql` -- `round_participant.tee_name_snapshot`, + backfylt fra rundens eksisterende tee for alle eksisterende rader. `_create_ + participant` skriver nå tee-navnet eksplisitt; `ParticipantCreate` (add_ + participant) fikk et valgfritt `tee_name` som faller tilbake til rundens + tee hvis utelatt. Ny `GET /rounds/{id}/tee-options` (gjenbruker + `_ResolvedCourse` sin rating-dict via en ny `tee_options()`-metode) -- + brukt av "rediger spiller"-panelet til å liste banens faktiske utslag. + `ParticipantUpdate` skrevet om til vanlig `exclude_unset`-PATCH-semantikk + (var tidligere kun `stat_level`, alltid påkrevd) -- nye valgfrie + `tee_name`/`handicap_index`-felt trigger en full re-beregning av + rating-snapshottene + `course_handicap_snapshot` for AKKURAT den + deltakeren, uten å røre spillerens egen `app_user.handicap_index`. + Eksplisitt `null` på `handicap_index` fjerner HCP-sporing for denne + deltakeren i denne runden (samme mønster som profil-PATCH). **Bevisst + avvist etter at runden er fullført** (409 `ALREADY_COMPLETED`, samme + presedens som `RoundUpdate` sitt bane-bytte -- differensialen er da + allerede beregnet fra det gamle grunnlaget); `stat_level` alene er + fortsatt tillatt uansett fullført-status. `RoundUpdate` sitt eksisterende + bane-bytte (ADR-033-oppfølging 2026-07-24) nullstiller nå eksplisitt ALLE + deltakeres `tee_name_snapshot` til rundens nye utslag (et bane-bytte gjør + individuelle tee-valg fra den gamle banen meningsløse). + **Frontend (`round-detail.tsx`):** søkeresultatene i "+ Medspiller" viser + nå HCP ved siden av hjemmeklubb. Ny "Utslag: X · HCP: Y"-rad under + statistikknivå-velgeren for aktiv spiller, med en "Rediger for denne + runden"-lenke (kun eier, kun før fullført) som åpner en ny + `EditParticipantPanel` -- utslagssted som en `<select>` fylt fra det nye + tee-options-endepunktet (filtrert på spillerens kjønn), HCP som fritekst, + forklarende "endrer ikke profilen"-tekst. Gjelder likt for eierens egen + rad som for medspillere (ingen spesialtilfelle). + **Scratch-verifisert, 27/27 sjekker** (isolert `teecup_app_scratch`- + rolle + isolert scratch-MinIO + engangs API-container): HCP i søk, legg + til medspiller med eksplisitt AVVIKENDE utslag fra eieren, gjest uten + eksplisitt tee faller korrekt tilbake til rundens tee, utslag uten rating + for kjønn avvist (400) både ved tilføyelse og redigering, tee-options + lister riktige utslag, rediger tee+HCP for medspiller lykkes og + course_handicap regnes om, **medspillerens egen profil-HCP forblir + UENDRET** (den kritiske sjekken -- bekrefter runde-scoping), delvis PATCH + (kun stat_level) lar tee/HCP stå urørt, eksplisitt `null` fjerner HCP + round-scoped, lenket medspiller (ikke eier) nektes å redigere deltakere + (403, forvaltning fortsatt eier-only), tom PATCH avvist (400), redigering + blokkert etter fullføring (409) mens stat_level fortsatt tillates. Full + regresjonskjøring av forrige rundes 35-punkts medspiller-søk-testsuite + (`test_round_coplayer.py`) mot samme friske scratch-database, alle 35 + fortsatt grønne. `test_isolation.sql` 12/12. Ekte typesjekket + produksjonsbuild kompilerte rent. + **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt: + migrasjon 028 kjørt mot ekte `teecup_db` (kolonne bekreftet `NOT NULL`, + 2 eksisterende rader backfylt korrekt, `test_isolation.sql` fortsatt + 12/12), deretter `docker compose up -d --build teecup_api + teecup_frontend`. Begge containere boot-et rent (`Application startup + complete`, Next.js `Ready`), `/health`/`/dashboard` → 200, `teeoff.no` + upåvirket. +- **V0-prompt for rundeleaderboardet rettet (2026-07-26), FØR den ble + kjørt i v0.app:** samme runde som over, brukeren ba eksplisitt om å få + leaderboardet for frittstående runder på plass. Backend + (`GET /rounds/{id}/leaderboard`) var allerede live siden tidligere samme + dag; V0-prompten (skrevet samme dag, se FEATURE_BACKLOG.md) hadde + derimot en utdatert antakelse -- den ba om et "Deg"-merke basert på at + kun eieren noensinne ser sin egen runde, som ikke lenger stemmer etter + ADR-036 fase 3 (medspillere kan nå også se runden). Rettet til to + 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. +- **Spillerliste-redesign HÅNDKODET (2026-07-26), IKKE ENNÅ RULLET UT:** + brukeren gikk tom for V0-credits rett etter at prompten over ble sendt. + Spurt eksplisitt (AskUserQuestion): bygg direkte, eller vent på fornyede + credits — svar: bygg direkte. Bygget nøyaktig etter samme prompt/data- + kontrakt som allerede var skrevet (se FEATURE_BACKLOG.md for full + detalj), i samme Tailwind/shadcn-stil som resten av `round-detail.tsx`. + Ny `PlayerList` (erstatter `PlayerTabs`) — vertikal liste av spillerkort, + hvert kort en stor "velg som aktiv spiller"-knapp + separate Rediger-/ + fjern-knapper (unngår nestede interaktive elementer), "Deg"/"Eier" som + `Badge`-komponenter. `EditParticipantPanel` utvidet med statistikknivå + (den frittstående `StatLevelPicker` er nå død kode, fjernet) og — kun + for gjester — Navn/Kjønn/E-post, med reaktivt utslagsfilter ved + kjønnsendring. "+ Medspiller"-flyten flyttet inn i selve `PlayerList`. + **Verifisert:** ekte typesjekket produksjonsbuild kompilerte rent, pluss + en engangs `next dev`-container mot scratch-backend (ekte runde med en + gjest med avvikende utslag/kjønn/e-post) — siden ga 200, ingen Next.js- + feilside. **Ærlig begrensning:** siden er en klient-komponent (data + hentes etter hydrering), så server-HTML-en viser kun last-skjelettet — + INGEN ekte nettleser-interaksjonstest utført (intet nettleserverktøy + tilgjengelig). + **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt: + ingen ny migrasjon, `docker compose up -d --build teecup_api + teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard` + → 200, `teeoff.no` upåvirket. **Bruker bør selv klikke gjennom flyten** + (spesielt gjeste-kjønnsendringens reaktive utslagsfilter og "velg vs. + rediger"-trykkflatene) før full tillit. +- **Tildelte slag + jevn korthøyde + rundeleaderboard, HÅNDKODET, IKKE ENNÅ + RULLET UT (2026-07-26):** to oppfølgingspunkter samme dag, full detalj i + FEATURE_BACKLOG.md. (1) Spillerkortet manglet "tildelte slag" (course + handicap for runden) og hadde variabel høyde — rettet: `Player` fikk + `courseHandicap`, kortet fikk fast `min-h-[68px]` med begge tekstlinjer + trunkert til én linje (ikke wrap), alle kort like høye uansett innhold. + (2) Brukeren spurte om jeg "med designerbrillene på" trodde jeg kunne få + rundeleaderboardet til å se like profesjonelt ut som V0 — svarte ja + (gjenbruk av etablerte mønstre, ikke fri utforskning) og bygget det: ny + `components/round-leaderboard.tsx` + rute `/my-rounds/[id]/leaderboard`, + gjenbruker `ScoreMark`s "form + farge"-språk fra `round-scorecard.tsx` + (som `ToParMark`), samme WS-sanntid-mønster som `round-stats.tsx`, delt + plassering ("T-N"), brutto/netto-veksling, mini-variant på rundens egen + side. Rangeringslogikken VERIFISERT UAVHENGIG i et frittstående + Node-script (19/19, inkl. tie-håndtering og uspilte spillere), pluss en + fersk scratch-runde med ekte API-kall som bekreftet leaderboard-JSON-en + stemte. Ekte typesjekket produksjonsbuild kompilerte rent. Samme ærlige + begrensning som over: ingen ekte nettleser-interaksjonstest. + **Rullet ut mot ekte systemer 2026-07-26**, 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. +- **Hullscorer i leaderboardet + lenke fra rundelisten, BYGGET, IKKE ENNÅ + RULLET UT (2026-07-26):** brukeren presiserte rett etter forrige rulling + tre krav: leaderboardet skal ha en egen visning (allerede tilfellet, se + over), med lenke fra scoreføringssiden (allerede der, `RoundLeaderboardMini`) + OG fra rundelisten (`/my-rounds`, manglet), og skal vise hullscorer + (manglet helt -- kun aggregerte tall fantes). + **Backend:** `LeaderboardEntryOut` (`GET /rounds/{id}/leaderboard`) fikk + et nytt `holes: list[LeaderboardHoleOut]`-felt (hole_number/par/ + stroke_index/played/score) -- data var ALLEREDE hentet per deltaker for + å beregne total_score/net_score_to_par, bare aldri eksponert rått før nå. + Ren tilføyelse, ingen migrasjon. + **Frontend:** `round-leaderboard.tsx` sine rader er nå klikkbare + (utvider/kollapser, default kollapset -- samme "trekkspill starter + lukket"-konvensjon som `round-stats.tsx`) og viser en horisontal strip + med per-hull-merker (`HoleMark`/`HoleStrip`, egen lokal variant av + `ScoreMark`s "form + farge"-språk fra `round-scorecard.tsx`, tilpasset en + kompakt flerspiller-liste i stedet for én tabell). `round-card.tsx` + (brukt av BÅDE `/my-rounds`-listen og dashbordets "Kommende runder") fikk + en ny "Se leaderboard"-lenke for runder med mer enn én deltaker -- + krevde å gjøre om kortets ytre element fra selve lenken til en `<div>` + med lenken som ETT av to barn (unngår nestet `<a>`, samme feilklasse som + tidligere nestet-form-/knapp-feller i prosjektet). + **Verifisert:** backend-endringen bekreftet mot en fersk scratch-runde + (ekte API-kall, `holes`-arrayet inneholder riktige 18 rader per deltaker + inkl. korrekt `played`/`score` for både spilte og uspilte hull), full + regresjon av forrige rundes 35-punkts co-player-testsuite fortsatt grønn, + `test_isolation.sql` 12/12 (uendret, ingen migrasjon). Ekte typesjekket + produksjonsbuild kompilerte rent (ingen nye ruter, kun endrede + komponenter). Samme engangs `next dev`-container-sjekk som tidligere -- + `/my-rounds`, `/my-rounds/[id]` og `/my-rounds/[id]/leaderboard` ga alle + 200, ingen Next.js-feilside. + **Samme ærlige begrensning som tidligere håndkodede runder:** ingen ekte + nettleser-interaksjonstest (klikk for å utvide en rad, se hull-stripen, + se "Se leaderboard"-lenken i rundelisten) er utført. + **Rullet ut mot ekte systemer 2026-07-26**, 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. +- **Leaderboard-oppfølging: poeng-modus + minimalistiske rader, BYGGET, IKKE + ENNÅ RULLET UT (2026-07-26):** brukeren påpekte at brutto/netto ble + presentert forskjellig (bryteren byttet BÅDE hvilket tall som vises OG + hele rangeringsrekkefølgen -- forvirrende hopp), og spurte om et eget + stableford-alternativ burde vært med. Foreslo og fikk bekreftet: gi alle + tre modiene (nå: Brutto/Netto/**Poeng**) IDENTISK radform (rangering, + navn, ETT tall, ferdig) i stedet for å prøve å vise flere tall samtidig. + Samtidig ba brukeren om å fjerne alt fra den kollapsede raden utover + navn+tall (Deg/Eier/pokal/hull-spilt-tekst) og fjerne selve + "kort"-følelsen -- radene skal flyte sømløst sammen, kun ekspandere ved + klikk. + **Backend:** `LeaderboardHoleOut` fikk `strokes_received` (samme + allerede-beregnede allokering som `net_score_to_par`, nå eksponert per + hull også). `LeaderboardEntryOut` fikk `total_points` (stableford-total, + samme formel/betingelse som `net_score_to_par` -- null uten HCP). Ingen + migrasjon. + **Frontend (`round-leaderboard.tsx`):** `Mode` utvidet til tre verdier, + poeng rangeres SYNKENDE (motsatt av til-par). Ny `PointsMark`/`ValueMark` + (poeng har ingen retning, kun fylt/uthevet for lederen). Listen er nå ÉN + sammenhengende `<ul>` (`divide-y`, kun ytterkanten avrundet/rammet) i + stedet for separate kort med mellomrom mellom hver rad -- ingen + per-rad-bakgrunn utenom en svak tone på en UTVIDET rad. Kollapset rad: + kun rangering+navn+tall+pil. Deg/Eier/pokal/fremdrift FLYTTET (ikke + fjernet) inn i den utvidbare seksjonen sammen med hull-stripen. + **Verifisert:** rangeringslogikken for poeng-modus (synkende sortering, + delt plassering, uten-HCP sortert nederst) UAVHENGIG testet i Node + (10/10). Selve stableford-formelen verifisert mot en fersk scratch-runde + med HÅNDREGNET forventet resultat (course handicap 9 over 18 hull → + slag KUN på de 9 laveste stroke-index-hullene, ikke jevnt fordelt -- + fanget en feilaktig antakelse i testens FØRSTE versjon, rettet før + den ble rapportert som bestått) -- 11/11, inkl. kryssjekk av + `total_points`/`net_score_to_par` mot uavhengig rekalkulering fra de + samme rå hull-dataene API-et returnerte. Full regresjon (35-punkts + co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12. Ekte + typesjekket produksjonsbuild kompilerte rent. Samme engangs `next + dev`-container-sjekk som tidligere -- alle tre rutene ga 200. + **Samme ærlige begrensning:** ingen ekte nettleser-interaksjonstest av + det faktiske "sømløse rader"-uttrykket eller poeng-modusen. + **Rullet ut mot ekte systemer 2026-07-26**, 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. +- **Hullscorer i leaderboardet: netto/poeng under brutto, BYGGET, IKKE ENNÅ + RULLET UT (2026-07-26):** brukeren viste to referansebilder fra en + konkurrentapp (Golf GameBook) sin leaderboard-visning -- brutto fremhevet + (fylt/farget merke) på én rad, netto/stableford i vanlig tekst RETT under, + eksplisitt presisert at det skal se likt STRUKTURELT ut men IKKE være en + kopi visuelt. Ren frontend-endring, ingen backend-endring (all data -- + `strokes_received` per hull -- var allerede lagt til forrige runde samme + dag). + **`HoleMark`** (`round-leaderboard.tsx`) fikk en tredje, umerket tekstlinje + RETT under det fremhevede brutto-merket, som viser netto eller + stableford-poeng for AKKURAT det hullet -- kun i netto-/poeng-modus (ingen + ny linje i brutto-modus, ingenting nytt å vise der). Nye lokale + `netForHole()`/`pointsForHole()`-hjelpefunksjoner (samme formel som + backend sin `total_points`, men per hull -- bruker `strokes_received` + direkte fra API-et, regner ALDRI ut egen slagfordeling). `HoleStrip` fikk + en liten "Slag · Netto"/"Slag · Poeng"-bildetekst over stripen når en + sekundærrad faktisk vises. + **Bevisst IKKE en kopi:** beholder TeeCups egne farger/former (sirkel/ + firkant, grønn/oransje) fra `ScoreMark`-språket som allerede var etablert + -- endret ikke fargevalg for å etterligne referansebildets blåtoner, kun + gjenskapte det STRUKTURELLE prinsippet (fremhevet brutto øverst, rolig + sekundærtall under). + **Verifisert:** netto/poeng-per-hull-formelen testet UAVHENGIG i Node + (9/9, inkl. et gulv-på-0-tilfelle og manglende-HCP/uspilt-hull), samme + tall som det håndregnede scratch-scenarioet fra forrige runde samme dag + (hull med slag: netto 3/poeng 3, hull uten slag: netto 5/poeng 1). Ekte + typesjekket produksjonsbuild kompilerte rent. Samme engangs `next + dev`-container-sjekk som tidligere -- leaderboard-ruten ga 200. + **Samme ærlige begrensning som resten av de håndkodede rundene:** ingen + ekte nettleser-interaksjonstest av selve det visuelle uttrykket. + **Rullet ut mot ekte systemer 2026-07-26**, 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. +- **Scoringsflyt: samlebånd-fremdrift + golf-term-taltastatur, BYGGET, IKKE + ENNÅ RULLET UT (2026-07-26), inspirert av en konkurrentapp (Golf + GameBook):** brukeren delte en skjermopptaksvideo av GameBooks + score-registrering og spurte om jeg forstod HVORFOR den flyten fungerer + bedre enn TeeCups, og om jeg kunne bygge det selv (ingen V0-credits + igjen). Video analysert bilde for bilde (ffmpeg i en engangs Docker- + container, samme mønster som en tidligere videoanalyse i prosjektet) -- + identifiserte presist samlebånd-mønsteret: taltastatur med kontekstuelle + golf-termer per tall (Eagle/Birdie/Par/Bogey ut fra hullets par, ikke + bare "par"-knappen merket), og at fullført registrering for én spiller + automatisk åpner NESTE spillers registrering for samme hull -- uten å + måtte navigere manuelt tilbake til en spillerliste. + **Bevisst IKKE en full skjermovertakende steg-for-steg-modal** (GameBooks + egen løsning) -- vurdert som unødvendig risikofylt å bygge korrekt uten + visuell testing. I stedet: samme etablerte inline-side beholdt, men to + konkrete forbedringer lagt til: + 1. `NumberPicker` sin Slag-instans fikk en ny `showGolfTerms`-modus -- + HVERT synlig tall viser nå Albatross/Eagle/Birdie/Par/Bogey/Dobbel + bogey relativt til hullets par (ikke bare selve par-knappen som før). + Ny lokal `golfTermForScore()`-hjelpefunksjon. + 2. Ny `isEntryComplete()`-sjekk (krever kun det `stat_level` faktisk gjør + obligatorisk -- slag alene, eller slag+putter; "full"-nivåets ekstra + detaljer forblir valgfrie og blokkerer ALDRI fremdrift) + ny + `advanceToNextPlayerOrHole()`. Bunnknappraden "Forrige/Neste hull" + endret til "Forrige hull" (uendret) + en kontekstsensitiv primærknapp + som enten viser "Neste: {navn på neste spiller}" (bytter aktiv + spiller på SAMME hull) eller "Neste hull" (er aktiv spiller den + siste, går videre til neste hull OG starter på spiller 1 igjen) -- + disabled med forklarende hjelpetekst til de påkrevde feltene er fylt. + **Verifisert:** golf-term-tabellen og fullført-sjekken UAVHENGIG testet + i Node (matcher videoens egne eksempler nøyaktig, f.eks. par 4 + slag + 6 = "Dobbel bogey"), samt selve samlebånds-syklusen simulert for 3 + spillere over flere hull-grenser OG for en solo-runde (ingen spillerbytte, + kun hull-fremgang) -- 21/21 sjekker. Full regresjon (35-punkts + co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12 (ingen + backend-endring denne runden). Ekte typesjekket produksjonsbuild + kompilerte rent, samme engangs `next dev`-container-sjekk som tidligere + ga 200. + **Samme ærlige begrensning som resten av de håndkodede rundene:** ingen + ekte nettleser-interaksjonstest av selve fremdrifts-følelsen (kun logikk + + server-render bekreftet). + **Rullet ut mot ekte systemer 2026-07-26**, 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. +- **Scoringsflyt v2: full skjermovertagende veiviser, BYGGET, IKKE ENNÅ + RULLET UT (2026-07-26) -- ERSTATTER forrige rundes forsøk.** Brukeren + testet forrige rundes "auto-fremdrift-knapp"-versjon live og var tydelig: + "ingen forbedring i det hele tatt", "visuelt like overveldende og + rotete" -- den inline-baserte tilnærmingen var IKKE nok, en ekte + skjermovertagende veiviser (som opprinnelig vurdert og lagt til side pga. + risiko uten visuell testing) var det som faktisk kreves. Bygget nå for + ekte, pluss et eksplisitt nytt krav: akkumulert score-så-langt for RUNDEN + synlig for HVER spiller samtidig (ikke bare aktiv), matchende + konkurrentappens vedvarende "E"/"+1"-visning ved siden av hvert navn. + **Datalasting endret** (nødvendig for punktet over): laster nå hull for + ALLE deltakere med det samme rundén lastes (ikke lenger lat lasting kun + for aktiv spiller), og sanntid-signalet (WS) henter nå alles hull på + nytt, ikke bare énes. + **Ny `ScoringWizard`-komponent** (fullskjerm, `fixed inset-0 z-50`, egen + stack utenfor `<main>`): tre steg maks, drevet av `stat_level` -- + "strokes_only" (kun Slag), "strokes_and_putts" (+ Putter/avstand første + putt), "full" (+ ett samlet detalj-steg: kølle/utslag/innspill/chip/ + bunker/straffeslag/anywayslag). Bevisst FÆRRE, grovere steg enn + konkurrentens egne 5-6 skjermer (risikoreduksjon uten visuell testing, + og TeeCups stat_level-modell gjør en så fin oppdeling mindre naturlig). + Alle spillerne vises som en fast, ikke-trykkbar kontekst-rad øverst i + veiviseren (aktiv fremhevet) -- speiler konkurrentappens "mist aldri + oversikten"-prinsipp. "Forrige"/"Neste" beveger seg gjennom stegene; + siste steg for siste spiller blir "Ferdig" (lukker + går til neste hull, + starter på spiller 1 igjen), ellers "Neste: {navn}" (bytter spiller i + SAMME veiviser, nullstiller til steg 1). + **Hovedsiden forenklet radikalt:** den gamle Slag/Putter/"flere + detaljer"-inline-blokken er FJERNET -- erstattet med én kompakt liste, + ett kort per spiller: navn, "HCP X · {til-par så langt} ({N} hull)", og + en stor rund knapp som viser gjeldende hulls slagtall (eller "–") og + åpner veiviseren ved trykk. `NumberPicker`s `showGolfTerms`-funksjon fra + forrige runde gjenbrukes uendret inni veiviserens Slag-steg (det arbeidet + var ikke bortkastet). + **Verifisert:** stegmaskinen (steg-antall per stat_level, fremover/ + bakover-navigasjon, disabled-gating per steg, "avbryt på steg 1 lukker", + "siste steg for siste spiller fullfører") og akkumulert-score- + beregningen UAVHENGIG simulert i Node (16/16). Et ekte API-rundtur- + script som sender NØYAKTIG samme felt-kombinasjon som veiviseren ville + sendt (fullt detalj-steg for en "full"-spiller, kun slag for en + "strokes_only"-gjest) bekreftet begge lagres korrekt. Full regresjon + (35-punkts co-player-testsuite) fortsatt grønn, `test_isolation.sql` + 12/12 (ingen migrasjon). Ekte typesjekket produksjonsbuild kompilerte + rent, samme engangs `next dev`-container-sjekk ga 200. + **Samme ærlige begrensning som alle håndkodede runder denne uken:** ingen + ekte nettleser-interaksjonstest -- gitt at FORRIGE runde ble avvist + nettopp fordi den så gal ut i praksis til tross for at logikken var + korrekt, er dette IKKE en ubetydelig forbehold denne gangen. Bruker bør + teste grundig før tillit. + **Rullet ut mot ekte systemer 2026-07-26**, 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. +- **Scoringsflyt v3: administrasjon og scoring adskilt i to faner, BYGGET, + IKKE ENNÅ RULLET UT (2026-07-26) -- direkte svar på at bruker fortsatt + fant siden "bråkete og lite intuitiv" etter v2.** Brukeren viste et NYTT + sidestilt skjermbilde-par (samme mønster som første gang) og påpekte at + TeeCup fortsatt hadde mye synlig FØR selve scoringslisten (leaderboard- + forhåndsvisning, Rediger/Fullfør/Slett, hele spillerlisten med Rediger- + ikoner, "+Medspiller") -- nøyaktig det jeg selv identifiserte som GameBooks + kjerneprinsipp i den aller første analysen ("administrasjon og scoring er + ADSKILTE tabs"), men aldri fullt ut gjennomførte i v2 (kun `ScoreSoFar` + ble flyttet forrige runde, resten av det administrative innholdet ble + stående igjen øverst). + **Fikset denne gangen for ekte:** ny `pageTab`-state (`"score" | "manage"`, + default `"score"`), en enkel fanevelger rett under feilmeldingen. "Score" + (default) inneholder nå KUN: fullført-banner (hvis relevant), hull- + navigasjon, og selve hull-panelet (header + scoringslisten fra v2 + + Forrige/Neste hull) -- ingenting annet. "Spillere og runde" samler + leaderboard-forhåndsvisning, Rediger/Fullfør/Slett, hele spillerlisten + (`PlayerList`), OG `ScoreSoFar` (flyttet HIT fra forrige rundes + "under scoringslisten"-plassering, siden den er detaljert stats-innsyn, + ikke selve registreringsoppgaven). + **Verifisert:** ekte typesjekket produksjonsbuild kompilerte rent, samme + engangs `next dev`-container-sjekk ga 200, full regresjon (35-punkts + co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12 (ren + frontend-omrokkering, ingen migrasjon). + **Samme ærlige begrensning som v1/v2:** ingen ekte nettleser- + interaksjonstest av selve fane-følelsen. + **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt: + ingen migrasjon, `docker compose up -d --build teecup_frontend` + (gjenskapte også `teecup_api` som vanlig bivirkning). Begge containere + boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. +- **`DESIGN_SYSTEM.md` opprettet (2026-07-27):** brukeren ba om en ny fil + som viser designsystemet arbeidet tar utgangspunkt i. Skrevet fra bunnen, + DESKRIPTIVT (hva som ER i koden, ikke et mål) — grunnet direkte i + `globals.css`/`components/ui/*.tsx` og etablerte tvers-fil-mønstre: + fargetoken (inkl. OKLCH-opprinnelsen til `primary`/`brand-orange` fra + ADR-009/016), typografi, avstand/hjørner/lag (inkl. "én sammenhengende + liste med `divide-y`, ikke separate kort"-regelen fra 2026-07-26s + leaderboard-fiks), lokale komponentmønstre (NumberPicker/Stepper/ + ChoiceRow/DirectionCross — bevisst per-fil, ikke delte importer), + golfscore-språket (`ScoreMark`/`ToParMark`/`PointsMark`/`HoleMark`), + ikonografi, tilbakemelding/tilstander, og navneformat (pekt til CLAUDE.md). +- **`teecup-scorekort-og-entry-spec.md` lest og evaluert, delvis BYGGET SOM + COMPLIANCE-PASS SAMME DAG (2026-07-27):** brukeren lastet opp et + PRESKRIPTIVT motstykke til DESIGN_SYSTEM.md (5-app-sammenligning: Golf + GameBook/Golf Pad/Golfshot/Hole 19/18Birdies) og spurte om det ga mening + og kunne forbedre TeeCup. Vurdert som reelt verdifullt — sylskarpeste nye + innsikt: "grønt-på-grønt"-diagnosen (§0.2) forklarer noe av "fortsatt + rotete"-følelsen fra v1-v3-rundene bedre enn tetthet alene: `primary` + (grønn) var brukt om hverandre for BÅDE "aktiv tilstand" og "generisk + fylt/positiv", uten at begge betydde det samme sted. Foreslo (og + brukeren bekreftet) å fikse spec-dokumentets §3 "Compliance-pass" FØR den + større strukturelle §1-omleggingen (scorekort som grid) vurderes som egen, + senere runde. + **To av sjekklistens fem punkter var KONKRETE, VERIFISERBARE bugs, bekreftet + direkte i koden (ikke antatt fra spec-teksten alene) FØR de ble fikset:** + 1. **"Deg Deg"-duplikat:** `playerLabel()` (`round-detail.tsx`) erstattet + tidligere selve navnet med "Deg" for viewer-relativ egen rad, OG en + separat `<Badge>Deg</Badge>` sto ved siden av samme sted (spillerkort, + scoringslisten) — bekreftet duplikat, matchet et tidligere skjermbilde + brukeren delte. **Fikset:** `playerLabel()` returnerer nå alltid det + faktiske `display_name` (aldri "Deg") — Badge-en er nå ENESTE + selv-indikator. Dette retter samtidig et videre, ikke tidligere flagget + avvik: spec-dokumentet krever eksplisitt "roster-kontekst → fullt navn" + for BÅDE scorekort-gridet og score-entry-headeren — veiviserens header/ + kontekst-rad/"Neste: {navn}"/fullført-banneret viste tidligere "Deg" i + stedet for et fullt navn der også, uten noen badge til å disambiguere + (reelt forvirrende på en delt telefon som sendes rundt en flight). + Det nå overflødige `rawName`-feltet (var identisk med `name` etter + fiksen) fjernet, tre kallsteder oppdatert. + 2. **Score-knappen fulgte ikke `§Golfscore-språket`:** den store runde + knappen i den kompakte scoringslisten (`round-detail.tsx`, bygget i + v2-runden) var `rounded-full`+grønn UANSETT om resultatet var under, + over eller på par — bogey og eagle så identiske ut. **Fikset:** ny + `scoreMarkClasses(diff)`-hjelpefunksjon som gjenbruker EKSAKT samme + `primary`/`brand-orange`-fargespråk og fylt-vs-border+10%-tint-omfangs- + regel som `ScoreMark` i `round-scorecard.tsx` (bekreftet ved å lese + `ScoreMark` sin kildekode direkte, ikke gjettet) — sirkel under par, + nøytral sirkel på par, `rounded-2xl` (bevisst mildere enn scorekortets + `rounded-[4px]`, for å matche denne skjermens øvrige 56px-trykkflate- + avrunding) over par, fylt ved 2+ slag fra par. + **Resten av sjekklisten (44px-trykkgulv, `tabular-nums`) auditert + systematisk mot AKKURAT denne filen** (samme fil brukerens skjermbilder + viste) — 2 manglende `tabular-nums` (HCP/tildelte slag i spillerkortet) + og 11 knapper/lenker under 44px (fane-bryteren `min-h-10`→`min-h-11`, + `EditRoundPanel`s lukk-ikon `size-8`→`size-11`, feilside-tilbakelenken, + og åtte knapper i bane-bytte-/legg-til-medspiller-skjemaene) rettet. + **Bevisst UTENFOR omfang denne runden:** spec-dokumentets §1 (scorekort + som fullt grid, celle åpner veiviseren) — en STØRRE strukturell endring + som fortjener et eget, bevisst ja fra brukeren, ikke bygget stille inn i + en "fiks kjente bugs"-runde. + **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` som + deployes) kompilerte rent, alle 24 ruter listet. **Samme ærlige + begrensning som ALLE håndkodede runder denne uken:** ingen ekte + nettleser-interaksjonstest av det faktiske visuelle resultatet (kun kode- + lesing + build-verifisering + bevisst gjenbruk av en allerede lest, + eksisterende komponents fargespråk for å holde risikoen lav). + **Rullet ut live 2026-07-27**, bruker bekreftet eksplisitt: ingen + migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte + også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge + containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` + upåvirket. +- **Spec-dokumentets §1 (scorekort som fullt grid) BYGGET OG LIVE + (2026-07-27), samme dag rett etter compliance-passet:** brukeren + bekreftet eksplisitt "JA" på at grid-redesignet er en egen, bevisst + runde, egen informasjonsarkitektur enn ett-hull-om-gangen-listen + bygget dagen før. `round-detail.tsx` sin "Score"-fane fikk hull-strip + + ett-hull-panel ERSTATTET av en ny `ScorecardGrid`: spillere som rader + (sticky venstre navnekolonne, navn+HCP, samme tap-target åpner + veiviseren for gjeldende hull), hull som horisontalt scrollbare + kolonner, `Hcp`/`Par`-referanserader over spillerradene (samme + konvensjon som den allerede shippede `round-scorecard.tsx`), sticky + `Ut`/`Inn`/`Sum` til høyre (Ut/Inn gruppert på FYSISK hullnummer 1-9/ + 10-18, uavhengig av øktens starthull — riktig konvensjon uansett + spillerekkefølge; kun vist for 18-hulls runder, 9-hulls runder får + én samlet Sum). Ny `ScorecardCell` gjenbruker EKSAKT samme klassifisering + og Tailwind-klasser som `ScoreMark`/`classify` i `round-scorecard.tsx` + (lest direkte, ikke gjettet) — ulikt compliance-passets softere + `rounded-2xl`-knapp-variant (fortsatt riktig der, egen visuell kontekst), + siden dette er en LITEN tabellcelle som spec eksplisitt ber om å følge + språket "UBRYTELIG". + **Interaksjon:** tapp en score-celle ELLER spillerens navnecelle ELLER + en hull-kolonneoverskrift åpner `ScoringWizard` (uendret komponent fra + v2-runden) for akkurat den (spiller, hull)-kombinasjonen — kolonne- + overskrift alene (uten å tappe en celle) setter kun "gjeldende hull" + uten å åpne veiviseren, samme jobb som den fjernede `HoleNav` gjorde. + Beholdt en kompakt "Forrige/Neste hull"-knapperad under gridet for + rask sekvensiell registrering uten bred scrolling. + **Dødt kode fjernet i samme runde:** `HoleNav`, `holeIsPlayed`, + GIR-merket/`showGir` (ga ikke lenger mening i en multi-hull-visning), + og OGSÅ compliance-passets `scoreMarkClasses`-hjelpefunksjon (var kun + brukt av den nå fjernede ett-hull-listens store runde knapp). + **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` + som deployes) kompilerte rent, alle 24 ruter listet. I TILLEGG en + engangs full runner-image bygget og kjørt i en isolert container (ikke + bare `--target builder`) — `/my-rounds/[id]` for en ukjent runde-id + ga 200, ingen React-feilgrense/krasj-markup i responsen. **Samme + ærlige begrensning som ALT håndkodet arbeid denne uken, men STØRRE + konsekvens denne gangen siden dette er en vesentlig strukturell endring + (ny informasjonsarkitektur), ikke en liten fiks:** ingen ekte + nettleser-interaksjonstest (scrolling, tapping av celler/hull- + overskrifter/navnerad, faktisk visuelt resultat av sticky-kolonnene) + er utført — flagget eksplisitt til bruker FØR utrulling, bruker bør + selv klikke seg grundig gjennom før full tillit. + **Rullet ut live 2026-07-27**, bruker bekreftet eksplisitt: ingen + migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte + også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge + containere boot-et rent, `/health`/`/dashboard`/`/my-rounds` → 200, + `teeoff.no` upåvirket. +- **Reell produksjonsbug funnet OG fikset SAMME DAG via ekte nettleser- + testing (2026-07-27) — FØRSTE gang denne økten en Chrome DevTools MCP + har vært tilgjengelig.** Brukeren ba meg selv åpne gridet + (`localhost:3000/my-rounds/{id}`), logget meg inn (passord feilet først + — kontoen mangler passord/miljøet pekte annerledes; løst med en ekte + magic-link brukeren limte inn), og jeg tok et ekte skjermbilde av det + nettopp bygde scorekort-gridet på en mobil viewport (390×844) mot ekte + produksjonsdata (runden `c5db2e31-...`, 2 spillere, 4/18 hull spilt). + **Fant umiddelbart en alvorlig, reell rendering-bug** som ALDRI ble + fanget av typesjekking/container-boot-verifisering: `position: sticky` + på `<td>`/`<th>` inni en `<table>` med `border-collapse` rendret + fullstendig ødelagt i Chrome — de sticky `Ut`/`Inn`/`Sum`-kolonnene + overlappet/utvisket hull-kolonnene bak seg (synlige sammenblandede + siffer, f.eks. "436"/"472" der Par-radens tall lå oppå hverandre). + Nøyaktig den typen feil den gjentatte "ingen ekte nettleser-test + utført"-forbeholdet advarte mot hele uken. + **Første fiks-forsøk (kun `border-collapse` → `border-separate + border-spacing-0`) løste IKKE problemet** — re-skjermbilde etter + redeploy viste samme overlapp. **Rot-årsaken var strukturell, ikke + syntaktisk:** å kombinere en sticky VENSTRE-kolonne MED sticky HØYRE- + kolonner i en tabell som er mye bredere enn viewporten er i seg selv + et ustabilt mønster — ved scroll-posisjon 0 blir de sticky høyre- + kolonnene umiddelbart trukket til synlig høyre kant, og alt som + "egentlig" befinner seg der i normal dokument-flyt (hull 3+) blir + liggende RETT BAK dem, delvis synlig gjennom `bg-muted/60`s + delvise gjennomsiktighet. **Fikset ved å fjerne sticky-posisjonering + fra `Ut`/`Inn`/`Sum`-kolonnene helt** (de scroller nå med resten av + hullene i normal flyt, samme velprøvde, veletablerte mønster som den + gjenværende sticky VENSTRE navnekolonnen, som fungerte korrekt hele + tiden) — droppet også `/60`-gjennomsiktigheten til fordel for en helt + opak `bg-muted`. + **Verifisert presist, ekte, etter fiksen** (ikke bare "ser bedre ut"): + nytt skjermbilde ved scroll-posisjon 0 viste alle tall rene og lesbare + (Hcp/Par-radene, begge spilleres scoringsceller, korrekt sirkel/firkant- + form for bogey/dobbel bogey/par), OG et script som scrollet gridet helt + til høyre (`scrollLeft = scrollWidth`) bekreftet `Ut`/`Inn`/`Sum` også + rene der (`Ut 36/Inn 36/Sum 72` på Par-raden — stemmer eksakt med en + 18-hulls par-72-bane), med navnekolonnen fortsatt korrekt pinnet til + venstre gjennom hele scrollingen. Tilgjengelighetstreet (`take_snapshot`) + bekreftet også at aria-labels med golftermer ("Bogey", "Dobbel bogey", + "Par") faktisk leses ut korrekt — `§Golfscore-språket`s "aldri farge + alene"-regel holder i praksis, ikke bare i teorien. + **Rullet ut live 2026-07-27**, samme dag: `docker compose up -d --build + teecup_frontend` kjørt to ganger (én for det mislykkede første forsøket, + én for den faktiske fiksen), `/health` 200 begge ganger, `teeoff.no` + upåvirket. + **Lærdom:** Chrome DevTools MCP-tilgangen endrer risikobildet for alt + fremtidig håndkodet frontend-arbeid denne økten — bruk den til å + FAKTISK se resultatet før noe rapporteres som ferdig, i stedet for kun + typesjekk+container-boot+`curl`-baserte proxyer for "det virker". +- **Full nettleser-gjennomgang av ALLE 22 skjermer, 2026-07-27 — + systematisk browsersjekk av alt som IKKE var reelt nettleser-testet + tidligere.** Brukeren spurte først hvilke visninger som faktisk finnes + (svart med en gruppert oversikt over `frontend/app/`s 22 `page.tsx`- + ruter), deretter ba om at ALLE de ikke-browsersjekkede skjermene faktisk + ble sjekket. Logget inn som `hei@erol.no` (spiller-konto, egne runder) + og — etter en egen runde med å spore opp riktig konto (magic-link fra + brukeren landet først på feil konto to ganger: `erol.haagenrud@gmail.com` + og kontoen manglet 2FA — satt opp TOTP for ekte ved å hente ut den rå + base32-secreten fra oppsett-skjermen og regne ut en gyldig 6-sifret kode + selv med et frittstående RFC 6238-script, ingen autentisator-app + involvert) — som `erol.haagenrud@envide.no` (org-eier for «Tjøme + Gents») for de org-/turnering-scopede skjermene. Gikk gjennom alle 22 + ruter med ekte skjermbilder + konsoll-feil-sjekk (`list_console_ + messages`) på hver. + **To reelle funn:** + 1. Scorekort-gridet (§1, bygget dagen før) hadde EN NY sticky-kolonne- + overlapp-bug som IKKE fantes i utgangspunktet -- se eget punkt over, + fikset samme økt. + 2. **Ny, ekte bug funnet i `round-scorecard.tsx`** (post-runde- + scorekortet, bygget i en tidligere økt) -- "til par" i BÅDE + header-hero-tallet og bunn-"TIL PAR"-brikken regnet + `totalGross - totalPar` der `totalPar` var summen av ALLE 18 hulls + par, ikke bare de faktisk spilte -- ga en absurd "−53 til par" for + en runde med 19 slag på kun 4 hull (skulle vært "+2"). Bekreftet + ved at `/my-rounds/[id]/stats` (en ANNEN komponent) viste riktig + "+2,00 til par" for SAMME runde, som isolerte feilen presist til + `round-scorecard.tsx`. Fikset med en ny `playedPar`-variabel + (paret for KUN spilte hull) brukt i de to til-par-utregningene -- + "Par"-brikken nederst beholdt bevisst `totalPar` (hele rundens + par, riktig som statisk referanse). Bunn-"Par"/øvre "Hcp"/"Par"- + referanseradene i `ScoreBlock` ble sjekket og bekreftet IKKE + rammet (brukes kun til den statiske referansen, aldri til en + til-par-utregning). + **For å nå de org-/turnering-scopede skjermene: satt org «Tjøme + Gents» sin `public_profile`/`slug` MIDLERTIDIG (bekreftet med bruker + FØR endring) for å teste `/clubs/[slug]`, deretter revertert + eksplisitt til nøyaktig opprinnelig tilstand (`slug=null`, + `public_profile=false`) — bekreftet med en direkte databasespørring + etterpå at reverten var eksakt. + **Alle 22 skjermer bekreftet uten krasj/konsoll-feil** (utenom + forventede 401/403 på steder som SKAL avvise — feil passord-forsøk, + privat lagchat, ugyldig verify-token). Full liste med status i + chat-loggen denne runden. + **Rullet ut live 2026-07-27**, ingen migrasjon for til-par-fiksen, + `docker compose up -d --build teecup_frontend`, verifisert direkte i + nettleseren mot den samme runden som viste bugen (nå "+2 til par", + korrekt). +- **Dashboard-runde: bane-navn-fiks, to nye designfarger, bane-detaljvisning + og aggregert statistikk — BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE + (2026-07-28):** fem punkter reist av brukeren i samme runde, alle + bekreftet eksplisitt før bygging (AskUserQuestion) unntatt fargevalget, + der brukeren ba om at jeg selv "tok på designerbrillene" og foreslo. + 1. **"Kommende runder" → "Runder"** på dashbordet (`dashboard.tsx`, + `UpcomingRounds`) — ren tekstendring, ingen annen forekomst i kodebasen. + 2. **Tjøme-bane-duplikatet rettet i ekte `teecup_db`:** presist + diagnostisert FØR noe ble kjørt — alle tre av `hei@erol.no` sine + runder pekte til nøyaktig samme `teeoff_facility_slug`/ + `teeoff_course_id` (`tjome-golfklubb`/140), kun `round. + course_name_snapshot`-TEKSTEN differerte (én runde fra FØR + enkeltbane-navnefiksen 25.07 hadde "Tjøme Golfklubb – Hovedbanen"). + Ingen delt banetabell å slå sammen — frittstående runder har (bevisst, + ADR-033 Beslutning C) ingen egen `course`-rad, kun et navn-snapshot per + runde. Fiksen var én presist scopet `UPDATE round SET + course_name_snapshot = 'Tjøme Golfklubb' WHERE id = 'fa6e528f-...'` + (1 rad), kjørt etter eksplisitt bekreftelse, verifisert med en + read-only spørring rett etterpå. + 3. **To nye kjernefarger** — brukeren ba eksplisitt om minst én, "kanskje + to", etter først å ha fått presentert (og avvist "kun én") en + tidligere anbefaling fra 25.07 om å løfte den allerede eksisterende + `--chart-3`-blåtonen. Løftet BEGGE allerede validerte statistikk- + fargene i stedet for å finne opp nye OKLCH-verdier: `--info` + (fra `--chart-3`, blå) og `--gold` (fra `--chart-4`, gul/gull) — + nye tokens i `globals.css` (`:root`/`.dark`/media-dark-blokken, alle + tre synkronisert som vanlig). Konkret, begrunnet førstebruk for + begge, ikke bare dekorativt: `--info` på et nytt "Hcp spilt til + X"-merke (før: ren grå tekst) på rundekort (`round-card.tsx`), + `--gold` på et nytt "Personlig rekord"-merke (`Medal`-ikon) som vises + når en fullført runde er brukerens laveste til-par blant MINST to + fullførte runder (unngår at den eneste fullførte runden feilaktig + kalles en "rekord"). Beregnet i `dashboard.tsx`/`own-rounds.tsx`/ + `course-rounds.tsx` sine respektive `findPersonalBestRoundId()`. + 4. **Bane-detaljvisning:** "Spilte baner" på dashbordet er nå klikkbar + (`PlayedCourses`, `dashboard.tsx`) — ny rute + `/my-rounds/course/[name]` (`components/course-rounds.tsx`), gjenbruker + eksisterende `GET /rounds` (ingen nytt backend-endepunkt), filtrerer + client-side på nøyaktig samme `course_name_snapshot`-nøkkel dashbordets + egen gruppering allerede bruker. "Personlig rekord" regnes likevel over + HELE rundelisten, ikke bare denne banens, for at merket skal bety det + samme uansett hvor et rundekort vises. + **Reell bug funnet OG fikset UNDER browserverifisering, ikke antatt + riktig fra kildekoden alene:** første versjon leste `params.name` rått + uten `decodeURIComponent` (matchet et eksisterende mønster i + `/clubs/[slug]/page.tsx` som aldri hadde blitt testet med et navn som + inneholder mellomrom/æøå) — et ekte skjermbilde viste tittelen som + `Tj%C3%B8me%20G...` og "0 runder funnet", siden matchen skjedde mot den + RÅ URL-kodede strengen. Rettet med et eksplisitt `decodeURIComponent`, + bekreftet med et nytt skjermbilde: riktig tittel og alle tre Tjøme- + rundene listet, inkl. både `--info`- og `--gold`-merkene rendret + korrekt. + 5. **Aggregert statistikk, klikkbar fra dashbordet:** ny + `GET /rounds/stats/summary` (`app/routers/rounds.py`), ny side + `/my-rounds/stats` (`components/rounds-stats-summary.tsx`), lenket + fra dashbordets "Statistikk"-seksjon ("Se full statistikk"). Bruker + valgte det BREDESTE av tre foreslåtte omfang (utover kun runder/snitt- + til-par/putt: fairwaytreff, GIR, én-putt, scrambling, sand save, + snitt chip/bunker/straffeslag/anywayslag per runde) — portert fra de + allerede R&A/manuelt verifiserte formlene i `round-stats.tsx` sin + `computeStats()` for ÉN runde, generalisert til å pole alle kvalifiserte + hull på tvers av ALLE fullførte runder (ikke gjennomsnitt av per-runde- + prosenter, som ville vektet små utvalg feil). + **Putt/18-hull-regelen** (brukerens eksplisitte instruks, presist + bekreftet tolkning FØR bygging via AskUserQuestion): `round_hole` har + alltid nøyaktig 18 rader per deltaker uansett `holes_planned` (9 eller + 18) — verifisert i `_create_participant`. For hver fullført runde der + puttsporing faktisk var på (`stat_level != 'strokes_only'`) telles + derfor alle 18 lagrede rader, et hull uten registrert putt-verdi + (uspilt, eller utenfor et 9-hulls spilleomfang) telles som 2 putter — + runder UTEN puttsporing holdes helt utenfor tallet (ellers ville alle + 18 hull feilaktig blitt padded). Kun putt-tallet padder — alle andre + andelstall bruker KUN faktisk registrerte hull, som instruert. + **Verifisert i to lag:** (a) en frittstående Python-simulering av + nøyaktig samme aggregeringslogikk mot et hånd-konstruert 3-runde- + datasett (én `strokes_only`-runde padding-ekskludert, én + `strokes_and_putts`-runde med 9 av 18 hull padded) — alle hånd-regnede + forventninger stemte eksakt, inkl. det kritiske tilfellet (padded runde + ga nøyaktig 36 putt/18, strokes_only-runden talte 0 mot totalen); (b) + ekte typesjekket produksjonsbuild + import-sjekk av hele FastAPI-appen + i det faktiske prod-imaget (ikke bare syntaks) + et ekte browserbesøk + som viste reelle, korrekt utregnede tall for `hei@erol.no` sine 2 + fullførte runder. + **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt for + databaseskrivingen (punkt 2) og fargevalget (punkt 3, "to farger i stedet" + for anbefalt én); resten bygget direkte på brukerens egen presise + instruks. Ingen migrasjon. `docker compose up -d --build teecup_api + teecup_frontend` (kjørt to ganger — én gang for hovedleveransen, én gang + for `decodeURIComponent`-fiksen over). `/health`/`/dashboard`/ + `/my-rounds/stats`/`/my-rounds/course/...` alle bekreftet 200 og + konsoll-feilfrie i en ekte innlogget nettleser-sesjon, `teeoff.no` + upåvirket. +- **Statistikk-siden utvidet: miss-retning, tidsvindu + forrige-periode- + sammenligning — BYGGET, TESTET OG LIVE (2026-07-28), samme dag, rett + etter forrige punkt:** brukeren reiste tre ting samtidig — ønsket + miss-RETNING (ikke bare treff%), trender, og et presist spørsmål om + hvorvidt GIR-fra-score-og-putt-inferens faktisk var forstått/utnyttet. + **Siste punkt avklart, ikke en bug:** bekreftet at `isGir = score - putts + <= par - 2` (allerede i `round-stats.tsx`, portert uendret til det nye + aggregerte endepunktet dagen før) ER akkurat denne generelle inferensen — + fungerer for ALLE score/putt/par-kombinasjoner, ikke bare brukerens + eksempel. Eneste reelle begrensning (forklart, ikke fikset): krever + putt-tall, så `strokes_only`-runder får aldri en GIR-verdi — score alene + er nesten aldri nok til å BEVISE GIR (en scrambling-birdie fra utenfor + green gir identisk score som en ekte GIR+1-putt). + **Miss-retning bygget:** `_summarize_rounds` i `app/routers/rounds.py` + utvidet med `fairway_left_pct`/`fairway_right_pct` (samme + `fairway_tracked`-pool som treff%) og `green_miss_long/short/left/ + right_pct` (egen pool — hull der `approach_result` er registrert og ≠ + "hit", UAVHENGIG av om putt er kjent, samme adskilte spor som + `round-stats.tsx` sin `missedGreen`/`missDir`). Ny `MissBar`-komponent i + `rounds-stats-summary.tsx` (fordelingsbar, samme visuelle idé som + `SegmentedBar`/`DistributionBar` andre steder i appen). + **Tidsvindu + forrige-periode bygget:** ny `StatsWindow`-type (`Literal`) + og `_resolve_stats_window()` — brukeren ba eksplisitt om BÅDE en full + velger (Siste runde/5/10/Denne måneden/I år/Siste år/Alltid) OG + sammenligningspiler mot forrige periode (utover det opprinnelig + anbefalte "kun siste 5 vs. alltid, ingen piler"). Én ENSARTET regel for + "forrige periode" på tvers av alle vindutyper — samme LENGDE (antall + runder for rullerende antall-vinduer, antall DAGER for dato-vinduer) + rett før gjeldende vindus start — i stedet for å måtte spesialdefinere + "forrige måned"/"forrige år" ulikt for hver kalenderbasert type. + `GET /rounds/stats/summary?window=...` returnerer nå `{window, current, + previous}` (`RoundStatsWindowSummary`) — samme `_summarize_rounds()`- + funksjon kalt to ganger på to ulike round_id-mengder, ingen duplisert + aggregeringslogikk. Frontend fikk en ny `Delta`-komponent (pil + farge, + farget etter en eksplisitt `goodDirection`-per-tall — lavere er bedre + for til-par/putt/chip/bunker/straffeslag/anywayslag, høyere er bedre for + treff-/rednings-prosentene, INGEN farge på de rene miss-retnings-tallene + siden venstre/høyre ikke er "bedre/verre"). + **Verifisert i to lag:** (a) en frittstående Python-simulering av + `_resolve_stats_window()` mot syntetiske datasett — rullerende antalls- + vindu (riktig current/previous-splitt, riktig tomt `previous` når for få + runder finnes), dato-vindu (kalender-til-dato + rullerende forrige + periode), og en isolert miss-retning-poolingstest (`None`-verdier + korrekt ekskludert fra nevneren); (b) ekte typesjekket produksjonsbuild + (måtte rette en TypeScript-nullbarhets-feil underveis — `current`/ + `previous` pakket ut via en IIFE inni JSX for at TS skulle smalne begge + riktig), full import-sjekk av hele FastAPI-appen i prod-imaget, OG et + ekte browserbesøk som viste reelle tall (fairway-miss 36/43/21, + green-miss-retning 0/75/13/13) + bekreftet vindu-velgeren fungerer + (klikket "Siste 10", pillen ble grønn, tallene oppdaterte seg) + et + direkte `fetch()`-kall fra siden selv som bekreftet `{window, current, + previous}`-formen (2 runder i `current`, korrekt tomt `previous` siden + brukeren kun har 2 fullførte runder totalt). + **Rullet ut live 2026-07-28**, ingen migrasjon, `docker compose up -d + --build teecup_api teecup_frontend`. Begge containere boot-et rent, + `/health` → 200, anonymt `GET /rounds/stats/summary?window=last_5` → 401 + (bekrefter query-parameteren ruter riktig gjennom Caddy/rewrites), + `teeoff.no` upåvirket. +- **Statistikk-siden: visuell v2 (kompass-diagram + donuter), HÅNDKODET og + BROWSERVERIFISERT MED EKTE ITERASJON, LIVE (2026-07-28), samme dag:** + brukeren delte et referansebilde av en konkurrent-app sin statistikk- + skjerm (donuter med sentertall, kompass for miss-retning) og ba om noe + tilsvarende. Skrev først et fullt V0-prompt (data-kontrakt, komponent- + for-komponent) — brukeren var tom for V0-credits og spurte eksplisitt om + jeg kunne "gi promptet til meg selv" og bygge det direkte, siden Chrome + DevTools MCP nå gjør det mulig å faktisk SE resultatet underveis (ulikt + tidligere håndkodede runder denne uken som kun var typesjekket). + **Backend:** tre nye felt i `RoundStatsSummary`/`_summarize_rounds` + (`app/routers/rounds.py`) — `putt_dist_one_pct`/`two_pct`/ + `three_plus_pct`, EGNE eksakte bøtter (`putts==1`/`==2`/`>=3`, samme som + `round-stats.tsx` sin `puttCategories`), bevisst forskjellig fra det + allerede eksisterende `one_putt_pct` (som bruker `<=1` — en annen, + allerede etablert rate, ikke en distribusjon). + **Frontend, `rounds-stats-summary.tsx` skrevet om betydelig:** ny + `GirCompass` — greentreff-prosenten i midten (grønn fremheving), fire + retningsceller rundt (Langt=topp, Kort=bunn, Venstre=venstre, + Høyre=høyre), samme visuelle språk som den allerede etablerte + `DirectionCross`/`DirButton` fra `round-detail.tsx` (plusstegn-rutenett, + `min-h-16 rounded-2xl border`-celler) — bevisst IKKE fargekodet + grønn/oransje på retningscellene (retning er beskrivende, ikke god/ + dårlig), kun sentercellen. Ny gjenbrukbar `Donut`-komponent + (conic-gradient, sentertall + valgfri forklaringsliste via + `showLegend`) brukt til BÅDE puttfordelingen (3 segmenter: 1-putt/ + 2-putt/3-putt+) og to enkle rednings-gauger (scrambling/sand save, ett + segment). Bevisst IKKE et flyt-/beslutningstre-diagram for scrambling/ + sand save (som referansebildet hadde) — det er to uavhengige prosenter i + datamodellen, ikke en forgrening, et flytdiagram ville antydet en + struktur som ikke finnes. Bevisst INGEN trendlinje (referansebildets + fjerde element) — for lite rundehistorikk til å vise noe meningsfullt + ennå, samme vurdering som tidligere samme dag. + **Reelt funn UNDER selve visuell iterasjon, ikke bare kodegjennomgang:** + første versjon av "Redning"-kortet viste samme tall TO GANGER per gauge + (`Donut` sin egen auto-genererte forklaringsliste "● Scrambling 9 %" RETT + OVER en egen, manuelt lagt til "Scrambling"-bildetekst) — sett direkte i + et ekte skjermbilde, ikke antatt. Fikset ved å legge til en + `showLegend`-prop på `Donut` og slå den av for de to gaugene, beholdt kun + min egen kompakte bildetekst+delta under ringen. + **Verifisert:** ekte typesjekket produksjonsbuild (to runder — én for + hovedversjonen, én for legend-fiksen), full backend-import-sjekk i + prod-imaget, OG ekte skjermbilder tatt FØR og ETTER legend-fiksen i en + innlogget nettleser-sesjon (samme mønster som sticky-kolonne-bug-fiksen + tidligere denne uken) — kompasset viser reelle tall (39 % greentreff, + 75 % kort, 13/13 % venstre/høyre, 0 % langt for `hei@erol.no` sine 2 + runder), puttfordelings-donuten viser korrekt fargede segmenter (17/61/ + 22 %), vindu-velgeren fungerer fortsatt uendret (klikket "Siste 5" etter + redesignet, ingen konsoll-feil). Ingen migrasjon. + **Rullet ut live 2026-07-28**, `docker compose up -d --build teecup_api + teecup_frontend` (kjørt to ganger). `/health` → 200 begge ganger, + `teeoff.no` upåvirket. + **Oppfølging, samme dag:** brukeren ba om at fairway-baren sin venstre/ + høyre-bom bruker SAMME farge (i stedet for to ulike chart-farger) -- + begge er tross alt bare "bom", ingen grunn til å skille dem visuelt. + Byttet begge til `--brand-orange` (appens etablerte "bom/over par"-farge), + beholdt grønn kun for selve fairwaytreffet. Verifisert med et nytt + skjermbilde. Rullet ut, ingen migrasjon. +- **"Til par: med vs. uten"-splitt, BYGGET, TESTET OG LIVE (2026-07-28), + samme dag:** brukeren ba eksplisitt om snitt-til-par splittet på om en + hull-hendelse inntraff eller ikke -- greentreff, fairwaytreff, bunker, + OG anywayslag (eksplisitt fremhevet). Fire nye par felt i + `RoundStatsSummary`/`_summarize_rounds` (`app/routers/rounds.py`): + `avg_to_par_with/without_gir`, `_fairway_hit/miss`, `_with/without_bunker`, + `_with/without_anyway` -- pooler ENKELTHULL på tvers av alle runder i + perioden (ikke per-runde-snitt, siden dette er hull-nivå-betingelser), + samme `diff()`-formel som `round-stats.tsx` sin `avgToParWithGir`/ + `avgToParFairwayHit`/`avgToParWithBunker` bruker for én runde (portert + uendret) -- anywayslag-splitten er en ny, konsekvent utvidelse av + akkurat samme mønster (fantes ikke fra før for enkeltrunder heller). + Ny `CompareToPar`-komponent i `rounds-stats-summary.tsx`: to bokser side + om side, den med FAKTISK lavest til-par denne perioden fremheves grønn + -- ingen hardkodet antakelse om hvilken side som "skal" vinne (bekreftet + reelt i data: "Med anywayslag" var faktisk verre enn "Uten anywayslag" + som forventet, men "I bunker" var marginalt BEDRE enn "Ikke i bunker" + for denne brukerens 2 runder -- fremhevingen fulgte automatisk det + virkelige tallet, ikke en antakelse). + **Verifisert:** en frittstående Python-simulering av alle fire splittene + mot et hånd-konstruert 5-hulls datasett (eksakte forventede gjennomsnitt + regnet ut for hånd og sammenlignet), full backend-import-sjekk i + prod-imaget, ekte typesjekket build, OG et ekte skjermbilde i innlogget + nettleser som viste reelle, korrekt utregnede og korrekt fargede tall + for alle fire kategoriene. Ingen migrasjon. + **Rullet ut live 2026-07-28**, `docker compose up -d --build teecup_api + teecup_frontend`, `/health` → 200, `teeoff.no` upåvirket. + +- **Gjennomgang av frittstående runder (ADR-033) + det viktigste hullet + lukket: faktisk (beregnet) HCP, BYGGET, SCRATCH-/BROWSERVERIFISERT OG + LIVE (2026-07-28, ADR-038):** brukeren ba om en vurdering av om alt var + tenkt gjennom for single-runder. Fant ved grep at hele WHS-indeksmotoren + (`handicap_index_from_differentials`/`low_handicap_index`/ + `apply_index_caps` i `handicap_engine.py`, 41/41 testet siden ADR-033) + ALDRI ble kalt fra noe API-endepunkt — `round_participant. + score_differential` ble regnet og lagret per runde, men `app_user. + handicap_index` endret seg kun manuelt. Bekreftet eksplisitt i + `rounds.py` sin egen moduldoc ("skjer IKKE automatisk her -- eksplisitt + uavklart punkt i ADR-033"). Sekundære, mindre hull notert samtidig + (offline-kø kun for turnering-scorekortet, ikke frittstående runder; + ingen Stableford; ingen rundedeling/visibility) — brukeren valgte å ta + tak i HCP-hullet. + **To load-bærende design-avklaringer** (AskUserQuestion, se ADR-038): + (1) hver INNLOGGET deltaker (ikke bare eieren) styrer sin egen + eksklusjon fra faktisk HCP; (2) faktisk HCP designes NÅ for å kunne + inkludere begge kilder (frittstående runder OG en fremtidig turnering- + kilde), men v1 bygger kun runder-delen — løst med ett bevisst tynt, + navngitt skjøtepunkt (`_gather_qualifying_differentials`), IKKE en ny + generell "scoring record"-tabell (for tidlig abstraksjon). + **Ny migrasjon `030_actual_handicap_index.sql`:** `app_user. + computed_handicap_index`/`computed_handicap_index_updated_at` (den + faktiske, beregnede WHS-indeksen — ALDRI direkte redigerbar, kun + avledet), `round_participant.exclude_from_handicap` (manuell opt-out, + uavhengig av den automatiske `counts_for_handicap`-kvalifiseringen), + `round.play_format` (`stroke`/`match`, selvdeklarert — ingen egen + match-motor for frittstående runder), `handicap_history.source` + (`manual`/`computed` — gjenbruker EKSISTERENDE tabell fra ADR-031- + oppfølgingen i stedet for en parallell historikk-tabell, siden Low + Handicap Index/cap (Rule 5.7/5.8) trenger nøyaktig samme + dato+indeks-form som allerede fantes der). + **WHS-kilde lest og lagt til grunn** (`WHS_Rules_of_Handicapping_2024. + pdf`, Rule 3.3): matchspill-scorer ER teknisk et gyldig HCP-grunnlag + under WHS, MEN et konsedert/ikke-utspilt hull krever en subjektiv "most + likely score" TeeCups rene slagregistrering ikke har noen vei til å + representere presist — begrunner "spør (med anbefalt eksklusjon), ikke + tving"-designet i stedet for et hardkodet forbud mot matchspill i + HCP-grunnlaget. + **Backend (`app/routers/rounds.py`):** ny `_recompute_computed_ + handicap_index(conn, user_id)` — henter de ≤20 nyeste kvalifiserende + differensialene, kaller den allerede-testede motoren, henter tidligere + `computed`-historikk for Low HI-cap (hopper bevisst over capping ved + FØRSTE beregning noensinne — Low HI er udefinert før en indeks er + etablert). Kalt fra `complete_round` (alle deltakere med `user_id`), + `update_participant` (eksklusjon endret på en ALLEREDE fullført runde), + `remove_guest_participant` (dekker faktisk enhver ikke-eier-fjerning, + til tross for navnet) og `delete_round` (fanger berørte brukere FØR + kaskade-slettingen fjerner radene). `update_participant` sin + autorisasjon utvidet presist: en ikke-eier kan KUN sende + `exclude_from_handicap`, KUN på sin egen rad (`_OWNER_ONLY_ + PARTICIPANT_FIELDS`-sjekk + eksplisitt eier-eller-selv-gate) — alle + andre felt forblir strengt eier-only, uendret. + **Backend (`app/routers/auth.py`):** `Me` fikk `computed_handicap_ + index`/`computed_handicap_index_updated_at`. Ny `POST /auth/profile/ + handicap/apply-computed` — kopierer gjeldende beregnet verdi inn i det + manuelt satte HCP-et (samme skrivevei/historikk-logging som en vanlig + manuell PATCH, kun `source='manual'`). `GET /auth/profile/handicap- + history` eksponerer nå `source` også. + **Frontend:** `/my-rounds/new` fikk en "Spilleform"-bryter + (Slagspill/Matchspill) — velges Matchspill, forhåndsutfylles (ikke + tvinges) en eksklusjons-avkrysning med forklarende tekst. + `round-detail.tsx` fikk en "Matchspill"-badge i headeren, en + spilleform-bryter i `EditRoundPanel` (ren metadata, redigerbar uansett + fullført-status), og `EditParticipantPanel` fikk en ny `restricted`- + modus: en ikke-eier som redigerer SIN EGEN rad, ELLER EIEREN etter at + runden er fullført, ser KUN eksklusjons-toggelen (ikke tee/HCP/navn/ + statistikk) — `PlayerList` sin "Rediger"-knapp vises nå også for en + ikke-eiers egen rad, ikke bare for eieren. `/account` fikk et nytt + "Faktisk HCP (beregnet)"-kort (verdi + sist-beregnet-dato + + "Bruk som mitt HCP →"-knapp), og HCP-historikk-listen viser nå + "beregnet"/"manuelt" per rad. + **Scratch-verifisert grundig, 111/111 sjekker** (isolert + `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API- + container via ekte HTTP, samme mønster som hele prosjektet): + hånd-utregnet WHS-matte bekreftet PRESIST (tre runder med kjente + differensialer [10.0, 12.0, 14.0] ga nøyaktig 8.0 — beste-1-av-3 med + -2.0-justering fra Rule 5.2a-tabellen), <3 tellende runder gir fortsatt + `null` (ikke en feil), sletting av en tellende runde regner faktisk HCP + på nytt (tilbake til `null` under 3), matchspill-runde eksplisitt + ekskludert ved opprettelse telles korrekt IKKE med, "Bruk som mitt + HCP"-overføring bekreftet (+ `handicap_history` bærer nå begge kilder), + og hele autorisasjonsmatrisen for eksklusjons-feltet (medspiller nektes + andre felt og eierens rad, men kan endre EGEN eksklusjon; eieren kan + fortsatt overstyre medspillerens). `test_isolation.sql` 12/12 uendret. + **Reelt funn UNDER selve scratch-oppsettet, ikke i produksjon:** første + forsøk på en engangs API-container monterte kildekoden til feil sti + (`/app/app` i stedet for `/srv/app`, som er `Dockerfile` sin faktiske + `WORKDIR`) — containeren boot-et rent, men kjørte stille det GAMLE, + innbakte imagekoden uendret. Fanget FØR noe ble stolt på, ved at + `/auth/me` manglet det nye feltet helt i et faktisk API-svar — rettet + ved å montere til riktig `/srv`-sti, deretter bekreftet på nytt. + **Ekte nettleser-verifisert** (Chrome DevTools MCP, engangs `next dev`- + container mot scratch-backend — samme "sett resultatet, ikke bare + typesjekk det"-arbeidsmåte som resten av uken): logget inn via ekte + magic-link, opprettet en matchspill-runde → bekreftet + forhåndsutfylt-men-overstyrbar eksklusjonsavkrysning i skjemaet, + "Matchspill"-badge i rundens header, fullførte runden → bekreftet + `EditParticipantPanel` automatisk bytter til restriktert modus (kun + eksklusjons-toggel, ingen av de andre feltene), lagret en endring → + bekreftet ekte `PATCH .../participants/{id}` 200 i nettverksfanen. + Ekte typesjekket produksjonsbuild (alle 24 ruter) kjørt og bekreftet. + **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: migrasjon + 030 kjørt mot ekte `teecup_db` (alle fire nye kolonnesettene bekreftet, + `test_isolation.sql` fortsatt 12/12 mot ekte database), deretter + `docker compose up -d --build teecup_api teecup_frontend`. Begge + containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` + upåvirket. Verifisert presist at den nye ruten faktisk når FastAPI: + anonymt `POST /auth/profile/handicap/apply-computed` over ekte https ga + korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404. + **Bevisst utenfor omfang, notert i ADR-038:** turnering-/organisasjons- + scoring teller fortsatt ikke mot faktisk HCP (venter på videre + ADR-037-arbeid), ingen egen "aging"-bakgrunnsjobb utover at spørringen + alltid henter kun de 20 nyeste differensialene. + +- **Ekte spillformer for frittstående runder (match/skins/fourball/ + foursome/greensome/scramble), BACKEND BYGGET OG SCRATCH-VERIFISERT, + IKKE ENNÅ RULLET UT MOT EKTE SYSTEMER (2026-07-28, ADR-039):** reist av + brukeren rett etter ADR-038: "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." Fire load-bærende avklaringer (AskUserQuestion, + to runder) FØR bygging — bruker valgte det bredeste omfanget i alle + runder: to sider (Beslutning A), ALLE fire par-/lag-underformater fra + start inkl. de to delt-ball-krevende (Beslutning B), skins med BEGGE + akser konfigurerbare av oppsetteren (netto/brutto OG rullerer/deles, + Beslutning D), og gå rett til migrasjon+motor samme økt (ikke bare + dokumentere). + **Kjerneinnsikt som gjorde dette trygt å bygge fort:** hele match-play- + motoren (`handicap_engine.py` sin `Format`/`AllowanceStrategy`-familie/ + `match_play_strokes`/`compute_match_state`) og hele "delt-ball vs. + individuell"-mønsteret (`hole_score.match_participant_id` NULLABLE, + delt-ball-rader identifisert av `team_side` alene) fantes ALLEREDE, + bygget og produksjonskjørt for org-scopede turnering-matcher. Denne + runden PORTERER dette mønsteret til frittstående runder (nye + `round_side`/`round_participant.round_side_id`/`round_participant. + playing_handicap`/`round_hole.round_side_id`) i stedet for å finne opp + noe nytt — kun skins (ingen turnering-motstykke) fikk EKTE ny + motorkode (`compute_skins`, 7 nye tester, 50/50 i `test_handicap_ + engine.py`). `app/handicap.py` sin `_SIDE_IS_UNIT` omdøpt til + `SIDE_IS_UNIT` (gjort delt for gjenbruk, samme "fjern understrek når + et andre bruksted dukker opp"-mønster som tidligere runder). + **Skjema, migrasjon `031_round_play_formats.sql`:** `round.play_format` + utvidet til 8 verdier, nye `round.skins_scoring`/`skins_tie_handling`, + ny `round_side`-tabell (nøyaktig to per runde, håndhevet i app-laget + som ADR-011s to-lags-grense), `round_participant.round_side_id`/ + `playing_handicap` (sistnevnte ALDRI det samme som det eksisterende + `course_handicap_snapshot` — den absolutte WHS-verdien rørt av INGEN + av denne rundens kode), `round_hole.round_participant_id` gjort + NULLABLE + ny `round_hole.round_side_id` + XOR-CHECK + to partielle + unike indekser (samme mønster som org-scopet `hole_score`, migrasjon + 001). + **Reelt, bekreftet funn UNDER selve designet (ikke antatt), avklart + eksplisitt med bruker FØR bygging:** foursome/greensome/scramble har + ÉN kombinert score per SIDE per hull -- INGEN individuell score + finnes i det hele tatt å bygge en Score Differential fra. Løst + (Beslutning E, bekreftet av bruker): disse deltakerne får + `counts_for_handicap` ALDRI sann for disse rundene -- og viste seg, + presist verifisert i scratch, å følge HELT AUTOMATISK av den + eksisterende `complete_round`-logikken uten noen kodeendring i det + hele tatt (en delt-ball-deltaker har null individuelle `round_hole`- + rader, så `played_count` blir alltid 0, som `round_counts_for_ + handicap` allerede tolker som "teller ikke" for både 9- og + 18-hulls-intensjon). + **API (`app/routers/rounds.py`):** `POST/DELETE .../sides` (eier-only, + maks to, avvist for slagspill/skins), `ParticipantCreate`/ + `ParticipantUpdate` fikk `round_side_id` (eier-only reassignment, + validerer siden hører til samme runde), `_recompute_side_handicaps` + (porterer `compute_and_store_side_handicaps` — venter på at siden når + forventet spillerantall FØR den skriver noe, samme + "ikke komplett ennå = ikke skriv"-filosofi som originalen), + `_relative_strokes_for_round` (porterer `relative_strokes_for_match`), + nye `GET/PATCH .../sides/{id}/holes/{n}` (delt-ball-scoring, kun + slagtall — ingen av de andre detalj-feltene gir mening for en delt + ball), og et nytt lese-endepunkt `GET .../format-result` som regner + løpende matchstatus (gjenbruker `compute_match_state`/`HoleResult` + uendret) for de to-sidede formatene, eller en skins-tavle + (`compute_skins`) for skins — aldri lagret, alltid avledet ved lesing. + **To reelle bugs funnet OG fikset UNDER scratch-testing, ingen nådde + produksjon:** (1) side-tildelings-recompute kjørte FØR responsen ble + hentet i stedet for ETTER — testen fanget dette presist (forventet + `playing_handicap` i responsen, fikk `null` fra FØR omregningen); (2) + individuell-ball-gren i format-result-spørringen nøkkel-forvekslet + "enhet" (satte `round_side_id` som nøkkel i stedet for + `round_participant_id`, mens `side_net()`-oppslaget forventet + deltaker-id) — ga et tomt `hole_results` til tross for gyldige + registrerte scorer, fanget da `match_holes_played` kom ut som 0 i + stedet for det forventede 3. + **Scratch-verifisert grundig, 96/96 sjekker** (isolert + `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API- + container via ekte HTTP, samme mønster som hele prosjektet): en + hånd-utregnet 3-hulls singles-match (hcp 5 vs. 10, riktig slagmottak + på de fem vanskeligste hullene) ga eksakt `lead=0`/"AS"/riktig + hole-for-hole-mønster; fourball bekreftet INDIVIDUELL (ikke kombinert) + 90 %-beregning per spiller, korrekt "ikke komplett ennå" før andre + spiller på siden var tildelt; foursome bekreftet 18 round_hole-rader + opprettet på SIDEN ved side-opprettelse (før noen deltaker lagt til), + korrekt kombinert 50 %-Playing-Handicap først når begge var tildelt, + ekte delt-ball-scoring via det nye sub-endepunktet, OG at + `counts_for_handicap` ble `False` for begge etter fullføring; skins + (netto+carry) ga eksakt `{sk3: 2.0}` for et 2-hulls scenario med et + bevisst konstruert uavgjort-så-carry-så-outright-vinn-mønster, OG + bekreftet skins teller NORMALT mot faktisk HCP etter full fullføring + (ulikt match). `test_isolation.sql` 12/12 uendret (additiv migrasjon). + Alle 50 handicap_engine-tester (43 eksisterende + 7 nye skins) grønne. + **IKKE bygget i denne runden, bevisst utsatt (Beslutning F):** + frontend — ingen skjerm for å opprette sider, tildele deltakere, + konfigurere skins, eller vise løpende matchstatus/skins-tavle. Backend + er fullt funksjonelt og testet, men ubrukelig fra selve appen inntil + frontend bygges i en egen, senere runde (samme lagdelings-mønster som + ADR-038: motor/skjema/API FØR frontend). + **Rullet ut mot ekte systemer 2026-07-28**, bruker bekreftet + eksplisitt: migrasjon 031 kjørt mot ekte `teecup_db` (nye kolonner/ + `round_side`-tabell bekreftet, `test_isolation.sql` fortsatt 12/12), + deretter `docker compose up -d --build teecup_api`. Ren boot, + `/health`/`/dashboard` → 200, ny rute bekreftet nåbar (anonymt + `POST .../sides` → 401, ikke en rå 404), `teeoff.no` upåvirket. + +- **Oppfølging samme dag: manglende minimums-spiller-håndhevelse fanget + og fikset, BYGGET OG SCRATCH-VERIFISERT, RULLET UT LIVE (2026-07-28):** + brukeren påpekte presist et hull ADR-039 selv ikke fanget opp: "Er det + match-spill, skins eller lagspill så MÅ det jo være flere spillere. + Dette fanges ikke opp." Riktig — ingenting hindret å fullføre en + "match" med kun eieren, eller la flere spillere enn formatet tillater + havne på samme side. Bekreftet med bruker (AskUserQuestion): skins + krever minst 3 spillere (2 gjør skins i praksis identisk med en vanlig + match — 3+ er der en oppsamlet, uavgjort pott faktisk gir mening). + **Bygget, ren Python-logikk, INGEN migrasjon:** ny + `_format_setup_status()` — slagspill alltid klar; skins krever minst + 3 deltakere; to-sidede formater (match/fourball/foursome/greensome/ + scramble) krever NØYAKTIG to sider, INGEN uassignerte deltakere, og + hver side nøyaktig riktig spillerantall for formatet + (`_SIDE_PLAYER_COUNT`, allerede definert). Ny `setup_complete`/ + `setup_message` på `RoundOut` (alltid synlig, uansett format/status). + `complete_round` avviser nå (409 `SETUP_INCOMPLETE`) hvis oppsettet + ikke er komplett — FØR noe regnes ut. Ny `_check_side_capacity()` + avviser (409 `SIDE_FULL`) proaktivt ved SELVE tildelingen (både + `POST .../participants` med `round_side_id` og `PATCH .../ + participants/{id}`), i stedet for å først oppdage overtallet ved + fullføring — med eksplisitt unntak for en no-op-reassignment til + samme side (en deltaker teller ikke seg selv ut av plassen sin egen + side har). + **Scratch-verifisert grundig, 122/122 sjekker** (samme isolerte + scratch-oppsett som resten av runden — full regresjon av alle + tidligere 96 sjekker PLUSS 26 nye): 3. spiller avvist på en full + match-/foursome-side (409 SIDE_FULL), `setup_complete` korrekt False + ved kun 1 av 2 sider / uassignerte deltakere / for få skins-spillere, + fullføring korrekt avvist (409 SETUP_INCOMPLETE) i alle disse + tilstandene, og korrekt True (+ vellykket fullføring) først når + oppsettet faktisk er komplett for formatet. `test_isolation.sql` + uendret (ingen skjemaendring). + **Rullet ut live 2026-07-28**, ingen migrasjon, kun + `docker compose up -d --build teecup_api`. Ren boot, `/health`/ + `/dashboard` → 200, `teeoff.no` upåvirket. +- **Frontend for ADR-039 (sider/skins/delt-ball-scoring) BYGGET, BROWSER- + VERIFISERT OG LIVE (2026-07-28), samme dag:** ingen backend-endring i + denne runden (alt allerede live) — ren frontend-jobb, testet reelt i + nettleser mot en isolert scratch-backend (Chrome DevTools), ikke bare + typesjekk. + **`new-round.tsx`:** spilleform-velgeren utvidet fra to (Slagspill/ + Match) til alle åtte format (Skins/Fourball/Foursome/Greensome/ + Scramble 2/4 lagt til), med en ny skins-konfigurasjonsseksjon (netto/ + brutto, rullerer/deles) som kun vises for `play_format="skins"` og et + forklarende "du setter opp sidene inni runden etterpå"-notat for de + to-sidede formatene (ADR-039 Beslutning A -- sider kan ikke opprettes + før deltakerne finnes). + **`round-detail.tsx` (hoveddelen):** ny `SidesPanel` (manage-fanen) -- + opprett/slett de to sidene, tildel/fjern deltakere (kompakte + "→ Side"-hurtigknapper, deaktivert når siden er full), viser + `playing_handicap` per side. Ny `FormatResultPanel` -- henter + `GET .../format-result`, viser løpende matchstatus (oversetter + motorens bokstavelige "(A)"/"(B)" til faktiske side-navn via + `round.sides[0]/[1]`, samme sorteringsrekkefølge som backend) eller en + skins-tavle (sortert synkende). `setup_complete`/`setup_message` + gater nå "Fullfør runde"-knappen klientside også (server er fortsatt + autoritativ). For delt-ball-formatene (foursome/greensome/scramble): + ny `SideScorecardGrid` (rader = sider, ikke spillere) + ny, forenklet + `SideScoreWizard` (kun slagtall, ingen putt/detalj-steg) mot de + eksisterende `GET/PATCH .../sides/{id}/holes/{n}`-endepunktene. + **To reelle stale-state-bugs funnet UNDER selve browserverifiseringen + (ikke i kodegjennomgang), begge fikset før utrulling:** + 1. `SidesPanel` sin `assign()` oppdaterte kun deltaker-listen lokalt + (via `onPatchParticipant`) -- `setup_complete`/`setup_message` + (server-beregnet) ble stående utdatert etter en vellykket + side-tildeling ("ikke tildelt en side" fortsatte å vises til tross + for at begge var tildelt). Fikset: `assign()` kaller nå + `onSidesChanged()` (full runde-refetch) etter en vellykket PATCH. + 2. `FormatResultPanel` sin refetch var kun koblet til hull-registrering + og WebSocket-signaler, ikke til side-/deltaker-tildeling -- + matchstatus ble stående på "venter..." selv etter at oppsettet var + komplett og "Fullfør runde" allerede var aktivert. Fikset ved å + bumpe `formatResultRefreshTick` ved HVER vellykket `loadRound()` + (enklere og mer robust enn å spore hvert enkelt kallsted som kan + påvirke handicap-beregningen). + **Verifisert grundig i en isolert scratch-nettleserøkt** (fersk + `teecup_scratch`-database, isolert scratch-MinIO, engangs API- + container, ekte `next dev` mot scratch-backend, Chrome DevTools MCP -- + ekte innlogging via magic-link, ekte profil-fullføring): tre komplette + runder bygget og spilt gjennom UI-et alene, ende-til-ende: + - **Match:** opprettet med eksklusjons-avkrysning forhåndshuket, + opprettet to sider, tildelte eier+gjest, bekreftet `playing_handicap` + (60/20) vist riktig, scoret hull 1 (4 mot 6) via `ScoringWizard` + (ubrørt komponent), bekreftet `FormatResultPanel` viste "1 UP + (Erol)" + riktig fargede hull-merker -- kryssjekket med + `aria-label`-attributtet direkte via `evaluate_script` for å bekrefte + semantisk korrekt side-navn bak den rå A/B-bokstaven. + - **Skins:** konfigurasjons-UI-et (netto/brutto, rullerer/deles) bekreftet + visuelt, opprettet med kun 1 spiller (satte-message "krever minst 3"), + la til to gjester til (meldingen forsvant idet den tredje ble lagt til, + "Fullfør runde" aktivert), scoret hull 1 for alle tre (via direkte + API-kall for hastighet, samme kontrakt som UI-et bruker), bekreftet + skins-tavlen -- **hånd-regnet og kryssjekket eksakt**: netto 1/4/3 for + de tre spillerne (course handicap 60/12/6, alle mottar 1 slag på + hull 1 unntatt eieren som mottar 4) ga korrekt "1 skin" til laveste + netto. + - **Foursome:** bekreftet "Opprett begge sidene..."-meldingen i Score- + fanen FØR sidene fantes (ingen krasj), opprettet to sider, la til tre + gjester, scoret hull 1 via `SideScorecardGrid`/`SideScoreWizard` + (5 mot 5 -- observerte LIVE at gridet oppdaterte seg bak selve + veiviseren), fant OG fikset de to stale-state-bugene over midt i + denne runden (glemte først å tildele spillerne til sider -- avdekket + nettopp fordi UI-et da IKKE viste feil tilstand, men en ekte utdatert + en), bekreftet til slutt `playing_handicap` kombinert riktig per side + (38/38 og 17/17) og at `FormatResultPanel` viste "1 UP (Rødt lag)" + -- kryssjekket for hånd at Rødt lag (høyere kombinert CH) mottar + slag på det vanskeligste hullet og derfor vinner nettoduellen 5 mot 5. + Ekte typesjekket + full produksjonsbuild kjørt på nytt ETTER + bug-fiksene (ikke bare før), alle 24 ruter listet. + **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen + migrasjon (ren frontend), `docker compose up -d --build + teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning, + ingen backend-kode rørt). Begge containere boot-et rent, `/health`/ + `/dashboard` → 200, `teeoff.no` upåvirket. **ADR-039 er dermed + fullstendig ferdig, backend og frontend, live.** +- **Auto-hopp i scoringsveiviserne, LIVE (2026-07-28), samme dag:** + brukeren ba om at "i det øyeblikket [scoren] nå registreres" skal + veiviseren hoppe videre av seg selv, uten å måtte trykke "Neste"/ + "Ferdig". Presiserende avklaring FØR bygging (AskUserQuestion): "Avstand + første putt" lå tidligere PÅ SAMME steg som selve putt-tallet i + `ScoringWizard` -- auto-hopp idet putt-tallet velges ville gjort + avstandsfeltet uoppnåelig (ingen annen inngang finnes). Løst ved å + splitte putt-steget i to (bekreftet anbefalt løsning): `WizardStep` + utvidet med et eget `"puttDistance"`-steg, `wizardStepsFor()` gir nå + `["strokes","putts","puttDistance"]`/`["strokes","putts","puttDistance", + "details"]` for de to høyere statistikknivåene. + **Mekanisme (samme mønster i `ScoringWizard` og den enklere + `SideScoreWizard` for delt-ball-formater):** to refs -- `enteredWithValueRef` + fanger om steget sitt eget felt ALLEREDE hadde en verdi idet steget ble + vist (et allerede utfylt hull skal ikke hoppe videre bare fordi + veiviseren åpnes, og "Forrige" tilbake til et allerede besvart steg skal + ikke re-trigge et nytt hopp), `firedRef` hindrer dobbelt-triggering. + Kun steg med ETT entydig felt (Slag, Putter, Avstand, samt hele + `SideScoreWizard` sitt eneste Slag-felt) auto-hopper -- "flere + detaljer"-steget (kølle/retning/chip/bunker/straffeslag/anywayslag) har + ingen enkelt "dette er ferdig"-verdi og beholder derfor "Neste"/ + "Ferdig"-knappen som manuell handling, bevisst uendret. + **Verifisert grundig i en isolert scratch-nettleserøkt** (fersk + database/MinIO/API-container, ekte `next dev`, Chrome DevTools): + full "full"-nivå-runde spilt gjennom Slag→Putter→Avstand (alle tre + auto-hoppet uten et eneste "Neste"-trykk) →detaljer (korrekt IKKE + auto-hoppet, krevde et bevisst "Ferdig"-trykk, som deretter gikk videre + til neste hull av seg selv). "Forrige" fra Putter tilbake til Slag + bekreftet trygt (viste den allerede valgte verdien, hoppet IKKE + automatisk fremover igjen). Delt-ball (`SideScoreWizard`, foursome) + bekreftet separat: valgt slagtall for "Rødt" hoppet umiddelbart til + "Blått" uten trykk. Ekte typesjekket + full produksjonsbuild kjørt før + utrulling, alle 24 ruter listet. + **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen + migrasjon (ren frontend), `docker compose up -d --build + teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard` + → 200, `teeoff.no` upåvirket. +- **Scorekort for spillformater (match/skins/fourball/foursome/greensome/ + scramble): to reelle bugs bekreftet og fikset, BYGGET, SCRATCH-/ + BROWSERVERIFISERT OG LIVE (2026-07-28):** brukeren ba om at scorekortene + faktisk sjekkes ved å simulere 3-4 spilte hull i hvert spillformat, med + et konkret forventningsbilde (en match bør vise hvem som vant hvilket + hull og HVORFOR -- brutto vs. netto, match-hcp). Simulert systematisk mot + en isolert scratch-backend (Python/urllib-testskript) FØR noe ble antatt + riktig -- fant to distinkte, bekreftede problemer, presentert til + brukeren som fikk velge omfang (AskUserQuestion) og valgte full løsning: + 1. **Ekte blokkerende bug:** foursome/greensome/scramble (ADR-039, + delt-ball -- score lagres PER SIDE, `round_hole.round_participant_id` + settes ALDRI for disse) viste 0 spilte hull/ingen score på + `/my-rounds/[id]/scorecard`, `/leaderboard` OG `/stats`, uansett + faktisk fremdrift -- disse tre sidene spurte alle kun mot + deltaker-endepunktet (`round_participant_id`), som strukturelt aldri + kan ha data for disse formatene. + 2. **Reell designmangel:** for match/fourball/skins var rå brutto/netto/ + stableford riktig, men "hvem vant hvilket hull, og hvorfor" fantes + KUN i `FormatResultPanel` (manage-fanen i `round-detail.tsx`) som et + rent vinn/tap-merke -- ingen synlige tall (brutto vs. netto, slag + mottatt) noe sted, og ingenting av dette på de dedikerte Scorekort-/ + Leaderboard-sidene brukeren faktisk testet. + **Backend:** ny `compute_skins_detail()` i `handicap_engine.py` + (hull-for-hull-forløp -- verdier/pott-før/tildelt/carried per hull), + `compute_skins()` omskrevet til en tynn wrapper rundt den (uendret + signatur/oppførsel, alle 50 eksisterende tester fortsatt grønne + 5 nye). + `GET /rounds/{id}/format-result` (ADR-039) utvidet med et nytt + `holes`-felt -- full hull-for-hull-oppløsning (brutto/netto/slag mottatt + PER enhet PER hull, pluss for fourball hvilken av de to partnernes netto + som faktisk talte for siden det hullet, R&A-regelen gjort synlig i + stedet for skjult). **Reell refactor-bug funnet OG fikset UNDER egen + scratch-verifisering, før noe ble stolt på:** en samlet `side_net()` for + BEGGE gren-typene (individuell-ball og delt-ball) brukte format-nivåets + `expected_players` (spiller-ANTALL, f.eks. 2 for foursome) som + fullstendighetssjekk også for delt-ball, der en side alltid er NØYAKTIG + ÉN enhet uansett spillerantall -- ga `match_holes_played=0`/tom + `holes`-liste for ALLE delt-ball-formater til tross for korrekt lagrede + side-scorer. Rettet med en egen `required_units`-variabel (1 for + delt-ball, `expected_players` for individuell-ball). `GET .../sides/ + {id}/holes` fikk samtidig et nytt `strokes_received`-felt (samme + allokeringsalgoritme, nå basert på sidens kombinerte `playing_handicap`). + **Frontend:** `round-scorecard.tsx` bruker nå SIDER (ikke deltakere) som + "enhet" for delt-ball-formater -- samme visuelle `ScoreBlock`-tabell, + bare mot `/sides/{id}/holes`, med en forklarende melding hvis sidene + ikke er opprettet ennå. Ny `MatchProgressTable`-seksjon (to-sidede + formater: Hull/Par/Side A/Side B/Resultat, brutto→netto per enhet, ikke- + tellende fourball-partner tonet ned i stedet for fjernet) og + `SkinsProgressTable` (skins: hull-for-hull med hvem som vant/hvilket + hull som rullet videre, netto med brutto i parentes). `round- + leaderboard.tsx`: to-sidede formater viser nå en `MatchStatusSection` + (status + hull-merker + lenke til scorekortets fulle oppløsning) i + stedet for en individuell rangering som uansett ikke gir mening for et + 1v1/lag-format (og alltid var tom for delt-ball) -- `RoundLeaderboardMini` + returnerer `null` for disse formatene i `round-detail.tsx` sin manage- + fane, siden `FormatResultPanel` allerede dekker akkurat det der. `round- + stats.tsx` viser en tydelig forklarende melding for delt-ball-formater + (individuell slag-for-slag-statistikk er strukturelt umulig der) i + stedet for en stille tom/misvisende side. + **Scratch-verifisert grundig, flere lag:** isolert `teecup_scratch`- + database + `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs + API-container (samme mønster som hele prosjektet), 55/55 + `handicap_engine`-tester, et Python/urllib-simuleringsskript som spilte + 4 hull i alle 6 ikke-trivielle formater og sammenlignet rå API-svar før/ + etter fiksen. **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome + DevTools MCP, engangs `next dev` mot scratch-backend, ekte innlogging): + opprettet alle 7 formatene (inkl. `stroke` som regresjonssjekk) via + ekte API-kall fra en innlogget nettleserøkt, besøkte deretter + scorekort/leaderboard/stats-sidene for hver -- foursome sitt tidligere + BLANKE scorekort viste nå korrekte side-tabs + reelle score + riktig + `Matchforløp`-tabell; fourball sin `Matchforløp` viste presist BEGGE + partnernes brutto/netto med den ikke-tellende partneren korrekt tonet + ned; skins sin hull-for-hull-tabell viste riktig vinner-navn og + "Uavgjort — rullet videre" nøyaktig der forventet; leaderboardets + matchstatus stemte hull-for-hull med scorekortets egen utregning (bevisst + kryssjekket for hånd); fanebytte mellom sider (Side A/Side B) bekreftet + å faktisk refetche og re-rendre riktig data. Ekte typesjekket + + produksjonsbuild (alle 24 ruter) kjørt både før og etter refactor-bug- + fiksen. `test_isolation.sql` uendret (ingen migrasjon, ren kode-endring). + **Rullet ut live 2026-07-28**, 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. +- **Offline-kø (ADR-028) utvidet til frittstående runder, BYGGET, + BROWSERVERIFISERT OG LIVE (2026-07-28):** brukeren ba om det som ble + identifisert som viktigste gjenstående hull etter forrige runde -- + ADR-028s offline-skrivekø var kun koblet til turnering-scorekortet + (`session-scorecard.tsx`), IKKE frittstående runder + (`round-detail.tsx`), nettopp der man oftest står alene ute på banen + uten dekning, og nå som frittstående runder er bekreftet appens + hovedfokus var dette et reelt hull i kjerneflyten. + **Enklere port enn originalen, ikke bare en kopi:** turnering- + scorekortets PATCH-endepunkter er en append-historie (krever egne + `pendingStrokes`/`pendingResults`-verdi-overlays for å vise queued + verdier). Frittstående runders `PATCH .../participants/{id}/holes/{n}` + og `PATCH .../sides/{id}/holes/{n}` erstatter derimot HELE hull-raden + per kall, og `statToPatchBody()` sine feltnavn matcher `ApiHole` 1:1 -- + en køet skriving kan dermed speiles direkte inn i + `holesByParticipant`/`holesBySide` med en enkel `{...h, ...body}`-spread, + ingen egen verdi-overlay nødvendig. Kun to lette `Set<string>` (nøkkel + `id:hullnummer`) beholdt, utelukkende til selve "lagret lokalt"- + indikatoren i veiviseren. + Samme kø/synk-mønster som originalen ellers: `navigator.onLine`-sjekk + FØR forsøk, `try/catch` rundt selve fetch-kallet som queuer ved en EKTE + nettverksfeil (ikke ved et avvist HTTP-svar -- det vises fortsatt som + vanlig feiltekst), auto-synk ved `window`s `online`-event, manuell + "Synkroniser nå"-knapp, og en engangs-sjekk for allerede køede + skrivinger fra en TIDLIGERE økt (f.eks. siden ble lukket mens offline) + ved mount. `flushPending()` matcher URL-mønsteret på hver synkronisert + kø-oppføring (`/participants/{id}/holes/{n}` vs. `/sides/{id}/holes/ + {n}`) for å vite hvilke deltakere/sider som trenger en ekte refetch + etterpå (reconciles bl.a. `strokes_received`, som den optimistiske + speilingen ikke kan regne ut selv). Banner ("Du er offline"/"N + endringer venter") lagt til rett under fane-velgeren, synlig uansett + fane. "Lagret lokalt · venter på synk"-indikator lagt til i BÅDE + `ScoringWizard` (vanlig scoring) og `SideScoreWizard` (delt-ball). + **Browserverifisert grundig, IKKE bare kodegjennomgang/build denne + gangen** (i motsetning til den opprinnelige ADR-028-runden, som + brukeren selv måtte teste manuelt siden intet nettleserverktøy var + tilgjengelig da) -- Chrome DevTools MCP sin ekte nettverks-emulering + (`Offline`, bekreftet at `navigator.onLine` faktisk flippet til + `false`) mot en isolert scratch-backend: registrerte slag+putter + offline for en vanlig runde -- bekreftet 2 kø-oppføringer i ekte + IndexedDB, optimistisk oppdatert scorekort-grid, "1 endring venter"- + banner, "Lagret lokalt"-indikator i veiviseren; koblet til nett igjen + -- bekreftet AUTOMATISK synk (ingen manuelt trykk), kø tom etterpå, OG + et direkte API-kall som bekreftet serveren faktisk hadde de riktige + verdiene (score=5, putts=1, strokes_received=1). Gjentok hele syklusen + for en ny foursome-runde via `SideScoreWizard` (delt-ball) -- samme + resultat, kø tom og server bekreftet score=4 for siden etterpå. Ingen + konsollfeil i noen av rundene. Ekte typesjekket + full produksjonsbuild + (alle 24 ruter) kjørt før utrulling. + **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen + migrasjon (ren frontend), `docker compose up -d --build + teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard` + → 200, `teeoff.no` upåvirket. +- **Turnering-scorekortets offline-flyt (den OPPRINNELIGE ADR-028, session- + scorecard.tsx) FAKTISK browserverifisert, samme dag (2026-07-28):** + eneste reelt gjenstående punkt fra forrige runde — offline-koden i + turnering-scorekortet er over ett år gammel (funksjonelt), men var ALDRI + browser-testet, kun kodegjennomgang/build (se opprinnelig ADR-028-notat). + Bygget en full isolert scratch-turnering fra bunnen via API for å nå + frem til selve scorekortet (organisasjon → turnering → to lag → to + spillere rostret som kapteiner → egendefinert bane med 18 hull + tee → + økt (`format=singles`, `scoring_mode=stroke`) → match → to + match-deltakere → begge lag låst) -- ingen slik full turnering-scaffold + fantes fra før i noe testskript denne uken (frittstående runder trenger + ikke dette apparatet i det hele tatt, ADR-033 Beslutning A), måtte bygges + fra grunnen ved å lese `tournaments.py`/`matches.py`/`courses.py` sine + faktiske endepunkt-kontrakter direkte. + **Verifisert identisk mønster som frittstående runder samme dag:** ekte + DevTools-nettverksemulering (`navigator.onLine` bekreftet `false`), + registrerte et slag (5, Spiller B, hull 1) offline -- bekreftet ÉN + kø-oppføring i ekte IndexedDB (`POST .../hole-scores`), "1 endring + venter"-banner, "Lagret lokalt · venter på synk"-tekst under + tallvelgeren, hull-navigasjonens sjekkmerke. Koblet til nett igjen -- + bekreftet AUTOMATISK synk (ingen manuelt trykk på "Synkroniser nå" + nødvendig), kø tom etterpå, "Bogey"-etiketten dukket opp (bevis på at + `refetchScorecard()` faktisk hentet det avledede resultatet fra + serveren), OG et direkte API-kall mot `.../scorecard` som bekreftet + serveren faktisk hadde `gross_strokes=5` lagret. Ingen konsollfeil. + **Ingen kodeendring i denne runden** — ren verifisering av allerede + levert funksjonalitet, ingen utrulling nødvendig. +- **Undersøkt: grensesnitt for å følge en venns runde live — BEKREFTET AT + DET IKKE FINNES (2026-07-28), ren undersøkelse, ingen kode skrevet.** + Brukeren spurte om en spiller kan gå inn på en venns profil og følge en + pågående frittstående runde live, gitt at rettighetene er gitt. Lest + direkte i koden (ikke antatt): (1) `frontend/components/friends.tsx` har + INGEN lenke/rute til en vennprofil-side i det hele tatt — kun + send/aksepter/avvis-knapper og kategorisering, ingen `/friends/[id]`- + eller lignende rute finnes noe sted i `frontend/app/`. (2) `round`- + tabellen har INGEN `visibility`-kolonne (bekreftet med grep over ALLE + migrasjoner — `visibility` finnes kun på `tournament`, fra + `009_landing_pages_and_visibility.sql`); `020_personal_rounds.sql` sin + egen kommentar (linje 39-42) slår eksplisitt fast v1-avgrensningen: en + runde er kun synlig for `owner_user_id`. (3) `app/routers/rounds.py` sin + `_get_accessible_round_or_404` (linje 1220-1240, lest direkte) gir + tilgang KUN til eieren ELLER en lenket `round_participant.user_id` + (ADR-036 fase 3) — ingen sjekk mot `friendship`-tabellen noe sted, + bekreftet med `grep -n -i "friend" app/routers/rounds.py` → null treff. + (4) `GET /ws/rounds/{id}/live` bruker NØYAKTIG samme + `_get_accessible_round_or_404`-sjekk FØR `websocket.accept()` (samme + fil, linje ~2763) — en venn som ikke er lenket deltaker får + websocketen lukket med kode 4403, ulikt turnering-live (ADR-027), som + bevisst TILLATER anonym tilgang. Ingen forberedt/påbegynt kode for + "følg venns runde" funnet noe sted (grep etter "follow"/"følg"/"friend" + i både `rounds.py` og `friends.py` — null relevante treff). + **Konklusjon: dette er nøyaktig ADR-036 fase 2 (rundevisibilitet + public/private/friends), som lenge har stått notert som IKKE bygget i + FEATURE_BACKLOG.md/CLAUDE.md** — bekreftet nå med presis kode-evidens i + stedet for bare et notat om at det mangler. Ingen ny beslutning tatt, + ingen kode skrevet — dette var en ren undersøkelse på brukerens + eksplisitte forespørsel. Se FEATURE_BACKLOG.md for ADR-036 fase 2 sitt + design (public/private/friends, eksplisitt gruppevalg). +- **ADR-036 fase 2 (rundevisibilitet) BYGGET, GRUNDIG SCRATCH-/ + BROWSERVERIFISERT OG LIVE (2026-07-28), samme dag:** direkte oppfølging + av undersøkelsen over — brukeren bekreftet eksplisitt retningen fra + Beslutning B (allerede fullt designet i ADR-036, ikke funnet opp på + nytt): tre nivåer (`public`/`private`/`friends`), der `friends` krever + et EKSPLISITT kategori-valg (ikke "alle venner"). + **Migrasjon `032_round_visibility.sql`:** `round.visibility_mode` + (`public`/`private`/`friends`, default `private`), ny tabell + `round_visible_category` (samme faste kategori-sett som + `friend_categorization`, migrasjon 025, bevisst duplisert CHECK fremfor + delt ENUM — samme pragmatiske mønster som resten av skjemaet). + **Kjernestykket, `app/routers/rounds.py`:** ny `_can_view_round()` — + eier ELLER lenket medspiller ser alltid; ellers `public`→alle (også + anonyme), `private`→ingen, `friends`→krever et AKSEPTERT vennskap MED + eieren OG at EIERENS kategorisering av viewer (retningen er bevisst + omvendt av hva man skulle tro — det er eieren som begrenser, basert på + egen gruppering) treffer minst én av rundens synlige kategorier. + **Reelt funn under selve designarbeidet:** `_can_view_round()` viste + seg å være en STRIKT SUPERSETT av den eksisterende + `_get_accessible_round_or_404()` sin logikk (dens to første grener ER + nøyaktig eier+medspiller-sjekken) — i stedet for å bygge en helt + parallell endepunkt-familie fra bunnen, ble fire eksisterende + autentiserte lese-endepunkters KROPP ekstrahert til delte + hjelpefunksjoner (`_build_participant_holes`/`_build_side_holes`/ + `_build_leaderboard`/`_build_format_result` — mekanisk gjort med et + Python-script for presis inndenting fremfor manuell redigering av + ~270+95 linjer, verifisert med `ast.parse` + full regresjonskjøring + etterpå), gjenbrukt av BÅDE de originale autentiserte endepunktene OG + syv nye `/public/rounds/*`-endepunkter (samme `get_current_user_ + optional`-mønster som turnering sin offentlige side, ADR-018/026/027) + pluss et nytt offentlig WS-endepunkt `/ws/public/rounds/{id}/live` + (ulikt det eksisterende PRIVATE `/ws/rounds/{id}/live`, som fortsatt + krever ekte autentisert eier/medspiller-sesjon, uendret). Egen, leaner + `PublicRoundOut` (aldri `guest_email`, som er PII, aldri `my_*`/ + `setup_*`, som kun gir mening for eier/deltaker) i stedet for å + gjenbruke den fulle `RoundOut` med etterhånds-redigering. + Ny `GET /people/{id}` (friends.py, samme lavsensitive felt-sett som + `/people/search`, bevisst OPTIONALT autentisert — en offentlig runde + må kunne nås via en delt lenke selv av en anonym leser, og + vennprofil-siden som leder dit må da fungere anonymt også) og + `GET /public/people/{id}/rounds` (rounds.py, lister eierens runder + filtrert gjennom `_can_view_round()` per rad — "pågår nå" alltid øverst). + **Scratch-verifisert grundig, 191 automatiserte sjekker i tre testløp** + (isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs + API-container, samme mønster som hele prosjektet): et NYTT 34-punkts + skript som dekket hele synlighetsmatrisen presist (tre brukere A/B/C + + en helt anonym opener) — B/C/anonym nektet privat OG venner-runde FØR + vennskap, alle ser offentlig (inkl. anonym), venner-runde fortsatt + nektet RETT ETTER vennskap men FØR kategorisering, tilgjengelig + UMIDDELBART etter riktig kategorisering, feil kategori (close_family + mot golf_friends) fortsatt nektet, PATCH kan endre synlighet i + etterkant (bekreftet begge retninger), offentlig+autentisert + leaderboard ga BEVIST IDENTISK resultat (kryssjekket), ukjent + runde-id ga 404 (ikke 403). PLUSS en full regresjonskjøring av to + eksisterende testsuiter fra tidligere runder denne uken (35-punkts + co-player-flyt, 122-punkts spillformat-flyt) — begge 100 % grønne, + bekrefter at ekstraheringen av de fire delte funksjonene ikke endret + noen eksisterende oppførsel. + **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP, + isolert browser-kontekst for en helt anonym tredje "bruker" ved siden + av to ekte innloggede faner): (1) synlighetsvelgeren i opprett-runde- + veiviseren fungerte som designet (tre knapper, kategori-multiselect + dukker kun opp ved "Venner", advarselstekst ved 0 valgte kategorier) — + runde opprettet med `friends`+`golf_friends`, bekreftet via API; + (2) rediger-runde-panelet viste korrekt FORHÅNDSUTFYLT synlighet, + PATCH til `public` bekreftet lagret; (3) en HELT ANONYM + nettleserkontekst (ingen cookies i det hele tatt) kunne se den + offentlige runden direkte på `/watch/{id}` (live-puls, leaderboard, + riktig eiernavn) OG via `/my-friends/{eier-id}`-profilsiden (navn/ + avatar/HCP/hjemmeklubb + rundeliste med lenke inn); (4) en ekte andre + bruker sendte venneforespørsel, eieren aksepterte og kategoriserte + vedkommende som "Golfvenner" VIA DEN FAKTISKE UI-EN (ikke bare API) — + satte deretter runden til `friends`+`golf_friends`, bekreftet vennen + fikk tilgang UMIDDELBART via profilsiden; (5) **negativ kontroll, + samme venn**: byttet runden til `friends`+`close_family` (en kategori + vennen IKKE var satt i) — profilsiden viste korrekt INGEN runder + lenger, og et direkte `/watch/{id}`-forsøk ga en tydelig "Du har ikke + tilgang"-melding (403), atskilt fra en egen "Denne runden finnes + ikke"-melding for en ukjent id (404) — begge bekreftet med ekte + skjermbilder. Ekte typesjekket + full produksjonsbuild (26 ruter, inkl. + de to nye `/my-friends/[id]` og `/watch/[id]`) kjørt før utrulling. + **Rullet ut mot ekte systemer 2026-07-28**, bruker bekreftet + eksplisitt (viste frem full plan for migrasjon FØR den kjørte, per + CLAUDE.md sin ufravikelige regel): migrasjon 032 kjørt mot ekte + `teecup_db` (kun additivt — ny kolonne med default, ny tabell, ingen + eksisterende rader rørt), `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. Verifisert presist at de nye rutene faktisk når FastAPI + (ikke bare at Next.js svarte): et ukjent person-id mot + `/public/people/{id}/rounds` over ekte https ga korrekt JSON-formet + `404 NOT_FOUND` (ikke en rå Next.js-404-side). + **ADR-036 er dermed HELT ferdig, alle tre faser** (venner-kjernen, + rundevisibilitet, ekte medspillere) — backend + frontend, live. + +- **Rundevarsler koblet til det eksisterende varslingssenteret, BYGGET, + SCRATCH-VERIFISERT OG LIVE (2026-07-28), samme dag:** direkte oppfølging + av ADR-036 fase 2 — varslingssenteret (2026-07-26) hadde fra start + reservert `type` for `"round"`/`"result"` (skjema OG frontendens + `KIND_ICON`/`KIND_LABEL`), men INGENTING skrev noensinne en slik rad — + kun venneforespørsler trigget et varsel. Bruker bekreftet omfang + eksplisitt (AskUserQuestion, alle tre valgt): (A) lagt til som ekte + medspiller på en runde, (B) en venn starter en runde du kan se + (offentlig, ELLER `friends`-synlig med treffende kategori), (C) en + runde du er koblet til (eier/medspiller/tredjeparts-venn fra B) blir + fullført. + **Ren backend-endring, INGEN frontend-endring nødvendig** — bekreftet + ved lesing av `notifications.tsx` FØR noe ble bygget: `NotificationKind`/ + `KIND_ICON`/`KIND_LABEL` dekker allerede `"round"`/`"result"` fullt ut, + siden de ble reservert med akkurat dette for øye i den opprinnelige + runden. + **Bygget i `app/routers/rounds.py`:** ny delt `_friends_who_can_see_ + round(conn, round_id, owner_user_id)` — GJENBEREGNER (ikke en lagret + mottakerliste) nøyaktig samme regel som `_can_view_round` (offentlig = + alle aksepterte venner, `friends` = kun de hvis EGEN kategorisering av + venn treffer rundens synlige kategorier), brukt BÅDE ved opprettelse og + ved fullføring — aldri ute av synk med selve tilgangskontrollen. + `create_round` sender (B) rett etter transaksjonen (kun for + `public`/`friends`, aldri `private`), lenke til `/watch/{id}`. + `add_participant` sender (A) inni transaksjonen når `user_id` er satt + (aldri for gjester — de har ingen konto å varsle), lenke til + `/my-rounds/{id}` (full tilgang, ikke tredjeparts-visningen). + `complete_round` sender (C) til to ATSKILTE mottakergrupper med ulik + lenke, siden de har ulik tilgang: lenkede medspillere (unntatt den som + selv fullførte) → `/my-rounds/{id}`; tredjeparts-venner fra (B), + gjenberegnet på nytt → `/watch/{id}` — ingen dobbel-varsling hvis noen + skulle være i begge grupper (settoperasjon), og aldri et varsel til + personen som selv utførte handlingen. + **Scratch-verifisert grundig, 28/28 nye sjekker** (isolert + `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API- + container, samme mønster som hele prosjektet, alle 32 migrasjoner kjørt + friskt): venn fikk `round`-varsel ved synlig opprettelse, fremmed fikk + det IKKE, privat runde ga INGEN varsel til noen, medspiller fikk + `round`-varsel med riktig `/my-rounds/`-lenke ved tilføyelse, fullføring + ga `result`-varsel til BÅDE medspiller (`/my-rounds/`) og venn + (`/watch/`) men ALDRI til den som selv fullførte, en privat solorunde + sin fullføring ga fortsatt ingen varsler, mark-as-read uendret. PLUSS + full regresjon av tre eksisterende testsuiter (co-player-flyt 35/35, + spillformat-flyt 122/122, ADR-036 fase 2-synlighetsmatrise 34/34) — alle + fortsatt 100 % grønne, ingen utilsiktet bivirkning av de nye + `create_notification()`-kallene inni eksisterende transaksjoner. + `test_isolation.sql` 12/12 uendret (ingen migrasjon, ren Python-logikk). + **Rullet ut live 2026-07-28**, ingen migrasjon, kun + `docker compose up -d --build teecup_api`. Ren boot + (`Application startup complete`), `/health`/`/dashboard` → 200, + `teeoff.no` upåvirket. + +- **E-post-fallback for varsler, per type, BYGGET OG SCRATCH-VERIFISERT + (2026-07-28), samme dag, rett etter rundevarsel-runden:** brukeren ba + eksplisitt om at mottakeren selv skal kunne velge HVILKE varseltyper som + skal utløse e-post — ikke en enkelt global av/på-bryter. Ny migrasjon + `033_notification_email_prefs.sql`: `user_notification_email_pref` + (`user_id`, `type`, samme CHECK-sett som `notification.type`) — samme + "tilstedeværelse = valgt"-mønster som `round_visible_category`/ + `friend_categorization`, TRYGG STANDARD ingen rad = ingen e-post for + noen type (samme "se ingenting til noen har valgt"-filosofi som resten + av appen). + **Bevisst ÉN felles innsnevring, ikke ett kallsted per varseltrigger:** + e-post-utsendingen ligger INNI `create_notification()` selv (etter selve + INSERT-en) — modul-docstringen kalte den allerede "den eneste + skrivevegen inn", så alle nåværende OG fremtidige varsel-triggere (i dag + 2 i friends.py, 4 i rounds.py) får e-post-støtte helt uten å røres, + ingen risiko for at et fremtidig kallsted glemmer det. Samme + `SMTP_CONFIGURED`-sjekk + try/except + `traceback.print_exc()`-mønster + som all annen e-postutsending i appen (routers/auth.py, + organizations.py) — en driftsfeil i selve SMTP-en skal ALDRI hindre at + in-app-varselet (allerede skrevet FØR e-post-forsøket) består. + Ny `send_notification_email()` i `app/email.py` — bevisst tospråklig KUN + i ramme-teksten (emne/hilsen/lenkeforklaring); selve `message`-teksten + er allerede en ferdig norsk snapshot-tekst (samme prinsipp som + in-app-varselet), ingen full i18n av selve varselinnholdet i denne + runden. + Nye `GET`/`PUT /notifications/email-prefs` — PUT er en FULL erstatning + (samme kontrakt som `PUT /friends/{id}/categories`), ikke en delvis + PATCH. + **Liten, men reell ryddejobb funnet FØR e-post-laget ble lagt på:** + forrige rundes `add_participant`-varsel (medspiller lagt til) lå INNI + den åpne DB-transaksjonen for selve deltaker-innsettingen — flyttet til + RETT ETTER (samme mønster som `create_round`/`complete_round` allerede + fulgte), slik at en fremtidig blokkerende SMTP-utsending aldri skjer + mens en transaksjon holder låser. + **Frontend:** ny seksjon "Varsler på e-post" i `/account` + (`NotificationEmailPrefsSection`, `account-settings.tsx`) — fire + avkrysningsbokser (Venneforespørsler/Runder/Resultater/Turneringer, sistnevnte + reservert for fremtidig bruk), samme avkrysningsboks-mønster som + kølle-bag-listen lenger opp i samme fil, lagrer umiddelbart ved hvert + klikk (ingen egen "lagre"-knapp). + **Scratch-verifisert grundig, 19/19 nye sjekker** (isolert + `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API- + container, alle 33 migrasjoner kjørt friskt): trygg standard bekreftet + (ingen typer valgt fra start), full-erstatning bekreftet (PUT med ett + sett fjerner det forrige, legger ikke til), e-post-fallback FAKTISK + logget (dev-log-varianten av `SMTP_CONFIGURED`-grenen) for en bruker med + `round`/`result` valgt inn — BÅDE ved medspiller-tilføyelse og ved + fullføring — INGEN e-post til brukeren som selv utførte handlingen, + INGEN e-post til en bruker (eieren) som ikke har valgt inn NOE, og etter + å ha slått AV `result` igjen: ingen ny e-post ved neste fullføring MENS + in-app-varselet fortsatt opprettes helt uendret (de to er reelt + atskilte, ikke koblet). PLUSS full regresjon av fire eksisterende + testsuiter (rundevarsler 28/28, co-player-flyt 35/35, spillformat-flyt + 122/122, ADR-036 fase 2-synlighetsmatrise 34/34) — alle fortsatt 100 % + grønne, ingen bivirkning av at `add_participant`s varsel flyttet utenfor + transaksjonen. `test_isolation.sql` 12/12 uendret. Ekte typesjekket + produksjonsbuild (samme `Dockerfile` som deployes) kompilerte rent, + alle 24 ruter listet uendret. + +- **Tre organisator-oppfølgingspunkter fra FEATURE_BACKLOG.md, ALLE + BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE (2026-07-28), samme dag:** + brukeren ba om at alle tre gjenstående, godt avgrensede oppfølgings- + punkter fra en tidligere gjennomgang av `.md`-filene tas i én runde, + inkludert oppdatering av de relevante backlog-/beslutningsfilene. + 1. **Flytte spiller mellom lag:** ny + `POST /orgs/{id}/teams/{team_id}/roster/{roster_id}/move` + (`{"target_team_id": ...}`, `app/routers/tournaments.py`) — + atomisk `UPDATE team_roster SET team_id = ...`, avviser 409 + `ALREADY_IN_MATCH` hvis spilleren allerede er lagt til i en match + (roster-raden er referert av `match_participant.team_roster_id` + med `ON DELETE RESTRICT` -- en flytting ville da gjort matchens + `team_side` inkonsistent med spillerens faktiske lag), 400 ved + flytting til samme lag, 404 ved ukjent mållag/roster-id. + Kapteinmerket nullstilles eksplisitt ved flytting (følger ikke med + til det nye laget). Frontend: ny "Flytt til {annet lag}"-handling i + `TeamPanel` sin per-spiller-meny (`tournament-detail.tsx`) — v1s + to-lags-grense (ADR-011) gjør målet entydig, ingen dropdown + nødvendig. Ingen migrasjon. + 2. **Individuell rangering PER ØKT i en org-turnering:** ny + `GET /orgs/{id}/sessions/{id}/individual-leaderboard` — bevisst + AVGRENSET til én økt (ikke summert på tvers av turneringen, samme + avklaring som rundeleaderboardets omfang tidligere), og KUN + meningsfull for `scoring_mode='stroke'`-økter i individuell-ball- + format (singles/fourball; delt-ball-formater og hole_result-modus + avvist med 400, ikke krasj, siden de ikke har noen individuell + brutto-score å rangere fra). Gjenbruker `mp.playing_handicap` + (allerede beregnet, allowance-justert) + `allocate_over_played_ + holes` (samme mønster som eksisterende match-play-scoring i + `scoring.py`, som fikk sin `_played_hole_numbers` omdøpt til + `played_hole_numbers` -- "fjern understrek når et andre bruksted + dukker opp"-mønsteret, samme som `SIDE_IS_UNIT` tidligere) — ingen + ny regnelogikk. Respekterer samme reveal-gating (`locked_team_ids`/ + `own_team_ids`, ADR-013/026) som den eksisterende matchlisten. + Frontend: ny side `/tournaments/[id]/sessions/[sessionId]/ + individual-leaderboard` (`session-individual-leaderboard.tsx`, + egen enklere lokal variant av `round-leaderboard.tsx` sitt + rangerings-/mode-toggle-mønster -- brutto/netto/poeng, delt + plassering "T-N"), lenket fra blind draw-skjermen for + kvalifiserende økter. Ingen migrasjon. + 3. **Midlertidige spillere + automatisk etter-runde-invitasjon + (økt-nivå):** de tre tidligere åpne spørsmålene avklart eksplisitt + (AskUserQuestion) -- nivå ØKT (ikke turnering), dobbel-utsending- + sperre JA, locale bevisst alltid `nb`. Ny migrasjon + `034_session_scorecard_invitations.sql` + (`match_participant.invitation_sent_at`, samme "tidsstempel = + skjedd"-mønster som `round.started_at` m.fl.). Ny + `POST /orgs/{id}/sessions/{id}/send-scorecard-invitations` — + sender KUN til spillere uten konto (`player.user_id IS NULL`) OG + med registrert e-post OG uten en tidligere sendt invitasjon for + akkurat denne (økt, deltaker)-kombinasjonen; responsen skiller + `sent`/`skipped_has_account`/`skipped_no_email`/ + `skipped_already_sent` for full gjennomsiktighet. Gjenbruker + SAMME magic-link-token-mekanisme som vanlig innlogging (ikke bare + en "logg inn senere"-henvisning) -- ny `send_session_result_email()` + i `app/email.py` (begge nb/en-maler klare, kun `nb` faktisk brukt). + E-postens innhold: matchresultat (`status_text`) + individuelt + slagtotal når tilgjengelig (individuell-ball-formater -- `SUM + gross_strokes` er naturlig `NULL` for delt-ball-formater uten noen + egen format-sjekk, siden `match_participant_id` aldri settes i + `hole_score` der). Frontend: ny "Send scorekort til alle med + e-post"-knapp i blind draw-skjermen (`session-blind-draw.tsx`), + synlig når økten har minst én match. + **Liten, men reell ryddejobb funnet FØR e-post-laget ble lagt på** + (delt med forrige rundes rundevarsel-mønster): dev-log-grenen for den + nye invitasjons-e-posten skrev først KUN en oppsummeringstekst, ikke + selve token-en -- umulig å teste innloggingslenken i et scratch-/dev- + miljø uten SMTP. Rettet til å skrive to linjer: én i SAMME format som + `request_magic_link` sin etablerte `[DEV] Magic link for ...`-linje + (så eksisterende dev-verktøy som harvester token derfra fungerer + uendret), pluss en egen lesbar oppsummeringslinje. + **Scratch-verifisert grundig, 54/54 nye sjekker** (isolert + `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API- + container, alle 34 migrasjoner kjørt friskt, full org-turnering- + scaffold bygget fra bunnen via API -- org/bane/18 hull/tee/turnering/ + to lag/seks spillere/roster/singles-stroke-økt/to matcher): flytting + happy-path + kaptein-nullstilling + alle tre feilveier (409/400/404) + bekreftet, individuell rangering bekreftet tom→fylt→korrekt brutto/ + netto/poeng for et hånd-utregnet 4-spiller-scenario (kryssjekket at + netto-til-par alltid ≤ brutto-til-par for alle fire), begge ikke- + kvalifiserende økt-typer (hole_result, foursome) avvist rent, + invitasjons-utsending bekreftet presist (1 sendt/2 manglet e-post/1 + hadde allerede konto via en EKTE innlogget "linket" bruker), andre + kall bekreftet idempotent (0 nye, riktig `skipped_already_sent`), OG + den utstedte lenken bekreftet FAKTISK brukbar (spilleren logget inn + med den, kontoen ble koblet til spiller-profilen, samme ADR-017- + mekanisme uendret). PLUSS regresjon av scoring.py-omdøpingen + (`test_holeless_course_crash.py`, 5/5) og to notifikasjons-/e-post- + testsuiter fra forrige runde (28/28, 19/19) — alle fortsatt grønne. + `test_isolation.sql` 12/12. Ekte typesjekket produksjonsbuild + kompilerte rent, ny rute `/tournaments/[id]/sessions/[sessionId]/ + individual-leaderboard` listet. + **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP, + isolert scratch-backend, ekte innlogging inkl. tvungen 2FA-oppsett for + en fersk organisasjonseier): flyttet en spiller mellom lag direkte i + UI-et og bekreftet begge lags roster-lister/-antall oppdatert + umiddelbart; åpnet individuell rangering og bekreftet alle tre + visningsmodiene (Brutto/Netto/Poeng) viste korrekte, ulike tall for de + samme to spillerne; trykket "Send scorekort til alle med e-post" og + bekreftet resultatteksten "1 invitasjon sendt, 1 mangler registrert + e-post" -- trykket samme knapp igjen og bekreftet "0 invitasjoner + sendt, 1 allerede sendt tidligere, …" (dobbel-sperren synlig direkte i + UI-et, ikke bare i et API-svar). Ingen konsollfeil i noen av de tre + rundene. + **Rullet ut mot ekte systemer 2026-07-28**, bruker bekreftet + eksplisitt: migrasjon 034 kjørt mot ekte `teecup_db` + (`invitation_sent_at`-kolonnen 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. + **Samtidig, ren dokumentasjonshygiene:** et par steder i + `FEATURE_BACKLOG.md`/`ARCHITECTURE_DECISIONS.md` hadde blitt hengende + etter `CLAUDE.md` (ADR-039-utrulling/frontend og PWA-offline- + browsertesting fremstod fortsatt som "ikke gjort" til tross for at + begge var fullført i tidligere økter denne uken) — rettet til å + stemme med den faktiske, allerede leverte tilstanden. + +- **Fire gjenstående forslag fra en tidligere "hva nå?"-runde, ALLE FIRE + BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-07-28, samme dag:** + brukeren ba eksplisitt om å ferdigstille alle fire samtidig + (PWA-installasjon, flere flighter i frittstående runder, scramble/ + greensome-statistikk, push-varsler til telefonens OS) — se + FEATURE_BACKLOG.md for full detalj per punkt, kort oppsummert her. + Tre nye migrasjoner (`035_round_flight_group.sql`, + `036_round_hole_selected_participant.sql`, `037_push_subscriptions.sql`), + alle rent additive. + 1. **PWA-installasjonsoppfordring** — `lib/pwa-install.ts` (globalt + fanget `beforeinstallprompt`, mountet via `sw-register.tsx` på alle + sider siden eventet kun fyres én gang per side-liv) + ny + `components/install-prompt.tsx`, vist på dashbordet rett under + hilsenen. Android/Chrome-familien får en ekte "Installer"-knapp, + iOS Safari et instruksjonsbanner (ingen programmatisk vei finnes + der), andre nettlesere uten reell installasjonsvei viser ingenting. + 2. **Flere flighter i én frittstående runde** (retning 1, løs + gruppering av separate `round`-rader bundet sammen av en klient- + generert delt UUID, `round.flight_group_id`) — ny + `GET /rounds/{id}/flight-group` + `GET .../flight-group/leaderboard` + (slår sammen hver tilgjengelig søsken-flights EGET leaderboard til + én rangert liste, `score_to_par` allerede normalisert og dermed + sammenlignbart på tvers av baner). Ny `FlightGroupPanel` i + `round-detail.tsx` ("+ Legg til en flight til" gjenbruker HELE + opprett-runde-flyten, forhåndsutfylt via URL-parametre — banen + velges på nytt per flight, bevisst), ny side + `/my-rounds/[id]/flights`. + 3. **Scramble/greensome: valgt utslag per spiller** — avgrenset til + frittstående runder (ADR-039 sitt delt-ball-format), IKKE + org-scopede turneringer i denne runden (bevisst scope-kutt, se + FEATURE_BACKLOG.md). Ny `round_hole.selected_participant_id`, + `PATCH .../sides/{id}/holes/{n}` fikk et nytt valgfritt felt (samme + "full overwrite hvert kall"-kontrakt som `played`/`score`). + `SideScoreWizard` fikk en ny valgfri "Hvem sitt utslag ble brukt?"- + seksjon (auto-hopp bevisst slått av her, ellers ville brukeren blitt + revet videre før valget kunne gjøres); `round-stats.tsx` fikk en ny + "Utslag brukt"-oppsummering per side. + 4. **Push-varsler til telefonens OS** (Web Push/VAPID) — ny + `push_subscription`-tabell, nytt `app/push.py` + (`send_push_to_user`, `pywebpush`), kalt fra `create_notification()` + for ALLE fire varseltyper. Bevisst INGEN egen per-type opt-in + (ulikt e-post-fallbacken) — å abonnere ER samtykket. VAPID-nøkler + valgfrie (`PUSH_CONFIGURED`), samme grasiøs-degraderings-mønster som + SMTP. `public/sw.js` fikk `push`/`notificationclick`-håndtering, ny + `lib/push-subscribe.ts` + seksjon i `/account` + (`PushNotificationSection`). + **VAPID-nøkkelpar generert lokalt** (Python `cryptography`, EC P-256, + rå base64url-kodet privat/offentlig nøkkel — samme format `pywebpush` + og nettleserens `applicationServerKey` forventer), ALDRI vist i + klartekst i chatten (skrevet direkte til en midlertidig fil med 600- + rettigheter, lest inn i ekte `.env` ved utrulling, slettet fra + scratchpad etterpå) — samme regel som alle andre hemmeligheter i + prosjektet. + **Scratch-verifisert grundig, 27/27 sjekker i ett delt testløp** + (isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs + API-container med `pywebpush` installert, alle 37 migrasjoner kjørt + friskt): flight-group happy-path + ugyldig UUID avvist + kryss-bruker- + isolasjon (403/404, ingen lekkasje) + slått-sammen leaderboard rangerer + korrekt på tvers av to flighter; scramble-valg PATCH lykkes + feil side + avvist (400) + eksplisitt null nullstiller; VAPID-nøkkel eksponert + + abonnement lagret + anonymt abonnement avvist (401) + en EKTE + `webpush()`-utsendelse forsøkt mot en syntaktisk ugyldig test-nøkkel + (`WebPushException` fanget og logget, in-app-varselet ble uansett + opprettet normalt — beviser feil-isolasjonen fungerer i praksis, ikke + bare i teorien). `test_isolation.sql` 12/12 uendret. + **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP, + isolert scratch-backend, ekte innlogging): PWA-knappen rendret og var + klikkbar (Chrome fyrte faktisk `beforeinstallprompt` i testøkten), ingen + konsollfeil; hele "+ Legg til en flight til"-flyten klikket gjennom FRA + KNAPPETRYKK TIL FERDIG RUNDE (riktig forhåndsutfylt skjema, korrekt + `flightGroupId` i URL-en, DIREKTE DATABASE-bekreftelse at begge rundene + delte samme gruppe-id etterpå), kombinert leaderboard-siden bekreftet + visuelt med korrekt rangering; scramble-valget satt i veiviseren, + bekreftet DIREKTE I DATABASEN, OG "Utslag brukt"-oppsummeringen + bekreftet visuelt på statistikksiden; push-UI-et rendret korrekt og + håndterte avslått tillatelse med en forklarende tekst uten konsollfeil + (denne automatiserte nettleserøkten hadde `Notification.permission` + forhåndssatt til "denied" av selve miljøet — en ekte innvilget + tillatelse → ekte levert OS-varsel er derfor IKKE bevist, ærlig + begrensning, bruker bør selv teste dette på en ekte enhet før full + tillit). Ekte typesjekket produksjonsbuild (`docker build --target + builder`) kompilerte rent, alle 27 ruter listet inkl. den nye + `/my-rounds/[id]/flights`. + **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt (plan vist + FØR kjøring, per CLAUDE.md sin ufravikelige regel): migrasjon 035-037 + kjørt mot ekte `teecup_db` (alle tre bekreftet med direkte spørring, + `test_isolation.sql` fortsatt 12/12), VAPID-nøkler lagt inn i ekte + `.env`, `docker compose up -d --build teecup_api teecup_frontend`. + Begge containere boot-et rent, `/health`/`/dashboard` → 200, + `GET /push/vapid-public-key` over ekte https ga korrekt offentlig + nøkkel, `GET /rounds/{ukjent-id}/flight-group` ga korrekt + `401 NOT_AUTHENTICATED` (ikke en rå 404 — bekrefter ruten faktisk når + FastAPI), `teeoff.no` upåvirket. +- **Designinstruksen erstattet/formalisert + seks brukerpunkter, ALLE + BYGGET OG LIVE (2026-07-29):** `alternativ designinstruks.md` (fra + dagen før) erstattet med en revidert versjon fra bruker (allerede + forankret i det eksisterende token-systemet, ikke lenger i konflikt med + oransje/hardkodet-slate-spørsmålene) — INNHOLDET er nå slått sammen inn + i `DESIGN_SYSTEM.md` som den gjeldende fasiten (8-punkts rutenett, + `shadow-md shadow-black/8`, `font-normal` OK ved `text-base`+god + kontrast, `:active`-tilstander, safe-area, 16px input, `transition-all + duration-200`). `alternativ designinstruks.md` selv beholdt kun som + arkiv/historikk. **Konsistens-sveip:** det gamle svake skygge-mønsteret + (`shadow-sm shadow-black/5`) erstattet mekanisk på tvers av 20 filer + (`sed`, verifisert 0 gjenværende treff) — IKKE en fullstendig + strukturell gjennomgang av alle 27 skjermer, kun dette ene, trygge, + mekaniske mønsteret. + **Hurtighandlinger:** dashbordets tre knapper er nå `grid-cols-3` også + på mobil (var `grid-cols-1 sm:grid-cols-3`, tok unødvendig mye plass) — + mindre ikon/tekst/høyde for å fortsatt være lesbare i smalere kolonner. + **Par/stroke-index manglet på rundeleaderboardet** (`/my-rounds/{id}/ + leaderboard`) — ny `HoleReferenceStrip` viser Hull/Par/Hcp ÉN gang + (banedata, identisk for alle deltakere) rett over den rangerte listen. + **Venner: kategorisering nå OBLIGATORISK fra vennskapet inngås** + (presisert av bruker: "det skal ikke finnes ukategoriserte venner") — + `POST /friends` og `POST /friends/{id}/accept` krever begge minst én + kategori (`Field(min_length=1)`), satt AV BEGGE PARTER på hvert sitt + naturlige tidspunkt (avsender ved sending, mottaker ved aksept) — ikke + en frivillig senere handling. `PUT .../categories` nekter også å sette + et tomt sett. Ny delt `CategoryPicker`-komponent (friends.tsx, alle + forhåndsvalgt — "man må heller velge bort", presisert av bruker) brukt + tre steder (send/godta/rediger), nekter å fjerne SISTE avkrysning. + Selv-helbredende sikkerhetsnett for venner fra FØR denne regelen: ny + advarselsbanner øverst i `/my-friends` ("N venner mangler kategori"), + tvinger vedkommendes kategori-panel åpent til det er løst — bekreftet + reelt nødvendig i produksjon (Erol hadde aldri kategorisert Tore + Morell, sin faste matchspill-motstander — vil nå bli fanget opp og + tvunget løst neste gang `/my-friends` besøkes). + **Synlighetsregelen for "Venner"-runder endret fra "minst én treffende + kategori" til "ALLE vennens kategorier må være i det synlige settet"** + (presisert av bruker: "'ikke vise' overstyrer 'vise'") — en venn med + ÉN ikke-valgt kategori ekskluderes nå helt, selv om en annen av + kategoriene deres er valgt. En venn med NULL kategorier vises ALDRI + (avklart eksplisitt med bruker via spørsmål — skal i praksis aldri + forekomme lenger pga. regelen over, kun en igjenværende tilstand fra + FØR den ble håndhevet). `_can_view_round`/`_friends_who_can_see_round` + (rounds.py) omskrevet til denne AND-semantikken (fra tidligere OR). + `new-round.tsx` sin kategori-multiselect for "Venner"-synlighet starter + nå med ALLE forhåndsvalgt (samme "velg heller bort"-prinsipp). + **"Match leaderboard" — undersøkt grundig, IKKE en backend-bug:** + bekreftet direkte mot ekte `teecup_db` (kun lesing) at brukerens egen + Tjøme-matchrunde (`d857289c-...`) hadde begge sider korrekt satt opp + med beregnet `playing_handicap` — `format-result`-endepunktet ville + altså allerede regnet ut riktig "1 UP (A)"-status. Det reelle hullet + var at `FormatResultPanel` (matchstatus/skins-tavle, med hull-for-hull + vinner/AS-visning) KUN lå under "Spillere og runde"-fanen, usynlig fra + "Score"-fanen der scoring naturlig skjer. Fikset ved å vise samme + komponent (ikke duplisert logikk) øverst på BEGGE faner. + **Scratch-verifisert grundig** (isolert rolle+MinIO+engangs API- + container, samme mønster som hele prosjektet): full kategori-håndheving + (422 uten kategorier ved både send/godta/rediger, 200 med), presis + eksklusjon-overstyrer-inklusjon-test (venn med golf_friends+close_family + ekskludert når kun golf_friends er synlig, inkludert når begge er + synlige — bekreftet mot BÅDE rå SQL og de faktiske API-endepunktene), + match-runde med sider satt opp fra bunnen ga korrekt "1 UP (A)". + Deretter en FULL, ekte nettleser-gjennomgang (Chrome DevTools, mobil + 390×844): 3-kolonners hurtighandlinger, matchstatus-banner synlig på + Score-fanen, Hull/Par/Hcp-referanserad på leaderboardet, hele venne- + søk→kategorivelger(alle forhåndsvalgt, avkrysning fungerer)→send- + flyten, og selv-helbredende-banner-flyten (simulerte en gammel + ukategorisert venn direkte i databasen, bekreftet banneret dukket opp + OG forsvant igjen etter kategorisering via UI-et). + **Rullet ut live 2026-07-29**, ingen migrasjon (kun eksisterende felt/ + logikk endret), `docker compose up -d --build teecup_api + teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard` + → 200, anonymt `POST /friends` ga korrekt `401` (ikke en rå 404 — + bekrefter ruten når FastAPI), `teeoff.no` upåvirket. + +- **Match-scorekort redesignet på tvers av appen (frittstående runde + + turnering), BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT OG LIVE + (2026-07-29):** brukeren delte et referansebilde av en konkurrentapp sitt + 1v1-matchscorekort (horisontalt rutenett, farget etter hvem som vant + hvert hull, løpende "Stilling"-rad AS/X UP/X&Y, navn+HCP-banner) og ba om + det tilsvarende for ALLE match-scorekort i TeeCup, uten direkte plagiat. + **Bevisst IKKE en kopi:** beholdt appens egne, allerede etablerte + fargespråk i stedet for referansens røde/blå -- frittstående runder bruker + primær/oransje (samme "side A/side B"-konvensjon som `ResultChip`/ + `FormatResultPanel` fra tidligere runder), turnering-matcher bruker lagets + faktiske `team.color` (samme konvensjon som resten av turnering-UI-et). + Droppet bevisst referansens "Matchplay NET/Stableford NET"-fane (ga ikke + entydig mening for delt-ball-formater) og "Lik/Kommentar til + spillfeeden/Spillere"-ikonraden (en helt ny sosial funksjon, ikke en + scorekort-redesign -- utenfor denne rundens omfang). + **Kjernemekanisme, ny og delt idé (separat implementert i begge filer, + ikke faktisk delt kode siden filene allerede har egne lokale typer per + etablert konvensjon):** `computeRunning()` speiler + `handicap_engine.py` sin `compute_match_state()`/`describe()` presist, + men regnet ETT PREFIKS om gangen client-side (backend cacher i dag kun + SLUTT-tilstanden) -- gir en løpende AS/X UP/dormie/X&Y-status per hull i + stedet for kun et sluttresultat. + **`round-scorecard.tsx`:** den generiske `ScoreBlock`-tabellen erstattet med `MatchScorecardGrid` -- horisontalt rutenett (Hull/ + liste) erstattet med `MatchScorecardGrid` -- horisontalt rutenett (Hull/ + Par/en rad per spiller-ELLER-side/Stilling), identitetsbanner (navn+HCP, + farget dot, løpende sentral status + "Ferdig"-merke når avgjort). Fargen + på scorecellen følger HVEM SOM VANT hullet (fylt sirkel), ikke over/under + par -- egen semantikk fra `ScoreMark` med vilje, siden dette er en match, + ikke en individuell runde. `ApiParticipant` fikk `playing_handicap` + (allerede eksponert av backend, kun en frontend-typeutvidelse). + **`session-scorecard.tsx`:** `HoleSummaryTable`/`OutcomeBadge`/ + `grossPairLabel` erstattet med `TournamentMatchGrid` (samme struktur, + brukt som HOVEDVISNING for pågående matcher, og i `DecidedView`s "Hull + for hull" -- én komponent, ikke to). `Unit`-typen fikk `playingHandicap`. + **Ekte, nødvendig backend-endring:** `match_participant.playing_handicap` + ble beregnet og lagret siden ADR-039, men var ALDRI eksponert i + `MatchParticipantOut` (matches.py) -- lagt til i modellen og i begge + SELECT-spørringene som populerer den (`fetch_matches` og + `add_participant`). **Reelt funn, fikset FØR utrulling:** + `add_participant` sin `row` hentes FØR + `compute_and_store_side_handicaps()` kjører -- en naiv + `MatchParticipantOut(**dict(row))` ville derfor alltid returnert + `playing_handicap: null` for en NYLIG lagt til deltaker, selv når verdien + faktisk ble beregnet et øyeblikk senere i samme kall. Fikset med et + eksplisitt re-oppslag av `playing_handicap` RETT FØR responsen bygges. + **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle + + isolert scratch-MinIO + engangs API-container, alle 37 migrasjoner kjørt + friskt, samme mønster som resten av prosjektet): en full match bygget fra + bunnen for BEGGE surfacene (en frittstående match-runde med to sider, OG + en full turnering-scaffold -- org/bane-import/tee/tournament/to lag/ + roster/singel-økt/match/deltakere -- bygget fra API-et for FØRSTE gang i + et testskript denne uken, siden frittstående runder aldri har trengt det + apparatet før). 8-9 hull registrert med et bevisst blandet vinn/tap/delt- + mønster, bekreftet at `format-result`/`scorecard` sine per-hull + resultater matchet forventet handicap-justert utfall. + **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP): + et REELT, tidkrevende miljøproblem ble diagnostisert og løst underveis -- + React-komponentenes egne `useEffect`-datahentinger kjørte ALDRI (evig + lastespinner på HVER side, ikke bare de nye) når dev-serveren ble besøkt + via `127.0.0.1:3100` i stedet for `localhost:3100`; Next.js 16 sin + `allowedDevOrigins`-beskyttelse blokkerer stille dev-ressurser (HMR- + websocket m.m.) for det som oppfattes som et fremmed opphav, og dette + fikk hele klient-hydreringen til å henge uten en eneste konsollfeil. + Løst ved å konsekvent bruke `localhost` i stedet for `127.0.0.1` -- + ren miljø-lærdom for fremtidige scratch-frontend-økter, ikke en bug i + selve appen. Etter fiksen: bekreftet BEGGE match-scorekortene visuelt, + i BÅDE lys og mørk modus, i BÅDE pågående (løpende "2 UP"/farget + Stilling-rad) og avgjort tilstand ("8 UP"/"Ferdig"-merke, cachet + banner), inkl. et helt nytt 2FA-oppsett fullført på fersk konto via en + engangs e-post-2FA-kode lest fra dev-loggen (org-eier/admin krever 2FA, + ADR-021) for å nå frem til turnering-siden. Ingen konsollfeil i noen av + rundene. + **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt: ingen + migrasjon, `docker compose up -d --build teecup_api teecup_frontend`. + Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` + → 200 upåvirket. + +- **Match-scorekort-redesignet også på selve LIVE scoringssiden + (`/my-rounds/{id}` sin Score-fane), BYGGET, GRUNDIG SCRATCH-/ + BROWSERVERIFISERT OG LIVE (2026-07-29), samme dag rett etter forrige + punkt:** brukeren viste tre skjermbilder (referansen på nytt, samt et + mobil- OG et PC-skjermbilde av "hvordan du har løst det nå") og spurte + direkte hvorfor det ikke så riktig ut ennå. **Presis diagnose FØR noe ble + bygget:** PC-skjermbildets URL (`teecup.teeoff.no/my-rounds/{id}`, uten + `/scorecard`) avslørte at forrige runde kun traff `round-scorecard.tsx` + (en egen, separat LESE-visning) og `session-scorecard.tsx` (turnering) -- + aldri `round-detail.tsx` sin egen, ELDRE `FormatResultPanel`+ + `ScorecardGrid`/`SideScorecardGrid`, som er det brukeren faktisk ser og + bruker under selve scoringen. `FormatResultPanel` viste dessuten en + reell, synlig svakhet: falt tilbake til det generiske "Side B" i stedet + for spillerens faktiske navn når siden ikke hadde et eget satt navn. + **Bygget:** `ApiFormatResult` utvidet med samme `holes`-per-hull- + oppløsning som round-scorecard.tsx allerede har (ingen backend-endring + -- feltet fantes allerede i API-svaret, bare ikke lest her). Selve + fetch-en LØFTET fra `FormatResultPanel` opp til hovedkomponenten (`Round + Detail`) som ny delt `formatResult`-state, siden BÅDE banneret OG de to + scorekort-gridene nå trenger den samme dataen (én henting, ikke to). + `FormatResultPanel` gjort om til en ren presentasjonskomponent (tar + `result` som prop) med et nytt identitetsbanner (navn+HCP, farget prikk, + stor sentrert løpende status, "Ferdig"-merke) -- samme visuelle språk + som `MatchScorecardGrid`/`TournamentMatchGrid` fra forrige runde, egen + lokal kopi per prosjektets "ett sted, én fil"-konvensjon. Den tidligere + hull-for-hull-sirkel-listen under banneret FJERNET (overflødig nå som + selve gridet under viser dette per hull). + `ScorecardGrid` (individuell-ball: match/fourball) og `SideScorecardGrid` + (delt-ball: foursome/greensome/scramble) fikk begge: en farget prikk ved + siden av spiller-/side-navnet (grønn=side A, oransje=side B), en ny + `MatchScorecardCell` som farger scorecellen etter HVEM SOM VANT hullet + (fylt sirkel) i stedet for over/under par -- men KUN når formatet + faktisk er to-sidet (`isTwoSided`-prop, gate på `TWO_SIDED_FORMATS`) -- + slagspill og skins beholder uendret over/under-par-fargelegging siden de + ikke har noe "vant hullet"-konsept. Fourball sin ikke-tellende + partner-score tones ned (samme "laveste netto teller"-nedtoning som + round-scorecard.tsx). Ny "Stilling"-rad nederst i BEGGE grid, regnet med + en lokal `matchStillingByHole()` (samme prefiks-vise + `compute_match_state()`-speiling som forrige rundes `computeRunning()`, + egen kopi her siden denne trenger å være justert til `holeOrder`, ikke + bare en flat liste). Alt dette er BEVISST lagt OPPÅ den eksisterende + klikk-for-å-registrere-interaksjonen (ScoringWizard/SideScoreWizard) + uendret -- ingen endring i selve registreringsflyten, kun presentasjonen + av allerede registrerte hull. + **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle + + isolert scratch-MinIO + engangs API-container, alle 37 migrasjoner kjørt + friskt): tre nye runder bygget fra bunnen via API for å dekke alle tre + strukturelle grenene -- en MATCH-runde (Erol vs. en gjest, ulik HCP), + en FOURBALL-runde (2v2, individuell netto, bevisst konstruert med et + hull der "feil" partner sin lavere brutto ikke telte pga. handicap- + justering), og en FOURSOME-runde (delt ball). Ekte typesjekket + produksjonsbuild kompilerte rent. + **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP, + samme `localhost`-ikke-`127.0.0.1`-lærdom fra forrige runde anvendt fra + start denne gangen): alle tre rundene bekreftet visuelt -- identitets- + banneret viste ekte spillernavn+HCP (ikke lenger "Side B"), gridcellene + fargela nøyaktig riktig hull som vunnet (kryssjekket presist mot de + faktiske innsendte tallene og aria-labelene, inkl. et hull der en + tilsynelatende brutto-uavgjort (4-4) faktisk ble avgjort på netto pga. + et stort HCP-gap -- bekreftet korrekt, ikke en bug), fourball sin + nedtonede ikke-tellende partner-celle bekreftet nøyaktig på det + konstruerte hullet, Stilling-raden fulgte riktig gjennom AS/1 UP/2 UP- + mønsteret for alle tre rundene. Bekreftet at et klikk på en scorecelle + FORTSATT åpner riktig veiviser (`ScoringWizard`/`SideScoreWizard`) + uendret, i begge grid-typer. Kjørte i tillegg en ren REGRESJONSSJEKK + mot en vanlig `stroke`-formatert runde -- bekreftet ingen Stilling-rad, + ingen fargede prikker, ingen bytte av cellespråk (uendret over/under- + par-styling som før). Ingen konsollfeil i noen av rundene, i verken lys + eller mørk modus. + **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt (ba om fiksen + rett etter diagnosen): ingen migrasjon, `docker compose up -d --build + teecup_api teecup_frontend`. Begge containere boot-et rent, + `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. **Alle tre stedene + et match-scorekort vises i appen (frittstående runde sin live Score- + fane, frittstående runde sin egen lesevisning, turnering-match) bruker + nå samme konsistente visuelle språk.** + +- **Dashbordets rundeboks: viewer-relativt matchresultat + netto til-par, + pluss mottatte slag vist FØR hullet fylles ut, BYGGET, SCRATCH-/ + BROWSERVERIFISERT OG LIVE (2026-07-29), samme dag:** brukeren ba om to + ting samtidig -- at rundeboksen (`round-card.tsx`, brukt av dashbordet, + "Egne runder" og "Spilte baner") viser spillerens eget resultat i + matchen/runden ("1 UP"/"+4 netto"/"-2 brutto" osv.), og at det er + tydelig hvor mange slag en spiller MOTTAR på et hull -- kommunisert på + scorekortet, allerede FØR hullet er fylt ut (ikke bare etterpå som + netto-tallet i dag). Fire load-bærende avklaringer bekreftet eksplisitt + (AskUserQuestion, alle anbefalte valg): vis resultat BÅDE for pågående + og fullførte runder (ikke bare fullførte som før), samme "UP"/"AS"/ + "X&Y"-golfkonvensjon som scorekortets Stilling-rad (ikke et eget + dagligdags språk kun for dashbordkortet), vis BÅDE netto og brutto + til-par for slagspill når HCP spores, og vis slag-indikatoren INNI selve + den tomme score-ruten (ikke en egen ny rad). + **Backend (`app/routers/rounds.py`):** `RoundOut`/`_load_round_out` + (delt av BÅDE `GET /rounds` (liste) OG `GET /rounds/{id}`, ingen egen + kode for listen) fikk tre nye felt: `my_net_score_to_par` (kun + `play_format=="stroke"` OG `course_handicap_snapshot` satt, samme + `allocate_strokes_by_index`-algoritme som list_holes/update_hole + allerede bruker -- ingen ny utregningsmåte), og `my_match_status`/ + `my_match_lead` (to-sidede formater KUN, ALLTID viewer-relativt -- + ny `_viewer_match_status_text()`-formatter tar en fortegns-FLIPPET + `lead` fra `_build_format_result` sitt allerede eksisterende, testede + resultat -- gjenbrukt UENDRET, ikke en ny match-motor -- slik at + positivt alltid betyr "spørrende bruker leder", uansett hvilken side de + faktisk sitter på). Et avgjort resultat vises som "Vunnet 2&1"/"Tapt + 2&1" (eksplisitt prefiks, siden "2&1" alene ikke lenger sier hvem sin + side det gjaldt når visningen ikke er side-A/B-basert). + **Frontend (`round-card.tsx`):** ny `Round.matchStatus`/`matchLead`/ + `netToPar`. To-sidede formater viser ett "Resultat"-felt med + matchstatusen (farget grønn ved ledelse, oransje ved etterslep, samme + "form+farge"-språk som resten av appen -- ALDRI backend sin bokstavelige + side A/B). Slagspill beholder "Resultat"+"Til par" (brutto) og fikk et + nytt "Netto"-felt ved siden av, kun når tilgjengelig. Resultatraden er + nå IKKE lenger gatet til kun fullførte runder -- vises også mens runden + pågår. Alle tre kallstedene (`dashboard.tsx`, `own-rounds.tsx`, + `course-rounds.tsx`) oppdatert til å lese og videresende de tre nye + API-feltene, samme mønster i alle tre (ingen egen logikk per fil). + **Slag-indikator FØR utfylling (`round-detail.tsx`):** `ScorecardGrid` + (individuell-ball) og `SideScorecardGrid` (delt-ball) viser nå en liten + blå "−N"-tekst inni den tomme score-ruten når spilleren/siden faktisk + mottar minst ett slag på det hullet -- samme `strokes_received`-verdi + som allerede ble hentet (kun aldri vist før noe var registrert). Egen + farge (`text-info`, den etablerte blåtonen) for å skille denne FØR- + visningen tydelig fra den eksisterende grå netto-visningen ETTER + utfylling. **Reelt, tidligere ubrukt hull funnet underveis:** + `SideScorecardGrid` sin egen frontend-type `ApiSideHole` manglet + `strokes_received` helt (backend har alltid eksponert feltet i + `RoundSideHoleOut`, bare aldri lest her) -- lagt til. + **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle + + isolert scratch-MinIO + engangs API-container, alle 37 migrasjoner kjørt + friskt): en slagspill-runde ga korrekt brutto til-par 4 / netto til-par + 2 (både i enkelt-GET og i listen), en matchrunde der spilleren vant + begge registrerte hull ga korrekt `"2 UP"`/`lead=2`. Deretter en FULL, + ekte nettleser-gjennomgang (samme `localhost`-lærdom anvendt fra start): + rundeboksen viste "2 UP" i grønt for en pågående matchrunde og "Resultat + 17 / Til par +4 / Netto +2" (oransje) for en pågående slagspill-runde, + i BÅDE lys og mørk modus; scorekortet viste "−1"-hint i blått på + nøyaktig de tomme rutene der spilleren/siden faktisk mottar et slag, + bekreftet i BÅDE individuell-ball- (match) og delt-ball-grid (foursome). + Ingen konsollfeil i noen av rundene. + **Rullet ut live 2026-07-29**, 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. + +- **Match-identitetsbanneret: territorium-bar i stedet for flat 50/50-boks, + BYGGET, GRUNDIG BROWSERVERIFISERT (inkl. en reell overlapp-bug funnet OG + fikset) OG LIVE (2026-07-29), samme dag:** brukeren lastet opp to bilder + av dagens banner (samme identitetskort som ble bygget dagen før -- navn+ + HCP i hver ende, en sentrert statusboks) og en "VELDIG DÅRLIG, kun + illustrerende" skisse av eget forslag: den ledende sidens halvdel bør + markeres OVER på motstanderens halvdel (ikke en statisk 50/50-boks), og + ved AS skal begge sider markeres likt (nøytralt). + **Design:** ny `leadZoneFraction(lead, totalHoles)` -- `0.5 + (lead / + totalHoles) * 0.5`, klippet til `[0.18, 0.82]` for å alltid holde begge + navn lesbare selv ved en ekstrem ledelse. Dominant side får en solid, + mettet farge (`bg-primary`/`bg-brand-orange`, hvit/kontrastfarget tekst + via allerede eksisterende `--primary-foreground`/`--brand-orange- + foreground`-tokens) og strekker seg proporsjonalt forbi midtlinjen; + underlegne side beholder samme fargetone som en svak, lys tint + (`/12`-opacity) med vanlig temafarget tekst. Ved AS (eller ingen hull + spilt ennå): begge soner nøyaktig 50/50 og nøytralt `bg-muted` -- ingen + side ser ut til å lede. Egen lokal kopi i alle tre filer der et match- + identitetsbanner finnes (samme "én fil, én kopi"-konvensjon som + MatchScorecardCell-arbeidet dagen før): `round-detail.tsx` sin + `FormatResultPanel` (primary/brand-orange-tokens), `round-scorecard.tsx` + sin nye delte `LeadBar`+`LeadZone` (samme tokens, portert fra den + tidligere `IdentitySide`+`CenterBadge`-duoen), `session-scorecard.tsx` + sin `IdentityBlock` (FAKTISK lagets `team.color`-hex i stedet for faste + tokens -- dominant sone `backgroundColor: color` + hvit tekst, svak sone + `color-mix(in srgb, ${color} 15%, transparent)`, samme kontrastvalg som + den eksisterende `SegmentedBar` i tournament-leaderboard.tsx). + **Reell overlapp-bug funnet OG fikset UNDER selve browserverifiseringen, + ikke antatt riktig fra kodegjennomgang alene:** første forsøk brukte en + `position:absolute`-sentrert statusboks OPPÅ to `width:X%`-delte soner -- + et ekte skjermbilde på en smal (390px) mobil-viewport viste at et langt + navn ("Erol Haagenrud") ble delvis SKJULT bak statusboksen ("l Haagenrud" + vist i stedet, ikke en ellipse-trunkering, et reelt visuelt overlapp). + Første fiksforsøk (CSS Grid med `minmax(0,X fr) auto minmax(0,Y fr)` i + stedet for absolutt posisjonering) løste IKKE problemet alene -- fortsatt + samme overlapp ved re-test. Rot-årsaken var dypere: identitetsboksen + brukte `items-start`/`items-end` på sin YTRE flex-kolonne, som sizer + boksen etter INNHOLDETS egen bredde (shrink-to-fit) i stedet for sonens + faktiske tildelte bredde -- innholdet kunne dermed visuelt strekke seg + utover sin egen sone og inn i midt-kolonnen uansett hvor korrekt selve + grid-/bredde-fordelingen var. Fikset ved at boksen alltid STREKKER SEG + (fjernet items-start/items-end helt), med venstre/høyre-justering i + stedet løst via `justify-start`/`justify-end` på navne-raden og + `text-left`/`text-right` på HCP-teksten -- INNI en boks som nå alltid har + sonens fulle, korrekte bredde, slik at `truncate` faktisk virker presist. + **Verifisert grundig i en isolert scratch-nettleserøkt** (fersk + `teecup_scratch`-database + isolert scratch-MinIO + engangs API- + container, ekte `next dev` mot scratch-backend, Chrome DevTools MCP, 390× + 844 mobil-viewport): bygget en ekte match-runde fra bunnen via API (egen- + definert 18-hulls bane, Erol HCP 10 vs. gjest "Tore Morell" HCP 54, + samme HCP-mønster som brukerens skjermbilde) og testet re-render etter + HVER kodeendring, ikke bare én gang til slutt -- fanget nettopp DERFOR + både at CSS Grid-fiksen alene ikke var nok, og at stretch-fiksen + faktisk løste det. Bekreftet: normal ledelse ("1 UP", dominant sone + moderat bredere), en EKSTREM ledelse (avgjort "9&7", dominant sone + nesten fyller hele baren, underlegne navn korrekt trunkert "T…"/"HCP…" + i stedet for å overlappe), AS/ingen-hull-spilt (nøytral 50/50, `bg- + muted`), BÅDE lys og mørk modus (kontrast bekreftet i begge), og samme + fiks bekreftet å fungere identisk i `round-scorecard.tsx` sin + `MatchScorecardGrid`-visning (samme runde, `/my-rounds/{id}/scorecard`). + `session-scorecard.tsx` sin tournament-variant IKKE egen nettleser- + testet denne runden (identisk kodemønster, kun `team.color`-hex i stedet + for faste tokens -- lavere risiko, men flagget ærlig som ikke eget + bevist). Ekte typesjekket produksjonsbuild kjørt (to runder -- én etter + første, mislykkede CSS Grid-only-forsøk, én etter den faktiske stretch- + fiksen), alle 24 ruter listet begge ganger. + **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt: ingen + migrasjon (ren frontend), `docker compose up -d --build + teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning, + ingen backend-kode rørt). Begge containere boot-et rent, `/health`/ + `/dashboard` → 200, `teeoff.no` upåvirket. + +- **Territorium-bar-språket fullført på de to siste "Matchstatus"-boksene, + BYGGET, BROWSERVERIFISERT OG LIVE (2026-07-29), samme dag -- brukeren ba + eksplisitt om "visuell konsistens ferdig helt ut" etter at jeg flagget + disse to som gjenstående:** `round-leaderboard.tsx` sin + `MatchStatusSection` (den innloggede rundens leaderboard-side) og + `watch-round.tsx` sin `MatchStatus` (den offentlige "Følger live"-siden + for en delt/synlig runde, ADR-036 fase 2) hadde begge fortsatt den gamle + flate boksen (kun stor tekst, ingen fargesone). Disse to har INGEN + navn å vise (deltakernavnene vises allerede andre steder på samme side) + -- lagt til KUN de to fargesonene + den sentrerte statuspillen (samme + `leadZoneFraction`+CSS-Grid-mønster som identitetsbanneret dagen før, + egen lokal kopi i hver fil), ikke selve `LeadZone`-identitetsboksen. + Begge filenes lokale `ApiFormatResult`-type manglet `match_lead` helt + (aldri lest her før, selv om backend alltid har eksponert det) -- lagt + til i begge. + **Verifisert i en isolert scratch-nettleserøkt** (fersk `teecup_scratch`- + database + isolert scratch-MinIO + engangs API-container, ekte `next + dev`, Chrome DevTools MCP, 390×844 mobil-viewport): bygget en ekte + OFFENTLIG (`visibility_mode:"public"`) match-runde fra bunnen via API, + bekreftet BEGGE sider (innlogget leaderboard OG anonym `/watch/{id}`) + viser identisk, korrekt fargesatt "1 UP (Side B)"-tilstand med riktig + dominant/lys sone -- OG en egen AS/0-hull-spilt-test (nøytral 50/50, + ingen farge). Begge bekreftet i BÅDE lys og mørk modus. Ingen + konsollfeil (kun en godartet, urelatert PWA-install-loggmelding). Ekte + typesjekket produksjonsbuild kjørt og bekreftet. + **Rullet ut live 2026-07-29**, ingen migrasjon (ren frontend), `docker + compose up -d --build teecup_frontend` (gjenskapte også `teecup_api` + som vanlig bivirkning, ingen backend-kode rørt). Begge containere + boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. + **Territorium-bar-språket er dermed konsistent på ALLE fem stedene en + matchstatus vises i appen** (tre fulle identitetsbannere + disse to + navnløse variantene) -- listevisningene (blind draw-reveal, offentlig + turnering-live) beholder bevisst sin egen, ulike kort-per-match-stil + (farget topplinje + statuschip), siden de viser MANGE matcher samtidig + og ikke er en fokusert enkelt-match-visning. + +- **Stableford som ekte spilleform + "plukket opp"-tilstand for frittstående + runder, BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT (inkl. TO reelle bugs + funnet OG fikset UNDER browserverifiseringen) OG LIVE (2026-07-29), samme + dag:** brukeren ba om å ta fatt på det siste, klart avgrensede hullet fra + ADR-038-gjennomgangen -- Stableford-POENGENE var allerede beregnet og vist + flere steder (round-detail.tsx/round-scorecard.tsx/round-leaderboard.tsx + sin `stablefordPoints()`/`total_points`, bygget 2026-07-25/26), men to + ting manglet reelt: (1) `'stableford'` fantes ikke som et faktisk + selvdeklarert `play_format` (kun "stroke"/"match" fantes som individuelle + format-etiketter), (2) ingen vei til å registrere at en spiller plukket + opp ballen fordi hullet uansett var klart 0 poeng. + **Design, ny migrasjon `038_stableford_and_pickup.sql`:** `round_hole. + picked_up boolean DEFAULT false`, `'stableford'` lagt til + `round_play_format_check`. "Plukket opp" lagres IKKE som en NULL-score -- + serveren skriver eksplisitt Net Double Bogey (par + 2 + mottatte slag, + `handicap_engine.py` sin allerede eksisterende og testede `max_hole_ + score_for_handicap()`, Rule 3.1b) som selve `score`-verdien når + `picked_up=true` sendes til `PATCH .../holes/{n}` -- dette gir automatisk + presis 0 Stableford-poeng (samme formel som ellers, ingen spesialkoding) + OG et korrekt AGS-bidrag for faktisk-HCP (ADR-038) helt uendret -- INGEN + ny motorlogikk trengs, kun ett nytt felt for VISNING ("PU"-merke i stedet + for det underliggende tallet, samme "form+farge, aldri farge alene"-språk + som resten av golfscore-visningen). `HoleUpdate` beregner strokes_received + for AKKURAT det aktuelle hullet FØR selve UPDATE-en (omstrukturert fra + tidligere "beregn etterpå"-rekkefølge) for å kunne skrive riktig NDB-verdi + i samme kall. Avvist tydelig (400 `VALIDATION_FAILED`) hvis deltakeren + ikke har noen beregnet course handicap ennå. + **To reelle bugs funnet OG fikset UNDER selve browserverifiseringen, ikke + antatt riktig fra kodegjennomgang alene** -- begge samme underliggende + mønster: eksisterende kode som spesialsjekket `play_format === "stroke"` + (for å bety "individuelt, ikke to-sidet format") måtte utvides til + eksplisitt å ekskludere `"stableford"` også, ellers falt det nye formatet + feilaktig gjennom til den TO-SIDEDE grenen: + 1. `new-round.tsx` sin `needsSidesSetup = playFormat !== "stroke" && + playFormat !== "skins"` -- et ekte skjermbilde viste "Du setter opp + sidene..."-teksten under Stableford-valget, til tross for at formatet + aldri bruker sider. Rettet ved å legge til `&& playFormat !== + "stableford"`. + 2. **Alvorligere:** `round-detail.tsx` sin `FormatResultPanel` (`if + (round.play_format === "stroke" || !result) return null`) -- et ekte + skjermbilde av en fersk Stableford-runde viste en fullstendig + meningsløs "Side A"/"Side B"-territorium-bar (samme komponent bygget + tidligere samme dag for ekte to-sidede formater) med "Ingen hull + spilt ennå", siden backend sin `_build_format_result()` returnerer en + harmløs tom `ready:true`-respons for ethvert format utenfor + `_TWO_SIDED_FORMATS`/`"skins"` (inkl. det nye "stableford"), som + frontend-sjekken ikke fanget opp. Rettet samme sted (lagt til + `round.play_format === "stableford"` i den samme betingelsen), pluss + den tilhørende `useEffect` som utløser selve HTTP-kallet (unødvendig + nettverkskall for Stableford, samme fiks som for "stroke"). + **Systematisk oppfølging etter de to funnene, IKKE bare disse to + stedene:** grep'et gjennom HELE frontend-kodebasen etter samme + `"stroke"`-spesialsjekk-mønster og fant tre til ekte hull i + `watch-round.tsx` (den offentlige "Følger live"-siden) -- leaderboard- + henting, `StrokeLeaderboard`-visning, og "ingen live-visning + tilgjengelig"-fallback-meldingen ville alle feilaktig behandlet en + offentlig Stableford-runde som enten to-sidet eller "ukjent format". + Alle tre rettet samme runde, FØR de kunne bli oppdaget i produksjon. + Backend-siden av samme klasse spesialsjekk (`app/routers/rounds.py`) + var allerede korrekt fra første forsøk (`_format_setup_status`, + `my_net_score_to_par`-gaten, `RoundCreate`/`RoundUpdate` sine + `Literal`-typer) -- kun frontend hadde det gjentatte hullet. + `EditRoundPanel` sin spilleform-brytar (rediger-runde-panelet) utvidet + fra to til tre valg (Slagspill/Stableford/Matchspill), samme mønster + som `new-round.tsx`. + **Scratch-verifisert grundig i to lag** (isolert `teecup_app_scratch`- + rolle + isolert scratch-MinIO + engangs API-container, alle 38 + migrasjoner kjørt friskt, samme mønster som hele prosjektet): et + hånd-utregnet scenario (course handicap 18, slope 113/rating lik par, + 1 slag mottatt per hull) bekreftet "plukket opp" på et par-4-hull med + 1 mottatt slag ga eksakt score 7 (4+2+1, identisk med håndregning), + leaderboardets `total_points` stemte eksakt (0 for plukket-opp-hullet + + 3 for et normalt netto-birdie-hull = 3), reversering (fjern plukket- + opp, sett ekte score, plukk opp igjen) fungerte, avvist tydelig for en + deltaker uten HCP (400), `_format_setup_status` bekreftet IKKE lenger + krasjer (KeyError) for stableford (`setup_complete:true` uten + sider), `RoundUpdate.play_format` PATCH til "stableford" i etterkant + bekreftet, runde fullført uten krasj. + **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP, + samme `localhost`-ikke-`127.0.0.1`-lærdom fra tidligere runder): hele + opprett-runde-flyten klikket gjennom i UI-et (Stableford-valg, riktig + beskrivelsestekst, INGEN sider-notis etter fiksen), "Plukket opp (0 + poeng)"-knappen i selve `ScoringWizard` bekreftet å auto-hoppe videre + akkurat som et vanlig slagtall, og "PU"-merket bekreftet konsekvent på + ALLE FIRE stedene det vises: scorekort-gridet på selve Score-fanen, + det post-runde `round-scorecard.tsx`-scorekortet (med korrekt "Poeng + 0"/netto 6), `round-leaderboard.tsx` (både i hull-stripen og i + Poeng-modus, total "3" korrekt), og "Så langt i runden"-tabellen. En + separat REGRESJONSSJEKK av et eksisterende `match`-format (to sider, + territorium-bar, "1 UP") bekreftet UENDRET oppførsel -- de to bug- + fiksene rørte kun stableford-grenen. Ingen konsollfeil i noen av + rundene. + **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt (plan vist + FØR migrasjonen, per CLAUDE.md sin ufravikelige regel): migrasjon 038 + kjørt mot ekte `teecup_db` (kolonne + utvidet CHECK-constraint + 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. + +- **Scramble/greensome: "utslag brukt"-statistikk NÅ OGSÅ for org-scopede + turneringer, BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT OG LIVE + (2026-07-29), samme dag:** direkte oppfølging av brukerens eget forslag + fra "hva nå?"-runden rett etter Stableford-arbeidet -- funksjonen ble + bevisst holdt utenfor migrasjon 036 (frittstående runder, 2026-07-25), + se dens egen kommentar; dette er den avgrensede oppfølgeren. + **Design, ny migrasjon `039_hole_score_selected_participant.sql`:** + `hole_score.selected_participant_id uuid` (nullable), composite FK + `(organization_id, selected_participant_id) REFERENCES match_ + participant(organization_id, id) ON DELETE SET NULL` -- samme mønster + som `hole_score.match_participant_id` sin egen FK, men SET NULL (ikke + CASCADE): fjernes en deltaker fra matchen (mulig FØR lås) skal ikke + slette allerede registrerte hull-scorer, kun nullstille selve valget. + Kun meningsfullt på en DELT-BALL-hull-rad (`match_participant_id IS + NULL`) -- håndhevet i app-laget (`submit_hole_score`), ikke en CHECK, + samme mønster som migrasjon 036. + **Backend (`app/routers/scoring.py`):** `HoleScoreCreate`/`HoleScoreOut` + fikk `selected_participant_id`. Validering: avvist (400 + `WRONG_PARTICIPANT_MODE`) for individuell ball (`match_participant_id` + satt) -- gir ingen mening der. For delt ball: validert å tilhøre + matchen OG samme `team_side` som selve scoren (400 `VALIDATION_FAILED` + ellers), speiler `round.py` sin identiske sjekk for frittstående runder. + Begge INSERT/UPSERT-setningene i `submit_hole_score` oppdatert (delt- + ball-grenen skriver/oppdaterer feltet, individuell-ball-grenen inkluderer + det aldri i det hele tatt -- alltid NULL der). `get_scorecard` sin + `stroke_entries`-SELECT utvidet -- feltet flyter automatisk gjennom + siden den allerede delte `HoleScoreOut`-modellen gjenbrukes. + **Frontend (`session-scorecard.tsx`):** ny "Hvem sitt utslag ble + brukt?"-knapperad (samme mønster som round-detail.tsx sin + `SideScoreWizard` fikk 2025-07-25) under `StrokePicker` for delt-ball- + enheter -- vises KUN når et slagtall allerede er registrert for hullet + (ulikt frittstående runders `round_hole`, som alltid forhåndsopprettes + med nullbar score, krever `hole_score`-raden faktisk å EKSISTERE først, + siden `gross_strokes` er `NOT NULL` i skjemaet -- en reell strukturell + forskjell fra migrasjon 036s mønster, løst ved å gjenbruke samme + `submitStroke`-funksjon med et nytt valgfritt fjerde argument som + enten beholder gjeldende valg (utelatt) eller setter et nytt (inkl. + eksplisitt `null` for å fjerne). Ny `SelectedDriverSummary`-komponent + (samme presentasjon som round-stats.tsx sin tilsvarende, tilpasset + denne sidens `teams`/`match.participants`/`scorecard.stroke_entries`- + datform) lagt inn i den eksisterende "Vis full oversikt"-seksjonen. + **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle + + isolert scratch-MinIO + engangs API-container, alle 39 migrasjoner + kjørt friskt): en FULL org-turnering-scaffold bygget fra bunnen via API + for FØRSTE gang i en test for akkurat denne funksjonen (org/egendefinert + bane/18 hull/tee/turnering/to lag/fire spillere/roster/foursome-økt+ + match/fire deltakere/lås) -- 51/51 sjekker: skriving uten valg, skriving + med valg (gross_strokes bevart), feil-side-valg avvist (400 + VALIDATION_FAILED), individuell-ball+valg avvist (400 + WRONG_PARTICIPANT_MODE), persistens bekreftet via `GET .../scorecard`, + korrekt opptelling (2 vs. 1 utslag), eksplisitt `null` fjerner valget. + `test_isolation.sql` 12/12 uendret (additiv migrasjon). Ekte typesjekket + produksjonsbuild kjørt og bekreftet. + **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP, + isolert scratch-backend, inkl. et ekte 2FA-oppsett for en fersk + organisasjonseier siden ADR-021 krever det): naviger til foursome- + matchens scorekort, bekreftet at allerede-registrerte valg (satt via + API) vises korrekt som trykte knapper for RIKTIG spiller på RIKTIG + hull -- OG et ekte klikk i UI-et som byttet valgt spiller for et hull, + bekreftet persistert direkte i databasen etterpå (ikke bare at UI-et + så riktig ut). "Vis full oversikt" sin nye "Utslag brukt"-seksjon + bekreftet med nøyaktig riktig opptelling for begge lag (2/1 og 1/0, + inkl. "Registrert på N av M spilte hull"-teksten). Ingen konsollfeil. + **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt (plan vist + FØR migrasjonen): migrasjon 039 kjørt mot ekte `teecup_db` (kolonne + + FK-constraint 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. **"Utslag brukt"-statistikken finnes dermed nå for BEGGE + domener** (frittstående runder siden 2026-07-25, org-scopede + turneringer siden i dag) -- se ARCHITECTURE_DECISIONS.md sitt åpne + spørsmål 4 (Scramble-grensesnitt), som dermed er helt avsluttet. + +- **ADR-037 (individuelle/flerrunde-turneringer): migrasjon + motor + API + BYGGET OG SCRATCH-VERIFISERT, IKKE ENNÅ RULLET UT (2026-07-30):** bruker + ba om å gå videre med anbefalingen fra en dypdykk-gjennomgang av + .md-filene -- det eneste gjenværende punktet med en ferdig, load-bærende + strukturbeslutning (ADR-037, 2026-07-26) uten kode. Bygget i tre lag, + samme "test i isolasjon FØR resten"-rekkefølge som ADR-005/033/038/039. + **Migrasjon `040_individual_tournaments.sql`:** `tournament.format_type` + (`team`/`individual`, default `team` -- alle eksisterende rader uendret) + + `tournament.scoring_method` (`stroke_gross`/`stroke_net`/`stableford`, + nullable). Fem nye, RLS-beskyttede tabeller: `tournament_round` (samme + rolle som `session`, peker til org-ens EGEN bane -- ingen snapshot, + ulikt ADR-033), `tournament_participant` (samme rolle som `team_roster`, + fryser `handicap_index_snapshot`), `tournament_round_participant` + (tee + cachet course/playing-handicap PER runde), `tournament_round_hole` + (rå brutto slag, kilde-sannhet), `tournament_round_score` (ferdig + utregnet brutto/netto/Stableford-total PER deltaker PER runde, cachet -- + samme mønster som `match.status_text`/`points_side_a/b`; sammenlagt over + flere runder summeres VED LESING i leaderboardet, ingen egen tredje + cache-tabell, Beslutning C). + **Scratch-verifisert alene FØR API-et ble bygget:** alle 40 migrasjoner + kjørte rent i rekkefølge, `test_isolation.sql` fortsatt 12/12, 10 egne + funksjonelle sjekker (kryss-org-isolasjon på BÅDE lesing og skriving, + `format_type`-CHECK+default, unik-constraints, `gross_strokes`-CHECK, + kaskade-sletting). + **Motor** (`handicap_engine.py`, ny seksjon rett etter + `allocate_over_played_holes`): `stroke_play_gross_total`/ + `stroke_play_net_total`/`stableford_points_for_hole`/`stableford_total` + -- rene funksjoner, ingen ny slagfordeling (bruker samme + `allocate_over_played_holes`-output som resten av motoren). 8 nye + tester, alle 63 (55 eksisterende + 8 nye) bestått i + `test_handicap_engine.py`. + **API** (nytt `app/routers/individual_tournaments.py`, registrert i + `main.py`): CRUD for runder/turnering-deltakere/rundedeltakere (med + handicap-beregning ved tilføyelse -- v1 har INGEN allowance-prosent for + individuelle turneringer, `playing_handicap` er alltid identisk med + avrundet `course_handicap`, ulikt lagturneringenes komplekse relative + `AllowanceStrategy`-familie, som er bygget for et to-siders oppgjør og + ikke gir mening for et flatt felt), hull-for-hull-scoring + (`PATCH .../holes/{n}`, cacher totalen på nytt ved hver innsending), + leaderboard som summerer på tvers av runder ved lesing. + `tournaments.py` sin `TournamentCreate`/`TournamentUpdate`/`Tournament` + utvidet med `format_type`/`scoring_method` (samme `exclude_unset`-PATCH- + mønster som resten av filen). + **To reelle funn, begge fikset FØR utrulling:** + 1. Leaderboard-endepunktet kunne IKKE hete + `/orgs/{id}/tournaments/{id}/leaderboard` -- den stien er allerede + `tournaments.py` sitt LAG-leaderboard, og siden `tournaments.router` + registreres FØR `individual_tournaments.router` i `main.py`, ville + det stille skygget for det nye endepunktet (funnet presist ved en + ekte API-test som krasjet på feil responsform). Løst med et eget + navn, `/individual-leaderboard` -- samme kollisjonsklasse som + `/rounds` vs. `/my-rounds` tidligere, denne gangen unngått fra start. + 2. `list_rounds`/`list_tournament_participants` manglet en eksplisitt + "finnes turneringen"-sjekk (samme mønster `list_sessions` allerede + har) -- ga stille en tom liste under RLS for en fremmed + turnering-id i stedet for 404 (ingen sikkerhetslekkasje, RLS + blokkerte fortsatt all faktisk data, men inkonsistent med resten av + API-et). Rettet til å matche `list_sessions` presist. + **Scratch-API-verifisert grundig, 52/52 sjekker** (isolert + `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs + API-container, ekte HTTP via `requests`, ekte magic-link-innlogging via + dev-log): full happy path fra org til leaderboard, hånd-utregnet netto- + kryssjekk for to spillere (course handicap 11/20, stemte eksakt), + `front_9`-runde avviser hull utenfor omfang, kryss-org-isolasjon + (bekreftet BÅDE lesing og en FK-basert skrivesperre), slette-vern (runde + MED deltakere avvist 409, turnering-deltaker fortsatt referert av en + rundedeltaker avvist 400 RESTRICT-FK, løst opp igjen etter fjerning). + `test_isolation.sql` 12/12 uendret (additiv migrasjon). + **IKKE bygget i denne runden, bevisst neste steg:** frontend (ingen + skjerm ennå -- samme lagdelings-rekkefølge som ADR-033/038/039). + Autorisasjon er bredt org-medlemskap for ALT i denne runden, inkl. selve + scoreregistreringen -- en senere innstramming (analogt ADR-023s + kaptein-only) er en naturlig, separat oppfølger. De fem konkrete + formatene (Københavner m.fl.) og Order of Merit fortsatt ikke designet. + **Rullet ut mot ekte systemer 2026-07-30**, bruker bekreftet eksplisitt + (plan vist FØR migrasjonen, per CLAUDE.md sin ufravikelige regel): + migrasjon 040 kjørt mot ekte `teecup_db` (alle fem nye tabeller + de to + nye `tournament`-kolonnene bekreftet, eksisterende turnering fikk + korrekt default `format_type='team'`, `test_isolation.sql` fortsatt + 12/12 mot ekte database), deretter `docker compose up -d --build + teecup_api` (kun backend, ingen frontend-kode denne runden). Containeren + boot-et rent (`Application startup complete`), `/health`/`/dashboard` + → 200, `teeoff.no` upåvirket. Verifisert presist at den nye ruten + faktisk når FastAPI gjennom hele produksjonsstacken (Caddy → Next.js + rewrite → `teecup_api`): et anonymt kall mot + `/orgs/.../tournaments/.../rounds` ga korrekt `401 NOT_AUTHENTICATED`, + ikke en rå 404. + +- **ADR-037 frontend HÅNDKODET, GRUNDIG BROWSERVERIFISERT (inkl. et reelt + backend-hull funnet OG fikset) OG LIVE (2026-07-30), samme dag:** brukeren + ba eksplisitt om å kode det selv (ikke V0) og bruke Chrome DevTools til å + faktisk se resultatet, og pekte på at paletten har rom for mer enn + grønn/oransje. + **Bygget:** ny `components/individual-tournament-detail.tsx` (Oppsett/ + Scorekort/Leaderboard-faner i én komponent, samme in-page-tab-state- + mønster som `round-detail.tsx`) + ny `components/tournament-router.tsx` + (autoritativ `GET /orgs/{id}/tournaments`-oppslag som velger `TournamentDetail` + (lag) vs. `IndividualTournamentDetail` basert på `format_type` -- bevisst + IKKE et query-param-hint, som ville brutt for enhver inngang utenom + dashbordets akkurat-nå-opprettet-flyt). `app/tournaments/[id]/page.tsx` + peker nå til routeren. `dashboard.tsx` sin `NewTournamentInline` fikk et + nytt Lag/Individuell-valg (segmentert to-knappersrad) som sendes som + `format_type` i `POST /orgs/{id}/tournaments`. + **Ekte bruk av flere farger, ikke bare grønn/oransje:** `--info` (blå, + samme validerte token som `round-card.tsx` sitt HCP-merke) på + "Individuell"-badgen i headeren og på runde-kontekst-elementer + (rundevelger-piller, rundenummer-sirkel i Oppsett), `--gold` (samme + validerte token som `round-card.tsx` sitt "Personlig rekord"-merke) på + leaderboardets 1.-plass-rad (medaljeikon + gullbakgrunn). + **Scorekortet** gjenbruker `round-scorecard.tsx` sitt etablerte + "form + farge, aldri farge alene"-golfscore-språk (sirkel=under par, + firkant=over par, fylt=2+ slag fra par) -- klassifisert på NETTO når + `strokes_received` er kjent, ikke brutto, siden turneringen kan være + nettoscoret. Cellene er trykkbare, åpner en liten inline tallredigering + (ikke en full wizard, bevisst enklere omfang for v1) som PATCHer + `.../holes/{n}` direkte. + **Ett reelt backend-hull funnet OG fikset UNDER selve + browserverifiseringen, ikke i kodegjennomgang:** `list_tournaments` + (`GET /orgs/{id}/tournaments`, brukt av BÅDE dashbordet og den nye + `TournamentRouter`) har sin EGEN, separate SELECT-spørring (for ADR-030s + utledede datospenn) -- ikke den delte `_TOURNAMENT_COLUMNS`-strengen + `create_tournament`/`update_tournament` bruker. Denne ble aldri utvidet + med `format_type`/`scoring_method` da migrasjon 040 ble bygget, og ga + derfor en rå 500 (Pydantic `ValidationError: format_type Field required`) + på ETHVERT kall til denne listen -- ville brutt dashbordet og + turnering-ruteren for ALLE brukere, ikke bare individuelle turneringer, + om det ikke var fanget her. Rettet med to nye kolonner i SELECT-en. + **Ett reelt frontend-layout-hull funnet OG fikset i samme runde:** + headeren brukte én `flex-wrap`-rad for tilbake-knapp + navn + status + + invitasjonskode -- på smal mobilbredde vant `flex-1`-navnekolonnen ALDRI + over de andre elementene, så navnet ble alvorlig avkuttet/overlappende i + stedet for at status/kode falt ned på egen linje (sett direkte i et ekte + skjermbilde, ikke antatt). Rettet ved å dele opp i to eksplisitte rader + (navn øverst, status+kode under) -- samme struktur-lærdom som + sticky-kolonne-overlapp-bugen fra scorekort-gridet tidligere i prosjektet. + **Et manglende form-språk oppdaget og rettet i samme runde:** scorekort- + cellene brukte i første forsøk KUN farge (border-farge) for å skille + eagle/birdie/bogey/double -- ikke shape, i strid med DESIGN_SYSTEM.md sin + "aldri farge alene"-regel og `round-scorecard.tsx` sin egen etablerte + `ScoreMark`. Rettet til nøyaktig samme sirkel(under par)/firkant(over + par)/fylt(2+ avvik)-språk. + **Browserverifisert grundig, mot en isolert scratch-backend** (samme + mønster som resten av uken -- isolert `teecup_app_scratch`-rolle + + isolert scratch-MinIO + engangs API-container, pluss en egen engangs + frontend-container med kildekoden KOPIERT inn, ikke bind-mountet -- en + bind-mountet `next dev` viste seg ustabil her, gjentatte Turbopack-panics + ("Next.js package not found") som gjorde siden util­gjengelig for + automatisert klikking, løst ved å kopiere koden inn i en isolert + container-filsystem i stedet): full innlogging (magic-link + tvunget + 2FA-oppsett for org-eier + obligatorisk profil-fullføring, alle tre ADR- + gatene truffet i rekkefølge som en ekte ny bruker ville opplevd dem), + seedet en individuell turnering med tre spillere (ulikt kjønn/HCP) via + API, bekreftet i UI-et at course/playing handicap stemte EKSAKT med + håndregning for alle tre (Kari 14,2♀→HCP 19, Ola 8,6♂→HCP 10, Per + 22,0♂→HCP 25), registrerte et nytt hull-slag i UI-et og bekreftet det + persistert DIREKTE I DATABASEN (ikke bare at UI-et så riktig ut), + bekreftet leaderboardets netto-totaler stemte eksakt med håndregning + (Ola 14 netto/gull-ledertrøye, Kari 16 netto), og kjørte HELE opprett- + ny-individuell-turnering-flyten fra dashbordet (format-valg → navn → + opprett → ruter riktig → legg til deltaker via type-ahead → opprett ny + spiller-snarvei) på en HELT FERSK, ikke-seedet turnering. Ingen + konsollfeil (`list_console_messages`) gjennom hele økten. Ekte + typesjekket produksjonsbuild (samme `Dockerfile` som deployes) kompilerte + rent til slutt, med begge fiksene inne. + **Rullet ut mot ekte systemer 2026-07-30**, 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, `GET /orgs/.../tournaments` (den fikset + ruten) bekreftet nåbar med korrekt `401` gjennom hele produksjonsstacken. + **ADR-037 er dermed live, backend + frontend** (grunnstruktur — de fem + konkrete formatene og Order of Merit fortsatt ikke designet, se + FEATURE_BACKLOG.md). + +- **To reelle punkter fra bruker, BEGGE FIKSET OG LIVE, samme dag + (2026-07-30):** rapportert med et vedlagt skjermbilde av + Chip/Bunker/Straffeslag-stepperne som gled over i hverandre på + `/my-rounds/{id}`, pluss en forespørsel om et TeeCup-relatert favikon. + 1. **Stepper-overlapp, root cause presist identifisert:** `ScoringWizard` + sin "flere detaljer"-seksjon (`round-detail.tsx`) er fast begrenset + til `max-w-sm` (384px) UANSETT hvor bred selve viewporten er -- men + Chip/Bunker/Straffeslag-gridet brukte et VIEWPORT-basert + `sm:grid-cols-3`-brudd (640px). Enhver skjerm bredere enn 640px + (dvs. de fleste telefoner i liggende modus, nettbrett, og skjermbildet + brukeren delte) trigget dermed 3-kolonne-modus INNI en 384px-bred + kolonne -- hver Stepper har to faste 44px-knapper (tilgjengelighets- + kravet, kan ikke krympes) + verdi + padding, som aldri kan bli + smalere enn ca. 190px, så tre av dem kolliderte alltid. Samme + klasse feil som de tidligere sticky-kolonne- og match-identitets- + boks-overlappene i prosjektet (fast innholdsbredde møter et + viewport-basert, ikke container-basert, brudd). Fikset ved å fjerne + `sm:grid-cols-3` helt -- alltid stablet i én kolonne, som uansett var + den eneste bredden som noensinne hadde plass i en 384px-container. + 2. **Favikon var faktisk V0s egen logo, ikke TeeCup:** `icon.svg` og + `icon-light-32x32.png`/`icon-dark-32x32.png` (selve nettleser-fane- + ikonet, styrt av `layout.tsx` sin `metadata.icons`) viste seg -- ved + faktisk å åpne og se på filene, ikke anta -- å fortsatt være en + ubrukt "V0"-logo (sort/hvit v0.app-merke) helt siden V0-eksporten, + aldri erstattet. `apple-icon.png` og selve PWA-ikonsettet + (`public/icons/icon-192.png` m.fl.) var derimot ALLEREDE korrekt + TeeCup-merket (grønt golf-flagg, fra PWA-runden 2026-07-19) -- kun + favikon-stien var glemt. Fikset ved å gjenbruke SAMME etablerte + golf-flagg-design: `icon-light-32x32.png`/`icon-dark-32x32.png` + regenerert (nedskalert fra `icon-512.png` via en engangs + `sharp`-scratch, samme verktøy som PWA-runden brukte) og `icon.svg` + skrevet på nytt som en ren vektor av samme flagg (grønn `#8BC24A`- + bakgrunn, hvit flaggstang+flagg -- appens egen merkevarefarge, ikke + funnet på). + **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` + som deployes) kompilerte rent BEGGE ganger (én mislykket 404-test mot + kun `--target builder`-imaget, som IKKE har `public/`-mappen kopiert inn + ved siden av `server.js` ennå -- forventet byggestrenge-artefakt, ikke en + reell bug -- rettet ved å bygge og teste det FULLE, faktiske + produksjonsimaget i stedet, som ga korrekt `200`/riktig `content-type` + for alle tre ikonfilene). Stepper-fiksen browserverifisert grundig i en + isolert scratch-økt (ekte innlogging, en fersk `full`-stat_level-runde + seedet via API, veiviseren kjørt gjennom Slag→Putter→Avstand→Detaljer): + bekreftet INGEN overlapp ved 800px viewport (der bugen reprodusertes + presist, matcher brukerens skjermbilde) OG ingen regresjon ved 390px + (alltid stablet der uansett, uendret oppførsel). Ingen konsollfeil. + **Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt (samme + "redeploy begge"-godkjenning som ADR-037): ingen migrasjon, + `docker compose up -d --build teecup_frontend` (gjenskapte også + `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge + containere boot-et rent, `/health`/`/dashboard` → 200, favikon-filene + bekreftet nåbare over ekte https med riktig `content-type`, `teeoff.no` + upåvirket. + +- **Individuelle turneringer (ADR-037): scoring-autorisasjon strammet inn, + BYGGET, SCRATCH-VERIFISERT OG LIVE (2026-07-30), samme dag som frontend- + runden:** direkte oppfølging av det noterte hullet fra byggerunden + tidligere samme dag — ethvert org-medlem kunne skrive score for HVEM SOM + HELST i en individuell turnering, ikke bare sin egen. + Ny `user_is_own_tournament_participant` i `app/team_authz.py` — samme + mønster som `user_is_match_participant` (ADR-023): krever at brukeren ER + spilleren bak `tournament_participant`-raden (via `player.user_id`), + eller er org-eier/admin. Brukt av `individual_tournaments.py` sin + `update_hole` (eneste endepunkt strammet inn). Runde-/deltaker-OPPSETT + (opprett/slett runde, legg til/fjern turnering-/rundedeltaker) forblir + bevisst på vanlig org-medlemsnivå — matcher presedensen fra `session`- + opprettelse og `team_roster`-tilføyelse i tournaments.py, som heller + aldri har vært captain-/admin-gatet. + **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle + + isolert scratch-MinIO + engangs API-container, alle 40 migrasjoner kjørt + friskt): full regresjon av den eksisterende 52-punkts testsuiten (uendret + grønn — org-eier brukes gjennomgående der, rammes ikke av innstrammingen), + pluss 13 nye målrettede sjekker: et fremmed org-medlem (uten kobling til + noen av deltakerne) NEKTES å score for både en annen deltaker OG en + tredje deltaker (403 `NOT_TOURNAMENT_PARTICIPANT`), en spiller KOBLET til + sin egen `tournament_participant` (via `player.email` + ADR-017s + kontokobling ved innlogging) FÅR score seg selv men NEKTES å score for en + annen, org-eier beholder uendret admin-fallback for begge, og lesing + (`GET .../holes`) er bekreftet uendret tilgjengelig for et vanlig + org-medlem (kun skriving er strammet inn, ikke lesing). + **Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ingen + migrasjon (ren Python-logikk), `docker compose up -d --build + teecup_api`. Containeren boot-et rent (`Application startup complete`), + `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. + +- **To notater fra brukeren, BEGGE BYGGET, GRUNDIG BROWSERVERIFISERT OG + LIVE (2026-07-30), samme dag som ADR-037-autorisasjonsfiksen:** + 1. **GIR-auto-inferens for "Innspill: Traff":** ny `useEffect` i + `ScoringWizard` (`round-detail.tsx`) -- idet "flere detaljer"-steget + nås, settes `stat.approach = "hit"` automatisk når + `stat.strokes - stat.putts <= hole.par - 2` (samme formel som den + allerede eksisterende GIR-STATISTIKK-inferensen i `round-stats.tsx` + sin `isGir`, nå også koblet til selve REGISTRERINGEN) -- men KUN når + `stat.approach` fortsatt er `null` (rører aldri et allerede satt + manuelt ELLER tidligere auto-satt valg). + 2. **Auto-prompt "Fullfør runde":** ny `allHolesEnteredForEveryone`- + beregning + `AllHolesEnteredBanner`-komponent i `RoundDetail` -- viser + en tydelig CTA øverst på Score-fanen ("Alle hull er ført — Klar til å + fullføre runden?") så snart ALLE spillere (eller BEGGE sider for + delt-ball-formater) har `played=true` på alle hull i `holeOrder`, + gatet på `round.setup_complete`. Kaller samme `finishRound()` som den + eksisterende, tidligere passive knappen. + **Browserverifisert grundig i en isolert scratch-nettleserøkt** (fersk + `teecup_scratch`-database + isolert scratch-MinIO + engangs API- + container + en isolert `next dev`-frontend-container med KOPIERT, ikke + bind-mountet, kildekode -- bind-mount ga gjentatte Turbopack-panics i + denne økten, løst ved å `tar`-kopiere kildetreet inn i en isolert + container-filsystem i stedet): GIR-auto-inferens bekreftet BÅDE positivt + (birdie+1-putt -> "Traff" auto-merket umiddelbart, uten klikk, bekreftet + visuelt OG direkte i databasen) og negativt (bogey+1-putt -> "Traff" + korrekt IKKE forhåndsmerket). Auto-prompt-banneret bekreftet å dukke opp + automatisk idet siste hull ble fylt for begge spillerne i en 18-hulls + 2-spiller-runde, og "Fullfør runden"-knappen i banneret bekreftet å + fullføre runden korrekt (håndterte den native `confirm()`-dialogen via + Chrome DevTools -- HCP-differensialer beregnet og vist etterpå). Ingen + konsollfeil i noen av rundene. + **Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ingen + migrasjon (ren frontend), `docker compose up -d --build + teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning, + ingen backend-kode rørt). Begge containere boot-et rent, `/health`/ + `/dashboard` → 200, `teeoff.no` upåvirket. + +- **Symbolforklaringen fjernet fra scorekortet + to design-notater fanget + opp (2026-07-30), samme dag:** brukeren viste et skjermbilde av + `round-scorecard.tsx` med "Under par (sirkel)/Over par (firkant)/Fylt + symbol..."-forklaringen sirklet inn og ba om at den fjernes — `Legend`- + komponenten (kun ett bruksted) fjernet fullstendig. Samtidig reist: + (1) spilleren bør kunne se historikk/statistikk for NØYAKTIG hullet som + spilles/er spilt (ikke bygget — krever en ny, ikke-triviell "aggreger på + tvers av runder, filtrert til banenavn+hullnummer"-spørring, notert i + FEATURE_BACKLOG.md); (2) manglende lenke scorekort→statistikk, og et + åpent spørsmål om scorekort/statistikk/live-registrering burde slås + sammen til én visning, med mottatte slag vist som prikker. Svarte + direkte (ikke bygget): behold scorekort og statistikk som to separate + sider (bevisst skilt 2026-07-25 nettopp for å unngå én lang/rotete + side) — legg heller til den manglende lenken; behold også selve + live-registrerings-gridet uendret (bygget 2026-07-27 som en AKTIV + data-entry-flate, ikke en lesevisning — et vertikalt front9/back9-delt + format ville svekket registreringsergonomikken). "Prikker for mottatte + slag" vurdert som en god, uavhengig senere polish-oppgave. Full + begrunnelse i FEATURE_BACKLOG.md. + **Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ekte + typesjekket produksjonsbuild kompilerte rent, `docker compose up -d + --build teecup_frontend` (gjenskapte også `teecup_api` som vanlig + bivirkning). Begge containere boot-et rent, `/health` → 200, + scorekort-siden bekreftet 200, `teeoff.no` upåvirket. + +- **De to avgrensede scorekort-oppfølgerne fra samme dag BYGGET, + BROWSERVERIFISERT OG LIVE (2026-07-30):** (1) ny "Se full + rundestatistikk"-lenke nederst på `round-scorecard.tsx` (symmetrisk med + den eksisterende motsatte lenken på statistikksiden); (2) mottatte + slag-hintet (vist FØR et hull er fylt ut) endret fra "−N"-tekst til + prikker (`StrokeDots`, `round-detail.tsx`, i BÅDE `ScorecardGrid` og + `SideScorecardGrid`) — selve tallet bevart i `aria-label` for + tilgjengelighet. Selve spørsmålet om å slå sammen scorekort/statistikk/ + live-registrering til én visning er BEVISST IKKE avgjort — bruker + ønsker en fremtidig brukertest først. + **Browserverifisert grundig i en isolert scratch-nettleserøkt** (samme + mønster som resten av uken): en HCP 28-spiller (course handicap 30) + bekreftet å vise nøyaktig to prikker på hull 1-12 i live- + registreringsgridet (riktig ut fra 30-18=12 ekstra slag), den nye + lenken bekreftet klikkbar og navigerte korrekt til `/stats`. Ekte + typesjekket produksjonsbuild kompilerte rent. Ingen konsollfeil (kun en + godartet, urelatert WebSocket-advarsel fra rask sidenavigasjon). + **Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ingen + migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte + også `teecup_api` som vanlig bivirkning). Begge containere boot-et + rent, `/health`/`/dashboard`/scorekort-siden → 200, `teeoff.no` + upåvirket. + +- **Åtte nye turneringsformater: BACKEND FERDIG, SCRATCH-VERIFISERT OG + RULLET UT LIVE (2026-07-30):** brukeren ba om å ta fatt på det + lenge noterte "flere turneringsformater"-punktet fra 2026-07-19. Listen + ble utvidet fra fire til åtte (Shamble, Chapman/Pinehurst, Bingo Bango + Bongo, Money Ball/Lone Ranger, Nassau Match Play lagt til; "High-low- + high"/"Try all" presist avklart av bruker med et fullt utregnet + eksempel; "Robbins" droppet). Full plan skrevet og godkjent (plan- + modus) FØR bygging, deretter bygget ETT format om gangen i rekkefølgen + Chapman → Nassau → Københavner → Bingo Bango Bongo → Flaggturnering → + Shamble → Money Ball → High-low-high, motor→migrasjon→API→scratch- + verifisering per format FØR neste startet. + **Arkitektonisk hjem** (bekreftet av bruker "begge, fra start"): flatt- + felt-formater (Københavner/BBB/Flag) → frittstående runder OG den + individuelle org-turnering-modellen (ADR-037), ALDRI org-lagturneringer + (ADR-011s to-lags-modell passer strukturelt ikke). To-siders-formater + (Chapman/Nassau/Shamble/Money Ball/High-low-high) → frittstående runder + OG org-lagturneringer, ALDRI den individuelle org-modellen. Shamble/ + Money Ball i frittstående runder: ETT LAG = HELE RUNDENS deltakersett + (bekreftet av bruker, INGEN `round_side`) -- flere lag kobles via + eksisterende `flight_group_id`-leaderboard. + **Ny delt `ENGINE_FORMAT_ALIASES`-mekanisme** i `app/handicap.py` -- + Chapman/Shamble/Money Ball/High-low-high aliaserer til + foursome/singles/singles/fourball for HCP-beregning (ingen dupliser + allowance-logikk), løst opp FØRST i `parse_allowance_config`/ + `compute_and_store_side_handicaps`/`relative_strokes_for_match`. + **Reelle funn/presiseringer underveis, alle rettet FØR utrulling:** + Nassau kan hindres av org-matchers eksisterende ALREADY_DECIDED-sperre + (ADR-012) hvis overall-matchen avgjøres tidlig -- dokumentert v1- + begrensning, ikke fikset. Københavner-poeng må regnes om for ALLE tre + deltakerne samtidig (eneste scoring_method som bryter "regn om for ÉN + deltaker"-mønsteret). Money Ball-rotasjon er basert på POSISJON i spilt + rekkefølge, ikke rått hullnummer. High-low-high passer ikke inn i den + ternære `HoleResult`-cachen -- egen dedikert leseendepunkt per system + (som Nassau), generisk `status_text` viser en ufarlig statisk "AS"- + plassholder for dette formatet. To reelle "manglende SELECT-kolonne"- + krasj (bbb_sweep_bonus_enabled, shamble_best_n) fanget og rettet under + scratch-testing, før noe nådde en antatt-ferdig tilstand. + **Verifisert grundig:** `handicap_engine.py`-enhetstester 98/98 (opp fra + 63 FØR denne runden), full scratch-API-verifisering i begge relevante + systemer per format (isolert `teecup_app_scratch`-rolle + isolert + scratch-MinIO + engangs API-container) -- over 400 sjekker totalt på + tvers av de åtte formatenes testskript, inkl. High-low-high sin + eksakte gjenskaping av brukerens eget håndregnede eksempel ("1-1 etter + hull 1", "2-1 til lag 2"). `test_isolation.sql` 12/12 uendret gjennom + hele runden (migrasjonene 041-050 er rent additive). + **IKKE bygget ennå, bevisst neste steg:** frontend for samtlige åtte + formater (nye format-valg i opprett-runde/opprett-økt, nye + resultatvisninger) — backend er fullt funksjonelt og testet, men + ubrukelig fra selve appen inntil frontend bygges, samme lagdelings- + mønster som ADR-037/038/039. + **Rullet ut mot ekte systemer 2026-07-30**, bruker bekreftet eksplisitt + ("Ja takk"): migrasjonene 041-050 kjørt i rekkefølge mot ekte + `teecup_db` (alle 10 rene, additive `DROP/ADD CONSTRAINT`+nye + tabeller/kolonner, ingen eksisterende rader rørt), `test_isolation.sql` + fortsatt 12/12, deretter `docker compose up -d --build teecup_api`. + Containeren boot-et rent (`Application startup complete`), `/health`/ + `/dashboard` → 200, `teeoff.no` upåvirket. Verifisert presist at det nye + Nassau-endepunktet faktisk når FastAPI gjennom hele produksjonsstacken: + et anonymt kall ga korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404. + Scratch-miljøet (isolert rolle/database/MinIO/API-container, alle + test-skript) ryddet opp etterpå, som vanlig. Se FEATURE_BACKLOG.md for + full detalj per format. + +- **Driftsvarsel ved ny konto, BYGGET, SCRATCH-VERIFISERT OG LIVE + (2026-07-30):** brukeren ba om en e-post til seg selv + (`hei@erol.no`) hver gang noen oppretter en HELT NY TeeCup-konto — + avklart eksplisitt (AskUserQuestion) at dette gjelder kontoopprettelse + generelt (ikke turnering-selvregistrering, ADR-017, som var det andre + alternativet). Ingen migrasjon — ren kode-endring. + Ny `settings.NEW_ACCOUNT_ALERT_EMAIL` (`app/config.py`, env-variabel + `TEECUP_NEW_ACCOUNT_ALERT_EMAIL`, default `hei@erol.no` — egen setting + fremfor hardkodet adresse, kan endres uten ny utrulling). Ny + `send_new_account_alert_email()` i `app/email.py` (alltid norsk — internt + driftsvarsel til én fast, kjent mottaker, ikke brukervendt i18n-tekst). + **Kjernestykket:** `verify_magic_link` (`app/routers/auth.py`) er den + ENESTE plassen en `app_user`-rad noensinne settes inn (bekreftet med + `grep -rn "INSERT INTO app_user"` — null andre treff) — utvidet til å + fange `is_new_account` (sann KUN når selve INSERT-en faktisk vant, ikke + ved en samtidig konflikt ELLER en sekundær-e-post-innlogging som løses + til en eksisterende konto, `FEATURE_BACKLOG.md` "Én person, flere + e-postadresser"). Selve e-postutsendingen skjer BEVISST ETTER at + `plain_connection()`-blokken er lukket (samme "e-post skal aldri sendes + mens en tilkobling/transaksjon holdes åpen"-prinsipp som resten av + appen), med samme `DEV_LOG_MAGIC_LINKS`/`SMTP_CONFIGURED`/try-except- + mønster som all annen e-postutsending — en driftsfeil i selve + sendingen kan aldri endre innloggingsresponsen. + **Scratch-verifisert, 8/8 sjekker:** varsel logget nøyaktig én gang ved + aller første innlogging for en ny e-post, INGEN nytt varsel ved en + påfølgende innlogging for SAMME konto, og et helt nytt, eget varsel for + en ANNEN ny e-post (uendret telling for den første). `test_isolation.sql` + 12/12 uendret (ingen skjemaendring). + **Rullet ut live 2026-07-30**, sammen med migrasjonene 041-050 over (én + samlet utrulling, bruker bekreftet eksplisitt): ingen migrasjon for + denne delen isolert, dekket av samme `docker compose up -d --build + teecup_api`-kjøring, `/health`/`/dashboard` → 200, `teeoff.no` + upåvirket. + +- **Login-skjermen redesignet ("Forest Green", inspirert av et eksternt + design-verktøy kalt "Stitch"), BYGGET OG LIVE (2026-08-01/02):** + brukeren ba eksplisitt om et V0-prompt basert på Stitchs skisse, kjørte + det selv, sendte zip-eksporten tilbake. `frontend/components/login- + form.tsx` skrevet fullstendig om — V0s nye visuelle skall (låst + fargepalett `const C = {...}`, WCAG-kontrast verifisert presist, ikke + anslått, for hvert fargevalg) flettet med 100 % ekte logikk fra den + gamle komponenten uendret: `sendLink()` (`POST /auth/request-link`), + passord-innlogging (`POST /auth/login-password`), invitasjonskode- + oppslag (`GET /public/tournaments/by-code/{code}` + navigasjon). + `TwoFactorVerifyForm`/`TwoFactorSetupForm` (ADR-021) beholdt HELT + uendret, kun rendret inni det nye kortskallet. **Reell bug funnet under + integrering, browserverifisert:** `app/page.tsx` hadde sin egen + `<Wordmark/>`+tagline-header OVER selve `LoginForm`, som nå OGSÅ hadde + fått sin egen header inni det nye kortet — duplikat. Fjernet den ytre + headeren + det nå ubrukte `Wordmark`-importet fra `page.tsx`. + **Rullet ut live**, bruker bekreftet eksplisitt ("Rull ut live. Jeg har + comitted til git og kan reversere om nødvendig."): ren frontend-endring, + ingen migrasjon. + +- **Dashbordet redesignet i samme visuelle retning, BYGGET OG LIVE + (2026-08-01/02), to runder samme dag:** første runde (V0-eksport) + skrev om `dashboard.tsx` fra bunnen — flettet ekte ADR-035-datalag + (organisasjoner/turneringer/runder/HCP-historikk/venner/varsler) med + V0s nye visuelle seksjoner, la til en NY «Venner på banen»-seksjon + (bekreftet av bruker at den skal ligge RETT UNDER hurtighandlingene, + jf. Stitchs egen skisse — jeg hadde ikke selv sjekket dette FØR + brukeren spurte eksplisitt, rettet før prompten ble sendt). + **Ny backend-endepunkt bygget for dette:** `GET /friends/on-course` + (`app/routers/rounds.py`, ny `FriendOnCourseEntry`-modell) — venner med + en PÅGÅENDE runde, eller en fullført innen siste 24 timer (bekreftet + av bruker), filtrert gjennom SAMME synlighets-SQL som `_can_view_ + round` sin venner-gren (ADR-036 fase 2) — ingen egen, parallell + synlighetsregel. + **Andre runde, presise fargekorreksjoner mot Stitchs FAKTISKE + skjermbilde** (ikke bare beskrivelse — brukeren lastet opp det ekte + bildet etter at jeg først måtte innrømme jeg hadde slettet den + opprinnelige zip-en og derfor sammenlignet blindt): pikselverdier + hentet presist via PIL i en engangs Docker-container (Stitchs egen + knappegrønn viste seg å være `#2d950c`, kontrast mot hvit tekst kun + 3,88:1 — FEILER WCAG AA, flagget proaktivt FØR den ble brukt). + `const C` fikk `primary:"#1f6b08"` (samme fargefamilie, kontrast + ~6,6:1, verifisert med samme relative-luminans-formel), `primaryInk: + "#ffffff"`, `borderSoft:"#e4ebe3"`, ny `AVATAR_HUES`-array (pastell- + par til venne-avatarer). Header endret fra glassmorfisme til flat + opak bakgrunn, flagg-ikonet fikk lys grønn bakgrunn i stedet for + oransje, `QuickAction`/`ShortcutButton` og `LiveFriends`-kortene + fargekorrigert tilsvarende. + **Ny fast bunn-fanerad** (`BottomTabBar`, eksportert fra + `dashboard.tsx`, brukt av flere sider): fem faner (Hjem/Runder/ + Turneringer/Profil/Mer). **Reell rute-kollisjon funnet og rettet FØR + utrulling** (samme klasse feil som tidligere i prosjektet, `/rounds` + vs. API-prefikset) — V0s genererte `href="/rounds"`/`href="/tournaments"` + pekte begge feil (ingen slik side finnes) — rettet til `/my-rounds` og + et ankerpunkt `/dashboard#kommende-turneringer` (ingen egen turnering- + liste-side finnes). Ny, tidligere ikke-eksisterende `/more`-side bygget + (`components/more-menu.tsx`, `app/more/page.tsx`) som femte fanes reelle + mål — samler Konto/Venner/Varsler/organisasjoner ett sted, ekte + `handleLogout()`. `<main>`-padding økt (`pb-24`) for å ikke overlappe + den nye faste bunnraden. + `install-prompt.tsx` fikk kun ny JSX (mørk grønn gradient-banner), + 100 % av den ekte iOS/Android-deteksjons-/`beforeinstallprompt`-/ + 14-dagers-utsettelseslogikken uendret. + **Rullet ut live**, bruker bekreftet eksplisitt: ingen migrasjon for + visuelle deler, ny `/friends/on-course`-ruten dekket av samme + `docker compose up -d --build teecup_api teecup_frontend`. Begge + containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` + upåvirket. + +- **«Det store grepet»: sammenhengende rundeoppsett + delt Score/ + Scorekort/Leaderboard-navigasjon — steg 1+2 av 5 BYGGET, SCRATCH-/ + BROWSERVERIFISERT OG LIVE (2026-08-02), se ADR-040 for full + beslutningslogg:** direkte oppfølging av login-/dashbord-redesignet — + brukeren pekte på to strukturelle hull utover selve fargespråket + (rundeoppsettet spredt over to skjermer, Score/Scorekort/Leaderboard + deler ingen fast navigasjon). Fem beslutninger bekreftet (ADR-040 + Beslutning A-E), plan skrevet som en levende Artifact-skisse før + bygging. + **Steg 1 (backend, HCP-prosent alltid justerbar + Match-HCP-bryter):** + ny migrasjon `051_round_allowance_override.sql` (`round.allowance_ + override jsonb`), koblet inn i `_recompute_side_handicaps`/`_relative_ + strokes_for_round`/`POST`+`PATCH /rounds` (`app/routers/rounds.py`). + Gjenbruker ADR-014s allerede ferdigbygde motor uendret (`match_play_ + strokes()`, `parse_allowance_config`) — frittstående runder manglet + bare selve kolonnen. + **Steg 2 (ren kode-fiks, ikke V0): spillere i scorekortet var ALDRI + gruppert lagvis** — reelt, tidligere udokumentert funn rapportert av + bruker med en konkret lenke (fourball-runde der to kjente lagkamerater + ble vist interleaved). Bekreftet i koden: `ScorecardGrid` (`round- + detail.tsx`) og `MatchScorecardGrid` (`round-scorecard.tsx`) sorterte + ingen av dem på `round_side_id`. Fikset med stabil sortering (side A + samlet, deretter side B) i begge. + **Scratch-verifisert presist:** 100 %→50 %-prosent ga `playing_ + handicap` 24/10 → 12/5 (nøyaktig som beregnet for hånd), `use_ + matchplay_handicap:false` ga rå 12/5 i stedet for differensial 7/0. + Lag-grupperingsfiksen browserverifisert mot en fersk scratch-fourball- + runde som gjenskapte brukerens eget scenario nøyaktig (interleaved + tilføyelsesrekkefølge A→Rødt, B→Blått, C→Rødt, D→Blått) — bekreftet + visuelt i BEGGE visningene at lagene nå vises samlet. + **Rullet ut mot ekte systemer 2026-08-02**, bruker bekreftet eksplisitt + (plan vist FØR migrasjonen): migrasjon 051 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, anonym + `PATCH /rounds/{ukjent-id}` ga korrekt `401` (ikke en rå 404), `teeoff.no` + upåvirket. + **Gjenstår (steg 3-5 av 5, ingen V0-prompt skrevet/sendt ennå):** + V0-prompt 1 (den samlede rundeoppsett-veiviseren), V0-prompt 2 (den + delte fane-raden + spørsmålet om lag-markering utover sortering), + deretter integrering/browserverifisering/utrulling av begge. Se + FEATURE_BACKLOG.md for detaljert arbeidsnotat. + +- **«Det store grepet»: steg 3 av 5 (V0-prompt 1, rundeoppsett- + veiviseren) BYGGET, GRUNDIG VERIFISERT OG LIVE (2026-08-02), samme + dag:** V0-prompten (Beslutning A/B) skrevet, kjørt av bruker (zip 25), + `frontend/components/new-round.tsx` skrevet fullstendig om — + V0s 5-stegs veiviser-skall flettet med ekte logikk (offisiell/egen + bane-søk inkl. nearby-geolokasjon, ekte egen-bane-opprettelse portert + fra tidligere versjon, ekte `/people/search`, ny `allowance_override`- + konstruksjon i steg 2 samme mønster som `CreateSessionCard`, og en helt + ny innsendingsorkestrering — runden+deltakere+sider/lineup finnes + FØRST når hele veiviseren er fullført, ulikt den gamle "opprett runden + først"-flyten). + **Reelle feil funnet og rettet under integrering:** V0s egen + `Tee`-type manglet kjønnsfelt (ville vist ufiltrerte utslag) — lagt til + `genders`, filtrert riktig. To TS-feil (prop-spredning som overskrev + `course`/`ownGender` tilbake til `null`, en `"x"`-kjønn-mismatch i + tee-filtreringen). + **Verifisert grundig i isolert scratch:** full produksjonsbuild (alle + ruter listet), to komplette nettleser-gjennomkjøringer mot en fersk + scratch-backend (én Fourball-runde med egen bane/gjest/to sider/90 % + HCP-prosent, én Slagspill-runde med rene standardvalg) — bekreftet + direkte mot databasen at `allowance_override` ble bygget nøyaktig + riktig i begge tilfeller (`per_player` for Fourball siden det ikke er + en side-enhet, `null` for Slagspill), sidene/tildelingene stemte, og + HCP ble beregnet korrekt fra ekte profildata. + **Rullet ut live 2026-08-02**, bruker bekreftet eksplisitt: ingen + migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte + også `teecup_api` som vanlig bivirkning). Begge containere boot-et + rent, `/health`/`/dashboard`/`/my-rounds/new` → 200, `teeoff.no` + upåvirket. Full detalj i ARCHITECTURE_DECISIONS.md (ADR-040). + +- **«Det store grepet»: steg 4-5 av 5 (V0-prompt 2, den delte Score/ + Scorekort/Leaderboard-fane-raden + integrering) BYGGET, GRUNDIG + VERIFISERT OG LIVE (2026-08-02), samme dag — ADR-040 dermed HELT + FERDIG:** V0-prompten (Beslutning C/D) bevisst smalere i omfang enn + prompt 1 — kun selve header/fane-chrome-en, ikke en redesign av + Scorekort-sidens innhold (den sammenslåingen med Statistikk er ren + kode-sammenstabling, løst direkte uten V0). Kjørt av bruker (zip 26) + — ny `components/round-header.tsx` (tatt inn uendret: kontekstblokk + + tre-fanet segmentert kontroll + en "Administrer"-knapp som åpner en + bunnsheet-dialog med "Spillere og runde"-lenke + "Fullfør runde" + + to-stegs "Slett runde"). + **Ekte datalag:** ny `components/round-page-shell.tsx` — henter + rundedata for headeren, implementerer `onFinishRound`/`onDeleteRound` + som egne, selvstendige kall (samme `POST .../complete`/`DELETE ...` + som før, men uavhengig siden komponenten deles av alle tre sidene). + **Wiret inn:** Score (`round-detail.tsx` sin gamle egne header + fjernet, erstattet med `RoundPageShell` — den interne "Score"/ + "Spillere og runde"-fanevekslingen bevisst BEHOLDT som lavrisiko-valg, + nås nå i tillegg via headerens dialog med en ny `?tab=manage`-URL- + parameter), Scorekort (`round-scorecard.tsx`+`round-stats.tsx` slått + sammen til ÉN side via en ny `embedded`-prop på begge -- statistikk + RETT UNDER scorekortet, gammel `/stats`-rute er nå en redirect dit), + Leaderboard. + **Reelt funn og fiks under integrering:** to `<main>`-landemerker på + samme side etter sammenslåingen (ugyldig HTML/a11y) -- rettet med en + `ContentTag`-switch (`div` når embedded) i `round-stats.tsx`. Fjernet + nå overflødige kryss-lenker, kollapset "Se scorekort"/"Se full + rundestatistikk" til én knapp i `round-detail.tsx` sitt fullført- + banner. + **Verifisert grundig i isolert scratch:** full produksjonsbuild, full + nettleser-gjennomkjøring av hele "Administrer"-flyten -- headeren + konsistent på tvers av alle tre fanene, dialogens lenke til "Spillere + og runde" bekreftet å faktisk åpne riktig intern fane, ekte "Fullfør + runde" (bekreftet `completed_at` satt via API etterpå) og ekte + to-stegs "Slett runde" (bekreftet navigerte korrekt til tom-tilstand + etterpå). Traff en Turbopack-flakighet i scratch-dev-serveren + underveis (stuck spinner) -- løst med frisk containerstart, bekreftet + IKKE en kodefeil. + **Rullet ut live 2026-08-02**, bruker bekreftet eksplisitt: ingen + migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte + også `teecup_api` som vanlig bivirkning). Begge containere boot-et + rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. + **Bevisst IKKE dekket:** Beslutning E sitt spørsmål om lag- + gruppering trenger mer enn celle-fargelegging (sortering i seg selv + ble fikset i steg 2) -- prompt 2 ble bevisst avgrenset til kun + header-chrome-en, dette spørsmålet er derfor fortsatt åpent, ikke + stilt til V0 ennå. Se FEATURE_BACKLOG.md. + +- **Rundeoppsett-veiviseren: dato/klokkeslett forhåndsutfylt, LIVE + (2026-08-02), samme dag:** brukeren ba om at dagens dato og gjeldende + klokkeslett skal være default når man setter opp en runde, i stedet + for tomme felt. `frontend/components/new-round.tsx` sitt Steg 1 fikk + to nye lokale hjelpefunksjoner (`todayIso()` -- lokal tidssone, ikke + UTC, portert fra en tidligere versjon av filen, samme presisjonshensyn + som `<input type="date">` alltid har krevd i dette prosjektet; + `nowTimeString()`) — `date`/`time`-state initialiseres nå med disse i + stedet for tomme strenger (dato respekterer fortsatt `prefillPlayedAt` + fra "legg til en flight til"-flyten der den er satt). Browserverifisert + i isolert scratch: begge feltene viste korrekt dagens dato/klokkeslett + ved første besøk til Steg 1s felt-visning. Rullet ut live, ingen + migrasjon, kun `docker compose up -d --build teecup_frontend`, `/health`/ + `/my-rounds/new` → 200, `teeoff.no` upåvirket. +- **Grønnfarge-vasken fjernet på tvers av hele appen, BYGGET, SCRATCH-/ + BROWSERVERIFISERT OG LIVE (2026-08-02), samme dag:** brukeren delte et + Stitch-referansebilde og et skjermbilde av `/my-rounds/new` og spurte + hvorfor TeeCup fortsatt virket "fast i det lysegrønne utseendet" til + tross for at Stitch-referansen tydelig bare bruker grønt to steder + (valgt tilstand + CTA). Diagnostisert presist FØR noe ble endret (ikke + gjettet): `--primary` (den mettede merkevaregrønnen) var riktig, men + (1) de "nøytrale" tokenene (`--background`/`--muted`/`--accent`/ + `--border`/`--secondary`) hadde alle en svak grønn hue (130-145) + iblandet i `globals.css` selv når de skulle være ren grå -- ga et + vedvarende grønt skjær på nesten hver bakgrunn/hover/kant i hele appen; + og (2) et helt separat, mye mer synlig problem: et `bg-primary/15 + text-primary`-ikonchip-/avatar-/pille-mønster var brukt UBETINGET + (uavhengig av valgt/aktiv-tilstand) i over 30 komponentfiler -- + `grep -rl "bg-primary/(5|10|15|20)|bg-accent/"` traff nesten hele + `components/`-mappen. + **To lag fikset, med bevisst avgrensning:** + 1. **Token-nivå** (`frontend/app/globals.css`, `:root`/`.dark`/ + `@media (prefers-color-scheme: dark)`, alle tre synkronisert): + nøytrale tokens satt til ekte `chroma 0` (f.eks. `--background: + oklch(0.985 0.005 130)` → `oklch(0.985 0 0)`). `--primary`/ + `--brand-orange`/`--destructive`/`--ring`/`--info`/`--gold`/ + `chart-1..6` UENDRET -- kun de tokenene som skulle vært "nøytral + grå" ble rettet, ikke merkevarefargene. `DESIGN_SYSTEM.md` sin + token-tabell + en ny historikk-note oppdatert til å reflektere dette. + 2. **Ikonchip-sveip** (~17 filer, ~35 enkeltsteder, gjennomgått ett og + ett med `grep -B1 -A3` for kontekst FØR endring, ikke et blindt + sed-sveip over hele treet): `bg-primary/{5,10,12,15,20}` + + `text-primary` byttet til `bg-muted`/`text-muted-foreground` KUN der + mønsteret var ubetinget (samme farge uansett tilstand) -- + seksjonshode-ikoner (`account-settings.tsx` × 7, `two-factor- + flow.tsx` × 3, `tournament-program.tsx` × 2, m.fl.), avatar-/ + initial-chips (`new-round.tsx` sin `Avatar`, `friend-profile.tsx`, + `friends.tsx`, `round-detail.tsx` sitt medspiller-søk), tomtilstand- + ikoner (`course-rounds.tsx`, `own-rounds.tsx`, `rounds-stats- + summary.tsx`), kategori-piller (`friends.tsx`), og en gjennomgående + navigasjons-ikonchip (`round-header.tsx`, vises på ALLE tre + rundeskjermene). **Bevisst latt urørt** der grønt faktisk BÆRER + mening (dokumentert i `DESIGN_SYSTEM.md`s "primær = valgt/aktiv, + bekreftet"-regel): ternary-baserte valgt-/aktiv-tilstander (`Choice + Card`, `Pill`, kategori-avkrysning), "bekreftet"-tilstander + (`public-tournament.tsx` sin "Du er påmeldt!"-status, + `verify-form.tsx`/`verify-email-form.tsx` sine suksess-skjermer), + territorium-baren (match-lederskap, dokumentert bespoke mønster), + GIR-kompassets bevisste sentercelle, scorekortets "hero-rad" + (kommentert i koden som en bevisst fremhevet rad), og "Valgt + bane"-bekreftelsesbanneret i `new-round.tsx` (bekrefter et fullført + valg, samme semantikk som en "confirmed"-tilstand). + **Scratch-verifisert grundig, to browserrunder** (isolert + `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API- + container + full produksjonsbuild av frontend, samme mønster som hele + prosjektet): ekte innlogging, profil-fullføring, og skjermbilder av + nøyaktig samme skjerm brukeren viste (`/my-rounds/new`), pluss + dashbord og `/my-friends` -- bekreftet ikonchips nå nøytral grå, + kortbakgrunner hvite, sidebakgrunn ekte grå, grønt kun på + steg-indikatoren/wordmarket/CTA-knappen. Ingen konsollfeil. Ekte + typesjekket produksjonsbuild kompilerte rent begge runder. + **Rullet ut live 2026-08-02**, bruker bekreftet eksplisitt (etter at + skjermbildene ikke lot seg vise i klienten -- bekreftet muntlig i + stedet: "implementer endringen"): ingen migrasjon, ren frontend- + endring, `docker compose up -d --build teecup_frontend` (gjenskapte + også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). + Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` + upåvirket. +- **Den EGENTLIGE gjenværende grønnfarge-kilden funnet og fikset, + BROWSERVERIFISERT AV BRUKER SELV, RULLET UT LIVE (2026-08-02), samme + dag:** brukeren fortsatte å se `#F7FAF8` som sidebakgrunn på ekte + produksjon selv etter forrige runde -- viste ekte DOM-inspeksjon fra en + nettleser som aldri hadde vært brukt før (utelukket cache/service + worker som årsak). Diagnostisert presist (ikke gjettet): `--background` + i `globals.css` var faktisk korrekt (`#fff`, bekreftet direkte mot den + servert CSS-bunten via `curl`), men `dashboard.tsx` og `more-menu.tsx` + hadde sin EGEN, LÅSTE JS-fargepalett (`const C = {...}`, fra "Forest + Green"-redesignet 2026-08-01) med `bg: "#f7faf8"`, satt via `style={{ + backgroundColor: C.bg }}` på sidens ytterste wrapper/header/loading- + spinner -- en INLINE STYLE bypasser CSS custom properties helt, uansett + hva `globals.css` sier. Dette brøt `DESIGN_SYSTEM.md` sin egen, + eksisterende regel ("ALDRI hardkodede farger... bryter automatisk lys/ + mørk-tema-logikk") -- introdusert av meg selv i en tidligere runde uten + å fange bruddet da. + **Fikset:** `bg: "#f7faf8"` fjernet fra `const C` i BEGGE filer, alle + fire `style={{ backgroundColor: C.bg }}`-stedene (dashboard.tsx sin + loading-spinner/hovedwrapper/header, more-menu.tsx sin hovedwrapper) + erstattet med Tailwind-klassen `bg-background` (nå faktisk `#fff`, + forrige rundes fiks) -- ren fjerning, ikke bare en ny hex-verdi, slik at + en FREMTIDIG token-endring automatisk forplanter seg hit også. Et femte + sted (en varselboble sin `boxShadow`-"utskjærings"-ring mot side- + bakgrunnen) endret fra `${C.bg}` til `var(--background)` -- kan ikke + bruke en Tailwind-klasse inni en inline `boxShadow`-streng, men + `var(--background)` holder den fortsatt koblet til token-systemet + fremfor en ny hardkodet verdi. `login-form.tsx`/`round-stats.tsx` sine + egne `const C`-paletter sjekket og bekreftet IKKE berørt (ingen egen + `bg`-bakgrunnsverdi der -- `round-stats.tsx` sin er allerede `var(-- + chart-N)`-referanser, riktig fra før). + **Verifisert presist, denne gangen mot selve det bygde bunten, ikke + bare kildekoden:** `grep -rl f7faf8` mot HELE `.next/static`-mappen + INNI det faktiske produksjonsimaget (`docker run --rm --entrypoint sh + teecup-teecup_frontend ... grep`) ga null treff -- streng-nivå-bevis at + verdien er borte fra det som faktisk sendes til nettleseren, ikke bare + fra kilden. Ekte typesjekket build kompilerte rent. + **Rullet ut live 2026-08-02**, bruker bekreftet eksplisitt (viste + konkret DOM-bevis, ba om fiks): ingen migrasjon, `docker compose up -d + --build teecup_frontend` (gjenskapte også `teecup_api` som vanlig + bivirkning). Begge containere boot-et rent, `/health`/`/dashboard`/ + `/more` → 200, `teeoff.no` upåvirket. + **Lærdom, notert eksplisitt for fremtidige runder:** når en visuell + fiks angivelig er utført men brukeren fortsatt ser feil farge, sjekk + ALLTID for hardkodede inline `style={{...}}`-verdier og låste lokale + JS-fargepaletter (`const C = {...}`, kjent mønster fra "Forest Green"- + arbeidet) FØR man antar det er nettleser-cache -- CSS-token-nivå- + verifisering alene (kun `globals.css`/den kompilerte CSS-bunten) er + IKKE tilstrekkelig bevis når V0-avledede skjermer kan ha sin egen, + parallelle fargekilde som omgår token-systemet helt. +- **CLAUDE.md splittet i regler + historikk (2026-08-02):** filen hadde + vokst til 6500+ linjer og ble injisert i sin helhet i konteksten hver + eneste forespørsel -- brukeren foreslo en konkret splitt (invarianter i + CLAUDE.md, kronologisk historikk i en egen fil), som ble vurdert å + genuint hjelpe (mindre kontekstforbruk per runde, mindre risiko for at + de faktiske reglene drukner i historien). Hele "Status"-seksjonen + flyttet til denne filen (`CHANGELOG.md`, ny), verifisert byte-for-byte + identisk med originalen via `diff` FØR noe ble slettet. CLAUDE.md + redusert til ~117 linjer (autoritative kilder, sikkerhetsregler, + arkitektur-invarianter, tilgjengelighet, navneformat, arbeidsmåte) + + en pekende seksjon til denne filen. 47 nå-utdaterte + `CLAUDE.md-status`/`CLAUDE.md sin statuslogg`-kryssreferanser rettet + til `CHANGELOG.md` på tvers av `ARCHITECTURE_DECISIONS.md`/ + `FEATURE_BACKLOG.md`/`DESIGN_SYSTEM.md`. Ren dokumentasjonsendring, + ingen kode/migrasjon/utrulling. +- **"Bruk course handicap-justering" skjult for ordinære formater, LIVE + (2026-08-02), samme dag:** brukeren påpekte at bryteren er unødvendig + for rene Slagspill-/Stableford-runder — bedt om å skjules (ikke + slettes) og alltid holdes funksjonelt PÅ for disse to formatene. + `frontend/components/new-round.tsx`: ny `ORDINARY_FORMATS = new + Set(["slagspill", "stableford"])`, en `useEffect` i `NewRound` som + tvinger `useCourseHcp` tilbake til `true` hver gang `format` går inn i + dette settet (dekker et bytte FRA et annet format der bryteren var + slått av), og selve `ToggleRow` for "Bruk course handicap-justering" + gatet bort i `Step2` for disse to formatene. Ren UI-/state-endring — + `buildAllowanceOverride()` er urørt, returnerer fortsatt `null` (ingen + override sendt) når alt er på standardverdi, akkurat som før. Ekte + typesjekket build kompilerte rent. **Rullet ut live**, ingen + migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte + også `teecup_api` som vanlig bivirkning). Begge containere boot-et + rent, `/health`/`/my-rounds/new` → 200, `teeoff.no` upåvirket. +- **"HCP-prosent"-feltet skjult når "Bruk handicap" er av, LIVE + (2026-08-02), samme dag, oppfølging av forrige punkt:** brukeren + påpekte at feltet ikke ga mening synlig når HCP uansett er slått av, + og presiserte eksplisitt at dette skal gjelde universelt (alle + formater), ikke bare de to ordinære. Løst med én eneste betingelse + (`{useHcp && (...)}`) rundt feltet i `Step2` — siden ALLE spilleformer + deler nøyaktig denne ene komponenten for "Avanserte handicap- + innstillinger", dekker denne ene endringen automatisk hver + spilleform-visning uten noen per-format-liste (ulikt forrige punkts + `ORDINARY_FORMATS`-gating, som var format-spesifikk med hensikt). + Bevisst IKKE nullstilt `hcpPercent`-state når feltet skjules — en + eventuell gjenværende verdi er funksjonelt harmløs (backend sin + `compute_and_store_side_handicaps` returnerer tidlig når + `use_handicap` er usann, FØR `strategy`/prosent leses i det hele + tatt), så ingen ekstra state-rydding var nødvendig utover selve + skjulingen. Ekte typesjekket build kompilerte rent. **Rullet ut + live**, ingen migrasjon, `docker compose up -d --build + teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning). + Begge containere boot-et rent, `/health`/`/my-rounds/new` → 200, + `teeoff.no` upåvirket. +- **Reell inkonsistens funnet av bruker og fikset, LIVE (2026-08-02), + samme dag:** brukeren viste et skjermbilde av Fourball med "Bruk + handicap" AV, men "Bruk course handicap-justering" OG "Bruk + matchplay-handicap" fortsatt vist som PÅ — begge sub-bryterne var kun + gatet på format (`ORDINARY_FORMATS`/`twoSided`), ALDRI på selve + `useHcp`-hovedbryteren. Ingen funksjonell bug (backend sin + `compute_and_store_side_handicaps` returnerer tidlig når + `use_handicap` er usann, FØR disse leses — samme resonnement som + forrige punkts `hcpPercent`), men en reell visuell selvmotsigelse. + Fikset ved å legge til `useHcp &&` foran begge betingelsene i `Step2` + (`frontend/components/new-round.tsx`) — siden ALLE 16 formater deler + denne ene komponenten, dekker denne ene endringen konsistent + atferd for hver spilleform uten en per-format-sjekk. Når "Bruk + handicap" er av, vises nå KUN selve hovedbryteren i "Avanserte + handicap-innstillinger" — ingen sub-innstillinger, uansett format. + **Samme bug funnet i en parallell, ikke-forespurt fil under + undersøkelsen** (`tournament-program.tsx` sin `CreateSessionCard`, + org-turnering-øktoppsettet) — samme tre brytere, samme mangel på + `useHcp`-gating, og der attpåtil UTEN `twoSided`-gating på + "Bruk matchplay-handicap" i det hele tatt (vises alltid, uavhengig av + format). Flagget til bruker, IKKE fikset i denne runden (egen fil, + ikke det som ble spurt om). Ekte typesjekket build kompilerte rent. + **Rullet ut live**, ingen migrasjon, `docker compose up -d --build + teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning). + Begge containere boot-et rent, `/health`/`/my-rounds/new` → 200, + `teeoff.no` upåvirket. +- **Samme bug fikset i tournament-program.tsx (org-turneringens + økt-oppsett), LIVE (2026-08-02), samme dag:** bruker bekreftet at den + flaggede parallellfilen også skulle rettes. Enklere fiks enn i + new-round.tsx: `SessionFormat` her har KUN 10 to-sidede lag-format + (foursome/greensome/scramble_2/scramble_4/fourball/singles/chapman/ + shamble/money_ball/high_low_high) — org-lagturneringer har ingen flat + individuell-formatvariant (den hører hjemme i den separate + individuell-turnering-modellen, ADR-037), så det finnes ingen + `ORDINARY_FORMATS`/`twoSided`-distinksjon å ta hensyn til her, ulikt + den andre filen. Løsning: alle tre (`Bruk course handicap-justering`, + `Bruk matchplay-handicap`, `HCP-prosent`) samlet i én + `{useHandicap && (<>...</>)}`-blokk i `CreateSessionCard` + (`tournament-program.tsx`) — bekreftet at dette er ENESTE stedet i + filen disse tre bryterne finnes (ingen separat rediger-økt-variant med + samme mangel). Ekte typesjekket build kompilerte rent. **Rullet ut + live**, ingen migrasjon, `docker compose up -d --build + teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning). + Begge containere boot-et rent, `/health`/`/dashboard` → 200, + `teeoff.no` upåvirket. +- **Reell Tailwind-cascade-bug funnet og fikset, BROWSERVERIFISERT MED + PRESISE DOM-MÅLINGER, LIVE (2026-08-02), samme dag:** brukeren + rapporterte at siden ikke lot seg scrolle helt ned på Steg 2 i + rundeoppsett-veiviseren, med et skjermbilde som kuttet av rett før + "Antall hull"-knappene. IKKE en scroll-bug -- root cause presist + diagnostisert i en isolert scratch-nettleserøkt (samme mønster som + resten av uken): `main`s className hadde `pb-32` (128px, reservert + klaring for den faste Tilbake/Neste-linjen) OG `sm:py-8` (32px, + ment for generell luft på større skjermer) samtidig. Tailwind + emitterer responsive (`sm:`)-varianter i en EGEN media-blokk ETTER + grunnklassene i den kompilerte CSS-en -- så `sm:py-8` vant cascaden + over `pb-32` på ALLE skjermer 640px og bredere, UANSETT rekkefølge i + selve className-strengen. Bekreftet presist med `getComputedStyle`: + `padding-bottom` var faktisk 32px, ikke 128px, ved en bred viewport + (1998px) -- mens en smal mobilviewport (390px, under `sm:`-grensen) + fortsatt fikk riktige 128px, noe som forklarer hvorfor bugen var usett + til nå (all tidligere browserverifisering denne uken har vært på + 390px). Effekten: ved bredder ≥640px der totalt innhold tilfeldigvis + var kortere enn viewporten, fikk siden RETT OG SLETT ikke lov til å + scrolle langt nok til å avdekke "Antall hull"-knappene fullt ut -- + den faste navigasjonslinjen dekket de nederste ~58 av 64 pikslene. + **Fikset** ved å dele opp de vertikale paddingene til KUN topp + (`py-6`→`pt-6`, `sm:py-8`→`sm:pt-8`) slik at ingenting lenger kan + konkurrere med `pb-32` om `padding-bottom` uansett skjermbredde -- + `pb-32` er nå den ENESTE kilden til bunn-klaring. Grep'et gjennom HELE + frontend-treet etter samme `pb-N`+`sm:py-N`-mønster -- kun dette ene + stedet (`new-round.tsx`), ingen andre skjermer rammet. + **Verifisert presist, ikke bare "ser bedre ut":** eksakte + `getBoundingClientRect()`/`getComputedStyle()`-målinger FØR fiksen + (padding-bottom 32px, "18 hull"-knappen fra y=1378 til y=1442, fast + navigasjon fra y=1384 -- 58px reell overlapp bekreftet med tall, ikke + bare visuelt) og ETTER (padding-bottom 128px, full synlig klaring, + scrollHeight økte fra 1474 til 1570px -- nøyaktig de manglende 96px + = 128-32). Regresjonssjekket på 390px mobilviewport (uendret riktig + oppførsel, som allerede fungerte). Ekte typesjekket build kompilerte + rent (måtte rette en selvpåført JSX-kommentar-bug underveis -- en + bokstavelig `*/`-sekvens inni kommentarteksten min egen selv lukket + kommentaren for tidlig, rettet ved å omformulere). + **Rullet ut live**, ingen migrasjon, `docker compose up -d --build + teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning). + Begge containere boot-et rent, `/health`/`/my-rounds/new` → 200, + `teeoff.no` upåvirket. + +Neste steg: +0a. **Spillerliste-redesign — nå FAKTISK nettleser-bekreftet + (2026-07-27, full 22-skjerms gjennomgang):** rendrer korrekt, ingen + konsoll-feil. Ikke hvert enkelt interaksjonsdetalj (f.eks. gjeste- + kjønnsendringens reaktive utslagsfilter) klikket gjennom stykke for + stykke, men grunnleggende rendring/lasting er bevist, ikke lenger + bare typesjekket. +0b. **Rundeleaderboard — nå FAKTISK nettleser-bekreftet (2026-07-27):** + brutto/netto/poeng-veksling testet direkte i nettleseren, viste + korrekte tall og riktig form/farge-språk. +1. **Ferdig, kun for historikk:** dashbord-redesign (ADR-035) og + venner/kategorisert deling fase 1 (ADR-036) — begge designet + 2026-07-25 og siden BYGGET, SCRATCH-VERIFISERT OG RULLET UT LIVE + samme dag (se status over). Venner fase 2 (rundevisibilitet) og + fase 3 (ekte medspillere) er fortsatt ikke bygget. +3. **Frittstående rundeføring + detaljert statistikk — ADR-033, skrevet + 2026-07-22 og siden BYGGET/ITERERT KONTINUERLIG hver dag fram til + 2026-07-29 (se hele status-loggen over -- dette punktet er nå appens + klart mest utviklede område, ikke lenger "ikke bygget").** Beholdt her + UENDRET som historisk kontekst for selve grunnbeslutningen fra + 2026-07-22 (hvorfor/hvordan ADR-033 ble designet) -- ikke som en + påstand om at arbeidet fortsatt gjenstår. Brukeren avklarte 2026-07-22 + at dette skulle bli appens HOVEDFOKUS (turneringsoppsett skulle bli + ekstremt enkelt ETTER dette var på plass) — den største enkeltbeslutningen + i prosjektet siden ADR-001, og det har stemt. Fire load-bærende + delbeslutninger avklart eksplisitt + (AskUserQuestion): nytt parallelt eierskapsmønster keyet på `user_id` + (gjenbruker det allerede beviste `plain_connection()`-mønsteret fra + personlig profil/HCP-historikk/sekundær e-post — IKKE en skjult + personlig organisasjon), fast sett navngitte statistikk-felt per hull + (ikke fri slag-for-slag-logg), full WHS HCP-indeksberegning bygges NÅ + (ikke utsatt), shotgun-start blir egen separat ADR-034. De tre + HCP-PDF-ene lest i sin helhet — Score Differential/Course + Handicap/Net Double Bogey-formlene er hentet derfra (kilde, ikke + hukommelse). + **Oppdatert samme dag:** brukeren bekreftet banedata-spørsmålet (LIVE + oppslag mot teeoff, ikke import — Beslutning C) og lastet opp en + FJERDE PDF, den offisielle "WHS Rules of Handicapping 2024" + (USGA/R&A) — lest i sin helhet, løste presist det som manglet: 9- + hulls-minimumsregelen (Rule 2.2, IKKE bare "minst 9 hull" — en + 9-hulls-runde krever ALLE 9 av et faktisk ratet sett, en 18-hulls- + runde krever minst 10 av 18), full HCP-indeks-pipeline (Score + Differential, Net Double Bogey, expected-score for uspilte hull, beste + 8-av-20, Low Handicap Index, soft/hard cap), OG en ny, presis + 9-hulls-Course-Handicap-formel som HALVERER indeksen først (Rule + 6.1b) — bevisst holdt atskilt fra det eksisterende front_9/back_9- + øktoppsettet i turnering-flyten (ADR-008), som løser et annet problem + av andre grunner. ADR-033 er dermed fullt kildebelagt, ingen store + åpne HCP-regelspørsmål gjenstår. + **Bygging påbegynt samme dag:** brukeren instruerte at "Expected + Score" (Rule 3.2b, upublisert WHS-formel) erstattes gjennomgående av + WHS sin egen "Net Par"-term for uspilte hull — løser samtidig hele + 9-hulls-runde-spørsmålet uten en egen separat formel (Rule 5.1b droppet + bevisst). Hele HCP-indeks-motor-komponenten (Net Double Bogey/Net Par, + Adjusted Gross Score, Score Differential, Handicap Index fra beste- + 8-av-20 m/opptrappingstabell for <20 runder, Low Handicap Index, soft/ + hard cap, 9-hulls Course Handicap) er BYGGET og TESTET i + `handicap_engine.py` — 41/41 tester (`test_handicap_engine.py`, kjørt + uten pytest da det ikke er installert i miljøet), flere verifisert mot + regelbokens egne tallregneeksempler (Rule 5.2a, Rule 5.1c, Diagram 5.8, + Diagram 3.1b). Ren Python, ingen DB/API/frontend rørt ennå — matcher + ADR-005s "test i isolasjon FØR resten". + **Deretter, samme dag:** full databasemigrasjon skrevet + (`020_personal_rounds.sql`, sju nye tabeller: `round`/ + `round_participant`/`round_hole` + fire for en global egendefinert- + bane-katalog) og scratch-verifisert (9 sjekker som + `teecup_app_scratch`, ikke superbruker — CHECK-constraints, XOR + user_id/guest_name, kaskade-sletting, GIR-derivering bekreftet mot + ekte data). INGEN RLS på disse tabellene (Beslutning A). `test_ + isolation.sql` fortsatt 12/12. + **Rullet ut mot ekte `teecup_db` 2026-07-22,** bruker bekreftet + eksplisitt: alle sju tabeller bekreftet opprettet, `test_isolation.sql` + fortsatt 12/12 mot ekte database. + **Deretter, samme dag: fullt API-lag bygget** (`app/routers/ + rounds.py` — opprett/liste/hent/slett runde, legg til/fjern gjest- + deltaker, PATCH hull-for-hull-stats, fullfør-runde som kjører hele + Adjusted-Gross-Score→Score-Differential-kjeden). Fant og fikset et + reelt hull underveis: `round_participant` manglet rating-tall- + kolonner (kun tee-NAVN var snapshotet, ikke selve Course/Slope/Par) — + ny migrasjon `021_round_participant_rating_snapshot.sql`. 26 scratch- + sjekker + en egen, ekte teeoff-live-oppslag-test (Beslutning C + bekreftet: ingen `course`-rad skrives noe sted). Rullet ut mot ekte + `teecup_db`/`teecup_api` 2026-07-22, `/health`/`/dashboard` → 200, + `teeoff.no` upåvirket. `frontend/next.config.mjs` fikk `/rounds`/ + `/personal-courses` lagt til i `rewrites()` proaktivt (ikke deployet + ennå, ingen frontend bruker den før neste runde). + **Notat fra bruker, IKKE designet:** planer om å måle lengde på slag + + opplyse avstand til ulike punkter på banen (golf-GPS/rangefinder-type + funksjonalitet) — krever geografiske/GPS-data ingen kilde har i dag + (verken teeoff eller `personal_course`). Se FEATURE_BACKLOG.md for + full detalj. + **Frontend bygget og rullet ut 2026-07-23, samme rekkefølge-prinsipp + (engine → skjema → API → frontend) fullført:** tre nye, + organisasjonsuavhengige endepunkter lagt til i `rounds.py` + (`GET /rounds/official-search`/`{slug}` — samme mønster som + `courses.py` sitt org-scopede søk, men uten org-kontekst, siden + frittstående runder ikke har noen; `GET /personal-courses/{id}` — + detalj med utslag+kjønn, manglet fra søk-runden) og en fjerde, + nødvendig tilføyelse oppdaget UNDER frontend-designet: `RoundOut` bar + aldri hull-nivå-data i det hele tatt — ny + `GET .../participants/{id}/holes`. Hull-PATCH-endepunktet endret til å + returnere hele den oppdaterte raden i stedet for `{"ok": true}`. + **Reelt kontraktsfunn, bekreftet i scratch FØR frontend stolte på det:** + hull-PATCH er IKKE et ekte delvis-PATCH — den skriver ALLE felt ved + hvert kall, så et utelatt felt (f.eks. putts) nullstilles stille hvis + frontend ikke sender det. Løst ved at `round-detail.tsx` alltid slår + sammen med gjeldende hull-data før hver PATCH, aldri sender et isolert + feltnavn alene — verifisert eksplisitt med en egen scratch-test som + FØRST beviste nullstillings-oppførselen, DERETTER beviste at + merge-mønsteret unngår den. + Nye sider: `/rounds` (liste), `/rounds/new` (bane-kilde teeoff/egen, + søk-før-opprett for egen bane samme idé som org-banene, utslag filtrert + på brukerens registrerte kjønn, dato/starthull/hull-antall), `/rounds/ + [id]` (deltaker-faner, hull-navigasjon fra starthull, slag/putt- + tallvelgere i samme stil som `session-scorecard.tsx` sin `StrokePicker`, + kølle/retning/innspill/chip/bunker/straffeslag bak en «flere detaljer»- + utvidelse, GIR utledet og vist klientside — aldri lagret, kun beregnet + fra `approach_result`+slag+putt ved lesing, fullfør-runde med HCP- + differensial-sammendrag). Lenket fra dashbordet som «Egne runder» + (bevisst adskilt navn fra det eksisterende «Mine runder», som gjelder + turnering-deltakelse — samme ord, to ulike konsepter). + **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle + + isolert scratch-MinIO + engangs API-container, samme mønster som hele + økten ellers): 22 sjekker som dekker egendefinert-bane-opprettelse med + to utslag/kjønn, søk, detalj, PATCH-kontrakten (inkl. det bevisste + nullstillings-beviset over), GIR-derivering, gjest-fjerning, + kryss-bruker-autorisasjon (403), og full fullføring med differensial + — PLUSS en egen, separat test av hele teeoff-baserte opprettelsesløpet + mot den ekte kjørende `teeoff_api`-containeren (Borregaard Golfklubb), + som bekreftet course_handicap ble beregnet riktig. Ekte typesjekket + PRODUKSJONSBUILD (`docker build --target builder`, samme steg som + `Dockerfile` faktisk bruker) kjørt og bekreftet — alle nye ruter listet. + **Rullet ut live 2026-07-23**, bruker bekreftet eksplisitt: ingen + migrasjon, `docker compose up -d --build teecup_api teecup_frontend`, + begge containere boot-et rent, `/health`/`/dashboard`/`/rounds` → 200, + `teeoff.no` upåvirket. **Frittstående rundeføring har dermed backend + OG frontend live** — se ADR-033 i ARCHITECTURE_DECISIONS.md for full + detalj om alle fire lagene (engine/skjema/API/frontend). + **Frontend ERSTATTET med V0-designet versjon, samme dag:** brukeren + påpekte berettiget at de tre skjermene over var hånd-kodet av meg i + stedet for designet i V0, som er prosjektets etablerte mønster for + ALL øvrig frontend. Bekreftet arbeidsmåten uendret (jeg skriver + V0-prompten, brukeren kjører den og sender koden tilbake, jeg + integrerer). Tre prompter skrevet (liste/opprett/hull-registrering, + inkl. eksplisitt tilgjengelighetskrav i hver), tre zip-eksporter + mottatt og diffet mot levende tre (samme "full re-eksport"-mønster som + alltid — kun `round-card.tsx`/`own-rounds.tsx`/`new-round.tsx`/ + `round-detail.tsx` + rutene var reelt nye). Samme kjente V0-feil dukket + opp igjen og ble hoppet over (`app/clubs/[id]/page.tsx`, feilnavngitt + slug-parameter — identisk feil som ble rettet under ADR-018). Mine + hånd-bygde komponenter ERSTATTET (ikke supplert), datalag skrevet om + fra V0s mock til ekte fetch — samme mønster som enhver annen skjerm. + Full detalj (inkl. de fire reelle tilpasningene utover ren om-kabling) + i ADR-033. Ekte typesjekket produksjonsbuild kjørt og bekreftet, ingen + ny interaktiv nettleser-test (intet slikt verktøy tilgjengelig, + flagget eksplisitt). **Rullet ut live 2026-07-23**, bruker bekreftet + eksplisitt: `docker compose up -d --build teecup_frontend` (gjenskapte + også `teecup_api` som vanlig bivirkning), begge containere boot-et + rent, `/health`/`/dashboard`/`/rounds`/`/rounds/new` → 200, `teeoff.no` + upåvirket. V0-zip-ene slettet fra prosjektroten. + **Reell produksjonsbug rapportert av bruker (2 skjermbilder) og + FIKSET samme dag:** `/rounds` var samtidig frontend-listesidens sti OG + backend-APIets ressursprefiks — en verre, begge-veier-variant av + ADR-016s medlemsside-felle. Statisk side vant over rewrite for det + eksakte `/rounds`-treffet (klientens `fetch("/rounds")`/`POST /rounds` + traff aldri backend, fikk Next sin egen HTML tilbake — derav "Klarte + ikke å hente rundene dine"/"Klarte ikke å opprette runden"), mens + rewriten vant over den DYNAMISKE `/rounds/[id]`-siden i motsatt + retning (selve rundedetalj-siden var dermed fullstendig uoppnåelig, + bekreftet direkte med `curl` før fiksen). Skjermbildets andre detalj — + utslagsnavn "55/50/44/32" — ble sjekket direkte mot ekte `teeoff_api` + og bekreftet Å IKKE VÆRE EN BUG (Tjøme Golfklubb sine faktiske + utslagsnavn, lengde i hundremeter). Fikset ved å flytte alle tre + frontend-rutene til et nytt, ikke-overlappende prefiks `/my-rounds/*` + — API-et uendret på `/rounds`. `next.config.mjs` sin advarsel utvidet + med denne nye varianten. Verifisert med `curl` mot ekte produksjon + BÅDE før og etter (anonymt `GET /rounds` → nå korrekt backend-JSON, + anonymt `GET /my-rounds/<uuid>` → nå korrekt `text/html`, altså + endelig oppnåelig). Rullet ut live 2026-07-23, bruker bekreftet + eksplisitt, kun `teecup_frontend` (+ vanlig `teecup_api`-bivirkning), + `teeoff.no` upåvirket. Full detalj i ADR-033. +4. **Del 1 (fri, ukrevd sekundær-e-post) er nå BYGGET OG LIVE** (2026-07-21, + se status over). **Del 2 (ekte konto-sammenslåing) fortsatt IKKE + designet:** hva skjer hvis den ønskede adressen ALLEREDE tilhører en + annen, eksisterende konto (i dag avvist tydelig med 409 DUPLICATE i + stedet for gjettet på)? Trenger egen, separat designrunde — se + FEATURE_BACKLOG.md for full analyse av hvorfor dette er vesentlig + vanskeligere enn del 1. +5. **Deltaker-tilgang til lag-chat/scorekort OG HCP-historikk er nå BEGGE + BYGGET OG LIVE** (2026-07-21, se status over) — ADR-031s tidligere + noterte "naturlig neste steg"-punkter er dermed alle tettet, unntatt + notifikasjons-/aktivitetsfeed og "Mine runder" for rene påmeldinger + (begge fortsatt IKKE bygget, se FEATURE_BACKLOG.md). +6. ~~Oppfølgingspunkt fra PWA-runden: offline-scoreregistrering er ALDRI + browser-testet i praksis~~ — **ferdig 2026-07-28** (se status-punkt + samme dag, "Turnering-scorekortets offline-flyt (ADR-028) FAKTISK + browserverifisert"). Beholdt her kun for historikk. +7. Fortsatt åpne beslutninger fra FEATURE_BACKLOG.md: kode-regenerering for + ADR-020, korrigering-godkjenning fra motpart, video/1-til-1-meldinger + (bevisst utsatt i ADR-025). +8. Flere turneringsformater utover Ryder Cup (Københavner/High-low-high/ + Robbins/Try all, notert 2026-07-19) — ingen ADR-runde startet ennå. +9. Ved fremtidige nye V0-skjermer/-design i V0 (fortsett i samme prosjekt), + FORVENT en full re-eksport hver gang — diff mot live-treet i et + scratch-område før noe pakkes ut over eksisterende filer, og sjekk om + V0-skjermen bygger inn handlinger backend ikke støtter ennå FØR + integrering. +10. **Resterende hull fra 2026-07-28-gjennomgangen av frittstående runder** + (det viktigste — faktisk HCP — er tettet, se ADR-038 over; offline-kø, + punkt (a), er nå OGSÅ tettet, se status 2026-07-28 over — punktet + beholdes her kun for historikk): (a) ~~offline-kø (ADR-028) er kun + koblet til turnering-scorekortet~~ — ferdig, browserverifisert og + live; (b) ~~ingen Stableford-poengberegning for frittstående runder + (kun rå slag/differensial), inkl. en uløst "plukket opp ballen"- + tilstand~~ — ferdig, scratch-/browserverifisert og live 2026-07-29, + se status over (`play_format='stableford'` + `round_hole.picked_up`, + migrasjon 038); (c) ~~rundedeling/visibility (ADR-036 fase 2, public/private/friends) + fortsatt ikke bygget~~ — ferdig, browser-/scratch-verifisert og live + 2026-07-28, se status over (ADR-036 er dermed HELT ferdig, alle tre + faser); (d) ~~flere flighter i én frittstående runde fortsatt kun + drøftet~~ — ferdig, retning 1 (løs gruppering) bygget, scratch-/ + browserverifisert og live 2026-07-28, se status over; (e) ~~varsler + koblet til venneforespørsler, ikke til rundehendelser ennå~~ — + ferdig, scratch-verifisert og live 2026-07-28, se status over + (medspiller lagt til/venn ser synlig runde/tilkoblet runde fullført). + Alle fem punktene (a)-(e) i denne listen er dermed ferdig. +11. **Ferdig, kun for historikk:** ekte spillformer (match/skins/fourball/ + foursome/greensome/scramble) for frittstående runder — backend + (ADR-039 + minimums-spiller-håndhevelse) OG frontend (sideoppsett, + skins-konfig i `/my-rounds/new`, matchstatus-/skins-tavle-visning, + delt-ball-scorekort/-veiviser, `setup_complete`-gating) er nå BEGGE + bygget, browserverifisert og live (se status 2026-07-28). Mulig + fremtidig finpuss (ikke bedt om ennå): redigere et sidenavn i + etterkant (kun opprett/slett finnes i dag), en tydeligere + skins-poeng-forklaring i UI-et. +12. **Ferdig, kun for historikk:** de tre organisator-oppfølgingspunktene + (flytte spiller mellom lag, individuell rangering per økt, midlertidige + spillere + etter-runde-invitasjon) — alle bygget, scratch-/ + browserverifisert og live 2026-07-28, se status over. Ingen nye åpne + spørsmål igjen fra denne runden. +13. **Ferdig, kun for historikk:** PWA-installasjonsoppfordring, flere + flighter i én frittstående runde (retning 1), scramble/greensome- + statistikk over valgt utslag (frittstående runder), og push-varsler + til telefonens OS (Web Push/VAPID) — alle fire bygget, scratch-/ + browserverifisert og live 2026-07-28, se status over for full detalj. + Gjenstående, IKKE bygget: samme scramble-utslagsstatistikk for + org-scopede turneringer (bevisst utenfor omfang, se + FEATURE_BACKLOG.md), ekte OS-nivå push-levering ikke bevist med en + reell innvilget tillatelse (automatisert testmiljø hadde + `Notification.permission` forhåndssatt til "denied") — bruker bør + selv teste push på en ekte enhet. +14. **Åtte nye turneringsformater (Chapman/Nassau/Københavner/Bingo Bango + Bongo/Flaggturnering/Shamble/Money Ball/High-low-high) — BACKEND + FERDIG, SCRATCH-VERIFISERT OG LIVE (2026-07-30, migrasjoner 041-050), + se status over.** Eneste gjenstående steg: FRONTEND for samtlige åtte + — nye spilleform-valg i opprett-runde/opprett-økt (frittstående + runder + org-lag + org-individuell, avhengig av format), nye + resultatvisninger (Nassau-vinduer, Københavner/BBB-poengtabeller, + Flag-nedtelling, Shamble/Money Ball-lagvisning, High-low-high-løpende + poeng). Backend er fullt funksjonelt og testet, men ubrukelig fra + selve appen inntil dette bygges — samme lagdelings-rekkefølge + (motor→skjema→API→frontend) som ADR-037/038/039. +15. **Ferdig, kun for historikk:** «Det store grepet» (ADR-040) — alle + 5 steg (backend/allowance_override, lag-sortering, rundeoppsett- + veiviseren, delt Score/Scorekort/Leaderboard-fane-rad, integrering) + BYGGET, VERIFISERT OG LIVE 2026-08-02, se status over. **Ett bevisst + utsatt, fortsatt åpent spørsmål:** trenger lag-grupperingen i + scorekortet mer enn celle-fargelegging for å skille lagene tydelig + nok (utover selve sorteringen, som allerede er fikset) — ikke stilt + til V0 ennå, egen liten vurdering om ønskelig. Se FEATURE_BACKLOG.md. diff --git a/CLAUDE.md b/CLAUDE.md index 6f29782..e881c7e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,10 +3,16 @@ Les dette først i hver økt. Det koder hva vi har bestemt og hvordan vi jobber. ## Autoritative kilder (les før du gjør noe) -- `ARCHITECTURE_DECISIONS.md` — hva som er bestemt og hvorfor (ADR-001…029). Fasit. +- `ARCHITECTURE_DECISIONS.md` — hva som er bestemt og hvorfor (ADR-001…). Fasit. - `FEATURE_BACKLOG.md` — hva som gjenstår, hva som er utsatt, hva som mangler. -- Endres en beslutning: legg til en ny ADR, ikke slett historikk. Hold begge - filene oppdatert når noe avgjøres. +- `CHANGELOG.md` — kronologisk arbeidslogg (hva som er bygget, når, hvordan + det ble verifisert, kjente fallgruver funnet underveis). IKKE noe å lese + i sin helhet hver økt — les den ved regresjon-feilsøking, eller når du + trenger å vite hvordan/hvorfor noe konkret ble bygget som det ble (grep + etter filnavn/ADR-nummer/feature-navn). Se filens egen header for full + bruksanvisning. +- Endres en beslutning: legg til en ny ADR, ikke slett historikk. Hold + disse filene oppdatert når noe avgjøres. - **Regelverk for HCP/slagfordeling — tre PDF-er lastet opp av brukeren til prosjektroten 2026-07-19** (ikke innsjekket i git, kun lokale filer på serveren): `spilletyper-og-spilleformer-2023.pdf`, @@ -88,7 +94,7 @@ Les dette først i hver økt. Det koder hva vi har bestemt og hvordan vi jobber. runden): dashbordets hilsen brukte fullt navn — rettet til `me.first_name ?? me.display_name` (`/auth/me` eksponerte allerede `first_name` separat, ingen backend-endring nødvendig). Se - CLAUDE.md-status lenger ned for full detalj. + CHANGELOG.md for full detalj. ## Arbeidsmåte - Inkrementelt. Ingenting tas for gitt før det er testet. Bekreft hvert steg før @@ -97,6333 +103,15 @@ Les dette først i hver økt. Det koder hva vi har bestemt og hvordan vi jobber. meldinger. - Er du usikker på omfang eller en beslutning: spør heller enn å gjette. -## Status (oppdater denne når ting endres) -Ferdig og verifisert: -- Handicap-motor + tester (24/24, R&A-verifisert). -- Skjema `001` + roller `002` + scoring/blind draw `003`. Isolasjon bevist med - `test_isolation.sql` (RLS-oppførsel, ikke bare at skjemaet kjører). -- 002 hadde en reell bug (psql interpolerer ikke `:'var'` inne i `DO $$...$$`) - — permanent fikset, verifisert mot scratch to ganger. -- API-et kjørt for ekte (ikke bare syntaks-sjekket) i en engangs Docker- - container mot en scratch-database, RLS bevist gjennom hele - asyncpg-pool-stacken (ikke bare i rå SQL). -- Oppsett-endepunktene er bygget og verifisert: `app/routers/players.py` - (spillerpool), `tournaments.py` (turnering/lag/roster/økter, ADR-011 - to-lags-grense håndhevet med `FOR UPDATE`-lås), `matches.py` (matcher/ - deltakere/blind draw-lås, ADR-013-synlighet push-down i SQL). Delt - feiloversettelse i `app/errors.py`, delte synlighetsspørringer i - `app/blind_draw.py`. `main.py` er nå bare app-factory + `include_router`. -- Scoring-runden er bygget og verifisert for ekte mot scratch-db (18-hulls - bane med `tee_rating`, 4 spillere for fourball-testing): `app/handicap.py` - (ADR-014 fire brytere via `parse_allowance_config`, handicap beregnes i - `compute_and_store_side_handicaps` rett etter deltaker-innsetting — - singles/fourball per spiller umiddelbart, foursome/greensome/scramble kun - når siden er komplett), `app/routers/scoring.py` (`hole-scores`/ - `hole-results`-upsert, `scorecard`-GET, matchstatus-recompute med - `FOR UPDATE`-lås mot race og SAMMENHENGENDE-prefiks-regel for uferdige - hull). `app/team_authz.py` skilt ut fra `matches.py` (delt med - `scoring.py`). Alle 10 planlagte tester bestått, inkl. fourball - better-ball-aggregering (MIN av to nettoer, venter til begge partnere har - registrert), poeng-caching ved tidlig avgjort match, og ADR-014-bryteren - `use_handicap=false`. - **Fant og fikset underveis:** `tournaments.py` sin `SessionCreate` manglet - `scoring_mode` helt (økter kunne aldri opprettes i `hole_result`-modus via - API-et) — lagt til. - **Bevisst utelatt/kjente begrensninger:** en side som aldri når forventet - deltakerantall (no-show) får aldri beregnet handicap og matchen kan da - aldri avgjøres — ingen manuell overstyring bygget. Score-skriving er - upsert (ingen avvisning ved duplikat) — ingen audit-trail på rettelser. - Kapteins-only autorisasjon fortsatt ikke bygget (FEATURE_BACKLOG ❓); bar - er «rostret på laget». -- **Match-lås ved avgjørelse (2026-07-16):** `submit_hole_score`/ - `submit_hole_result` avviser nå 409 hvis `match.points_side_a IS NOT NULL` - (matchen er avgjort) — FØR upserten kjøres, både for nye hull og - korrigering av allerede talte hull. Tetter en reell bug: uten dette kunne - «spøkelses-hull» lagt inn etter avgjørelse endre en allerede cachet margin - ved neste omregning. Automatisk, ingen ny autorisasjon involvert. -- **Ekte autentisering bygget og verifisert (2026-07-16):** `X-Debug-User-Id`- - stubben er HELT fjernet (ingen fallback). Magic-link + JWT-sesjon i - `app/routers/auth.py` + `app/auth.py` (`request-link`/`verify-link`/ - `logout`/`me`), ny migrasjon `004_auth.sql` (`magic_link_token`-tabell + - unik e-post-indeks på `app_user`). Token = `secrets.token_urlsafe(32)`, kun - SHA-256-hash lagres, atomisk forbruk (`UPDATE ... RETURNING`, ikke - les-sjekk-skriv), generisk respons uansett om e-posten finnes (unngår - enumerering), gamle uforbrukte lenker ugyldiggjøres når en ny utstedes, - `app_user` opprettes FØRST ved vellykket verifisering (ikke ved - forespørsel). Sesjons-JWT (PyJWT, `algorithms=["HS256"]` eksplisitt) i - HttpOnly/SameSite=Lax/dynamisk-Secure-cookie, 30 dager, med et ekte - eksistens-oppslag mot `app_user` på hver forespørsel (faktisk - tilbakekalling — en slettet bruker kan ikke ri ut sesjonen). Alle 12 - planlagte tester bestått. - **Fant og fikset underveis:** `ON CONFLICT (email)` matchet ikke den nye - PARTIELLE unike indeksen uten eksplisitt `WHERE email IS NOT NULL` (samme - klasse feil som `hole_score`s partielle indekser i scoring-runden). - **Fant, IKKE fikset i denne runden (egen runde rett etterpå — se under):** - `organization`-tabellens RLS-policy kastet en 500 i stedet for skjemaets - lovede "trygg standard: se ingenting" ved tomstreng-GUC. -- **RLS-tomstreng-bug FIKSET (2026-07-16):** ny migrasjon - `005_rls_null_guard.sql` — delt `STABLE` SQL-funksjon `app_current_org()` - gjør `NULLIF(current_setting('app.current_org', true), '')::uuid` i stedet - for det rå uttrykket, brukt av alle 15 RLS-policyer (`ALTER POLICY`, - 14 `org_isolation` + `org_self`). Verifisert med 3 nye regresjonstester i - `test_isolation.sql` (Test 10-12) OG ved faktisk å gjenskape original- - buggen mot en ekte container (pool-størrelse 1, varm opp med - `org_connection()`, deretter `/auth/me` på samme gjenbrukte tilkobling — - gikk fra 500 til 200). - **Viktig presisering fra denne runden:** fiksen gjør IKKE at `/auth/me` kan - joine `organization` direkte via `plain_connection()` — det var en feilaktig - antakelse i forrige runde. `org_self` krever fortsatt en MATCHENDE - `app.current_org` for å vise en rad (riktig RLS-design, ikke noe fiksen - skulle endre), og 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, - N = antall org-er brukeren tilhører) — dette er riktig løsning, ikke en - omvei. -- **Organisasjon-bootstrap bygget og verifisert (2026-07-16):** nytt - `POST /orgs` (`app/routers/organizations.py`) — det ENESTE stedet i API-et - som setter inn en `organization`-rad. Fant under statusgjennomgang at dette - manglet helt (alle tidligere org-er var seedet med superbruker-SQL). Ingen - ny migrasjon. Selvrefererende RLS-bootstrap bekreftet å fungere: generer - org-ens uuid i Python, sett `app.current_org` til nøyaktig den via - eksisterende `org_connection()`, sett inn `organization`-raden med samme - id — `org_self`s implisitte `WITH CHECK` blir da trivielt sann, ingen - privilegert tilkobling nødvendig (i motsetning til hva 002s kommentar - antydet). Verifisert med 5 tester inkl. en negativ kontroll (mismatchende - id avvist med `insufficient_privilege`) og full kryss-org-isolasjon mellom - to uavhengig opprettede organisasjoner. -- **Ekte SMTP-utsending bygget og verifisert (2026-07-16):** ny `app/email.py` - (`send_magic_link_email`, `smtplib` via `asyncio.to_thread`, håndterer - både implisitt TLS/port 465 og STARTTLS dynamisk). Brukeren la egne - SMTP-credentials i `.env` (`TEECUP_SMTP_*`, `TEECUP_FROM_EMAIL` — ADR-009, - ikke delt med teeoff); jeg leste kun nøkkelnavnene for å bekrefte de - fantes, aldri verdiene. `app/config.py` sin `SMTP_CONFIGURED` er valgfri - (ikke `_required`) — dev-only logging (`TEECUP_DEV_LOG_MAGIC_LINKS`) - fortsatt fungerer uendret når SMTP ikke er satt opp. Driftsfeil i - utsendingen lekker aldri til klientresponsen (bevarer anti-enumerering). - **Verifisert med faktisk levering:** sendte én ekte test-e-post til en - adresse brukeren oppga — brukeren bekreftet mottak. Første gang noe i - prosjektet er bevist ved ekte levering, ikke bare curl/scratch. -- **Containerisert og LIVE på `teecup.teeoff.no` (2026-07-16):** ekte - `teecup_db` opprettet (migrasjoner 001→005 kjørt permanent, `test_isolation.sql` - består), `Dockerfile` + `docker-compose.yml` (tjeneste `teecup_api`, joiner - det eksisterende `teeoff_default`-nettverket), Caddy-blokk lagt til i - `/opt/teeoff/deploy/Caddyfile`. Ekte innlogging (magic-link → e-post → - JWT-sesjon med `Secure`-cookie) verifisert ende-til-ende mot den live - stacken. `teeoff.no` upåvirket gjennom hele prosessen. - **To reelle hendelser underveis, begge løst:** - 1. **Caddy plukket ikke opp filendringen** — `teeoff_caddy` sin - `Caddyfile`-mount er en ENKELTFIL-bind-mount, låst til inoden som fantes - da containeren sist startet. Min fil-redigering (atomisk rename) laget - en ny inode på samme sti, så containeren fortsatte å lese den GAMLE - filen uansett hvor mange ganger `caddy validate`/`caddy reload`/admin-API - `/load` ble kjørt (alle validerte/lastet den uendrede gamle filen, derav - ingen feilmelding). Løst med en full `docker restart teeoff_caddy` - (brukeren bekreftet — avvek fra planens "kun graceful reload, ingen - omstart"-løfte, noen sekunders nedetid for `teeoff.no`). - 2. **Alvorlig nettverksalias-kollisjon** (funnet RETT ETTER omstarten, da - ekte teeoff-trafikk som `/api/facilities?...` med ekte klubb-slugs dukket - opp i `teecup_api` sin logg): `docker-compose.yml` sin service-nøkkel var - `api:` — SAMME nøkkel som teeoffs eget `api`-servicenavn - (`docker-compose.prod.yml`). Docker Compose registrerer nettverksalias - basert på service-NAVNET (ikke bare `container_name`) på delte nettverk, - så BEGGE containerne fikk alias `api` på `teeoff_default` — Caddys - `reverse_proxy api:8000` i teeoff sin egen config kunne da tilfeldig - treffe enten ekte `teeoff_api` eller `teecup_api`. **Rettet umiddelbart** - (stoppet `teecup_api` først for å hindre videre feilruting av ekte - teeoff-trafikk, ga service-nøkkelen navnet `teecup_api` i stedet, - gjenopprettet — bekreftet med `docker network inspect` at alias `api` nå - KUN peker på ekte `teeoff_api`). - **Mindre driftslærdom:** (a) jeg eksponerte ved et uhell - `TEECUP_SMTP_PASS`/`TEECUP_FROM_EMAIL` i eget debug-output mens jeg - feilsøkte en `.env`-korrupsjon (manglende linjeskift fra min egen - `>>`-tilføyelse) — brukeren roterte passordet som forsiktighetsregel; (b) - Docker leser IKKE `.env` på nytt for en allerede kjørende container — - `docker compose up -d --force-recreate` kreves etter enhver `.env`-endring - som skal tas i bruk; (c) et `#`-tegn i et upassordet `.env`-passord kuttes - som en kommentar av Compose sin parser — anførselstegn (fortrinnsvis enkle) - løser dette. -- **Dato/klokkeslett + feilkode-kontrakt + i18n-forberedelse (2026-07-17, - ADR-015):** reist av brukeren rett før frontend-arbeidet. Ny migrasjon - `006_scheduling_and_locale.sql` (alt additivt): `tournament.end_date`, - `session.scheduled_at`/`tee_interval_minutes`/`start_hole`, - `match.tee_time_override`, `app_user.preferred_locale`, - `magic_link_token.locale`. `match.tee_time` er UTLEDET i Python - (`scheduled_at + (sequence-1)*tee_interval_minutes`, override vinner hvis - satt) — aldri lagret per match. Full retrofit av ALLE 39 daværende - `HTTPException(..., detail="norsk streng")`-steder på tvers av 6 filer til - en delt `app_error(status_code, code, message)`-factory - (`app/errors.py`) — responsformen er nå konsekvent - `{"detail":{"code":...,"message":...}}` i hele API-et, verifisert med et - siste `grep -rn 'detail="' app/` som ga NULL treff. i18n: `locale` - (`nb`/`en`) sendes av klienten ved `request-link`, styrer e-postmalen - (ekte engelsk mal lagt inn i `app/email.py`, ikke bare rørlegging) OG - settes som en HELT NY brukers `preferred_locale` — en eksisterende bruker - som logger inn på et annet språk får IKKE sin lagrede preferanse - overskrevet. - **Fant og fikset underveis:** `tournament.start_date` har ligget i - skjemaet siden migrasjon 001, men var ALDRI koblet til - `TournamentCreate`/`Tournament`-modellene — funnet som en naturlig - bivirkning av å legge til `end_date`. - **Verifisert grundig mot fersk `teecup_scratch`** (001→006, - `test_isolation.sql` fortsatt 12/12): feilkode-form bekreftet på tvers av - 5 filer (`NOT_FOUND`/`LIMIT_REACHED` tournaments.py, `DUPLICATE` roster, - `NOT_AUTHENTICATED` uten cookie, `NOT_ROSTERED_ON_TEAM` matches.py), - tee_time-beregning bekreftet (10 min intervall → 08:00/08:10/08:20, - override vinner, `scheduled_at=null` gir `tee_time:null` ikke feil), full - i18n-runde bekreftet (ny bruker `locale:"en"` → `preferred_locale:"en"`; - påfølgende `request-link` for samme bruker med `locale:"nb"` skiftet - e-postmalen men IKKE den lagrede preferansen, bekreftet via `/auth/me`). - **Kjørt mot ekte `teecup_db` 2026-07-17** (bruker bekreftet eksplisitt i - samme økt): migrasjonen kjørte rent, `test_isolation.sql` fortsatt 12/12 - (ruller alltid tilbake, ingen domenerader berørt). - **Mindre driftslærdom, funnet OG rettet samme runde:** et `.env`-filter - (`grep -v -i 'pass|secret|key'`) jeg brukte for å lese ikke-sensitive - nøkler fanget ikke opp `TEECUP_DATABASE_URL`, som bar `teecup_app`- - passordet innebygd i selve URL-en (i tillegg til at det SAMME passordet - allerede lå rent i `TEECUP_APP_PASSWORD` — duplisert, ikke bare skjult) — - passordet ble dermed synlig i et verktøyresultat. Flagget til brukeren - umiddelbart (samme mønster som SMTP-passord-hendelsen over). -- **`.env`/tilkobling ryddet opp (2026-07-17), samme runde:** roten til - hendelsen over var at `TEECUP_DATABASE_URL` var én sammensatt DSN-streng - med passordet URL-kodet inni — usynlig for navnebaserte secret-filtre. - Erstattet med fem separate, rent navngitte felt: - `TEECUP_DB_HOST`/`TEECUP_DB_PORT`/`TEECUP_DB_NAME`/`TEECUP_DB_USER`/ - `TEECUP_DB_PASS` (sistnevnte omdøpt fra `TEECUP_APP_PASSWORD`, kun - nøkkelnavnet — verdien aldri lest eller skrevet av meg). `app/config.py` - og `app/db.py` bygger nå `asyncpg`-poolen fra disse fem separate feltene - (`host=`/`port=`/`user=`/`password=`/`database=`) i stedet for én DSN — - fjerner også URL-prosentkoding-problemet fra SMTP-passord-hendelsen sin - klasse av feil. `docker-compose.yml` sin `environment:`-liste oppdatert - tilsvarende. **Krevde et fullt image-rebuild, ikke bare - `--force-recreate`:** `Dockerfile` sin `COPY app/ app/` bakes inn i - imaget ved build-tid (ingen bind-mount i prod, i motsetning til - scratch-verifiseringens engangscontainere) — en ren `--force-recreate` - gjenbrukte det GAMLE imaget og krasjet umiddelbart på den nå fjernede - `TEECUP_DATABASE_URL`. Rettet med `docker compose up -d --build - --force-recreate`. **Verifisert ende-til-ende mot den live stacken:** - containeren boot-et rent (`Application startup complete` i loggen, som - krever en vellykket `init_pool()` — asyncpg ville kastet og forhindret - akkurat den logglinjen ved feil tilkoblingsparametre), `/auth/me` over - ekte https ga et rent `401 NOT_AUTHENTICATED` (ikke 500/502), `teeoff.no` - upåvirket (`200` gjennom hele omstarten, kun `teecup_api` restartet — ikke - delt Caddy-instans, ikke samme risikoklasse som Caddy-hendelsen fra - containeriseringsrunden). +## Status og neste steg +Den detaljerte, kronologiske statusloggen (hva som er ferdig, verifisert +og rullet ut, runde for runde) flyttet til `CHANGELOG.md` 2026-08-02 — +denne filen ble for stor til å injiseres i sin helhet hver økt uten å +fortrenge de faktiske reglene over. `CHANGELOG.md` sin egen "Neste +steg"-seksjon nederst har den ferskeste, mest detaljerte punktlisten over +åpne tråder; `FEATURE_BACKLOG.md` har det bredere, mindre ferske bildet +av hva som gjenstår/er utsatt. Oppdater `CHANGELOG.md` (ikke denne filen) +når noe bygges, verifiseres og rulles ut — samme format og disiplin som +før: dato, hva som ble gjort, hvordan det ble verifisert, hva som ble +rullet ut og hvordan. -- **Frontend startet, innlogging LIVE (2026-07-17, ADR-016):** første - frontend-skjerm i prosjektet. Designet i V0 (Next.js + Tailwind + - shadcn/ui), hentet inn som `frontend/` — merkevare-form/farge fra - Teeoff-logoen, IKKE navn/logo (egne, separate produkter, se ADR-009). - **Kvalitetsrunde før bruk:** V0s fargetokens var OKLCH-TILNÆRMINGER, ikke - eksakte — regnet ut presise verdier fra `#8bc24a`/`#ff5722` og rettet alle - 6 forekomster i `globals.css`. Fjernet `@vercel/analytics` (unødvendig på - egen-hostet infra), fjernet `typescript: { ignoreBuildErrors: true }` - (ekte typesjekk kjører nå), fjernet dødt `pnpm.overrides`-felt. - `frontend/.gitignore` manglet `.pnpm-store/` — årsaken til at brukerens - VSCode viste 10 000 "ukjente" filer ved første commit-forsøk; rettet. - **Kablet mot ekte API:** `next.config.mjs` sin `rewrites()` proxyer - `/auth/*`/`/orgs/*`/`/health` server-side til `teecup_api` — same-origin, - ingen CORS, cookie uendret (full begrunnelse i ADR-016). Login-skjermet - sender ekte `POST /auth/request-link`; ny `/verify`-side mottar - `?token=...` fra e-postlenken (auto-verifiserer) eller viser et manuelt - "lim inn koden"-felt. `app/email.py` fikk en ny `PUBLIC_BASE_URL`- - innstilling og sender nå en EKTE klikkbar lenke (koden beholdes som - fallback). - **Reell fallgruve funnet og fikset ved containerisering:** Next.js sin - `rewrites()` løses ved BUILD-tid for `output: "standalone"`, ikke ved - container-oppstart — en runtime `-e TEECUP_API_ORIGIN=...` ble stille - ignorert (proxy-kall feilet med `ECONNREFUSED` mot `localhost:8000`). - Løst med en Docker build-time `ARG TEECUP_API_ORIGIN` i - `frontend/Dockerfile`, satt via `docker-compose.yml` sin `build.args`. - **Rullet ut live:** ny `teecup_frontend`-tjeneste i `docker-compose.yml`. - Caddy (`teecup.teeoff.no`, i det SEPARATE `teeoff`-repoet, - `/opt/teeoff/deploy/Caddyfile`) endret fra å peke direkte på `teecup_api` - til å peke på `teecup_frontend` — samme stale-inode-oppførsel som - containeriseringsrunden (graceful `reload` plukket IKKE opp endringen, - `/` fortsatte å gi `teecup_api` sin egen 404 i stedet for innloggingssiden - til reload faktisk skjedde). Løst likt: full `docker restart - teeoff_caddy`, brukeren bekreftet eksplisitt på forhånd. `teeoff.no` - upåvirket gjennom hele omstarten. - **Verifisert med FAKTISK e-postlevering:** ekte magic-link sendt til - brukerens egen adresse over `https://teecup.teeoff.no`, ekte e-post - mottatt med en ekte klikkbar lenke, åpnet i nettleser, landet på en - fungerende `/verify`-side, sesjon opprettet — brukeren bekreftet innlogget - status. Første gang en hel bruker-vendt flyt er bevist ende-til-ende i - produksjon, ikke bare API-et isolert. - **Merk for neste økt:** `deploy/Caddyfile`-endringen ligger uncommitted i - det SEPARATE `/opt/teeoff`-repoet, ikke i `teecup`-repoet — lett å glemme - siden denne økten ellers kun har jobbet i `/opt/teecup`. -- **Dashboard-skjerm LIVE (2026-07-18):** andre V0-skjerm — organisasjon- - bytter/-opprettelse + turneringsliste (`/dashboard`), samme mønster som - login-runden. **Reell integrasjonsfelle unngått:** V0s eksport denne gangen - var en FULL re-eksport av hele prosjektet (inkl. `login-form.tsx`, - `next.config.mjs`, `package.json`), ikke bare de nye filene — en naiv - utpakking ville stille reversert `rewrites()`-proxyen, `output: - "standalone"`, den ekte fetch-kablingen i login-skjemaet, og alle - V0-uavhengige opprydninger fra forrige runde. Løst ved å pakke ut til et - scratch-område FØRST, diffe mot live-treet fil for fil, og kun ta inn det - som faktisk var nytt (`dashboard.tsx`, `tournament-card.tsx`, - `tournament-status-badge.tsx`, `wordmark.tsx`, `badge.tsx`, - `dropdown-menu.tsx`, `app/dashboard/page.tsx`) — `next.config.mjs`, - `package.json`, `globals.css`, Docker-filene ble bevisst IKKE overskrevet. - `login-form.tsx` fikk en kirurgisk patch (kun Wordmark flyttet til egen - fil, som V0 selv hadde gjort — all egen fetch-/feilhåndteringslogikk - urørt). - **`dashboard.tsx` sitt datalag skrevet om fra bunnen** (V0 leverte kun - mock `useState`): henter `/auth/me` for organisasjonsmedlemskap, - `/orgs/{id}/tournaments` per valgt org, `POST /orgs`/`POST - /orgs/{id}/tournaments` for opprettelse, `POST /auth/logout` for utlogging - — presentasjonskomponentene (kort, bytter, tomme tilstander) beholdt - uendret fra V0. `/verify`-siden oppdatert til å sende brukeren videre til - `/dashboard` etter vellykket innlogging (fantes ingen dit å gå før nå). - **Verifisert:** ekte typesjekket build, redeploy av kun `teecup_frontend` - (ingen Caddy-endring nødvendig denne gangen — ADR-016s mønster holder), - `teecup.teeoff.no/dashboard` → 200, `teeoff.no` upåvirket. - **Skrive-flyten bekreftet med EKTE data samme dag (brukeren testet selv, - ikke meg):** organisasjon "Tjøme Gents" og turnering "De Gamle er Eldst" - opprettet via UI-et mot den ekte `teecup_db` — statusmerket viste riktig - "Utkast", "Ingen datoer satt" håndtert korrekt (ingen krasj på manglende - dato), ett-org-visningen viste riktig uten unødvendig bytter-UI. Første - gang en HEL skrive-flyt (ikke bare lesing) er bevist ende-til-ende fra - frontend mot ekte produksjonsdata. -- **Lag/roster-skjerm LIVE (2026-07-18):** tredje V0-skjerm - (`/tournaments/[id]`), samme re-eksport-mønster som dashboard-runden — - diffet mot live-treet, tok kun inn `tournament-detail.tsx` og et - `Link`-basert `tournament-card.tsx` (navigasjon fra dashbordet). - **URL-design bevisst avvikende fra V0s forslag:** V0s genererte side leste - aldri `params.id` og hadde ingen organization_id i det hele tatt — holdt - derfor V0s flate `/tournaments/[id]`-struktur (i stedet for en nøstet - `/orgs/[orgId]/tournaments/[id]`, som ville krevd manuell ombygging ved - HVER fremtidig V0-reeksport) og la `org`+`name` til som søkeparametre i - `tournament-card.tsx` sin lenke — API-et krever organization_id på alle - team-/roster-kall (RLS). - **Reelt hull funnet FØR integrering, ikke etter:** V0-skjermen bygger inn - "fjern spiller"/"gjør til kaptein"-handlinger, men backend hadde KUN - GET/POST på `team_roster` — ingen DELETE eller PATCH. Spurte bruker - eksplisitt (samme mønster som andre scope-avklaringer denne økten) — svar: - bygg de to endepunktene nå. Lagt til i `app/routers/tournaments.py`: - `PATCH .../roster/{roster_id}` (bevisst enkel — setter/fjerner - `is_captain` på NØYAKTIG denne raden, håndhever IKKE "kun én kaptein per - lag", siden kaptein fortsatt bare er et merke, ikke en egen - autorisasjonsrolle) og `DELETE .../roster/{roster_id}` (204, idempotent - `NOT_FOUND` ved dobbel sletting — ikke krasj). - **`tournament-detail.tsx` sitt datalag skrevet om fra V0s mock:** henter - lag + roster (roster-radene bærer allerede `display_name`/ - `handicap_index_snapshot` fra APIet, så V0s separate `poolById`-oppslag - ble fjernet som overflødig) og organisasjonens spillerpool - (`GET /orgs/{id}/players`, brukt til type-ahead ved "legg til spiller"). - Alle fem mutasjonene (opprett lag, legg til eksisterende spiller, opprett - ny spiller inline + rostre, endre kaptein, fjern fra roster) kablet mot - ekte endepunkter — presentasjonskomponentene (kort, type-ahead, - fargevelger, bekreft-fjerning) beholdt uendret fra V0. - **Verifisert:** de to nye endepunktene testet mot en fersk - `teecup_scratch` (PATCH setter kaptein + riktig `NOT_FOUND` på ugyldig id, - DELETE gir 204 + idempotent `NOT_FOUND` ved gjentak, `test_isolation.sql` - fortsatt 12/12), ekte typesjekket frontend-build (5 ruter), redeploy av - BEGGE containere (backend-endepunktene er nye), `teecup.teeoff.no/dashboard` - → 200, `teeoff.no` upåvirket. Selve skrive-flyten på `/tournaments/[id]` - (opprett lag/roster) ikke testet med ekte data i denne runden — venter på - brukeren, samme mønster som dashboard-rundens skrive-test. -- **ADR-017 + migrasjon 007 (2026-07-18):** brukeren reiste selvregistrering - rett etter at roster-skrive-flyten var bekreftet. Full ADR skrevet - (5 beslutninger: offentlig påmelding uten innlogging, e-post som - sammenkoblingsnøkkel mot forhåndsopprettede spillere, egen - `tournament_registration`-tabell atskilt fra `team_roster` med - konfigurerbar godkjenning/kapasitet/venteliste, utvidet spillerprofil + - obligatorisk samtykke, og `public_tournament_org()`). Migrasjon - `007_registration_and_player_fields.sql` skrevet og scratch-verifisert - (001→007 kjører rent, `test_isolation.sql` fortsatt 12/12). - **Reelt arkitekturproblem løst underveis, ikke bare skjema:** et - offentlig (uautentisert) påmeldingskall kjenner en turnering-id, men - ingen org-kontekst — og uten `app.current_org` slipper RLS ingen rader - gjennom, heller ikke oppslaget for å FINNE riktig org. Løst med en snever - `SECURITY DEFINER`-funksjon (`public_tournament_org`) som KUN eksponerer - uuid→uuid-koblingen. **Verifisert presist, ikke bare "kjørte uten feil":** - kalt funksjonen som `teecup_app`-rollen med ingen `app.current_org` satt - — ga korrekt org-id for en kjent turnering, `NULL` (ikke feil) for en - ukjent — OG et RÅTT `SELECT` på `tournament` på SAMME tilkobling/rolle ga - fortsatt 0 rader, som beviser RLS ikke er brutt generelt, bare dette ene - smale unntaket eksisterer. - **Kjørt mot ekte `teecup_db` 2026-07-18** (bruker bekreftet eksplisitt i - samme økt): migrasjonen kjørte rent, `test_isolation.sql` fortsatt 12/12. -- **Registrerings-API LIVE (2026-07-18), samme dag:** ny - `app/routers/registration.py` — `GET /public/tournaments/{id}` og - `POST /public/tournaments/{id}/register`, begge UTEN `get_current_user` - eller `get_authorized_org` (helt uautentisert, egen `/public`-prefiks, - bevisst atskilt fra `/orgs/...` i koden). Bruker `public_tournament_org()` - (migrasjon 007) til å slå opp org-kontekst FØR RLS kan håndheve noe. - `app/routers/players.py` utvidet med alle sju nye ADR-017-feltene - (`mobile`/`email`/`birth_date`/`nickname`/`country`/`club`/ - `club_member_number`). `frontend/next.config.mjs` sin `rewrites()` - utvidet med `/public/*` (ADR-016s konsekvens: enhver ny API-prefiks MÅ - inn her). - **Ny brukers-oppdaget notat fanget FØR bygging, ikke etter:** brukeren - krevde eksplisitt at synlighet (offentlig/kun org/kun turnering- - deltakere) må være et VALG for fremtidige landingssider — notert grundig - i `FEATURE_BACKLOG.md` (koblet til samme åpne spørsmål for "Banter - Board"-feeden) FØR dette registrerings-API-et ble bygget, akkurat for at - det ikke skal gå i glemmeboken til landingsside-runden. - **Verifisert grundig mot fersk `teecup_scratch`** (001→007, - `test_isolation.sql` 12/12): hele registreringsløpet testet reelt — - samtykke-avvisning (400), duplikat-avvisning (409 `DUPLICATE`), - e-post-matching mot en organisator-forhåndsopprettet spiller (BEKREFTET: - ingen duplikatrad, mobil fylt inn via `COALESCE`, `display_name` IKKE - overskrevet), kapasitet+`waitlist`-policy (→ `waitlisted`), - kapasitet+`closed`-policy (→ 409 `LIMIT_REACHED`), `registration_ - requires_approval` (→ `pending`), utløpt frist (→ 409 - `REGISTRATION_CLOSED`), og `confirmed_count` i `GET`-responsen talt - riktig (kun `confirmed`+`pending`, ikke `waitlisted`/avviste). - **Rullet ut live:** begge containere redeployet, `teecup.teeoff.no/ - dashboard` og `/health` fortsatt 200, `teeoff.no` upåvirket, det - offentlige endepunktet bekreftet nåbart over ekte https (ukjent - turnering-id ga korrekt `404`/`NOT_FOUND`, ikke-destruktiv sjekk — selve - påmeldingsflyten med ekte data ikke testet mot prod i denne runden). - **E-post-basert kontosammenkobling LIVE, samme dag:** ny migrasjon - `008_link_player_by_email.sql` — `link_player_by_email(user_id, email)`, - samme `SECURITY DEFINER`-mønster som `public_tournament_org()` (007), - denne gangen for en tverr-org UPDATE i stedet for et lese-oppslag. - `verify_magic_link` (`app/routers/auth.py`) kaller den på HVER - innlogging (idempotent — funksjonens `WHERE user_id IS NULL` gjør - gjentatte kall til en no-op), ikke bare ved førstegangsopprettelse. - **Verifisert presist:** to separate org-er, hver med sin egen - organisator-opprettede "Kari"-rad (samme e-post, ulik store/små - bokstaver for å teste case-insensitivitet også) — ved Karis FØRSTE - innlogging ble BEGGE radene koblet til kontoen hennes, i to org-er hun - aldri har vært medlem av. Eksplisitt bekreftet: `organization_membership` - har NULL rader for henne etterpå — ren identitetskobling, ingen - privilegie-eskalering (å ha `player.user_id` satt gir ingen ny tilgang - gjennom `get_authorized_org`, som fortsatt krever ekte org-medlemskap - uavhengig av dette). Andre innlogging idempotent, ingen feil. - `test_isolation.sql` fortsatt 12/12. **Kjørt mot ekte `teecup_db` - 2026-07-18**, bruker bekreftet eksplisitt, backend redeployet, live - sjekker OK, `teeoff.no` upåvirket. - **ADR-017s backend er dermed komplett** (registrering + kontokobling). - Gjenstående: frontend-påmeldingsskjema og landingssider — egen, senere - ADR-runde (se `FEATURE_BACKLOG.md`). -- **ADR-018 + migrasjon 009: landingssider, backend LIVE (2026-07-18), - samme dag:** trenivås synlighet (`tournament.visibility`: - `public`/`org`/`participants`, default `org` — trygg standard) + - `organization.public_profile`. **Reell presisering funnet underveis, ikke - antatt på forhånd:** RLS (`org_isolation`) beskytter kun TENANT-grenser - (org A ser aldri org B), IKKE innholds-synlighet innenfor riktig - org-kontekst — det eksisterende `GET /public/tournaments/{id}` (ADR-017) - leste allerede fullt innhold uten synlighetssjekk, fordi RLS er fornøyd - så snart org-konteksten er satt, uansett hvem som spør. `visibility` - håndheves derfor eksplisitt i `app/routers/registration.py`, på BÅDE - lesing og registrering (ADR-018 Beslutning D: kan du ikke se turneringen, - kan du heller ikke melde deg på den — bekreftet med bruker FØR bygging). - Ny `get_current_user_optional` i `app/auth.py` (som `get_current_user`, - men returnerer `None` i stedet for 401 — offentlige endepunkter skal - fungere for anonyme lesere også). Ny "deltaker"-autorisasjonsvei: en - innlogget bruker med `player.user_id` koblet (ADR-017) OG en - `tournament_registration`- eller `team_roster`-rad for NØYAKTIG den - turneringen får se `participants`-synlige turneringer, uansett - org-medlemskap. - **Ekte migrasjonsfeil funnet OG rettet UNDER scratch-verifisering, ikke - antatt riktig:** migrasjonen feilet først ("column slug already exists") - — `organization.slug` har ligget i skjemaet siden migrasjon 001 - ("f.eks. subdomene/URL-vennlig", allerede med en plain `UNIQUE`), noe jeg - hadde oversett fullstendig og forsøkt å legge til på nytt. Rettet ved å - fjerne den doble `ADD COLUMN` + den overflødige partielle unik-indeksen - (001 sin plain `UNIQUE` dekker "unik når satt" allerede, siden Postgres - behandler NULL som distinkt), beholde kun de nye `CHECK`-constraintene. - Kjørte rent på ny etter fiksen. **Lærdom:** grep alltid eksisterende - skjema for feltnavn FØR en ny migrasjon skrives, ikke bare stol på - hukommelsen om hva som "sikkert" ikke finnes fra før. - Ny tabell `tournament_sponsor` (navn+lenke aktivt, `logo_key` inert til - MinIO-runden — samme med `tournament.hero_image_key`). Tredje - `SECURITY DEFINER`-bro i prosjektet: `public_org_by_slug()` (etter - `public_tournament_org` 007, `link_player_by_email` 008) — returnerer - `NULL` for BÅDE "finnes ikke" og "finnes, men er privat", samme - anti-enumerering som magic-link. - **Fylte også et implisitt hull oppdaget underveis:** ADR-en beskrev - hvordan synlighet skulle håndheves, men ingen tidligere runde hadde bygget - noen vei for organisator til faktisk å SETTE disse feltene. Lagt til: - `PATCH /orgs/{id}/tournaments/{id}` (visibility/description/ - registrerings-innstillinger, ekte PATCH-semantikk via Pydantic sin - `exclude_unset` — et utelatt felt nullstilles IKKE), `PATCH /orgs/{id}` - (slug/public_profile), full sponsor-CRUD. `_fetch_sessions()` trukket ut - som delt hjelpefunksjon i `tournaments.py` (delt mellom den innloggede - og den nye offentlige `GET /public/tournaments/{id}/sessions` — blind - draw-hemmelighold, ADR-013, arves automatisk, ikke reimplementert). - **Verifisert grundig mot fersk `teecup_scratch`** (001→009, - `test_isolation.sql` 12/12): hele synlighetsmatrisen testet med ekte - HTTP-kall — anonym avvist på `org`-synlig turnering (både lesing OG - registrering), `PATCH` til `public` + beskrivelse + sponsor fungerte, - anonym lesing fungerte deretter, `participants`-synlighet bekreftet - reell chicken-and-egg-konsekvens av Beslutning D (ingen kan selv- - registrere seg til en `participants`-synlig turnering, kun organisator - kan legge til direkte — korrekt, ikke en bug), en organisator-rostret - spiller som logget inn fikk tilgang, en tilfeldig innlogget FREMMED - (ikke deltaker) ble fortsatt avvist, org-landingsside viste KUN - `public`-synlige turneringer, `CHECK`-constraint (`public_profile` - krever `slug`) avvist korrekt, ugyldig slug-format avvist av Pydantic, - sponsor-sletting fungerte. - **Kjørt mot ekte `teecup_db` 2026-07-18**, bruker bekreftet eksplisitt, - backend redeployet, live sjekker OK, `teeoff.no` upåvirket. Ingen - frontend-endring nødvendig for selve API-tilgangen (det brede - `/public/:path*`-mønsteret fra ADR-016 dekker allerede `/public/orgs/*`). - **Gjenstår:** selve landingsside-SKJERMENE i frontend (V0), og - MinIO/bildeopplasting — bevisst utsatt, egen runde. -- **Offentlig turnering-landingsside LIVE (2026-07-18), samme dag:** fjerde - V0-skjerm (`components/public-tournament.tsx`, ny rute `/t/[id]`) — - banner (bevisst permanent farge-/gradient-utseende, ikke en "bilde - kommer"-plassholder), presenterende tekst, status (datoer + "X av Y - plasser"), program, sponsorer (navn+lenke), og et påmeldingsskjema med - navn+e-post synlig først og resten bak en "flere detaljer"-utvidelse — - tre distinkte bekreftelsestilstander (bekreftet/venteliste/godkjenning - venter), ikke én generisk "takk". - **Ingen ny reeksport-kollisjon denne gangen** — kun étt genuint nytt - filnavn (`public-tournament.tsx`), resten var kjent V0-revert av - allerede-tilpassede filer, samme mønster som før. - **V0 opprettet komponenten, men ingen rute** — la selv til `app/t/[id]/ - page.tsx` (bevisst en FLAT `/t/[id]`-sti, ikke nøstet under `/orgs/...` - som den innloggede turnering-detalj-siden, siden det offentlige API-et - kun trenger turnering-id, ikke org-id). - **Datalaget skrevet om fra V0s mock:** ekte `fetch` mot - `GET /public/tournaments/{id}` + `/sessions`, ekte `POST .../register`. - Håndterer 403 (`NOT_VISIBLE`, ADR-018) og 404 med en egen tilgang-avvist- - tilstand V0 ikke hadde bedt om (fantes ikke i prompten, men trengs for at - siden faktisk skal fungere for `org`/`participants`-synlige turneringer). - Mappet `status:"waitlisted"` fra API-et til komponentens `"waitlist"`, og - skjemaets norske kjønnsvalg ("kvinne"/"mann"/"annet") til API-ets - `^[mfx]$`-mønster — to reelle navnekollisjoner mellom V0s UI-språk og - API-kontrakten, ikke bare et rett-frem felt-for-felt-uttrekk. - **Reell driftsfeil funnet OG rettet under scratch-test, ikke i - produksjon:** `pnpm install` (uten `--ignore-scripts`) i dev-server- - testoppsettet feilet stille med tom logg og exit 1 — corepack hadde - hentet en NY pnpm-versjon (11.13.1 → 11.14.0) siden sist, som gjør - "ignored builds"-varselet til en hard feil i stedet for bare en - advarsel. Rettet ved å bruke samme `--ignore-scripts`-flagg som - `frontend/Dockerfile` allerede bruker (upåvirket av denne — bekreftet - ved at selve prod-buildet fortsatt gikk rent). **Lærdom:** `Dockerfile` - sin pinning av *kode* er ikke det samme som å pinne *verktøyene rundt* - (corepack henter alltid siste pnpm) — verdt å huske neste gang et - scratch-dev-server-oppsett plutselig feiler uten åpenbar grunn. - **Verifisert grundig mot fersk `teecup_scratch` + en ekte kjørende - frontend-dev-server** (ikke bare `next build`): ekte turnering med - beskrivelse, kapasitet, sponsor og økt opprettet via API-et, hentet - gjennom frontend-proxyen (ikke direkte mot backend) og bekreftet - byte-for-byte riktig — inkludert en ekte `POST`-registrering som økte - `confirmed_count` fra 0 til 1. - **Rullet ut live**, kun `teecup_frontend` (ingen backend-endring denne - runden), `teeoff.no` upåvirket. - **Gjenstår:** org-landingssiden (egen V0-prompt), Open Graph-metadata for - deling, MinIO/bilder. -- **Offentlig klubb-landingsside LIVE (2026-07-18), samme dag:** femte og - siste V0-skjerm i ADR-018 (`components/public-club.tsx`, ny rute - `/clubs/[slug]`). Samme banner-språk som turnering-siden, liste over - klubbens turneringer (gjenbrukte eksisterende `TournamentCard`), egen tom- - tilstand. - **Reell delt-komponent-kollisjon løst, ikke duplisert bort:** - `TournamentCard` var bygget for KUN den innloggede konteksten (krevde - `orgId`, lenket til `/tournaments/{id}?org=...`). I stedet for en egen - kopi av kortet for den offentlige siden, gjort `orgId` valgfri — - satt (dashbordet) gir innlogget lenke, utelatt (klubbsiden) gir - `/t/{id}` i stedet. Samme kort, to kontekster, ingen duplisering. - Bekreftet dashbordets egen bruk uendret/upåvirket etterpå. - **V0 opprettet denne gangen selv en rute** (`app/clubs/[id]/page.tsx`, - uten noen props sendt inn i det hele tatt) — men navnga parameteren - `[id]` selv om den faktisk er en SLUG (`public_org_by_slug()`, ADR-018). - Skrev selv en ny, riktig `app/clubs/[slug]/page.tsx` i stedet for å bruke - V0s (feilnavngitte og prop-løse) versjon. - **Verifisert mot fersk `teecup_scratch` + ekte kjørende frontend-dev- - server:** org med slug+`public_profile`, én turnering satt `public`, én - latt stå på default `org` — klubbsiden viste GJENNOM frontend-proxyen - kun den ene offentlige turneringen, den org-private var korrekt - usynlig (samme filtermønster som API-et selv, bekreftet fra - klientsiden også). Ukjent slug ga korrekt 404. - **Rullet ut live**, kun `teecup_frontend`, `teeoff.no` upåvirket. - **ADR-018s planlagte skjermer er dermed komplette.** Gjenstår: Open - Graph-metadata for deling, MinIO/bilder — begge bevisst egne, senere - runder. -- **Open Graph-metadata LIVE (2026-07-18), samme dag — ADR-018 dermed - helt ferdig:** `generateMetadata()` lagt til på `/t/[id]` og - `/clubs/[slug]` (ekte tittel/beskrivelse fra API-et, `og:site_name`, - trygg fallback-tittel ved ukjent id/slug — feil her skal ALDRI hindre - selve siden i å laste). - **Reell driftsfeil funnet FØR den nådde produksjon, ikke etter:** - `generateMetadata()` kjører server-side ved REQUEST-tid, ikke i - nettleseren — går derfor IKKE gjennom `next.config.mjs` sin - `rewrites()` (som kun gjelder nettleser-trafikk inn til Next.js- - serveren). Måtte derfor lese `TEECUP_API_ORIGIN` direkte, men den - variabelen fantes KUN i `Dockerfile` sitt builder-steg — `ENV` satt i - ett `FROM`-steg arves ikke til et senere. Rettet ved å sette samme - `ARG`/`ENV` på nytt i runner-steget også. - **Verifisert presist at fiksen faktisk virker, ikke bare at bygget gikk - gjennom:** bygget det EKTE produksjonsimaget (ikke dev-server) pekt mot - en scratch-backend med en ekte offentlig turnering+org, hentet den - faktiske server-rendrede HTML-en og bekreftet ekte `<title>`/`og:title`/ - `og:description` — ikke bare at TypeScript kompilerte. Ukjent - turnering-id ga korrekt trygg fallback-tittel. - **Bevisst utenfor omfang:** `og:image` — ingen ekte bilde finnes ennå - (MinIO-runden). La til `metadataBase` i `app/layout.tsx` nå likevel, som - forarbeid slik at et fremtidig relativt bilde-URL løses riktig uten en - egen fiks da. - **Rullet ut live**, kun `teecup_frontend`, `teeoff.no` upåvirket. -- **MinIO-runden LIVE (2026-07-18), samme dag — ADR-018 dermed HELT - ferdig, ingenting utsatt igjen:** ny `teecup-minio`-tjeneste (persistent - volum, genererte credentials i `.env`), backend-endepunkter for ekte - bildeopplasting til `tournament.hero_image_key`/`tournament_sponsor. - logo_key` (som lå inerte siden migrasjon 009), offentlige API-svar bygger - nå fulle URL-er, `og:image` koblet på i `/t/[id]` sin `generateMetadata`. - **Sent, men viktig presisert krav underveis:** brukeren avbrøt en - verktøyskall midtveis for å presisere at ALLE bilder skal konverteres - til AVIF for plassbesparelse. Snudde HELE opplastingsarkitekturen på - dette: droppet den opprinnelige planen om presignerte URL-er (nettleser - laster opp DIREKTE til MinIO) til fordel for ekte multipart-opplasting - GJENNOM API-et, som konverterer til AVIF (Pillow + pillow-avif-plugin) - FØR lagring. Dette forenklet arkitekturen betydelig: kun ÉN MinIO-klient - trengs nå (før: to separate klient-oppsett for henholdsvis internt - admin-arbeid og offentlig presignering), og Caddy sin nye rute trenger - ikke lenger bevare Host-headeren presist (relevant kun for SigV4- - signaturverifisering av presignerte URL-er, ikke for anonym public-read). - **To reelle feil funnet UNDER scratch-verifisering, aldri i produksjon:** - (1) `pillow-avif-plugin` krever ingen ekstra systempakker i - `python:3.12-slim` -- verifisert med et frittstående encode/decode- - rundtrip FØR det ble tatt i bruk i selve API-et. (2) MinIO validerer - `Host`-headeren STRENGT og avviser understrek som ugyldig vertsnavn -- - et tjenestenavn med understrek (`teecup_minio`, konsistent med - `teecup_api`/`teecup_frontend`) feilet umiddelbart ved oppstart - ("Invalid Request (invalid hostname)"). Bekreftet presist ved å teste - samme oppsett med bindestrek i stedet (`teecup-minio`) -- fungerte - umiddelbart. Alle referanser rettet til bindestrek FØR noe ble forsøkt - mot ekte infrastruktur. - **Caddy:** ny `/teecup-media/*`-rute på det EKSISTERENDE - `teecup.teeoff.no`-blokket (IKKE et nytt subdomene -- en tidlig sjekk - avdekket at `media.teecup.teeoff.no` fantes som en wildcard DNS-post, - men pekte til en IPv6-adresse denne serveren ikke har i det hele tatt; - path-prefiks på et allerede fungerende domene unngikk hele den DNS- - avhengigheten). Samme stale-inode-oppførsel som alle tidligere Caddy- - runder -- full `docker restart teeoff_caddy`, brukeren bekreftet - eksplisitt. `teeoff.no` upåvirket. - **Verifisert grundig, i flere lag:** frittstående AVIF-encode/decode- - test, full scratch-kjede (fersk Postgres + en ISOLERT scratch-MinIO- - container) med et ekte opplastet bilde -- bekreftet konvertert til - gyldig AVIF (800×400, 406 bytes for et helfarget testbilde), bekreftet - lagret med riktig nøkkel, bekreftet lesbart ANONYMT direkte mot MinIO - (uten Caddy, isolerer bucket-policyen), bekreftet `hero_image_url`/ - `logo_url` bygget riktig i det offentlige API-svaret. Alle tre - valideringsveier testet (ugyldig content-type, korrupt bildeinnhold, - for stor fil >8MB) -- alle ga korrekt `VALIDATION_FAILED`. Etter - Caddy-omstart: et ekte anonymt kall mot `/teecup-media/...` på - produksjonsdomenet ga en ekte MinIO S3-XML-feilrespons (`NoSuchKey` for - en scratch-nøkkel som naturligvis ikke finnes i prod) -- beviser ruten - treffer MinIO selv, ikke frontend sin egen 404-side. - `test_isolation.sql` fortsatt 12/12 gjennom hele runden. - **Bevisst utenfor omfang:** ingen faktisk opplasting av et EKTE bilde - til en EKTE, live turnering i denne runden (ville krevd å skrive - test-data i brukerens ekte konto uten å bli spurt) -- tilbudt, ikke - utført. Selve dra-og-slipp-opplastingsskjermen i frontend (V0) er - fortsatt ikke bygget, som avtalt fra starten av runden. - -- **Program-skjerm (økter/tidsplan), bygget og SCRATCH-verifisert, IKKE - live ennå (2026-07-18):** sjette V0-skjerm, første i "bygg i rekkefølgen - ting brukes"-serien (etter lag/roster: program → blind draw → scorekort → - leaderboard). `components/tournament-program.tsx`, ny rute - `/tournaments/[id]/program`. Tidslinje over økter + opprett-skjema - (format, hullomfang, scoringsmodus, poeng, klokkeslett+intervall, - starthull, kollapsbar "avansert"-seksjon med ADR-014s fire brytere). - **Reelt blokkerende hull funnet FØR integrering:** `SessionCreate. - course_id` er påkrevd, men INGEN endepunkt kunne noensinne produsere en — - ADR-004s teeoff-integrasjon er fortsatt bare vedtatt (ingen utgående - HTTP-klient finnes). Spurte bruker eksplisitt — svar: bygg enkel - course-CRUD nå. Ny `app/routers/courses.py` (`GET`/`POST /orgs/{id}/ - courses`, kun `source='custom'`). Program-skjemaet fikk et - type-ahead-felt for bane (samme mønster som spiller-type-ahead i - roster-skjermen). - **Reell korrekthetsfeil rettet FØR integrering:** 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 -- ren `"scramble"` avvises med 400. Rettet i - frontend-mappingen til to segment-knapper. - **`allowance_override`-JSON-formen verifisert eksakt** mot - `app/handicap.py` sin `parse_allowance_config`/`_strategy_from_json` - (`{type: "combined"|"per_player", percentage: 0..1}`, ikke en flat - prosent) -- frontend velger riktig `type` ut fra om formatet er - side-enhet eller spiller-enhet, konverterer 0–100-skjemafelt til 0–1. - **Scratch-infrastruktur denne runden, bevisst forskjellig fra tidligere:** - brukte en ISOLERT `teecup_app_scratch`-rolle (`GRANT teecup_app TO - teecup_app_scratch`) i stedet for den ekte `teecup_app`-rollen -- den er - nå cluster-global og produksjonskritisk (delt Postgres-instans med - `teecup_db`), så et eldre plandokuments "drop teecup_app-rolle"- - opprydning (skrevet FØR go-live) er utdatert og ble bevisst IKKE fulgt. - Egen isolert scratch-MinIO-container også (app-oppstart krever en - nåbar MinIO for `ensure_bucket()`). - **Verifisert:** courses opprettet+listet, kryss-org-isolasjon bekreftet, - økt med klokkeslett, økt med `scramble_4`+full `allowance_override`- - rundtur, gammel `"scramble"`-verdi avvist (400), `test_isolation.sql` - 12/12, ekte typesjekket PRODUKSJONSBUILD (samme `Dockerfile` som faktisk - deployes) kjørt og bekreftet. - **Diffet V0-eksporten mot live-treet FØR noe ble tatt inn** (samme mønster - som alle tidligere runder): kun tre reelt nye filer, resten forventede - full-reverts, ikke rørt. Fjernet V0s dev-only forhåndsvisnings-toggle; - lagt til fanerad ("Lag og spillere" / "Program") i BEGGE skjermene siden - V0 ikke visste om den andre når den ble generert i egen prompt. - **Rullet ut live 2026-07-18**, bruker bekreftet eksplisitt: `docker - compose up -d --build teecup_api teecup_frontend` (kun disse to, `teecup- - minio` urørt). Verifisert: begge containere boot-et rent (`Application - startup complete`, Next.js `Ready`), `teecup.teeoff.no/health` og - `/dashboard` → 200, `teeoff.no` upåvirket (200). -- **ADR-004 (teeoff-banedata) kartlagt, IKKE bygget (2026-07-18):** brukeren - påpekte rett etter program-skjerm-rullingen at ADR-004s teeoff- - integrasjon fortsatt bare er vedtatt, ikke bygget (kun `source='custom'` - finnes). Kartlagt `/opt/teeoff/backend/main.py` (annet repo, kun lest): - `GET /api/facilities/{slug}` (offentlig, INGEN auth/API-nøkkel) returnerer - allerede alt teecup trenger — `courses[].holes[]` (`par`, `hcp_index` - = stroke index), `courses[].tees[]` (`name`, `cr_men`/`slope_men`, - `cr_women`/`slope_women` — kjønnsdelt WHS-rating). CORS-lista på teeoff- - siden ekskluderer teecups origin, men er IRRELEVANT for et server-til- - server-kall (kun nettleser-fetch rammes av CORS). Ingen dokumentasjon - (README/OpenAPI) finnes for dette API-et i teeoff-repoet — kartleggingen - over er basert på å lese koden direkte. Stabil identifikator: `facilities. - slug` (f.eks. `borregaard-golfklubb`) — selve banen har kun en intern - serial-id, ingen egen slug, så `course.external_course_ref` må bære - facility-slug + bane-id sammen. - **Oppdatering samme dag: brukeren ba meg ta fatt på dette NÅ** (bevisst - sidesprang fra "bygg i rekkefølgen ting brukes"-planen — blind draw- - skjermen er fortsatt neste steg i den planen ETTERPÅ, ikke droppet). - Design besluttet og skrevet som **ADR-019** (se ARCHITECTURE_DECISIONS.md): - import (kopi) ved organisators eksplisitte valg, ikke live oppslag ved - hver bruk — fryser data på importtidspunktet, samme reproduserbarhets- - prinsipp som handicap-snapshotten (ADR-007). Server-til-server-kall - (`teecup_api` → `http://teeoff_api:8000`, internt Docker-nettverk, ingen - auth trengs). Ny migrasjon `010` (unik `external_course_ref` per org, - hindrer dupliserte importer). Se ADR-019 for alle fem delbeslutningene. - **Bygget, scratch-verifisert MOT EKTE `teeoff_api`** (ikke en simulert - respons — ekte HTTP-kall til den kjørende produksjonscontaineren, kun - lesing): søkte opp "Borregaard" i teeoff sine 174 publiserte anlegg, - hentet Borregaard Golfklubb sin hovedbane (18 hull, 4 tee-farger × - kjønn), importerte den til en scratch-org — alle 18 hull med riktig - par/stroke-index (1-18, unik), alle 8 tee/tee_rating-rader med riktig - full_18 course/slope-rating verifisert direkte i databasen, opprettet - deretter en ekte økt med den importerte banen som `course_id` (beviser - hele veien til handicap-motoren fungerer, ikke bare selve importen). - Reimport av samme bane korrekt avvist (409 DUPLICATE, migrasjon 010 sin - indeks). Kryss-org-isolasjon bekreftet, ukjent teeoff-slug ga korrekt 404, - `test_isolation.sql` 12/12. Ekte typesjekket produksjonsbuild av - frontend-utvidelsen (bane-søk i program-skjemaet: søk anlegg → velg bane → - importer, samme UI-mønster som spiller-/bane-type-ahead ellers i appen). - **Rullet ut live 2026-07-18**, bruker bekreftet eksplisitt: migrasjon 010 - kjørt mot ekte `teecup_db` (kun én ny partiell unik-indeks, ingen - eksisterende rader rørt), begge containere bygget+redeployet, live - sjekker OK, `teeoff.no` upåvirket. **"Bygg i rekkefølgen ting brukes"- - planen gjenopptas nå** — blind draw-skjermen er neste steg. - **Reell produksjonsbug funnet OG fikset samme dag, rapportert av bruker - som faktisk brukte funksjonen:** brukeren klikket seg korrekt via - dashbord → turnering → Program-fane (bekreftet med skjermbilde + full - klikk-sti, ikke gjettet), åpnet "Hent bane fra teeoff", søkte "Tjøme", og - fikk feilmeldingen "Mangler organisasjon i lenken" — URL-en hadde da - MISTET `?org=...&name=...`. Root-cause: `OfficialCourseSearch` sitt eget - søke-`<form onSubmit={runSearch}>` var rendret INNI `CreateSessionCard` - sitt eksisterende `<form onSubmit={handleSubmit}>` -- nestede - `<form>`-elementer er ugyldig HTML. Nettleseren slår sammen de to - skjemaene i den faktiske DOM-en, så "Søk"-knappen submittet i praksis det - YTRE økt-skjemaet som en ekte native side-navigasjon (GET til gjeldende - sti, ingen navngitte felt => tom spørrestreng) -- dette vasket bort - org-parameteren og landet brukeren på siden sin egen org-guard. **Fikset** - ved å fjerne det indre `<form>`-elementet helt (vanlig `<div>` + - Enter-tast-håndtering på inputet + `type="button"` i stedet for - `type="submit"` på søkeknappen) -- gjør nestede skjemaer strukturelt - umulig fremover for denne komponenten. Ekte typesjekket - produksjonsbuild kjørt på nytt, kun `teecup_frontend` redeployet (ingen - backend-endring). **Lærdom for fremtidige skjermer:** en ny - søk-/underskjema-widget som skal plasseres INNI et eksisterende skjema - (slik som denne bane-søk-widgeten ligger inni økt-opprett-skjemaet) må - ALDRI være et eget `<form>` -- bruk `<div>` + eksplisitt - klikk-/Enter-håndtering. - -- **Organisator-overstyring i `team_authz.py`, LIVE (2026-07-18):** reist av - brukeren rett før blind draw-skjermen skulle designes: `app/team_authz.py` - sin `user_may_act_for_team` krevde tidligere en `team_roster`-rad med - lenket bruker-konto — ingen vei for organisatoren til å låse/legge til - deltakere/føre score hvis ingen spiller hadde logget inn ennå (vanlig - tidlig i en turnering). Utvidet til også å godta org-eier/admin - (`organization_membership.role IN ('owner','admin')`), på ETHVERT lag, - ingen unntak for at organisatoren selv er rostret på motstanderlaget. - **Det unntaket ble bevisst vurdert og avvist** (brukeren spurte selv om - det, jeg anbefalte det opprinnelig, men vi kom sammen frem til at det ville - skapt en verre låsning: er organisatoren spillende på Lag A og - Lag B heller ikke har noen innlogget spiller, ville Lag B blitt helt - låst ute) — TeeCup er et tillitsbasert klubb-/vennegjeng-verktøy, ikke en - sikkerhetsgrense mot en fiendtlig organisator som uansett allerede ser - begge lags fulle troppe-liste (blind draw skjuler kun selve - kamp-paringen). Alle 5 kallsteder oppdatert (matches.py sin - add_participant/lock_lineup, scoring.py sin submit_hole_score/ - submit_hole_result). **Verifisert grundig i scratch:** org-eier uten - roster kan nå låse BEGGE lag + føre score, en vanlig 'member'-rolle - fortsatt blokkert (uendret), en rostret spiller fungerer uendret - uavhengig av org-rolle. `test_isolation.sql` 12/12. Ingen migrasjon (ren - Python-endring) — kun `teecup_api` redeployet, `teeoff.no` upåvirket. - -- **Tee-endepunkter bygget og LIVE (2026-07-18), rett før blind draw-skjermen:** - fant et hull som blokkerte selve blind draw-flyten: `match_participant. - tee_id` er påkrevd, men det fantes INGEN `GET`-vei for å liste en banes - tee-er (selv offisielt importerte baner har tee-rader, men ingenting - eksponerte dem) OG egendefinerte («custom») baner har ALDRI hatt noen - vei til å FÅ tee-er i det hele tatt — dette er ikke noe ADR-019 innførte, - det var et hull som fantes fra før custom-baner ble lagt til i første - omgang, bare usynlig til nå. Konsekvens før fiksen: en turnering satt opp - på en manuelt navngitt bane kunne ALDRI få en ekte deltaker lagt til på - noen match. **Presisering (brukeren spurte eksplisitt):** dette er IKKE - noe som må løses i teeoff.no sin kode — egendefinerte baner er per - definisjon baner teeoff ikke kjenner til, så dette er en ren - teecup-intern funksjon, uavhengig av ADR-019-integrasjonen. - Ny `GET/POST /orgs/{id}/courses/{id}/tees` i `app/routers/courses.py`. - `POST` avviser eksplisitt forsøk på offisielle baner (400 - `VALIDATION_FAILED` — de får tee-ene sine fra teeoff-importen, ikke - manuelt). Kun full_18-rating dekket (samme begrunnelse som ADR-019 - Beslutning D). **Bevisst UTENFOR omfang denne runden, egen senere sak:** - hull-/stroke-index-data for egendefinerte baner (`hole`-tabellen forblir - tom for custom-baner) — trengs for korrekt slagfordeling i SCORING-fasen - (ADR-008), ikke i blind draw, så det løses naturlig når scorekort- - skjermen bygges (samme "bygg i rekkefølgen ting brukes"-logikk). - **Verifisert grundig i scratch:** tom tee-liste på fersk custom-bane, - opprett+list tee på custom-bane, offisiell bane viser alle 8 importerte - tee-er, manuell tee-opprettelse avvist på offisiell bane, og — den - faktiske payoff-en — en deltaker lagt til en match på en custom-bane-økt - for FØRSTE gang noensinne (`POST .../matches/{id}/participants` lykkes - nå med en tee_id fra en nyopprettet custom-tee). `test_isolation.sql` - 12/12. Ingen migrasjon, kun `teecup_api` redeployet, `teeoff.no` - upåvirket. - -- **Blind draw-skjermen LIVE (2026-07-18):** syvende V0-skjerm, - `components/session-blind-draw.tsx`, ny rute - `/tournaments/[id]/sessions/[sessionId]`. To lag-kolonner, hver med sine - matcher ("flights"), legg til/fjern spiller+tee per plass (antall plasser - avhenger av format), lås-knapp med bekreftelse, avslørings-visning når - begge lag har låst. Program-skjermens økt-kort er nå klikkbare inn hit. - **Datamodell tilpasset fra V0s mock til API-ets faktiske sett-modell:** - V0 designet faste, indekserte "Slot"-arrays (kontrollert skjema-state); - API-et har verken PATCH på `match_participant` eller noen slot-indeks — - kun opprett/slett av en løs deltaker-mengde per side. Løst med en - "legg til spiller"-inline-form (samme mønster som spiller-/bane-type-ahead - ellers i appen) i stedet for faste dropdown-rader; funksjonelt likeverdig, - strukturelt riktigere for API-ets faktiske form. - **To reelle hull funnet og fikset FØR/UNDER integrering:** - 1. Ingen `DELETE` fantes for `match_participant` — en kaptein kunne aldri - angre et valg før låsing uten å etterlate en foreldreløs rad. Ny - `DELETE /orgs/{id}/matches/{match_id}/participants/{participant_id}` - (samme ALREADY_LOCKED/NOT_ROSTERED_ON_TEAM-sjekker som opprett). - 2. **"Skriv blindt"-hull, funnet under selve scratch-testingen (ikke bare - tenkt ut på forhånd):** forrige rundes org-admin-overstyring i - `team_authz.py` lot en organisator LEGGE TIL deltakere på et lag de - ikke selv er rostret på, men `app/blind_draw.py` sin `own_team_ids()` - sjekket KUN rostret spiller for SYNLIGHET — så organisatoren så aldri - sine egne tilføyelser igjen før begge lag hadde låst. Fikset ved å gi - `own_team_ids()` samme owner/admin-utvidelse som `user_may_act_for_ - team()` — en org-admin ser nå BEGGE lag umiddelbart (konsistent med at - de uansett allerede har full tilgang, se forrige rundes resonnement), - mens en faktisk rostret kaptein fortsatt kun ser sitt eget lag før - reveal. Kun ett reelt kallsted (`matches.py` sin `list_matches`). - **Tredje, urelatert bug fanget under samme scratch-økt og fikset på - brukerens eksplisitte forespørsel:** `POST .../matches/{id}/participants` - krasjet med en rå 500 (`TypeError` i `handicap_engine.py` sin - `course_handicap_raw`) hvis spilleren manglet `handicap_index` OG - `use_handicap` var på (standard) — preeksisterende, ikke noe denne - runden introduserte. Fikset i `app/routers/matches.py` sin - `add_participant`: sjekker nå `handicap_index_snapshot IS NULL` EKSPLISITT - FØR innsetting når `use_handicap` er sann, avviser med en klar - `VALIDATION_FAILED` (400) i stedet for å krasje. Bekreftet at - `use_handicap=false` fortsatt tillater en spiller uten handicap - (scratch-spill), og at en spiller MED handicap fortsatt fungerer uendret. - **Verifisert grundig i scratch, flere runder:** DELETE-endepunktet - (fjern+idempotent 404 ved gjentak+409 etter lås), synlighetsfikset (org- - admin ser begge sider, rostret kaptein ser fortsatt kun eget lag), - null-handicap-fikset (avvist rent, scratch-modus upåvirket, normal - spiller upåvirket), full opprett-match→legg-til-deltaker→lås→avslør- - syklus ende-til-ende. `test_isolation.sql` 12/12 etter hver runde. Ekte - typesjekket produksjonsbuild. - **Rullet ut live**, bruker bekreftet eksplisitt: begge containere - bygget+redeployet (ingen migrasjon), `teeoff.no` upåvirket. - -- **Backend-forarbeid for scorekort-skjermen, LIVE (2026-07-18):** to hull - funnet ved gjennomlesing av `scoring.py` FØR V0-prompten ble skrevet, - samme føre-var-mønster som resten av økten. - 1. **Hull-/stroke-index-data for egendefinerte baner** (bevisst utsatt fra - tee-rundens status-notat) — `hole`-tabellen har ALDRI hatt noen vei inn - for custom-baner, som blokkerte `stroke`-scoringsmodus helt (`hole_ - result`-modus trenger den ikke). Ny `GET/POST /orgs/{id}/courses/{id}/ - holes` i `app/routers/courses.py` — engangs alle-18-på-en-gang - (avviser feil antall, avviser hvis banen allerede har hull, avviser på - offisielle baner som får hullene sine fra teeoff-import). - 2. **`GET scorecard` viste kun UTLEDET vinn/tap/delt per hull, aldri de - faktiske tallene som var registrert** — umulig å bygge en skjerm som - viser gjeldende tilstand ved gjenlasting. Utvidet `Scorecard`-modellen - i `app/routers/scoring.py` med `stroke_entries`/`hole_result_entries` - (rå `hole_score`/`match_hole_result`-rader, nøyaktig ett av de to fylt - ut avhengig av øktens scoring_mode). - **Verifisert grundig i scratch:** hull-validering (feil antall avvist, - gjentatt oppsett avvist), og en FULL `stroke`-modus scoringsrunde med - ekte handicap-justert nettoberegning på en egendefinert bane for FØRSTE - gang noensinne (ulikt slagtall — 4 mot 5 — ga korrekt "halved" etter - handicap-utjevning), scorecard sin nye `stroke_entries` bekreftet å - returnere nøyaktig det som ble registrert. `test_isolation.sql` 12/12. - **Rullet ut live**, ingen migrasjon, kun `teecup_api` redeployet, - `teeoff.no` upåvirket. - -- **Scorekort-skjermen LIVE (2026-07-18):** åttende V0-skjerm, - `components/session-scorecard.tsx`, ny rute `/tournaments/[id]/sessions/ - [sessionId]/matches/[matchId]`. Ett hull i fokus om gangen (banebruk, - ikke et regneark) — store slag-steppere for `stroke`-modus, tre-valgs - vinner-knapper for `hole_result`-modus, hull-chip-navigasjon, kollapsbar - full oversikt, feiret "avgjort"-banner som låser alt til read-only. - Blind draw sin avslørte visning lenker nå til hver match sitt scorekort. - **Ingen nye backend-hull denne runden** — forrige rundes forarbeid - (hull-endepunkter + `stroke_entries`/`hole_result_entries` i scorecard) - dekket akkurat det skjermen trengte. - **Verifisert grundig i scratch, inkludert et fullt oppsett som speiler - NØYAKTIG frontend-ens egen last-sekvens** (sessions→teams→matches→ - holes→scorecard, deretter en ekte `POST hole-scores` for begge spillere - på hull 1 og en refetch): status_text/derivert resultat oppdaterte seg - korrekt ("1 UP (A)", hull 1 → "a"), alle feltnavn stemte eksakt med - TypeScript-typene uten justering. `test_isolation.sql` 12/12, ekte - typesjekket build. - **Rullet ut live**, ren frontend-endring, `teeoff.no` upåvirket. - -- **Leaderboard-backend LIVE (2026-07-18):** ny `GET /orgs/{id}/ - tournaments/{id}/leaderboard` i `app/routers/tournaments.py` — summerer - poeng på tvers av ALLE økter/matcher i turneringen, både total og - per-økt-delsum. - **Reelt korrekthetshull funnet OG designet rundt FØR koden ble skrevet, - ikke oppdaget i ettertid:** `match.team_a_id`/`team_b_id` settes PER - MATCH ved opprettelse, ikke garantert konsistent på tvers av matcher — - en organisator kunne i prinsippet opprettet match 1 med team_a=Rød og - match 2 med team_a=Blå. En naiv summering av `points_side_a`/`points_ - side_b` ville da blandet sammen poeng fra to ULIKE fysiske lag. Løst ved - å ALDRI summere på "a"/"b"-labelen — kun på det ekte lag-id-et - (`points_by_team: dict[team_id, float]`, både i totalen og per økt). - **Verifisert presist, ikke bare "kjørte uten feil":** bygget et scratch- - scenario med 3 matcher over 2 økter der match 2 sin team_a/team_b - BEVISST var byttet om i forhold til de to andre — satte poeng direkte - (Rød vinner alle tre, ett delt) og bekreftet at leaderboardet likevel ga - riktig total (Rød 2.5, Blå 0.5) og riktig per-økt-delsum, til tross for - swap-en. `test_isolation.sql` 12/12. Ingen migrasjon, kun `teecup_api` - redeployet, `teeoff.no` upåvirket. Frontend-skjermen gjenstår. - -- **Leaderboard-skjermen LIVE (2026-07-18), samme dag:** niende og siste - V0-skjerm i "bygg i rekkefølgen ting brukes"-serien for kamp-play-flyten. - `components/tournament-leaderboard.tsx`, ny rute `/tournaments/[id]/ - leaderboard`. Stort scoreboard-kort med de to lagenes totalpoeng - (lederen fremhevet), pluss en per-økt poeng-fordelingsliste med - fullført/ikke-startet-status. V0 la selv til et tredje faneelement - ("Leaderboard") i BÅDE program- og roster-skjermens fanerad denne - runden — portert inn i de LIVE versjonene av begge (med riktig `org`- - parameter, som V0s eksport som vanlig manglet). - **Ingen nye backend-hull** — forrige rundes leaderboard-endepunkt dekket - akkurat det skjermen trengte. - **Verifisert i scratch:** full datamodell-runde (samme felt-for-felt - som backend-verifiseringen dagen før) OG et eget tomtilstand-scenario - (fersk turnering med 2 lag, 0 økter — `sessions: []`, `matches_total: 0`) - for å bekrefte at "ingen økter opprettet ennå"-meldingen vises riktig - i stedet for å krasje på et tomt array. `test_isolation.sql` 12/12, ekte - typesjekket build. - **Rullet ut live**, ren frontend-endring, `teeoff.no` upåvirket. - **Hele "bygg i rekkefølgen ting brukes"-serien for match-play-flyten er - dermed komplett:** oppsett (lag/roster) → program → blind draw → - scorekort → leaderboard. -- **Offisiell bane-import: idempotent + tydeligere navn, LIVE (2026-07-18), - rapportert av brukeren som faktisk brukte funksjonen på ekte - produksjonsdata:** to reelle problemer, begge funnet ved å lese (kun - lesing) de faktiske dataene for "De Gamle er Eldst" FØR noe ble antatt. - 1. **`POST .../courses/official-import` var IKKE idempotent** — å - importere samme bane på nytt (helt vanlig: flere økter spilles ofte - på samme bane) ga en 409 `DUPLICATE`-feil (migrasjon 010 sin sperre) - i stedet for å bare gi tilbake den allerede importerte banen. Fikset: - sjekker nå `external_course_ref` FØR noe teeoff-kall gjøres — finnes - banen fra før, returneres den eksisterende raden direkte (også - raskere, og robust mot at teeoff er nede akkurat da). - 2. **Lagret navn var kun selve banens navn ("Hovedbanen"), ikke hvilken - klubb** — ubrukelig til å skille baner fra hverandre, siden mange - klubber navngir hovedbanen sin identisk. Fikset: navnet kombineres nå - til "{anlegg} – {bane}" (f.eks. "Tjøme Golfklubb – Hovedbanen") ved - import. - **Reell konsekvens av hull #1 funnet i produksjonsdata:** brukeren hadde, - mens hen forsøkte å søke opp Tjøme via det ØVERSTE banefeltet (som søker - organisasjonens EGNE baner, ikke teeoff), ved et uhell trigget «Opprett - ny bane: «Tj»»-snarveien og fått en tom, søppel `custom`-bane hengende på - Foursome-økten -- en ekte forvekslingsfelle mellom de to adskilte - bane-søkeflatene (eget vs. teeoff), ikke en kodefeil i seg selv. - **Data ryddet opp i EKTE `teecup_db`, bruker bekreftet eksplisitt:** - Foursome-økten pekt om til den allerede importerte, ekte Tjøme-banen - (samme bane som Fourball-økten allerede brukte -- nøyaktig det - organisatoren egentlig ønsket), søppel-«Tj»-banen slettet (bekreftet - ingen matcher/tee-er/hull hang på den FØR sletting), og den ekte banens - navn oppdatert til "Tjøme Golfklubb – Hovedbanen". Verifisert i etterkant - at begge økter nå peker til samme, korrekt navngitte bane. - **Verifisert mot ekte teeoff_api i scratch FØR utrulling:** importerte - Borregaard på nytt to ganger — andre kallet ga nøyaktig samme course-id - (ikke en duplikat-rad), navnet kom ut som "Borregaard Golfklubb – - Hovedbanen". `test_isolation.sql` 12/12. Ingen migrasjon, kun `teecup_api` - redeployet, `teeoff.no` upåvirket. -- **`PATCH`/`DELETE` for økter, LIVE (2026-07-18), samme dag:** brukeren - spurte rett etter opprydningen om det i det hele tatt var mulig å - rette/slette en feiloppsatt økt — det var det ikke (kun `POST`/`GET` - fantes). Ny `PATCH /orgs/{id}/sessions/{id}` (`app/routers/ - tournaments.py`) for enkle felt (navn, klokkeslett/intervall, starthull, - poeng, handicap-brytere) via vanlig `exclude_unset`-mønster, PLUSS en egen - gren for bane-bytte. Ny `DELETE /orgs/{id}/sessions/{id}` — kun tomme - økter (ingen matcher), avviser med 409 ellers (bruk PATCH til å korrigere - i stedet). - **Banebytte-scenarioet brukeren selv reiste** ("5 hull spilt, oppdager - feil bane — slagene er ekte, utregningen er trolig feil") krevde egen - design: `match_participant.tee_id` peker til en tee som HØRER til den - gamle banen. Løst med `_remap_course()` — finner en tee med samme - navn+kjønn på den nye banen for hver allerede tillagte deltaker, flytter - dem dit, og avviser HELE bane-byttet tydelig (400, ingenting skrevet, - bekreftet transaksjonell rollback) hvis den nye banen mangler en - tilsvarende tee. Etter et vellykket bytte: handicap regnes om for alle - berørte deltakere, og matchstatus/poeng regnes om for HVER match i - økten — **bevisst uavhengig av om matchen allerede er avgjort** (brukeren - bekreftet eksplisitt at en bane-korrigering skal kunne endre et allerede - cachet resultat). `recompute_and_cache_match_state` i `app/routers/ - scoring.py` gjort delt (fjernet ledende understrek) for gjenbruk fra - tournaments.py. - **Reelt, urelatert funn underveis i scratch-testingen, IKKE fikset:** - `tee_rating` lages i dag ALLTID kun med `full_18`-omfang (både ved - teeoff-import og manuell tee-opprettelse) — en økt satt til `front_9`/ - `back_9` i `stroke`-modus kan derfor ALDRI få handicap beregnet - (`compute_and_store_side_handicaps` sin `tee_rating`-join finner aldri - noen rad), og dermed aldri avgjøre noen hull. `hole_result`-modus - upåvirket. Flagget til bruker, bevisst latt urørt denne runden. - **Verifisert grundig i scratch:** enkelt feltbytte (kun navn), fullt - banebytte-scenario bygget nøyaktig som brukerens eksempel (10 hull spilt - under feil bane, matchen allerede avgjort 10&8, PATCH til riktig bane → - tee-er ombyttet korrekt, handicap endret fra 7/9 til 10/13 under den nye - banens rating, matchstatus regnet på nytt med UENDREDE rå slagtall), - avvist bane-bytte ved manglende tee-match (bekreftet full rollback, - også av det urelaterte navnefeltet i samme kall), DELETE avvist på økt - med match (409) og godtatt på tom økt (204). `test_isolation.sql` 12/12. - Ingen migrasjon, kun `teecup_api` redeployet, `teeoff.no` upåvirket. - Frontend (rediger-/slett-knapper i program-skjermen) ikke bygget ennå — - kun backend-kapasiteten denne runden. -- **Rediger/slett-UI for økter LIVE (2026-07-19):** V0-utvidelse av den - eksisterende Program-skjermen (ingen ny rute) — "..."-meny (samme - DropdownMenu-mønster som roster-skjermens per-spiller-handlinger) med - "Rediger"/"Slett" per øktkort. Rediger bytter kortet til et inline-skjema - (samme felt som PATCH støtter: navn, poeng, starthull, klokkeslett/ - intervall, bane). Slett viser en bekreftelse; treffer den ekte 409-en - (økt har matcher), vises backend sin egen feiltekst i stedet for en - forhåndsberegnet klient-tilstand. - **Reell regresjon funnet OG UNNGÅTT i selve V0-eksporten, ikke i - etterkant:** denne rundens V0-prompt handlet kun om rediger/slett, men - eksporten hadde samtidig (utilsiktet) FJERNET bane-feltet fra "Legg til - økt"-skjemaet helt — nye økter ville stille blitt satt til en hardkodet - mock-bane. Fanget under diff-mot-live-treet FØR noe ble tatt inn (samme - rutine som alltid) — `CreateSessionCard` (med sitt ekte bane-søk/ - teeoff-import) beholdt fullstendig urørt; kun de nye rediger/slett- - delene ble hentet inn. - **Bane-bytte i redigeringsskjemaet er bevisst ENKLERE enn opprett- - skjemaets bane-felt** — kun velg blant organisasjonens eksisterende - baner eller hent fra teeoff, INGEN "opprett ny bane: X"-snarvei her - (PATCH sin `course_id` må være en ekte, allerede eksisterende bane, ikke - en tekststreng) — unngår at samme "Tj"-forvekslingsfelle fra forrige - banebytte-hendelse kan gjenta seg via redigeringsveien. - **Verifisert:** ekte PATCH med nøyaktig skjemaets feltform, ekte DELETE - (204 tom økt, 409 med matcher — bekreftet at `{"detail":{"code", - "message"}}`-formen leses riktig av UI-et), `test_isolation.sql` 12/12, - ekte typesjekket build. Rullet ut live, ren frontend-endring, `teeoff.no` - upåvirket. - -- **Invitasjonskode + ledende side + projisert stilling, BACKEND LIVE - (2026-07-19, ADR-020):** brukeren reiste tre relaterte hull rett etter at - "bygg i rekkefølgen ting brukes"-serien var ferdig: (1) ingen vei inn til - en turnering for en spiller som bare har fått muntlig beskjed, (2) - leaderboardet viser kun faktisk opptjente poeng, ikke hva stillingen ville - blitt om pågående matcher holder seg, (3) ingen fargekoding i matchlister - for hvem som leder. Full ADR-020 skrevet (4 delbeslutninger, se - ARCHITECTURE_DECISIONS.md) — nøkkelbeslutning bekreftet eksplisitt av - bruker FØR bygging: en invitasjonskode OVERSTYRER `tournament.visibility` - helt (koden ER selve invitasjonen, ikke en snarvei som fortsatt krever - eksisterende tilgang). - Ny migrasjon `011_join_code_and_leading_side.sql`: `tournament.join_code` - (6 tegn, alfabet uten 0/O/1/I, globalt unikt, backfylt for eksisterende - rader), `match.leading_side` (cachet fortegn av `MatchState.lead`, samme - mønster som `status_text`/`points_side_a/b`), fjerde - `SECURITY DEFINER`-bro `public_tournament_by_code()` (etter - `public_tournament_org` 007, `link_player_by_email` 008, - `public_org_by_slug` 009). - **Backend bygget:** `create_tournament` genererer koden (retry-løkke ved - kollisjon, astronomisk usannsynlig med 33^6 kombinasjoner). Ny - `GET /public/tournaments/by-code/{code}` (MÅ registreres FØR - `/{tournament_id}` i routeren, ellers tolkes "by-code" som en ugyldig - UUID). `GET /public/tournaments/{id}`, `GET .../sessions` og - `POST .../register` godtar alle en valgfri `code`-parameter som — når den - matcher — hopper `_check_visibility()` helt over. - `recompute_and_cache_match_state` (scoring.py) cacher nå `leading_side` - ved HVER hull-innsending, ikke bare ved avgjørelse. Leaderboard- - endepunktet fikk `projected_points`/`projected_points_by_team`: - avgjorte matcher bidrar likt til faktisk og projisert, ikke-avgjorte gir - hele `points_per_match` til `leading_side` (delt 0,5/0,5 ved "AS"/ikke - startet) — speiler hvordan Ryder Cup-TV-dekning viser "hvis det sluttet - nå". - **Reell hendelse underveis, håndtert transparent (ikke skjult):** en - feilformulert `docker exec teeoff_db env | grep -i POSTGRES`-kommando - (ment å liste variabelNAVN) fanget opp `POSTGRES_PASSWORD` sin VERDI også, - siden selve nøkkelnavnet matchet søkemønsteret — eksponerte `teeoff_db` - sitt superbruker-passord (`teeoff_admin`) i verktøyresultatet. Alvorligere - enn de to tidligere passord-hendelsene i prosjektet siden dette er - superbrukeren for HELE den delte Postgres-klyngen (teeoff OG teecup), ikke - en enkelt tjeneste-credential. Flagget til bruker umiddelbart, som valgte - å rotere. Rotert trygt UTEN å noensinne re-eksponere gammel ELLER ny verdi - i noe synlig kommandoresultat: `ALTER ROLE` kjørt via lokal - Unix-socket-`trust`-auth (bekreftet ved å lese `pg_hba.conf`, ingen - hemmelighet involvert i den sjekken) — krevde altså IKKE det gamle - passordet i det hele tatt. Nytt passord generert med `openssl rand -hex - 32` (hex, ikke base64 — unngår SAMME klasse URL-enkodings-felle som - `TEECUP_DATABASE_URL`-hendelsen tidligere, siden verdien også ligger i en - `postgres://`-DSN). `/opt/teeoff/.env` sine TO forekomster - (`POSTGRES_PASSWORD` og `DATABASE_URL`) oppdatert med `sed`-mønstre som - ALDRI leser/skriver ut den gamle verdien. `teeoff_api`/`teeoff_worker` - service-nøklene i `docker-compose.prod.yml` viste seg å hete `api`/ - `worker` (ikke `teeoff_api`/`teeoff_worker` — det er kun - `container_name`), samme "service-nøkkel ≠ container-navn"-fallgruve som - nettverksalias-hendelsen fra containeriseringsrunden. `docker compose up - -d --force-recreate api worker` gjenskapte OGSÅ `teeoff_db` selv (ikke - eksplisitt navngitt) — Compose oppdager konfigurasjonsendring - (`${POSTGRES_PASSWORD}` i `db`-tjenestens egen `environment:`) og - gjenskaper uansett hvilke tjenester som ble navngitt. Verifisert grundig - ETTERPÅ: `teeoff_db`-loggen viste "Skipping initialization" (datavolum - urørt, ikke reinitialisert) + ren oppstart, `teeoff_api`/`teeoff_worker` - ren oppstart uten en eneste feil-/auth-/passord-linje i hele loggen, - `teeoff.no` OG `teecup.teeoff.no/health` begge `200` etterpå. - **Scratch-verifisert grundig** (fersk `teecup_scratch` 001→011, - isolert `teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs - API-container): `test_isolation.sql` 12/12 (måtte først rettes — tre - RÅ `INSERT INTO tournament`-steder i selve testfilen predaterte - `join_code` og traff den nye NOT NULL-constrainten, rettet med - dummy-koder). Full ende-til-ende-runde: to turneringer opprettet, ulike - koder bekreftet; `by-code`-oppslag bekreftet for kjent OG ukjent kode - (404); anonym lesing av en `org`-synlig turnering BLOKKERT uten kode - (403 NOT_VISIBLE), TILLATT med riktig kode (inkl. case-insensitivt), - FORTSATT blokkert med feil kode; samme mønster bekreftet for - `POST .../register`. Full hull-for-hull-simulering av en singel-match - (hole_result-modus): `leading_side`/`status_text` fulgte hverandre - eksakt gjennom "1 UP (A)" → "AS" (leading_side=null) → avgjort "9&7 - (A)", leaderboardets `projected_points` traff nøyaktig 1.0/0.0 mens A - ledet, 0.5/0.5 ved "AS", og ble likt `points`/`projected_points` (begge - 1.0/0.0) etter avgjørelse. - **Rullet ut mot ekte `teecup_db` 2026-07-19**, bruker bekreftet - eksplisitt: migrasjon 011 kjørt (eneste eksisterende turnering fikk - automatisk generert kode), `test_isolation.sql` fortsatt 12/12, kun - `teecup_api` redeployet (ingen frontend-endring i denne del-runden), - `teeoff.no` upåvirket. - **Frontend fullført samme dag, egen del-runde:** `login-form.tsx` fikk et - eget kode-modus (`JoinByCode`) — «Har du en invitasjonskode?»-lenke bytter - ut e-post-skjemaet, slår opp `/public/tournaments/by-code/{code}` og - navigerer til `/t/{id}?code=...` med Next sin `useRouter`. `code` - query-param tres gjennom hele veien: `app/t/[id]/page.tsx` leser - `searchParams`, `public-tournament.tsx` sender den med på BÅDE - info-/sessions-lesingen og selve `POST .../register` (ikke bare det - første oppslaget som tok deg dit). - `tournament-detail.tsx` viser koden i en egen kopier-chip i headeren — - ingen enkelt-turnering-`GET` fantes, så komponenten henter i stedet hele - org-ens turneringsliste (som allerede bærer `join_code`) og finner egen - rad, i stedet for å legge til et nytt endepunkt kun for dette. - `tournament-leaderboard.tsx` fikk en ny `SegmentedBar`-komponent — ETT - fargesegmentert rektangel per bar (ikke tall side om side), proporsjonalt - med hvert lags poeng, 50/50 nøytralt ved 0-0. To slike bares rett under - headeren: "Stilling nå" (faktisk) og "Projisert (hvis pågående matcher - holder seg)" — den EKSISTERENDE store tall-scoreboarden beholdt uendret - lenger ned som detaljvisning. - `session-blind-draw.tsx` sin `RevealedView`: matchkortet får nå en farget - toppkant (leaderens `team.color`) og en `status_text`-chip (fylt farge - ved avgjort match m/ konfetti-ikon, lys tone ved pågående) i stedet for - kun klokkeslettet; `RevealSide` for ledende side får en svak fargetonet - bakgrunn. `leading_side`/`status_text`/`points_side_a/b` lagt til - `ApiMatch`-typen (var der allerede i API-et, bare ikke konsumert - frontend-siden før nå). - **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` - som deployes, ikke dev-server) kompilerte rent, alle 10 ruter listet. - Rullet ut (kun `teecup_frontend`, ingen backend-endring i denne delen), - `teecup.teeoff.no/dashboard` og `/` → 200, `teeoff.no` upåvirket. Ekte - smoke-test i produksjon: `by-code`-oppslag for "De Gamle er Eldst" sin - faktiske kode ga riktig turnering-id, `/t/{id}?code=...` ga 200. - **ADR-020 er dermed helt ferdig** (backend + frontend, alle fire - del-ønsker: kode-basert oppdagelse, kode-felt på login, projisert - stilling, fargekoding av matcher) — bortsett fra kode-regenerering, som - er bevisst utsatt (se FEATURE_BACKLOG.md). - **Reell driftshendelse underveis** (mellom backend- og frontend-delen, - under scratch-oppsett): en feilformulert `grep -i POSTGRES`-kommando - eksponerte `teeoff_db` sitt superbruker-passord ved et uhell. Flagget - umiddelbart, brukeren valgte å rotere — se detaljene under - backend-avsnittet over for hele hendelsen og hvordan roteringen ble - gjennomført uten å noensinne re-eksponere gammel eller ny verdi. -- **Sesjons-bug diagnostisert og FIKSET, LIVE (2026-07-19):** brukeren - rapporterte at hen måtte be om ny magic-link-kode ved HVERT besøk til - `teecup.teeoff.no`, til tross for ADR-009s 30-dagers sesjonscookie. Bad - brukeren sjekke den EKTE cookien i nettleseren fremfor å gjette — kom - tilbake korrekt satt i alle henseender (`Expires` 30 dager frem, - `Secure`/`HttpOnly`/`SameSite=Lax`). Rot-årsaken var derfor IKKE cookien - eller backend-en: `frontend/app/page.tsx` (rot-siden) viste ALLTID - innloggingsskjemaet uten noensinne å sjekke om en gyldig sesjon allerede - fantes. `Dashboard`-komponenten sjekker `/auth/me` og sender til `/` ved - MANGLENDE sesjon, men ingen kode gjorde det motsatte — en bruker som - besøkte roten direkte (i stedet for å navigere til `/dashboard`) så - derfor alltid innloggingsskjemaet uansett sesjonsstatus. - **Fikset:** `page.tsx` gjort om til en async server-komponent som leser - sesjonscookien via `next/headers`, kaller `/auth/me` server-til-server - direkte mot `TEECUP_API_ORIGIN` (samme mønster som `generateMetadata` i - `app/t/[id]/page.tsx` — IKKE gjennom `next.config.mjs` sin `rewrites()`, - som kun gjelder nettleser-trafikk), og sender en allerede innlogget - bruker videre til `/dashboard` med `redirect()` FØR innloggingsskjemaet - når rendres. - **Verifisert presist mot den ekte, live stacken, med brukerens EGEN - ekte sesjonscookie** (ikke en syntetisk test): `curl` uten cookie mot - `https://teecup.teeoff.no/` ga `200` (skjemaet vises, riktig for en - anonym besøkende); samme kall MED den ekte cookien ga `307` til - `/dashboard` (riktig — sender en allerede innlogget bruker rett videre). - Ekte typesjekket produksjonsbuild kjørt FØR utrulling (build-outputet - viste selv at `/` nå er `ƒ` dynamisk i stedet for `○` statisk — bekrefter - at server-sjekken faktisk ble tatt i bruk). Rullet ut live, kun - `teecup_frontend`, ingen migrasjon, `teeoff.no` upåvirket. - **Samme runde:** brukeren stilte to oppfølgingsspørsmål om - autentisering/autorisasjon — «er flere-organisasjoner-eierskap tenkt - gjennom» (bekreftet: ja, ADR-002 fra dag én, ingen kodeendring nødvendig) - og et ønske om passord (valgfritt tillegg)+2FA. Fullt design skrevet som - **ADR-021** (passord/2FA) og **ADR-022** (dele/invitere/frasi seg - eierskap + superadmin) — se ARCHITECTURE_DECISIONS.md. -- **ADR-021 (passord/2FA) + ADR-022 (org-eierskap) BYGGET OG LIVE - (2026-07-19), samme dag:** brukeren ba om begge sammen («Bygg det», - bekreftet eksplisitt at det gjaldt begge ADR-ene i samme runde). Ny - migrasjon `012_password_2fa_and_org_invitations.sql`: `app_user. - password_hash`/`two_factor_method`/`totp_secret`/`is_super_admin`, ny - tabell `two_factor_code` (samme hash-og-utløp-mønster som - `magic_link_token`), ny tabell `organization_invitation` (RLS - org-isolert, INGEN egen klikkbar aksept-lenke — godtas automatisk ved - neste innlogging med matchende e-post), femte - `SECURITY DEFINER`-bro `accept_pending_invitations_by_email()` (etter - `public_tournament_org` 007, `link_player_by_email` 008, - `public_org_by_slug` 009, `public_tournament_by_code` 011). - **Sesjons-STADIER innført i `app/auth.py`:** en sesjonscookie er ikke - nødvendigvis en full sesjon lenger — `create_session_token()` tar nå en - `stage`-parameter (`full`/`pending_2fa`/`must_enroll_2fa`), lagt inn som - et JWT-claim. `get_current_user` avviser eksplisitt alt annet enn `full` - ELLER en ELDRE token uten stage-claim i det hele tatt (utstedt før denne - runden — behandlet som `full` for bakoverkompatibilitet, ingen - eksisterende bruker logget brått ut). To nye avhengigheter: - `get_pending_user` (kun for 2FA-verifiseringsendepunktene) og - `get_current_or_enrolling_user` (godtar BÅDE en full sesjon — frivillig - 2FA-oppsett fra kontoinnstillinger — OG `must_enroll_2fa` — tvunget - oppsett rett etter innlogging — samme oppsett-logikk dekker begge - veiene). `get_current_user_optional` fikk samme stage-sjekk for - konsistens. - **Passord (Argon2id, ikke bcrypt):** bevisst valg for å unngå bcrypt sin - stille 72-byte-trunkering, siden brukeren eksplisitt ba om korrekt - håndtering av spesialtegn/mellomrom. `POST /auth/set-password`/ - `/remove-password`/`/login-password` — sistnevnte svarer med IDENTISK - 401 uansett om e-posten finnes, mangler passord, eller passordet er - feil (samme anti-enumerering som magic-link). - **2FA (TOTP via `pyotp` ELLER e-post-engangskode via eksisterende SMTP, - brukerens eget valg):** `POST /auth/2fa/setup/start` genererer en - TOTP-secret UTEN å lagre den (rundturer til klienten, som ekkoer den - tilbake i `/setup/confirm` — unngår en halvferdig 2FA-tilstand i - databasen hvis brukeren forlater oppsettet). QR-kode generert - server-side (`qrcode`-biblioteket + eksisterende Pillow-avhengighet, - ingen ny ekstern tjeneste). `POST /auth/2fa/verify` fullfører en - PÅGÅENDE innlogging. - **Tvungen 2FA for org-eier/admin (ADR-021 Beslutning D):** - `user_requires_2fa_enrollment()` sjekket ved HVER innlogging (ikke bare - første gang, siden en bruker kan bli eier av en NY org etter at kontoen - allerede eksisterer uten 2FA) — verifisert eksplisitt: en fersk - org-eier uten 2FA ble korrekt blokkert fra all normal tilgang (401) og - tvunget inn i oppsett-flyten før noe annet ble tilgjengelig. - **Organisasjonseierskap (ADR-022):** `POST/GET/DELETE - /orgs/{id}/invitations` (owner→enhver rolle, admin→KUN member — ellers - en privilegie-eskaleringsvei), `PATCH/DELETE /orgs/{id}/memberships/ - {id}` (owner kan endre/fjerne hvem som helst; en bruker kan ALLTID - SENKE egen rolle selv — aldri heve den, ville vært selv-forfremmelse — - og alltid forlate selv), «siste eier»-vern (409 `LAST_OWNER`, `FOR - UPDATE`-lås mot race på alle eier-rader, samme TOCTOU-mønster som - ADR-011s to-lags-grense). Superadmin (`app_user.is_super_admin`, KUN - manuelt DB-tildelt — bevisst INGEN API-vei til å gi seg selv eller - andre flagget) får en parallell autorisasjonssti - (`get_superadmin_user`) som kan sette medlemskap på ENHVER org, - uavhengig av eget medlemskap — bevisst avgrenset til nøyaktig dette, - ikke generell tilgang til andres turnering-/spillerdata. - **Tre reelle bugs funnet OG fikset UNDER scratch-testing, ingen nådde - produksjon:** - 1. `verify_magic_link` sendte en rå asyncpg-`UUID` (ikke streng) videre - til sesjonsutstedelse — `jwt.encode()` sin JSON-serialisering - krasjet rått (500) på selve innloggingen. Fant umiddelbart ved første - reelle innloggingstest. Rettet med en eksplisitt `str()`. - 2. OG 3. Både `2fa/setup/confirm` og `2fa/verify` kalte først den delte - `_issue_login_result()`-hjelpefunksjonen (ment for PRIMÆR - autentisering) EN GANG TIL etter at 2FA nettopp var bekreftet — som - så (korrekt, men feil kontekst) at `two_factor_method` nå var satt - og krevde EN NY runde med 2FA for akkurat den samme innloggingen, - en uendelig løkke. Fant ved å faktisk fullføre hele innloggings- - syklusen med ekte genererte TOTP-koder (`pyotp` i test-scriptet), - ikke bare ved å lese koden. Rettet ved at begge endepunktene nå - utsteder en full sesjon DIREKTE etter vellykket 2FA-bekreftelse, - ikke via gjenbruk av den generelle sjekken. - **Frontend:** `login-form.tsx` fikk en tredje modus (passord, ved siden - av magic-link og ADR-020s invitasjonskode-modus). Ny delt - `two-factor-flow.tsx` (`TwoFactorVerifyForm`/`TwoFactorSetupForm`), - brukt av BÅDE `login-form.tsx` og `verify-form.tsx` siden begge - primær-autentiseringsveiene kan returnere samme 2FA-mellomtilstand. Ny - `/account`-skjerm (sett/fjern passord, aktiver/deaktiver 2FA) og ny - `/orgs/[id]/members`-skjerm (invitere, endre rolle, fjerne/forlate), - begge lenket fra dashbordets header/org-visning. `next.config.mjs` sin - `rewrites()` utvidet med `/superadmin/:path*` (ADR-016s konsekvens for - enhver ny API-prefiks, selv om ingen frontend-UI faktisk bruker den - ennå). - **Verifisert grundig mot fersk `teecup_scratch`** (001→012, - `test_isolation.sql` 12/12): full magic-link-bakoverkompatibilitet - (eksisterende flyt uendret for brukere uten 2FA), passord med ekte - spesialtegn/mellomrom/æøå satt og brukt til pålogging, full TOTP-runde - (oppsett→bekreft→logg ut→logg inn→krev 2FA→verifiser med ekte - `pyotp`-generert kode), full e-post-2FA-runde (samme mønster, feil kode - avvist, kode ikke gjenbrukbar), tvungen 2FA-registrering for ny - org-eier, invitasjon→auto-aksept ved førstegangsinnlogging, - rolle-eskalering-forsøk avvist på tre distinkte måter (medlem kan ikke - invitere, admin kan ikke gi eierskap, bruker kan ikke forfremme seg - selv), siste-eier-vern (både PATCH og DELETE), duplikat-invitasjon - avvist, superadmin-sti fungerer/avvises riktig i begge retninger. Ekte - typesjekket produksjonsbuild av frontend (alle nye ruter listet). - **Rullet ut mot ekte systemer 2026-07-19**, bruker bekreftet eksplisitt: - migrasjon 012 kjørt mot ekte `teecup_db`, `test_isolation.sql` fortsatt - 12/12, begge containere (`teecup_api`, `teecup_frontend`) redeployet og - bekreftet ren oppstart, `/health` og `/dashboard` fortsatt 200 (eksisterende - sesjoner uendret av stage-bakoverkompatibiliteten), `/auth/login-password` - bekreftet nåbart over ekte https, `teeoff.no` upåvirket. - **ADR-021 og ADR-022 er dermed begge helt ferdig** — backend + frontend. - Bevisst utenfor omfang: dedikert superadmin-UI (brukes via API av en - betrodd operatør), SMS som 2FA-metode. - -- **Program-skjerm: tydeligere klikk-hint på øktkort (2026-07-19):** brukeren - påpekte at ingenting i grensesnittet indikerte at et øktkort er klikkbart - inn til blind draw-skjermen. Lagt til en synlig "Sett opp flights og lås - oppstilling →"-rad nederst i hvert kort (`components/tournament-program.tsx`). - Ren frontend-endring, ingen backend-rørt. -- **Brukerroller: kaptein som reell autorisasjon, deltaker-avgrenset scoring - (2026-07-19, ADR-023):** direkte oppfølging av det lenge åpne - "Brukerroller"-punktet i FEATURE_BACKLOG.md. Fire beslutninger avklart - eksplisitt med bruker (AskUserQuestion) før bygging — alle anbefalte valg. - **Bygget:** `app/team_authz.py` skrevet om — `user_is_team_captain` - (erstatter `user_may_act_for_team`) krever `is_captain=true` på - `team_roster` (eller org-eier/admin) for å legge til/fjerne deltakere og - låse et lag (`matches.py`); ny `user_is_match_participant` krever en ekte - `match_participant`-rad for brukeren i AKKURAT den matchen (valgfritt - side-spesifikk via `team_side`) for å føre/korrigere score (`scoring.py`) - — uavhengig av kapteinmerket. Nye feilkoder `NOT_TEAM_CAPTAIN` og - `NOT_MATCH_PARTICIPANT` (erstatter `NOT_ROSTERED_ON_TEAM` på disse fem - stedene). `app/routers/tournaments.py` sin `PATCH`/`POST .../roster` - håndhever nå "kun én kaptein per lag" (fjerner automatisk forrige - kapteins merke i samme transaksjon). - **Reelt funn FØR utrulling, ikke antatt:** sjekket (kun lesing, superbruker - mot ekte `teecup_db`) om noen eksisterende lag ville blitt låst ute av en - ren kaptein-only-regel — "De Unge" i "De Gamle er Eldst" har i dag 0 av 2 - roster-rader merket kaptein. Designet derfor en bevisst fallback i - `user_is_team_captain`: har laget INGEN utpekt kaptein ennå, godtas enhver - rostret spiller i stedet for å låse laget helt ute. Ingen lag hadde flere - kapteiner, så "kun én kaptein"-håndhevelsen krevde ingen data-opprydning. - **Reell bug funnet OG fikset UNDER scratch-testing:** `user_is_match_ - participant` sin SQL sammenlignet `mp.team_side` (enum-kolonne) direkte - mot en tekst-parameter uten cast når `team_side=None` (hole_result-modus) - — ga en rå 500 (`UndefinedFunctionError: operator does not exist: team_side - = text`). Rettet med et eksplisitt `mp.team_side::text = $3`. - **Verifisert grundig mot fersk `teecup_scratch`** (isolert - `teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container): - et 15-punkts Python/httpx-testskript som simulerte fire innloggede - brukere (organisator + tre rostrede spillere på to lag) gjennom hele - syklusen — lag uten kaptein tillater enhver rostret (fallback bekreftet), - kaptein utpekt fjerner andre rostredes rettighet, "kun én kaptein" bekreftet - (ny kaptein avsetter automatisk forrige), org-admin fungerer uendret - uavhengig av kaptein, og scoring (`hole_result`-modus) bekreftet begrenset - til faktiske matchdeltakere (en kaptein som IKKE selv spiller matchen ble - korrekt avvist med `NOT_MATCH_PARTICIPANT`, mens en faktisk deltaker og - org-admin begge fikk føre score). Måtte også oppdage og legge til et - forutsetning-steg underveis: `get_authorized_org` krever - `organization_membership` for ALLE org-scopede endepunkter uansett — en - rostret spiller må derfor også være invitert som org-medlem (minimum - 'member', ADR-022s invitasjonsflyt) for i det hele tatt å nå - team_authz-vurderingen; ikke en bug, men en forutsetning testskriptet - først manglet. `test_isolation.sql` 12/12 uendret (ingen skjemaendring). - Ekte typesjekket produksjonsbuild av frontend (øktkort-hintet fra samme - runde) kjørt og bekreftet, alle 11 ruter listet. - **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen - migrasjon, `docker compose up -d --build teecup_api teecup_frontend`, - begge containere boot-et rent (`Application startup complete`, Next.js - `Ready`), `/health` og `/dashboard` → 200, `teeoff.no` upåvirket. - -- **Walkover/konsesjon LIVE (2026-07-19, ADR-024):** direkte oppfølging av - Brukerroller-runden samme dag — brukeren ba eksplisitt om å ta fatt på - dette som naturlig neste steg. Løser det lenge kjente hullet: en side som - aldri stiller nok spillere fikk aldri beregnet handicap og matchen kunne - derfor aldri avgjøres — hang uendelig. - Fire beslutninger avklart eksplisitt med bruker (AskUserQuestion) før - bygging — tre anbefalte valg, ett (omfang: match+turnering-nivå samtidig, - ikke bare match) valgt utover anbefalingen. - **Bygget:** ny `apply_concession`-hjelpefunksjon i `app/routers/ - scoring.py` (skriver til de samme fire kolonnene som - `recompute_and_cache_match_state` -- `status_text`/`points_side_a/b`/ - `leading_side` -- men direkte, ikke utledet fra hull). Ny - `POST /orgs/{id}/matches/{id}/concede` (kun kaptein for det TAPENDE laget, - speiler ekte golf-etikette -- du gir bort DITT tap, krever ikke seier på - motstanderens vegne -- eller org-admin, gjenbruker `user_is_team_captain` - fra ADR-023 uendret). Ny `POST /orgs/{id}/tournaments/{id}/concede` - (`app/routers/tournaments.py`) som gir opp ALLE ikke-avgjorte matcher - laget har i turneringen i én operasjon -- v1s to-lags-grense (ADR-011) - gjør dette trivielt (bare én motstander uansett), `FOR UPDATE`-låser alle - berørte match-rader (samme race-vern som ellers). Kan erklæres uansett - hvor mange hull som allerede er registrert (match-play teller kun - seier/tap/delt for poeng, ikke marginen) -- allerede registrerte hull i - `hole_score`/`match_hole_result` forblir urørt, kun matchens - avgjørelses-felt endres. - **Frontend:** `session-scorecard.tsx` fikk en kollapsbar - "Gi opp matchen (walkover)"-seksjon (to knapper, én per lag, med - bekreftelsessteg) synlig når matchen ikke er avgjort. - `tournament-detail.tsx` sin `TeamPanel` fikk en tilsvarende "Gi opp resten - av turneringen for laget"-knapp nederst i hvert lagkort. Ingen - klientside-forhåndsfiltrering på kapteinstatus noe sted -- begge knappene - vises alltid, en 403 fra backend vises bare som vanlig feiltekst (samme - mønster som resten av appen). - **Verifisert grundig mot fersk `teecup_scratch`** (isolert - `teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs - API-container): to separate 15-punkts Python/httpx-testløp. Match-nivå: - vinnende lags kaptein NEKTES å konsedere på vegne av det tapende laget - (kan ikke kreve seier på andres vegne), tapende lags kaptein FÅR, allerede - avgjort match avvist 409, fremmed team_id avvist 400, konsesjon ETTER at - ett hull allerede er registrert bekreftet å fungere OG bekreftet at det - registrerte hull-resultatet forblir synlig i scorekortet etterpå (ikke - overskrevet), org-admin FÅR konsedere direkte uavhengig av kapteinmerke. - Turnering-nivå: feil lags kaptein nektes, riktig kaptein FÅR gi opp - resten (kun de faktisk ikke-avgjorte matchene telles -- allerede avgjorte - matcher fra match-nivå-testene i samme løp ble korrekt hoppet over), - gjentatt kall er trygt (0 nye, idempotent i praksis), ukjent team_id gir - 404. `test_isolation.sql` 12/12 uendret (ingen skjemaendring). Ekte - typesjekket produksjonsbuild av frontend kjørt og bekreftet, alle 11 - ruter listet. - **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen - migrasjon, `docker compose up -d --build teecup_api teecup_frontend`, - begge containere boot-et rent, `/health` og `/dashboard` → 200, - `teeoff.no` upåvirket. - -- **Turnering-status via API, SCRATCH-VERIFISERT (2026-07-19):** brukeren - valgte dette som neste steg etter walkover/konsesjon-runden (fikk velge - mellom denne lille opprydningen og å starte Kommunikasjon-runden). `status` - (draft/active/completed/archived) har ligget i skjemaet siden migrasjon - 001, men INGEN endepunkt kunne endre det — kun `INSERT`-defaulten `'draft'` - fra `create_tournament`. - **Bygget:** lagt til i `TournamentUpdate` (`app/routers/tournaments.py`), - settes via det eksisterende generiske `PATCH /orgs/{id}/tournaments/{id}` - (`exclude_unset`-mønsteret, ekte PATCH-semantikk uendret). - **Reelt funn UNDER scratch-testing, ikke antatt riktig på forhånd:** - `status` er -- ulikt `visibility` (som er ren `text`+`CHECK`) -- en EKTE - Postgres ENUM-type (`tournament_status`). Den generiske - `set_clauses`-byggeren (`f"{key} = ${i}"`) hadde derfor trengt et - eksplisitt cast for at asyncpg sin ukjent-typede parameter skulle løses - riktig mot en enum-kolonne -- lagt til en spesialsjekk (`::tournament_status` - kun for `status`-nøkkelen) FØR jeg antok mekanismen "bare fungerer" fordi - den gjør det for de andre feltene. - Bevisst INGEN tilstandsmaskin/overgangsregler bygget (kan f.eks. gå fra - `completed` tilbake til `draft` fritt) -- samme tillitsnivå som resten av - appen, ikke etterspurt. - **Frontend:** `tournament-status-badge.tsx` fikk en ny redigerbar - `TournamentStatusPicker` (dropdown over de fire verdiene, optimistisk - UI-oppdatering med rollback ved feil), koblet inn i `tournament-detail.tsx` - sin header ved siden av invitasjonskode-chipen. Den eksisterende - skrivebeskyttede `TournamentStatusBadge` (dashbordets kortliste) urørt. - **Verifisert i scratch:** ny turnering får riktig default `draft`, PATCH - til `active` bekreftet enum-castet faktisk løser problemet, PATCH med - status+et annet felt samtidig fungerer, PATCH UTEN status-felt lar - verdien stå urørt (regresjon på eksisterende PATCH-semantikk), ugyldig - status-verdi avvist med 422 (Pydantic-mønster), `GET`-listen viser samme - verdi etterpå. `test_isolation.sql` 12/12 uendret (ingen skjemaendring). - Ekte typesjekket produksjonsbuild kjørt og bekreftet. - **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen - migrasjon, `docker compose up -d --build teecup_api teecup_frontend`, - begge containere boot-et rent, `/health` og `/dashboard` → 200, - `teeoff.no` upåvirket. - -- **Kommunikasjon LIVE (2026-07-19, ADR-025) — det største enkeltløftet i - prosjektet så langt:** lag-intern chat («det hemmelige rommet») + offentlig - runde-feed («Banter Board»), begge med bilder og ekte WebSocket-sanntid, - bygget i samme runde. Fire hovedbeslutninger avklart eksplisitt med bruker - (AskUserQuestion) før bygging — brukeren valgte den mest ambisiøse - kombinasjonen på alle fire (begge deler nå, WebSockets fremfor polling, - ekte privat chat, bilder fra start). - **Datamodell:** ny migrasjon `013_messaging.sql` — delt `message`-tabell - med `scope`-diskriminator (`team`/`tournament_feed`) i stedet for to - separate tabeller, RLS org_isolation som ellers. `author_display_name` - FRYSES ved skrivetidspunkt (samme prinsipp som handicap-snapshot, - ADR-007) — spillerens `player.display_name` i org-en hvis den finnes, - ellers e-postens lokaldel (dekker org-ansatte uten egen spillerprofil). - **Lag-chat er BEVISST ekte privat** — ny `user_is_rostered_on_team` i - `app/team_authz.py`, med VILJE uten org-admin-fallback, ulikt de to andre - funksjonene i samme fil (`user_is_team_captain`/`user_is_match_participant`, - ADR-023, som begge har et slikt unntak). Første sted i hele appen der - org-eier/admin er strukturelt utestengt fra noe. - **Offentlig feed:** LESING gjenbruker `registration.py` sitt eksisterende - trenivå-visibility-mønster (ADR-018) helt uendret, inkl. anonym tilgang. - POSTING er strengere enn lesing — krever ekte innlogging OG org- - medlemskap/faktisk deltakelse, selv på en `public`-synlig turnering (en - helt urelatert innlogget bruker skal ikke kunne poste på en fremmed - offentlig side). Moderering: forfatteren selv ELLER org-eier/admin kan - slette et feed-innlegg (motsatt av lag-chatten, som ikke har noen - ekstern moderator). - **`registration.py` sine fire interne hjelpefunksjoner gjort delt** - (fjernet ledende understrek — samme "gjort delt for gjenbruk"-mønster som - tidligere runder): `resolve_org`, `is_participant`, `code_matches`, - `check_visibility`. `team_authz.py` sin `_is_org_admin` likeens → - `is_org_admin`. Ingen atferdsendring, kun navn, for at `messaging.py` - skulle kunne gjenbruke dem uendret i stedet for å duplisere logikk. - **WebSockets, ikke polling:** in-memory tilkoblingsregister PER PROSESS i - `app/routers/messaging.py` — trygt med dagens ene `teecup_api`-container, - men deles IKKE på tvers av flere prosesser/containere (samme klasse - begrensning som den allerede aksepterte in-memory-cachen, se ARCHITECTURE_ - DECISIONS.md "Åpne spørsmål"). Ny `get_current_user_from_websocket` i - `app/auth.py` — WS-ruter kan ikke bruke `get_current_user`/ - `get_authorized_org` direkte via `Depends()` (de er `Request`-typet, ingen - ekte HTTP Request finnes i en WS-scope), derfor en bevisst minimal, egen - kopi av samme cookie-dekode-/oppslagslogikk. - **Reell infrastrukturoppdagelse FØR noe ble forsøkt, ikke i etterkant:** - Next.js sin `rewrites()` proxyer ikke WebSocket-oppgraderinger pålitelig i - "standalone"-modus — løst likt som MinIO-media-ruten (ADR-018): en egen - Caddy-rute (`handle /ws/* { reverse_proxy teecup_api:8000 }`) rett til - API-et, forbi Next.js/`teecup_frontend` helt. Caddyfile ligger i det - SEPARATE `/opt/teeoff`-repoet — samme stale-bind-mount-inode-oppførsel som - ALLE tidligere Caddyfile-runder (graceful reload plukker ikke opp - endringen), løst likt: full `docker restart teeoff_caddy`, brukeren - bekreftet eksplisitt på forhånd, noen sekunders nedetid for `teeoff.no`. - **Verifisert grundig mot fersk `teecup_scratch`** (isolert - `teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container - med `websockets`-Python-biblioteket installert kun for testen): et - 20-punkts asyncio/httpx/websockets-testskript som dekket BEGGE - meldingstyper ende-til-ende. Kritiske personvern-/sanntid-funn, alle - bekreftet med ekte tilkoblinger (ikke bare REST): - - Rostret spiller på lag A FÅR lese/skrive lag A sin chat; rostret spiller - på lag B NEKTES; **organisatoren (org-eier) NEKTES OGSÅ** — bekreftet - BÅDE over REST (`GET`) og over selve WebSocket-håndtrykket (avvist med - lukkekode 4403 før `accept()` i det hele tatt kalles). - - Sanntid bekreftet reelt: A1 koblet til lag A sin chat-socket, A2 sendte - en melding over vanlig REST, A1 mottok den umiddelbart over den åpne - WebSocket-tilkoblingen (ikke bare at REST-svaret så riktig ut). - - Bildeopplasting i chat bekreftet (ekte AVIF-konvertert `image_url` - returnert). - - Offentlig feed: anonym NEKTES å poste (401), en tilfeldig INNLOGGET men - uvedkommende bruker NEKTES (403 `NOT_A_PARTICIPANT`), org-medlem FÅR, - en faktisk deltaker (rostret, IKKE org-medlem) FÅR — beviser - `is_participant`-veien fungerer uavhengig av `is_member`-veien. Anonym - WebSocket-tilkobling til en `public`-synlig turnerings feed FÅR lov og - mottar sanntidsoppdateringer. - - Moderering bekreftet: forfatter sletter eget innlegg, org-admin sletter - ANDRES innlegg (feeden), uvedkommende NEKTES å slette andres innlegg. - `test_isolation.sql` 12/12 uendret (additiv migrasjon). Ekte typesjekket - produksjonsbuild av frontend kjørt og bekreftet, inkl. den nye - `/tournaments/[id]/teams/[teamId]/chat`-ruten. - **Frontend:** ny `components/team-chat.tsx` (meldingsliste med egen/andres- - styling, bildeopplasting, sanntid via nettleserens native `WebSocket`, - slett-egen-melding), lenket fra en ny chat-ikon-knapp i `tournament- - detail.tsx` sin `TeamPanel`. Ny seksjon `TournamentFeed` i - `components/public-tournament.tsx` (nederst på den offentlige - turneringssiden) — viser 401/403-svar fra posting som forklarende - inline-tekst ("logg inn for å poste" / "du må være medlem/deltaker") i - stedet for en generisk feilmelding. - **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt for alle tre - stegene (migrasjon, containere, Caddy-restart): migrasjon 013 kjørt mot - ekte `teecup_db`, `test_isolation.sql` fortsatt 12/12, begge containere - boot-et rent, Caddy validert (`caddy validate` — "Valid configuration") - FØR restart, restarten ren (ingen feil i loggen). Verifisert grundig - etterpå: `/health`/`dashboard` → 200, `teeoff.no` → 200, et ekte - `wss://`-håndtrykk over produksjons-https bekreftet å nå helt frem til - applikasjonslaget (testet mot en ukjent turnering-id — ingen ekte data - berørt — ga korrekt 404 fra selve WS-ruten, ikke en Caddy/Next.js-feil), - og et ren-HTTP-kall mot en `/ws/*`-sti bekreftet å returnere FastAPI sin - egen JSON-404 (`{"detail":"Not Found"}`) og ikke Next.js sin HTML-404 — - beviser Caddy-ruten faktisk treffer `teecup_api`, ikke `teecup_frontend`. - -- **Tilskuer-rolle LIVE (2026-07-19, ADR-026):** brukeren valgte dette som - neste steg rett etter Kommunikasjon-runden — "tilskuer" var bevisst - utsatt til feed-synligheten (ADR-025) fantes, og nå gjorde den det. - **Kjernebeslutning:** ingen ny rolle/tabell — "tilskuer" er ganske enkelt - enhver som kan SE en turnering per `tournament.visibility` (ADR-018), - utvidet til også å dekke LIVE-data (leaderboard, matcher, scorekort), ikke - bare info-siden/programtidene som før. - **Bygget:** `GET /orgs/.../leaderboard`, `.../sessions/{id}/matches` og - `.../matches/{id}/scorecard` fantes allerede (organisator-/spiller-siden), - men krevde org-medlemskap — en ren spectator kunne aldri se dem. Løst ved - å ekstrahere den delte kjernelogikken til gjenbrukbare funksjoner - (`fetch_leaderboard` i tournaments.py, `fetch_matches` i matches.py, - `fetch_scorecard` i scoring.py — samme "gjort delt"-mønster som tidligere - runder), og la tre nye offentlige endepunkter i `registration.py` kalle - dem etter egen visibility-sjekk. `own_team_ids()` (`blind_draw.py`) gjort - null-sikker (`user_id: str | None`) -- en anonym leser har per definisjon - ingen egne lag, korrekt oppførsel er tom mengde (ser kun avslørte - matcher), ikke en feil. - **To nye sikkerhetssjekker funnet under DESIGN, ikke i etterkant, samme - disiplin som tidligere ADR-018 Beslutning B-lærdommen:** - 1. `session_id`/`match_id` i URL-en må eksplisitt verifiseres å høre til - NØYAKTIG `tournament_id` i samme URL — `org_connection()` setter kun - TENANT-grensen (RLS), ikke at stiens id-er faktisk henger sammen. Uten - dette kunne noen med tilgang til én offentlig turnering i en - organisasjon lest en HVILKEN SOM HELST økt/match i samme organisasjon - (inkl. en privat en) ved å gjette/prøve id-er. - 2. Scorekortet krever eksplisitt at BEGGE lag har låst oppstillingen - (blind draw, ADR-013) — leaderboard/matchliste arver reveal-skjuling - automatisk via `own_team_ids()`, men scorekortet har ingen tilsvarende - innebygd sjekk. - **Bruker valgte omfang utover anbefalingen:** BÅDE leaderboard+matchliste - OG fullt hull-for-hull-scorekort per match i samme runde (anbefalingen var - kun de to første). - **Frontend:** ny `/t/[id]/live`-side (`components/public-live.tsx`) — - fargesegmentert stillingsbar (faktisk + projisert, samme visuelle idé som - den org-autentiserte leaderboard-skjermen, men egen enklere implementasjon - siden komponentene har ulik autentiseringskontekst), utvidbar øktliste → - matchliste → hull-for-hull-scorekort (fargede hull-chips per lag). Lenket - fra hovedsiden (`public-tournament.tsx`) med en ny "Følg live"-knapp. - **Verifisert grundig mot fersk `teecup_scratch`** (isolert - `teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container): - 16 automatiserte sjekker, inkl. en PRESIS test av den nye tenant-vs-sti- - sjekken (ikke bare en ukjent id, men en EKTE ANNEN turnering i SAMME org - — bekreftet at match/økt fra turnering A fortsatt ikke kan leses via - turnering B sin offentlige URL), full blind-draw-skjuling FØR/ETTER - reveal for en anonym leser, kode-overstyring (ADR-020) fungerer uendret - for de nye endepunktene, scorekort eksplisitt nektet før reveal. Ekte - typesjekket produksjonsbuild av frontend, inkl. den nye `/t/[id]/live`- - ruten. - **Rullet ut live 2026-07-19**, 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. - -- **"Følg live"-siden koblet til sanntid (2026-07-19, ADR-027):** brukeren - fulgte anbefalingen fra forrige runde — `/t/[id]/live` (bygget i - tilskuer-runden samme dag) krevde omlasting for nye resultater. - **Bygget:** ny, RUTEFRI modul `app/realtime.py` (in-memory - tilkoblingsregister + `broadcast_live_update(tournament_id)`) -- ligger - bevisst BAK alle routere i importgrafen for å unngå en sirkulær import - (`registration.py` importerer allerede fra `scoring.py`/`tournaments.py`/ - `matches.py`, og `messaging.py` importerer fra `registration.py`; en - kringkastingsfunksjon i noen av routerne ville derfor bitt seg selv i - halen). Kringkastingen er lagt INN I `recompute_and_cache_match_state` og - `apply_concession` selv (sistnevnte fikk en ny påkrevd `tournament_id`- - parameter) -- kallerne (hole-score/hole-result-innsending, walkover på - både match- og turnering-nivå) trenger ikke huske å gjøre noe selv. Nytt - WS-endepunkt `/ws/public/tournaments/{id}/live` i `messaging.py` - (gjenbruker `/ws/*`-Caddy-ruten fra ADR-025 uendret -- ingen ny - infrastruktur). Sender bevisst kun et "noe endret seg, hent på nytt"- - signal, ikke selve dataene -- unngår å duplisere leaderboardets - projeksjons-regnestykke/blind draw-filtrering i kringkastings-payloaden. - **Frontend:** `components/public-live.tsx` åpner en WebSocket ved - montering; hver melding teller opp en `refreshKey` som utløser refetch av - leaderboardet ALLTID, og av en økts matcher/et scorekort KUN hvis det - faktisk er utvidet/åpent på skjermen akkurat da (ingen unødvendige kall - for lukket innhold). Liten pulserende prikk lagt til ved siden av - "Følg live"-teksten som visuell bekreftelse. - **Verifisert i scratch:** anonym avvist på live-WS for en `org`-synlig - turnering (samme visibility-sjekk som REST), satt til `public`, deretter - bekreftet FAKTISK sanntidsmottak (ikke bare at REST-svaret så riktig ut) - for BÅDE et vanlig hull-resultat OG en walkover-konsesjon -- en åpen - WebSocket-tilkobling mottok kringkastings-signalet i begge tilfeller. - Leaderboard fortsatt korrekt lesbar over REST etterpå (regresjon). Ekte - typesjekket produksjonsbuild kjørt og bekreftet. - **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen - migrasjon, ingen ny Caddy-endring, `docker compose up -d --build - teecup_api teecup_frontend`, begge containere boot-et rent. Verifisert - grundig etterpå: `/health`/`dashboard` → 200, `teeoff.no` upåvirket, OG et - ekte `wss://`-håndtrykk mot den NYE `/live`-ruten over produksjons-https - (ukjent turnering-id, ingen ekte data berørt) ga korrekt 404 fra selve - applikasjonslaget. - -- **Fire brukerrapporterte UI-/UX-hull, DIAGNOSTISERT OG NOTERT, IKKE fikset - (2026-07-19):** brukeren rapporterte fire ting fra faktisk bruk av - `teecup.teeoff.no` rett før PWA-runden startet. Root cause funnet ved - kodegjennomgang for tre av fire (ikke gjettet). Full detalj i - FEATURE_BACKLOG.md sin nye seksjon "Rapporterte UI-/UX-hull (2026-07-19)". - Kort: - 1. Dashboard-turneringskortet viser "Ingen datoer satt" alltid — leser - `tournament.start_date`/`end_date` (eget felt, ADR-015), som INGEN - UI-skjema noensinne skriver til. Skal enten få et faktisk skjemafelt, - eller kortet bør heller utlede datoen fra øktenes `scheduled_at`. - 2. **Reell, bekreftet rewrite-bug:** `/orgs/{id}/members` gir en rå - FastAPI-404 (`{"detail":"Not Found"}`) i stedet for medlemssiden. - `next.config.mjs` sin `rewrites()` returnerer en plain array (implisitt - "afterFiles") — DYNAMISKE Next.js-sider sjekkes ETTER rewrites, så - `/orgs/:path*`-proxy-regelen (ADR-016) fanger kallet FØR - `app/orgs/[id]/members/page.tsx` noensinne nås. Dette er den FØRSTE - frontend-siden som er nestet direkte under et allerede proxyet prefiks - — ingen tidligere skjerm har truffet dette. Selve siden/komponenten er - riktig bygget, kun ruten dit er blokkert. - 3. Ingen UI-vei til å opprette en ANDRE organisasjon når man allerede har - én — `CreateOrganizationState` i `dashboard.tsx` vises kun ved null - org-er. Backend støtter det fullt ut allerede (ADR-021). - 4. Ingen sammendrag/indikator noe sted for "alle runder har fått dato" — - må sjekkes manuelt per øktkort på program-skjermen. - **Ingen av de fire fikset i denne runden** — kun dokumentert på brukerens - eksplisitte instruks, PWA-runden prioriteres først. - -- **PWA: installasjon + full offline scoreregistrering, BYGGET, IKKE ENNÅ - RULLET UT (2026-07-19, ADR-028):** bruker valgte det mest ambisiøse - omfanget (installerbar app OG offline scoreregistrering, ikke bare - installasjon), og valgte å generere enkle ikoner nå fremfor å vente på - ekte design (med eksplisitt beskjed om at de er midlertidige). - **Ikoner:** ingen `PIL`/`rsvg-convert`/`imagemagick` tilgjengelig i miljøet - — løst med et scratch npm-prosjekt (`sharp@0.33.5`, node18-kompatibel - versjon; nyeste `sharp` krever node ≥20 og feilet først) som genererte et - enkelt grønt golf-flagg-ikonsett (`public/icons/icon-192.png`, - `icon-512.png`, `icon-maskable-512.png`) + erstattet den gamle - `public/apple-icon.png` (var v0.app sin generiske plassholderlogo, ikke - TeeCup-merkevare i det hele tatt). - **Bygget:** `app/manifest.ts` (Next.js sin innebygde manifest-generator, - ikke en statisk `manifest.json`), `appleWebApp`-metadata i `layout.tsx` - (iOS leser ikke manifest.json for hjemskjerm-oppførsel), - `components/sw-register.tsx` (stille no-op uten SW-støtte), - hånd­skrevet `public/sw.js` (ingen next-pwa/workbox-avhengighet — nettverk - først/cache-fallback for navigasjon + `/orgs/*`-GET-er, BEVISST ikke - stale-while-revalidate, se ADR-028 for hvorfor), `public/offline.html`. - **Offline scoreregistrering:** ny `lib/offline-queue.ts` (IndexedDB-kø, - ren klientkode — ikke i SW-en), koblet inn i `session-scorecard.tsx` sin - `submitStroke`/`submitHoleResult`: sjekker `navigator.onLine` først, køer - kun ved en EKTE nettverksfeil (ikke ved et avvist HTTP-svar som "matchen - er avgjort" — det vises fortsatt som vanlig feiltekst). Lokalt overlay - (`pendingStrokes`/`pendingResults`) viser køede verdier umiddelbart, - merket "Lagret lokalt · venter på synk". Auto-synk ved `window`s - `online`-event PLUSS en manuell "Synkroniser nå"-knapp (bevisst IKKE - Background Sync API — iOS Safari støtter den ikke). Et definitivt avvist - synk-forsøk (f.eks. matchen ble avgjort på en annen enhet mens denne var - offline) fjernes fra køen og vises som feilmelding, henger aldri for - alltid. - **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` som - deployes) kjørt og bekreftet — alle 16 ruter listet inkl. - `/manifest.webmanifest`. Kort container-boot + `curl` bekreftet manifest/ - service worker/ikoner/offline.html alle svarer riktig (200, riktig - innhold). - **IKKE gjort denne runden, viktig å være ærlig om:** ingen faktisk - nettleser-basert offline-test (Chrome DevTools sin Offline-bryter, - faktisk "Legg til på hjemskjerm") — intet nettleserverktøy tilgjengelig i - denne økten. Kun kodegjennomgang + build-verifisering. **Brukeren bør selv - teste scorekort-siden med DevTools Offline-modus før tillit i skarp - bruk.** - **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: `docker - compose up -d --build teecup_frontend`. Compose gjenskapte OGSÅ - `teecup_api` som en bivirkning (avhengighets-oppløsning i `up --build`) - — ingen backend-kode rørt, ren uendret gjenoppbygging, begge containere - boot-et rent (`Application startup complete` / Next.js `Ready`). - Verifisert: `/health`/`dashboard` → 200, `/manifest.webmanifest`/`sw.js`/ - `icons/icon-512.png` alle 200 over ekte https, `teeoff.no` upåvirket. - -- **Fire nye hull rapportert fra faktisk testing av blind draw + scorekort - (foursome), DIAGNOSTISERT OG NOTERT, IKKE fikset (2026-07-19):** rett - etter PWA-runden. Full detalj i FEATURE_BACKLOG.md sin nye seksjon - "Rapporterte hull, blind draw + scorekort (2026-07-19)". Kort: - 1. **Bekreftet root cause:** valgt spiller forsvinner ikke fra - nedtrekkslisten i `AddSlotForm` (`session-blind-draw.tsx`) — den - merkes kun `disabled` på `<option>`-nivå, som HTML fortsatt viser - (bare gråtonet). Skal FILTRERES bort, ikke deaktiveres. - 2. **Bekreftet, større enn antatt:** tee-valg (Dame/Herre) bør følges av - spillerens registrerte kjønn automatisk. Krever en backend-utvidelse - først — `RosterEntry`/`list_roster` (`tournaments.py`) mangler - `player.gender` helt i responsen, selv om feltet finnes i skjemaet og - alt eksponeres via `GET /orgs/{id}/players`. Åpne spørsmål om låst vs. - forhåndsutfylt valg, og fallback ved ukjent kjønn/manglende - matchende tee, før bygging. - 3. Ren frontend-UX: tallvelger (1–9 + utvidbar "10 eller flere") i stedet - for pluss/minus-steppere for slagregistrering. Ingen backend-endring. - 4. **HCP "ikke hensyntatt" i foursome-test — IKKE bekreftet som bug.** Ett - definitivt, bekreftet hull uavhengig av alt annet: det beregnede - `course_handicap`/`playing_handicap` eksponeres ALDRI noe sted i - API-et eller UI-et (sjekket `matches.py`, `scoring.py`, begge - frontend-skjermene) — usynlig selv når beregningen er korrekt. I - TILLEGG en kjent, tidligere dokumentert begrensning som kan ha slått - inn: foursome/greensome/scramble sin side-handicap beregnes kun når - BEGGE deltakere har en matchende `tee_rating`, og `tee_rating`-rader - lages i dag ALLTID kun med `full_18`-omfang — en `front_9`/`back_9`- - testøkt ville derfor ALDRI fått handicap beregnet i det hele tatt. - **Trenger avklaring:** var testøktens `hole_config` `full_18`, og - hadde begge sider alle sine deltakere+tee lagt til før scoring? - **Ingen av de fire fikset i denne runden** — kun dokumentert på brukerens - eksplisitte instruks. - -- **Tre av fire hull FIKSET OG SCRATCH-VERIFISERT (2026-07-19), samme dag:** - bruker ba eksplisitt om punkt 1 og 3, og presiserte punkt 2 (se under) - samt bekreftet den nøyaktige handicap-formelen for punkt 4. - 1. **Spillerliste-fiks:** `AddSlotForm` (`session-blind-draw.tsx`) - filtrerer nå allerede-valgte roster-rader helt bort i stedet for å - bare `disabled`-merke `<option>`-en (som HTML uansett viser gråtonet). - 2. **Tee-valg — OMDEFINERT etter brukerens presisering, IKKE bygget - ennå:** min opprinnelige antakelse ("lås tee til spillerens kjønn") - var feil i premisset — en golfbane har ikke fysisk kjønnsdelte - utslag, kun eventuelt kjønnsdelt RATING av samme utslag (noen klubber - sloper bevisst ikke ett utslag for ett kjønn). Bekreftet mot ekte - Tjøme-data: hvert fysisk utslag ligger i dag som TO `tee`-rader med - samme navn (én per kjønn) — nøyaktig konflateringen brukeren pekte - på. Riktig fiks er å flytte `gender` fra `tee` til `tee_rating` - (skjemaendring + datamigrering + import-/handicap-/remap-kode). - Betydelig større enn antatt — se FEATURE_BACKLOG.md for full - analyse og de tre åpne designspørsmålene som trengs FØR bygging. - 3. **Tallvelger:** ny `StrokePicker`-komponent - (`session-scorecard.tsx`) — 1-9 direkte, "10+"-knapp åpner 10-19, - med en "tilbake"-lenke. Erstatter ±-stepperen og all dens døde kode - helt. - 4. **HCP-bug, BEKREFTET og FIKSET:** brukeren beskrev selv riktig - formel (kombinert hcp/2 justert for prosent, laveste side til 0 - mottatte slag ved matchplay-hcp, resten fordelt fra stroke index 1) — - lest direkte mot `handicap_engine.py` og bekreftet at koden allerede - implementerer NØYAKTIG dette. Bugen lå ikke i formelen, men i at den - ALDRI kjørte for front_9/back_9-økter: `compute_and_store_side_ - handicaps` (`app/handicap.py`) joinet `tee_rating` på øktens - `hole_config` som rating-scope, men slike rader lages i praksis kun - med `scope='full_18'` (matcher ADR-008 sin allerede etablerte - design — full_18-ratingen skal alltid brukes, front/back-9- - fordelingen skjer senere ved selve slagtildelingen). Bekreftet - direkte mot EKTE `teecup_db` (read-only): brukerens rapporterte - testøkt (`front_9`+`foursome`) hadde `course_handicap`/ - `playing_handicap = NULL` på alle fire deltakere. 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 - funksjonen og alle tre kallstedene (var død etter fiksen). - **Scratch-verifisert presist:** samme scenario gjenskapt (foursome+ - front_9, hcp 10/20 mot 5/15) — course/playing handicap kom ut nøyaktig - som beregnet for hånd (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) — direkte bevis på at hcp nå faktisk brukes. - **Ny, urelatert bug funnet under samme scratch-test, IKKE fikset:** - stroke-modus-innsending på en bane uten registrerte hull krasjer rått - (500 `IndexError` i `allocate_over_played_holes`) i stedet for en ren - `VALIDATION_FAILED` — samme klasse feil som en tidligere fikset - manglende-handicap-krasj. Notert i FEATURE_BACKLOG.md, ikke bygget. - **Scratch-infrastruktur:** isolert `teecup_scratch`-database + - `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API- - container (`python:3.12-slim`, `app/` og `handicap_engine.py` montert - read-only), alt ryddet opp etter verifisering. Typesjekket - produksjonsbuild kjørt for frontend-fiksene (1+3), alle 16 ruter listet. - **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt, se eget - punkt lenger ned for punkt 2 (som ble bygget og rullet ut sammen med - disse tre i én utrulling). - -- **Punkt 2 (tee/kjønn) BYGGET OG SCRATCH-VERIFISERT (2026-07-19, ADR-029), - samme dag, rett etter designavklaringen:** bruker bekreftet begge - anbefalte alternativer (helautomatisk tee-valg, "feil høyt" ved - manglende kjønn/rating). Ny migrasjon `014_tee_gender_to_rating.sql`: - flytter `gender` fra `tee` til `tee_rating` (unikhet - `(tee_id, scope)` → `(tee_id, scope, gender)`), slår sammen - eksisterende kjønns-par-tee-rader til én fysisk tee-rad per (bane, - navn) — velger laveste id som "beholder", flytter `tee_rating`- og - `match_participant.tee_id`-referanser dit, sletter duplikatene. - **Reell bug funnet OG fikset UNDER selve migrasjonsskrivingen** (ikke i - produksjon): første versjon prøvde å droppe den GAMLE - `(tee_id, scope)`-unikheten ETTER sammenslåingen i stedet for FØR — - kolliderte da midlertidig med beholder-tee-ens egen eksisterende rad - for samme scope. Rettet ved å bytte rekkefølge (drop gammel unikhet FØR - sammenslåing, legg til ny kjønnsbevisst unikhet ETTER). - **Kodeendringer:** `app/handicap.py` sin `compute_and_store_side_ - handicaps` joiner nå også på `player.gender` (i tillegg til forrige - rundes full_18-fiks). `app/routers/matches.py` sin `add_participant` - validerer FØR innsetting: spiller har registrert kjønn, OG valgt utslag - har en matchende rating — begge avvist med klar `VALIDATION_FAILED`. - `app/routers/tournaments.py` sin `_remap_course` (bane-bytte) matcher nå - på tee-navn OG bekrefter matchende kjønnsrating for hver berørte - spiller. `app/routers/courses.py`: `TeeCreate` redesignet fra ett flatt - kjønn+rating-sett til en `ratings`-liste (1-2 elementer, distinkte - kjønn); ADR-019 sin `import_official_course` lager nå ÉN tee-rad per - fysisk teeoff-utslag (før: to, én per kjønn) med inntil to - `tee_rating`-rader under. `session-blind-draw.tsx` forenklet — ingen - "H"/"D"-suffiks, ingen kjønnslogikk i det hele tatt lenger (serveren - løser det). - **Verifisert grundig, flere separate scratch-runder:** - 1. Selve fletting-migrasjonen kjørt mot SYNTETISK data som gjenskaper - Tjøme-mønsteret nøyaktig (to par + én enslig utslag, pluss en - `match_participant`-rad som bevisst pekte til DUPLIKATEN, ikke - beholderen) — bekreftet: to rader ble til én, `tee_rating.gender` - riktig fylt inn for begge, `match_participant.tee_id` korrekt - reparert til beholderens id, det enslige utslaget urørt. - 2. Full API-runde (18 automatiserte sjekker via et Python/urllib- - testskript — httpx var ikke tilgjengelig i vertsmiljøet, løst med - stdlib `http.cookiejar`/`urllib` i stedet): manuell tee-opprettelse - (to ratinger, kun én rating, duplikat kjønn avvist), `GET tees` - viser riktig sammenslått struktur, kvinne+dame-rating lykkes, - mann+kun-dame-rating avvist tydelig, spiller uten kjønn avvist - tydelig, full kjønnsblandet singel-match scoret korrekt. - 3. Offisiell import kjørt mot EKTE `teeoff_api` (Borregaard Golfklubb, - samme mønster som ADR-019 sin opprinnelige verifisering) — bekreftet - 4 fysiske utslag importert med begge kjønnsratinger hver, ikke 8 - doble rader. - 4. `_remap_course`: bane-bytte til en bane UTEN matchende kjønnsrating - avvist tydelig, bane-bytte til en bane MED matchende rating lykket. - Ekte typesjekket produksjonsbuild av frontend kjørt på nytt og - bekreftet etter blind draw-forenklingen. - **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: migrasjon - 014 kjørt mot ekte `teecup_db` FØRST (Tjømes 8 tee-rader slått sammen - til 4 — bekreftet 0 brutte `match_participant.tee_id`-referanser - etterpå med en direkte spørring), deretter `docker compose up -d - --build teecup_api teecup_frontend` sammen med punkt 1/3/4 i samme - utrulling. Begge containere boot-et rent, `/health`/`dashboard` → 200, - `teeoff.no` upåvirket. - -- **Dashboard-dato-fiks (ADR-030) + rediger spiller, BYGGET OG SCRATCH- - VERIFISERT (2026-07-19/20):** brukeren viste et faktisk skjermbilde av - det tidligere dokumenterte datovisning-hullet (økt planlagt til "11. - juli" på Program-fanen, men dashbord-kortet viste fortsatt "Ingen - datoer satt") og ba samtidig om å kunne redigere spilleres HCP. - **Dato-fiks:** `list_tournaments` (`app/routers/tournaments.py`) utleder - nå datospennet fra øktenes `scheduled_at` (`COALESCE` med et evt. - eksplisitt satt `tournament.start_date`/`end_date`, som fortsatt vinner - om det noensinne settes — ingen UI gjør det i dag). Kun denne ene - spørringen endret, `_TOURNAMENT_COLUMNS` (brukt av opprett/PATCH sine - `RETURNING`-klausuler) urørt. Se ADR-030. - **Rediger spiller:** ny `PATCH /orgs/{id}/players/{id}` - (`app/routers/players.py`, vanlig `exclude_unset`-mønster, dekker alle - spillerfelt) + ny "Rediger spiller"-handling i rosterradens meny - (`tournament-detail.tsx`). **Bevisst grense, forklart i selve UI-et:** - endrer spillerpoolen, IKKE et lags allerede frosne - `handicap_index_snapshot` (ADR-007) — reproduserbarhet for allerede - opprettede lag er et bevisst, tidligere designvalg, ikke noe denne - fiksen skulle endre. Skjemaet sier dette rett ut i stedet for å late som - endringen slår inn overalt. - **Scratch-verifisert, 9 sjekker:** spiller-PATCH (hcp-endring, delvis - PATCH lar andre felt stå urørt, tomt PATCH avvist, ukjent id gir 404), - 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), dato-utledning (ingen økter → null, - to økter 11./12. juli → riktig utledet spenn, eksplisitt satt dato - vinner over utledet). Ekte typesjekket produksjonsbuild kjørt og - bekreftet. - **Rullet ut live 2026-07-20**, bruker bekreftet eksplisitt: `docker - compose up -d --build teecup_api teecup_frontend`, ingen migrasjon. - Begge containere boot-et rent, `/health`/`dashboard` → 200, `teeoff.no` - upåvirket. Verifisert mot EKTE data (ikke bare scratch): "De Gamle er - Eldst" viser nå korrekt 11. juli 2026 i stedet for "Ingen datoer satt". - -- **To nye punkter reist 2026-07-20, GJENNOMTENKT OG FORESLÅTT, IKKE - bygget:** brukeren ba eksplisitt om at punkt 1 tenkes grundig gjennom - og legges frem som et forslag FØR bygging (ikke kode med en gang), og - markerte det eksplisitt som prioritet over punkt 2. - 1. **Personlig landingsside for enhver registrert bruker (PRIORITERT).** - Bekreftet reelt hull ved kodegjennomgang: `app/page.tsx` sender - enhver innlogget bruker til `/dashboard`, som viser "opprett - organisasjon" så snart `organizations.length === 0` — også for en - bruker som KUN er spiller (koblet via `player.user_id`, - ADR-017 B), aldri organisator. Fullt forslag skrevet i - FEATURE_BACKLOG.md: ett samlet dashboard (ikke to atskilte ruter), - ny "Mine runder"-seksjon (tverr-org, krever en ny SECURITY - DEFINER-bro `player_organizations_for_user()` + utvidelse av - `/auth/me`, samme mønster som `public_tournament_org()` m.fl.), - organisasjonsseksjonen uendret under. Venter på brukerens - bekreftelse på retningen før bygging starter. - 2. **Midlertidige spillere + automatisk etter-runde-e-post (lavere - prioritet, likevel dokumentert grundig).** Presisert ved - kodegjennomgang: det meste av "midlertidig spiller"-behovet - dekkes ALLEREDE av eksisterende `POST /orgs/{id}/players` - (krever aldri en konto). Det som faktisk mangler er en PROAKTIV - e-post-utsending etter runden (scorekort + innloggingslenke) — - foreslått som en eksplisitt organisator-knapp per økt (ikke en - automatisk bakgrunnsjobb, for å unngå uventede e-poster fra en - gjettet "runden er ferdig"-deteksjon). Tre åpne spørsmål notert i - FEATURE_BACKLOG.md (økt- vs. turnering-nivå, dobbel-utsending- - sperre, locale). - **Ingen kode skrevet for noen av de to ennå** — dette var bevisst en - tenke-og-foreslå-runde, ikke en byggerunde. - -- **Punkt 1 BYGGET OG SCRATCH-VERIFISERT (2026-07-20), samme dag, rett etter - forslaget over:** bruker svarte "Gjør punkt 1" med et utvidet omfang — - inkluder også opprettelse/redigering/sletting av personlig informasjon - (profilbilde, fornavn, etternavn, fødselsdato, kjønn, HCP, hjemmeklubb). - **Ny migrasjon `015_user_profile.sql`:** `app_user` får de sju nye - profilfeltene — ETT sett PER KONTO, bevisst IKKE slått sammen med de - org-scopede `player`-radene (se ADR-031 Beslutning B for full - begrunnelse — to reelt atskilte konsepter). Ny - `player_organizations_for_user()`-bro (femte instans av samme - SECURITY DEFINER-mønster som `public_tournament_org()` m.fl.). - **Backend:** `/auth/me` utvidet med profilfeltene + `avatar_url` + - `my_tournaments` (turneringer brukeren er ROSTRET i, tverr-org, samme - N+1-org_connection()-mønster som organisasjonslisten). Ny - `PATCH /auth/profile` (vanlig exclude_unset), `POST`/ - `DELETE /auth/profile/avatar` (samme ekte multipart→AVIF-mønster som - turnering-hero-bilder). - **Reelt sikkerhetshull funnet UNDER bygging, ikke antatt på forhånd:** - testet "Mine runder" mot en EKTE ren spiller (rostret, ingen - org-medlemskap) og oppdaget at `check_visibility()` (ADR-018) kun ga - deltaker-tilgang for `visibility='participants'` — IKKE for `'org'` - (DEFAULT for enhver ny turnering). En ren spiller ville altså vært - stengt ute fra sin EGEN, helt vanlige turnering — nøyaktig - brukergruppen "Mine runder" er bygget for. Fikset: deltaker-sjekken - gjelder nå begge ikke-offentlige tier, med eksplisitt begrunnelse om - at visibility styrer eksponering mot UTENFORSTÅENDE, aldri mot faktiske - deltakere. Verifisert presist at dette er en REN UTVIDELSE, ingen - innstramming: samme scratch-test bekreftet at en helt ubeslektet - FREMMED (innlogget, ikke deltaker) og en ANONYM leser fortsatt begge - avvises identisk som før (403 NOT_VISIBLE). - **Frontend:** `account-settings.tsx` fikk en ny "Personlig profil"- - seksjon (avatar-opplasting/fjerning, fornavn/etternavn/fødselsdato/ - kjønn/HCP/hjemmeklubb-skjema). `dashboard.tsx` fikk en ny - `MyToursSection` («Mine runder», øverst, lenker til den offentlige - turnering-siden) + en mykere, sekundær utgave av "opprett organisasjon"- - tomtilstanden når brukeren allerede har spiller-data å vise. - **Bevisst UTENFOR omfang, klart flagget, IKKE en del av denne rundens - leveranse:** "Mine runder" lenker IKKE til lag-chat/scorekort ennå — de - krever fortsatt ekte organisasjonsmedlemskap (`get_authorized_org`), en - strengere, bredt brukt sperre som ikke ble endret denne runden (egen, - større og mer risikofylt endring, se ADR-031). - **Scratch-verifisert, 15 sjekker:** full profil-CRUD (alle felt satt, - delvis PATCH lar andre felt stå urørt, eksplisitt `null` sletter et - felt, tomt PATCH avvist, avatar lastet opp med ekte AVIF-URL og - slettet igjen, ugyldig filtype avvist), "Mine runder" for en EKTE ren - spiller uten org-medlemskap, OG den kritiske sikkerhetssjekken over. - Ekte typesjekket produksjonsbuild kjørt og bekreftet. - **Rullet ut live 2026-07-20**, bruker bekreftet eksplisitt: migrasjon - 015 kjørt mot ekte `teecup_db` (bekreftet nye kolonner + funksjon - finnes), deretter `docker compose up -d --build teecup_api - teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`/ - `/account` → 200, `teeoff.no` upåvirket. - -- **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 etterspurte mobil med landsnummer. - Spurte samtidig hvorfor V0 ikke brukes til det visuelle her — svarte at - jeg ikke har V0 som et verktøy jeg selv kan kalle (all V0-bruk i - prosjektet har vært brukeren som designer i v0.app og sender meg - zip-eksporter), og at disse siste tilføyelsene er små, inkrementelle - skjemafelt i eksisterende komponenter (gjenbruker allerede etablerte - Tailwind/shadcn-mønstre) der en full V0-runde (design→eksport→diff→ - sammenslåing) ville vært en unødvendig omvei. - **Mobil:** `mobile_country_code`+`mobile_number` (to separate felt, ikke - én sammensatt streng), lagt til i den EKSISTERENDE `PATCH /auth/profile` - — ren tilføyelse, ingen ny sikkerhetsvurdering nødvendig. - **E-post — bevisst IKKE en enkel PATCH:** e-post er innloggings- - identifikatoren (magic-link-mål) — en vanlig PATCH ville latt en - skrivefeil eller en kapret sesjon stjele kontoen for godt. Bygget som et - ekte to-stegs bekreftelsesløp i stedet, samme `token_hash`+ - `expires_at`+`consumed_at`-mønster som `magic_link_token` (migrasjon - 004): ny `email_change_token`-tabell (migrasjon `016_profile_ - contact.sql`). `POST /auth/profile/email` (krever sesjon, sender - bekreftelseslenke til den NYE adressen -- ikke den gamle, beviser - eierskap av MÅLET). `POST /auth/profile/email/confirm` (ingen sesjon - påkrevd, samme mønster som selve magic-link-verifiseringen -- lenken - kan åpnes på en annen enhet enn den som ba om byttet). E-posten endres - ALDRI før lenken faktisk åpnes. Duplikat-sjekk kjøres TO GANGER (ved - forespørsel og rett før selve byttet, i tilfelle adressen ble tatt i - mellomtiden), pluss den eksisterende unike indeksen som siste - bakstopper. Ny `/verify-email`-side (samme mønster som `/verify`). - **Scratch-verifisert, 10 sjekker:** mobil satt via vanlig PATCH; vanlig - profil-PATCH rører aldri e-post; bytte til allerede brukt adresse - avvist (409); e-post uendret helt til bekreftelse; ugyldig kode avvist; - gyldig kode fullfører byttet; SAMME kode kan ikke gjenbrukes; en helt ny - innlogging med den GAMLE adressen oppretter en fersk, tom konto (beviser - byttet er reelt og fullstendig). Ekte typesjekket produksjonsbuild kjørt - og bekreftet (ny `/verify-email`-rute listet). - **Rullet ut live 2026-07-20**, bruker bekreftet eksplisitt (spurte - samtidig og bekreftet at `teecup.teeoff.no/dashboard` nå er DEN samme - adressen for enhver innlogget bruker uansett rolle — nettopp poenget - med ADR-031/032): migrasjon 016 kjørt mot ekte `teecup_db` (bekreftet - nye kolonner + `email_change_token`-tabell finnes), 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. - -- **Medlemsside-ruten FIKSET OG LIVE (2026-07-20):** brukeren ba eksplisitt - om å ta fatt på dette (det mest presserende av de fire UI-hullene notert - 2026-07-19 — siden var helt utilgjengelig). Root cause var allerede - presist diagnostisert: `next.config.mjs` sin `rewrites()` (plain array, - implisitt "afterFiles") fanger `/orgs/:path*` FØR Next.js sine egne - DYNAMISKE sider sjekkes, så `app/orgs/[id]/members/page.tsx` ble aldri - nådd — kallet gikk til FastAPI i stedet, som ga en rå 404. - **Fikset:** siden flyttet til `app/organizations/[id]/members/page.tsx` - (utenfor `/orgs/*`-prefikset), eneste lenke (`dashboard.tsx`) oppdatert. - Lagt til en forklarende kommentar i `next.config.mjs` sin `rewrites()` - for å forhindre samme feil ved en fremtidig ny side. - **Verifisert med ekte produksjonsbuild + container-boot** (ikke bare - typesjekk): den nye ruten (`/organizations/{id}/members`) rendrer - faktisk `OrgMembers`-komponenten med riktig `organizationId`/`orgName` - i RSC-payloaden, IKKE en 404 eller innloggingssiden. - **Rullet ut live**, ren frontend-endring, ingen migrasjon, `teeoff.no` - upåvirket. - -- **De to siste UI-/UX-hullene fra 2026-07-19 FIKSET OG LIVE (2026-07-21):** - alle fire punkter i den runden er dermed fikset. - 1. **«Ny organisasjon»:** ny `NewOrganizationControl` i `dashboard.tsx` - (identisk inline-ekspanderende-form-mønster som `NewTournamentControl`), - lagt til i `OrganizationView` sin header ved siden av - «Medlemmer»/«Ny turnering» — synlig uansett hvor mange org-er brukeren - allerede har. Ingen backend-endring (`POST /orgs` hadde aldri en - grense). - 2. **Dato-sammendrag:** ny `DateCoverageSummary` i `tournament- - program.tsx`, vist øverst i øktlisten når minst én økt finnes: «X av Y - runder har fått dato og klokkeslett» (uthevet når alle er satt). Ren - klientside-telling av allerede lastet `scheduled_at`, ingen - backend-endring. - **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` som - deployes) kjørt og bekreftet, alle 16 ruter listet. **Rullet ut live**, - bruker bekreftet eksplisitt: `docker compose up -d --build - teecup_frontend` (gjenskapte også `teecup_api` som compose sin vanlige - avhengighets-bivirkning, ingen backend-kode rørt). Begge containere - boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. - -- **Dashboard/konto-runde (2026-07-21): to store punkter reist samtidig av - brukeren.** (1) "Alt relatert til dashboard/account/brukerkontoer" — - scopet sammen med brukeren via spørsmål: tom-tilstand-redesignet ble - PAUSERT (bruker ba om "juster retningen" og reiste et dypere spørsmål om - hvorvidt organisasjon fortsatt bør være "det som meldes først" — se eget - punkt i FEATURE_BACKLOG.md, min vurdering: nei, bør bli ett likestilt - valg blant flere). (2) Et helt nytt, stort forslag om frittstående - rundeføring + detaljert statistikk (putter/chip/bunkerslag/straffeslag/ - førsteputt-lengde) UTEN turnering/organisasjon — grundig notert i - FEATURE_BACKLOG.md med en eksplisitt arkitektur-advarsel: dette - UTFORDRER tenant-invarianten (`organization_id` på alle domenetabeller) - direkte og trenger en egen ADR, ikke bygget denne runden. - **Deltaker-tilgang til lag-chat/scorekort — ✅ BYGGET OG LIVE - 2026-07-21** (én av tre konkrete følgepunkter brukeren bekreftet i samme - runde, de to andre — sekundær e-post, HCP-historikk — tas fortløpende - etterpå): fjernet den blanke `get_authorized_org`-sperren fra ni - endepunkter på tvers av `messaging.py`/`scoring.py`/`matches.py`/ - `tournaments.py`/`courses.py`, erstattet med de ALLEREDE eksisterende - domene-sjekkene (`user_is_rostered_on_team`/`user_is_match_participant`/ - `user_is_team_captain`) som viste seg å støtte ikke-org-medlemmer helt - fint fra før — de var bare aldri nåbare. To nye delte hjelpefunksjoner i - `team_authz.py` (`is_org_member`, `user_is_tournament_participant` — - sistnevnte FLYTTET dit fra `registration.py` for å unngå sirkulær - import) dekker de endepunktene som IKKE hadde noen finkornet sjekk fra - før (ren fjerning der ville åpnet dem for enhver innlogget bruker). - `/auth/me` sin `my_tournaments` fikk `my_session_id`/`my_match_id`; - "Mine runder"-kortet fikk "Lag-chat"/"Scorekort"-lenker. - **Scratch-verifisert grundig, 43 sjekker** (isolert scratch-rolle+MinIO+ - engangs API-container): rostret ikke-medlem fikk korrekt tilgang - overalt (inkl. faktisk sendt chat-melding og hull-resultat), fortsatt - avvist fra det ANDRE lagets chat, en helt fremmed bruker avvist overalt, - org-eier beholder alt UNNTATT lag-chat (uendret, med vilje), kryss-org- - isolasjon bekreftet. **Reelt funn UNDER selve scratch-testingen:** en - rostret-men-ikke-kaptein spiller ble først uventet GODTATT til walkover - — viste seg å være en allerede tiltenkt, dokumentert fallback - (`user_is_team_captain`: "ingen kaptein utpekt ennå = enhver rostret - spiller godtas"), ikke en bug — testen ble rettet (la til en faktisk - kaptein) og bekreftet deretter riktig avvisning. `test_isolation.sql` - 12/12 uendret (ingen skjemaendring). Ekte typesjekket produksjonsbuild - 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. - -- **Sekundær e-postadresse (del 1, det enkle tilfellet) — ✅ BYGGET OG LIVE - 2026-07-21**, samme dag, rett etter deltaker-tilgang-runden. Ny - migrasjon `017_secondary_email.sql` (`secondary_email_token` + - `user_secondary_email`, samme token-hash-og-utløp-mønster som ADR-032s - `email_change_token`). Nye endepunkter `POST /auth/secondary-email`, - `POST /auth/secondary-email/confirm`, `DELETE /auth/secondary-email/{id}`. - **Kjernestykket:** `verify_magic_link`/`login_with_password` slår nå opp - `user_secondary_email` FØR sitt vanlige `app_user.email`-oppslag — en - innlogging på en verifisert sekundæradresse løses til EIERENS - eksisterende konto i stedet for å opprette en ny, separat en (nøyaktig - det hullet som gjorde funksjonen nødvendig i utgangspunktet). Lagt i - `/account` (ikke dashbordet som opprinnelig bedt om — bevisst avvik, - flagget eksplisitt: dette er kun del 1, dashbord-plassering er trolig - riktigere når/hvis del 2 (kontosammenslåing) bygges). - **Scratch-verifisert, 20 sjekker:** adresse ikke lagt til før bekreftet, - token ikke gjenbrukbart, dupliserte adresser (som andres primær- ELLER - sekundæradresse) avvist tydelig, innlogging via sekundæradresse (magic- - link OG passord) bekreftet å resolve til SAMME eksisterende konto, - fremmed kan ikke slette andres adresse, fjernet adresse oppretter en - genuint NY konto ved neste innlogging (beviser fjerning er reell). - `test_isolation.sql` 12/12. Ekte typesjekket produksjonsbuild kjørt og - bekreftet. - **Rullet ut live 2026-07-21**, bruker bekreftet eksplisitt: migrasjon - 017 kjørt mot ekte `teecup_db` (begge tabeller 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`/`/verify-email` → 200, `teeoff.no` - upåvirket. Del 2 (ekte kontosammenslåing) fortsatt IKKE designet, egen - fremtidig runde, se FEATURE_BACKLOG.md. - -- **HCP-historikk over tid — ✅ BYGGET OG LIVE 2026-07-21**, samme dag, - siste av de tre bekreftede punktene fra dashboard/konto-runden. Ny - migrasjon `018_handicap_history.sql` (append-only `handicap_history` — - kun for personlig profil sin `app_user.handicap_index`, IKKE de org- - scopede `player`/`team_roster`-radene, som har sitt eget uendrede - reproduserbarhets-prinsipp fra ADR-007). `PATCH /auth/profile` logger nå - en ny rad KUN ved en FAKTISK endring til en tallverdi — leser gjeldende - verdi FØR overskriving for å unngå duplikater ved gjentatt lagring av - samme verdi, og logger bevisst IKKE ved nullstilling. Ny - `GET /auth/profile/handicap-history`. Frontend: «Vis HCP-historikk»- - lenke i `/account` sin profilseksjon. - **Scratch-verifisert, 18 sjekker:** ingen duplikat ved uendret - gjenlagring, korrekt logging ved reell endring, ingen logg ved - nullstilling, ny logg ved gjeninnsetting etter nullstilling, - kronologisk rekkefølge riktig, full isolasjon mellom to brukeres - historikk. `test_isolation.sql` 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, sekundær e-post del 1, - HCP-historikk). - -- **Obligatorisk profil-fullføring ved innlogging LIVE (2026-07-22):** - svar på det pauserte "dashbordets tom-tilstand"-spørsmålet over — - brukeren avklarte at det ALLER første en innlogget bruker med en - ufullstendig profil skal se, er en fokusert «Fullfør profilen din»- - visning, ikke dashbordet. Ny migrasjon `019_profile_country_bio.sql` - (`app_user.country`, `app_user.bio` — samme nullable-kolonne-mønster - som resten av profilen, "obligatorisk" håndheves i app-laget). - `/auth/me` fikk et nytt beregnet felt `profile_complete` (sant når - fornavn/etternavn/fødselsdato/kjønn/HCP/hjemmeklubb/land ALLE er - utfylt — bilde og beskrivelse er bevisst unntatt, valgfrie). - **HCP-grensetilfelle avklart med bruker FØR bygging** (nybegynnere har - sjelden en offisiell HCP ennå): WHS-maksimum 54 brukes som - forhåndsutfylt standardverdi i skjemaet (ikke en DB-default), og - `ProfileUpdate.handicap_index` fikk en hard `le=54`-grense (kan aldri - registreres høyere) — løser grensetilfellet uten en egen "har ikke - HCP ennå"-avkrysning. - `AccountSettings` (`/account`) grener nå: er profilen ufullstendig, - vises KUN et nytt, fokusert `ProfileOnboarding`-skjema (de obligatoriske - feltene + valgfri beskrivelse, «Logg ut» tilgjengelig, INGEN tilgang - til resten av kontosidene) — er den komplett, vises den vanlige - innstillingssiden som før (nå med land+beskrivelse lagt til i det - vanlige profilskjemaet, for redigering i etterkant). `app/page.tsx` - (rot-siden) 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`, som selv gjør sjekken ved mount). - **Bevisst avgrenset:** gaten håndheves kun ved disse to naturlige - inngangspunktene, ikke ved dypere direktelenker til andre autentiserte - sider — samme skope-disiplin som tidligere runder. - **Scratch-verifisert, 16 backend-sjekker** (isolert scratch-rolle+ - MinIO+engangs API-container): fersk konto starter `profile_complete: - false`, delvis utfylling forblir ufullstendig, HCP>54 avvist (422), - full utfylling gir `true`, beskrivelse er reelt valgfri, å nullstille - et obligatorisk felt i etterkant slår `profile_complete` tilbake til - `false`, full isolasjon mellom to kontoer. `test_isolation.sql` 12/12 - uendret. Ekte typesjekket produksjonsbuild + et ekte HTTP-nivå-bevis - mot en kjørende produksjonscontainer (anonym mot `/` → 200 innloggings- - skjema, en ekte innlogget-men-ufullstendig sesjonscookie mot `/` → - `307 → /account`). - **Rullet ut live 2026-07-22**, bruker bekreftet eksplisitt: migrasjon - 019 kjørt mot ekte `teecup_db` (kolonner 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`/`/` (anonym) → 200, `teeoff.no` upåvirket. **Merk:** BEGGE - brukerens egne kontoer (`erol.haagenrud@envide.no` — eier av «Tjøme - Gents» — og `hei@erol.no`) mangler i dag alle disse feltene og vil - derfor begge se profil-fullførings-skjemaet ved neste innlogging — - bekreftet tilsiktet, ikke en bug. -- **Sju punkter fra faktisk bruk av scorekort-skjermen, BYGGET OG LIVE - 2026-07-24:** starthull-bug fikset (`currentHole` respekterte aldri - `round.start_hole` — `1` er truthy i JS, så `prev || start_hole` var en - no-op — forklarer trolig også det samtidig rapporterte GIR-avviket, - siden formelen selv var korrekt), kølle-bag på profilen (28 faste - typer, maks 14), nytt statistikkfelt «Anywayslag», valgfritt - statistikknivå per deltaker (default kun slag — ny kolonne - `round_participant.stat_level`), putt-avstand endret fra fritekst til - seks faste bøtter, «Hullet er spilt»-avkrysningen fjernet (overflødig - — spilt settes allerede automatisk ved slagtall). Ny migrasjon - `022_round_stats_and_bag.sql`. Numpad-layout/retningskors-ikoner for - tallvelgerne (brukerens punkt 6) er BEVISST holdt utenfor — egen - V0-prompt utarbeidet i stedet, ikke bygget selv. Full detalj i - ADR-033. 18 scratch-sjekker, `test_isolation.sql` 12/12, rullet ut mot - ekte `teecup_db`/`teecup_api`/`teecup_frontend`, `teeoff.no` upåvirket. -- **V0-prompten for numpad/retningskors/sveip-vurdering (punkt 6) BYGGET - OG LIVE, samme dag:** bruker kjørte prompten, sendte zip 13. V0 valgte - trykk-baserte Score/Statistikk-faner fremfor sveip (godt begrunnet — - unngår en tredje sveiperetning på en skjerm som allerede har to). - Flettet inn i EKSISTERENDE, allerede fungerende datalag (statLevel- - gating, kølle-bag, anywayslag, putt-bøtter, merge-før-PATCH, - starthull-fiks, `/my-rounds`-lenker) — ikke en ren erstatning, siden - V0 ikke kjente til den runden. Full detalj i ADR-033. Typesjekket - build kompilerte rent, rullet ut (kun `teecup_frontend`), `teeoff.no` - upåvirket. -- **Enda en runde brukerpunkter, ALLE BYGGET OG LIVE, samme dag:** - utslagstidspunkt + automatisk tidsbruk-visning (gjenbruker eksisterende - "Fullfør runde" som "Ferdig", ny `round.started_at`-kolonne, migrasjon - 023), "Idx" → "Hcp", "par"-merking på slag-tastaturet, ni-hulls- - navigasjonsbug fikset (respekterte aldri `holes_planned`, hoppet feil - ved "Forrige"), tak på putter/chip/bunker/straffeslag/anywayslag (kan - ikke overstige antall slag), "Slett runde"-knapp (backend fantes, - manglet UI), og en ny `PATCH /rounds/{id}` for å rette bane/utslag/ - antall hull MIDT i runden uten å røre allerede registrerte slag — - sperret etter fullføring. 22+8 scratch-sjekker, `test_isolation.sql` - 12/12, ren build. Full detalj i ADR-033. -- **To til punkter, BYGGET OG LIVE samme dag:** starthull kan nå endres - uansett (ren metadata), utslagstid justeres når som helst, og - fullført-tidspunkt kan korrigeres i etterkant — men KUN på en allerede - fullført runde (løser "glemte å trykke Fullfør runde i flere timer"). - Pluss en ny "Nærmest deg"-liste i bane-søket ved ny runde (Haversine- - avstand mot alle 174 teeoff-anlegg, geolokasjon i nettleseren, feiler - stille hvis avslått). 14 nye scratch-sjekker inkl. et ekte nearby-kall - mot teeoff. Full detalj i ADR-033. -- **Reell UX-bug fikset + "Så langt i runden"-oversikt bygget, samme - dag (2026-07-25), klar for utrulling:** brukeren rapporterte at "All - statistikk" ikke viste noe utover slag/putter -- bekreftet (kun - lesing) mot ekte `teecup_db` at `stat_level='full'` FAKTISK var - lagret riktig, så feilen var presentasjonen: alle detaljfeltene lå - bak en "Score"/"Statistikk"-fane fra forrige V0-runde som brukeren - aldri oppdaget. Fikset ved å fjerne faneløsningen helt -- alt vises nå - alltid samlet. Samtidig bygget en ny "Så langt"-oversikt - (`ScoreSoFar`-komponent: kompakt linje + utvidbar full-oversikt- - tabell med netto per hull), med et nytt backend-felt - `strokes_received` (`allocate_strokes_by_index()`, ingen ny - algoritme). 11 nye scratch-sjekker (inkl. et presist tall-eksempel på - slagfordelingen), ren build. Full detalj i ADR-033. - **Rullet ut live 2026-07-25**, bruker bekreftet eksplisitt: kun - `teecup_api`+`teecup_frontend` redeployet, ingen migrasjon, - `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. -- **Reell produksjonsregresjon rapportert av bruker rett etter utrullingen - over, funnet og fikset umiddelbart samme dag (2026-07-25):** - "Ingenting er klikkbart i den avanserte statistikken." Root cause var - IKKE frontend (all onClick-kabling var korrekt) -- funnet ved å faktisk - gjenskape brukerens klikk-sekvens mot en fersk scratch-container: - `update_hole` (hull-PATCH) manglet fortsatt det nye påkrevde - `strokes_received`-feltet i responsen sin (lagt til i GET-endepunktet i - runden rett over, glemt i PATCH) -- ga en 500 (Pydantic-valideringsfeil) - på HVER hull-lagring, ikke bare de avanserte feltene. Frontend svelger - feilresponsen stille, så symptomet så ut som "ingenting skjer" for - ALT, ikke bare avansert statistikk (brukeren merket det trolig først - der siden Slag/Putter fra tidligere runder allerede hadde lagrede - verdier som så riktige ut). Fikset: `update_hole` beregner nå - `strokes_received` for hullet som oppdateres, samme algoritme som - `list_holes`. 14 scratch-sjekker som gjenskaper eksakt klikk- - rekkefølgen, alle bestått. **Rullet ut live 2026-07-25**, bruker - bekreftet eksplisitt: kun `teecup_api` redeployet, `/health`/ - `/dashboard` → 200, `teeoff.no` upåvirket. -- **Rediger/Fullfør/Slett tonet ned + flyttet til toppen, auto-scroll ved - hull-bytte, LIVE samme dag (2026-07-25):** brukeren rapporterte at - "Fullfør runde"/"Slett runde" var for lette å trykke på ved et uhell - (lå rett under "Neste hull"). Flyttet alle tre handlingene til en - nedtonet rad øverst -- må nå aktivt scrolles til. Samtidig: "Neste - hull"/"Forrige" scroller nå automatisk opp til toppen av hull-panelet - (også ved direkte hull-valg), så det nye hullets Slag-felt alltid er - synlig med en gang. Ren frontend-endring, ingen backend/migrasjon. - Rullet ut, `teeoff.no` upåvirket. -- **"Så langt i runden" utvidet med netto/stableford-sum, putt-/kølle- - statistikk-totaler og grafisk fairway-/innspill-fordeling, LIVE samme - dag (2026-07-25):** etterspurt av bruker. Ny `StatPill`-rutenett - (Slag/Til par/Netto/Stableford/Putt/Chip/Bunker/Straffeslag/ - Anywayslag), Stableford-kolonne i hull-tabellen, og to nye - `DistributionBar`-seksjoner (Fairwaytreff/Innspill, N-kategori-variant - av `SegmentedBar`-mønsteret fra tournament-leaderboard.tsx) + - gjennomsnittlig brutto score-til-par (to desimaler, fortegn) splittet - på treff-vs-bom for både fairway og innspill. Ingen backend-endring -- - alt beregnes klientside fra data `GET .../holes` allerede returnerer. - Stableford beregnes alltid ut fra netto score når mulig (appen har - ingen egen "spilleform"-innstilling). Logikk verifisert manuelt mot et - regnet eksempel før utrulling. Ren frontend-endring, ingen migrasjon, - rullet ut, `teeoff.no` upåvirket. -- **"Avstand første putt" flyttet rett under "Putter", LIVE samme dag - (2026-07-25):** lå tidligere lenger ned i "full"-statistikk-blokken - (etter kølle/retning/chip-gruppen) -- flyttet til å bli første felt i - den blokken, rett under Putter-NumberPickeren (som ligger utenfor - "full"-gaten). Ren omrokkering, ingen ny logikk. Ekte typesjekket - build, rullet ut, `teeoff.no` upåvirket. -- **V0-prompt for en rikere rundestatistikk-skjerm skrevet, IKKE bygget - ennå (2026-07-25):** brukeren lastet opp en skjermopptaksvideo - (`screen-20260724-133013-1784892586987.mp4`, 26 sek, av en KONKURRENT- - apps statistikkskjerm) og ba om et V0-prompt inspirert av innholdet, - eksplisitt IKKE et plagiat. Video analysert bilde for bilde (ffmpeg i - en engangs Docker-container, ikke installert på verten). Innholdet - identifisert dekkes i stor grad av data vi ALLEREDE sporer - (fairwaytreff/innspill-retning/putts/chip/bunker/straffeslag/putt- - lengde-bøtte) -- ingen backend-endring skulle trengtes for de fleste - målene. Prompt skrevet til bruker i chatten (ikke lagret som egen - fil) -- beskriver mål/informasjonsarkitektur/tilgjengelighetskrav - abstrakt (donut med sentertall, gauge-stolper for avvik fra par, - retnings-diagram for bom-retning) UTEN å kopiere konkurrentens - eksakte fargevalg/layout/ordlyd, og ber eksplisitt om TeeCups egen - merkevareidentitet (grønn/oransje) + en egen visuell vri. - **Én bevisst forskjell fra videoen, valgt for å unngå plagiat OG fordi - det matcher vårt eget datamodell:** videoens puttlengde-bøtter - (<1m/1-2/2-4/4-8/+) er ANDRE enn TeeCups allerede lagrede seks bøtter - (<1m/<2m/<3m/<5m/<8m/8m+, ADR-033) -- promptet ber om TeeCups egne - bøtter, ikke videoens. "Lengste drive" (krever GPS/avstandsmåling vi - ikke har) bevisst utelatt fra promptet. - **Bygget og rullet ut live 2026-07-25, samme dag:** zip 14 mottatt. - Diffet mot live-treet FØR noe ble tatt inn (samme rutine som alltid) -- - kun to reelt nye filer (`components/round-stats.tsx`, - `app/rounds/[id]/stats/page.tsx`), resten var V0s vanlige uvitende - reverts (bl.a. sin egen `/rounds/[id]`-ruteversjon fra FØR - `/my-rounds`-omdøpingen ADR-033 gjorde 2026-07-23 -- korrekt hoppet - over). Ruten lagt inn som `app/my-rounds/[id]/stats/page.tsx` i stedet - (samme kollisjon-unngåelse). `globals.css` sin nye `--chart-1..6` - data-viz-fargeskala slått sammen inn (V0 sine egne, mer omtrentlige - `--primary`/`--ring`/`--brand-orange`-verdier IKKE tatt inn -- beholdt - de presise OKLCH-verdiene fra ADR-016). - **Datalag skrevet fullstendig om fra mock:** henter `GET /rounds/{id}` - + `GET .../participants/{id}/holes` (samme endepunkter round-detail.tsx - allerede bruker) -- ingen ny backend. Ny `computeStats()`-funksjon - regner ut ALT fra rå hull-data ved lesing (score-kategorier, - snitt-til-par totalt/per hulltype, fairway-fordeling + score-splitt, - GIR totalt/per hulltype/kryss-fairway + score-splitt, bom-retning på - green, putt-fordeling 1/2/3-putt + snitt per hulltype + med/uten GIR, - én-putt% per TeeCups egne seks puttlengde-bøtter + lengdefordeling - hit/miss, chip-fordeling, scrambling%, sand save%, bunker/straffeslag - per runde + score-splitt). Hver seksjon skjules helt når det ikke - finnes nok data (samme "vis kun det som faktisk finnes"-prinsipp som - "Så langt i runden"). Ny enkel spillervelger (pill-rad) lagt til når - runden har flere deltakere -- default til eieren. - **"Se full rundestatistikk"-lenke** lagt inn i `CompletedBanner` i - round-detail.tsx (V0s egen versjon hadde denne, men i sin ellers - fullstendig reverterte fil -- portert manuelt inn i vår LIVE versjon - i stedet for å ta hele filen). - **Matematikken verifisert FØR utrulling:** `computeStats()`-logikken - portert til et frittstående Node-script og kjørt mot et - hånd-etterregnet 6-hulls syntetisk datasett (blandet par 3/4/5, - fairwaytreff/-bom, GIR-treff/-bom, bunkerslag) -- alle 15+ utledede - tall stemte eksakt med manuell utregning. Ekte typesjekket - produksjonsbuild kjørt og bekreftet (`/my-rounds/[id]/stats` listet - som ny rute). - **Rullet ut live 2026-07-25**, ren frontend-endring, ingen migrasjon, - `docker compose up -d --build teecup_frontend`, `/health`/`/my-rounds` - → 200, `teeoff.no` upåvirket. -- **Rundeliste + scorekort-redesign, LIVE samme dag (2026-07-25):** - brukeren delte en ny skjermopptaksvideo av "Egne runder"-listen (som - manglet ALL score-informasjon) og av scorekort-registreringen (rapportert - som "veldig dårlig designet" -- for mange ulike knapp-typer stablet - oppå hverandre) + "Runde fullført"-siden (rapportert som "veldig mye - dobbel informasjon"). Reflektert over problemstillingen (kort, per - brukerens ønske) før to V0-prompter ble skrevet. - **Fikset direkte, uten V0:** "Runde fullført"-duplikatet -- `ScoreSoFar` - ("Så langt i runden") skjules nå helt når runden er fullført, siden - den nye rundestatistikk-siden dekker akkurat det samme, langt - grundigere. Ny backend-beregning i `_load_round_out` - (`app/routers/rounds.py`): `owner_holes_played`/`owner_total_score`/ - `owner_score_to_par`, en enkel aggregatspørring mot `round_hole` for - eierens egen deltaker-rad -- verifisert med 10 scratch-sjekker - (inkl. at en gjests score IKKE påvirker eierens aggregat). - **Zip 15 og 16 mottatt** (samme v0.app-prosjekt, kontinuerlig -- - `round-card.tsx`/`own-rounds.tsx` identiske i begge, zip 16s - `round-detail.tsx` var den nyeste med selve scorekort-redesignet; zip - 15 brukt kun for å bekrefte at zip 16 var det riktige, endelige - eksportet). - `round-card.tsx` fikk en ny `ScoreTile` -- prominent resultat+til-par - for fullførte runder, hull-fremdrift-bar ("6/18 hull spilt") for - runder som pågår, pluss en HCP-differensial-chip når runden telte. - Datalag i `own-rounds.tsx` skrevet om fra mock til ekte fetch, kobler - de nye `owner_*`-feltene fra backend + eierens `score_differential` - (kun vist når `counts_for_handicap`). - `round-detail.tsx` fikk en ny kollapsbar "Flere detaljer"-seksjon - (lukket som default) som nå rommer kølle/utslag-retning/innspill- - retning/chip-bunker-straffeslag/anywayslag -- Slag/Putter/Avstand - første putt forblir alltid synlig over. Datalag/statLevel-gating/ - bucket-basert puttlengde/maxValue-capping (alt bygget tidligere denne - økten) bevisst IKKE revertert til V0s eldre mock-baseline, kun selve - kollaps-mekanismen og plasseringen ble hentet derfra. Lagt til en kort - kode-kommentar (ikke noe bygget UI ennå) som reserverer plass ved - siden av hull-headeren til en fremtidig avstandsmåling-indikator, - bekreftet av bruker at dette kommer senere. - Ekte typesjekket produksjonsbuild kjørt og bekreftet (alle 19 ruter). - **Rullet ut live 2026-07-25**, ren frontend-endring (+ den lille - backend-tilføyelsen over), ingen migrasjon, `/health`/`/my-rounds` → - 200, `teeoff.no` upåvirket. -- **Reelt hull funnet og fikset SAMME dag, rapportert av bruker rett - etter forrige punkt ("Hvor er scorekortet?"):** da "Så langt i runden" - ble skjult for fullførte runder (se over), forsvant OGSÅ den eneste - plassen den rå hull-for-hull-tabellen (Hull/Par/Score/Netto/Sum) fantes - -- `round-stats.tsx` (den nye dedikerte statistikk-siden) hadde KUN - utledet/aggregert statistikk (donuter, stolper), ingen tabell med de - faktiske tallene per hull. Fikset ved å legge til en ny "Scorekort"- - seksjon FØRST på statistikk-siden (samme tabell-mønster som "Så - langt" hadde -- Hull/Par/Score/Netto/Stableford/Sum), åpen som - default. La til `start_hole` i `round-stats.tsx` sin `ApiRound`-type - og `strokes_received` i `ApiHole`-typen (sistnevnte kom allerede fra - backend, bare ikke lest av denne siden ennå) for å kunne vise hullene - i rundens FAKTISKE rekkefølge (samme sirkulære start_hole-logikk som - round-detail.tsx) i stedet for bare rå hullnummer 1-18. Ekte - typesjekket build kjørt og bekreftet. Rullet ut, ren frontend-endring, - ingen backend/migrasjon, `teeoff.no` upåvirket. -- **Ny scorekort-presentasjonsregel + V0-prompt skrevet, IKKE bygget - ennå (2026-07-25):** brukeren delte et referansebilde av et - tradisjonelt horisontalt golf-scorekort og formulerte en generell - regel: hull listet HORISONTALT (som kolonner) → sum TIL HØYRE; hull - listet VERTIKALT (som rader) → sum UNDER. Dagens "Scorekort"-tabell - (bygget rett over samme dag) er vertikal med sum som løpende KOLONNE - -- ikke i tråd med regelen. V0-prompt skrevet (horisontalt scorekort, - hull 1-9/10-18 som kolonner, Ut/Inn-sum til høyre for hver halvdel, - Hcp/Par/Score/Netto/Stableford-rader, håndterer både 9- og 18-hulls - runder med vilkårlig start_hole), bevisst IKKE et plagiat av - referansebildet (egne farger, egen "Hcp"-term i stedet for bildets - "Slope", ingen kopiert spiller-header). Full detalj i - ARCHITECTURE_DECISIONS.md. **Presisert samme dag, før noe ble sendt:** - brukeren spurte om et mykt "scroll hvis nødvendig"-unntak fanget opp - målet om ALDRI å måtte scrolle -- svart nei (reell breddekonflikt med - "lesbar uten briller"-kravet, ikke bare ordlyd) og skrevet om til et - hardt "ingen scroll"-krav som eksplisitt forteller V0 HVORDAN det - oppnås (kompakte fete høykontrast-siffer i selve rutenettet, smale - forkortede rad-labels i en trang gutter -- "lesbar uten briller" - avgrenset til labels/knapper, ikke enkeltsifre). Full detalj i - ARCHITECTURE_DECISIONS.md. - **Zip 17 mottatt og BYGGET/LIVE samme dag:** en ekte HTML `<table>` med - `<colgroup>` faste kolonnebredder -- ingen scroll-container i det hele - tatt, løst med kompakte celler i stedet. Score-cellene bruker FORM - (sirkel=under par, firkant=over par) + fylt/ufylt (2+ slag av) for å - aldri stole på farge alene, pluss en egen symbolforklaring. Egen, - NY dedikert side `/my-rounds/[id]/scorecard` - (`components/round-scorecard.tsx`) -- ikke slått sammen med - `round-stats.tsx`, siden V0 designet den med egen side-chrome (header, - rundesammendrag). Datalag skrevet om fra mock til ekte fetch; V0s egen - `strokesReceived()`-formel (generisk modulo) BEVISST forkastet til - fordel for backend sin allerede beregnede `strokes_received` (samme - `allocate_strokes_by_index()` som resten av appen -- unngår to ulike - HCP-slagfordelings-implementasjoner). Samme sirkulære - start_hole-rekkefølge som resten av rundeskjermene. Den gamle - vertikale "Scorekort"-tabellen i `round-stats.tsx` FJERNET (erstattet - med en lenke til den nye siden) -- `round-stats.tsx` er nå rendyrket - aggregert statistikk, det rå scorekortet bor kun ett sted. V0 la selv - til en "Se scorekort"-knapp i `CompletedBanner` (round-detail.tsx) ved - siden av den eksisterende "Se full rundestatistikk" -- tatt inn. - **Samtidig, rapportert av bruker:** Anywayslag manglet helt fra - rundestatistikk-siden sin "Chip, bunker og straffeslag"-seksjon. - **Feilrettet rett etterpå, samme dag:** min første fiks slo feilaktig - sammen Anywayslag-tallet i DEN eksisterende seksjonen og omdøpte hele - seksjonen til "Annet" -- brukeren påpekte at "Chip, bunker og - straffeslag" skulle beholde navn+innhold uendret, og at "Annet" skulle - være en EGEN, ny seksjon RETT ETTER med kun anywayslag-tall (total per - runde + andel hull med anywayslag). Rettet umiddelbart. Notert at et - fritekst-notatfelt trolig havner i "Annet" senere. Ekte typesjekket - build kjørt og bekreftet (ny rute `/my-rounds/[id]/scorecard` listet). - Rullet ut (to runder), ren frontend-endring, ingen backend/migrasjon, - `teeoff.no` upåvirket. -- **Rundestatistikk-seksjonene starter nå kollapset, LIVE samme dag - (2026-07-25):** brukeren ba om at "trekkspillet" (StatCard-seksjonene - på `/my-rounds/[id]/stats`) skal vises sammenslått -- må klikkes for å - se innholdet. `StatCard` sin `defaultOpen` endret fra `true` til - `false` (ingen kallsted overstyrte den, så én linje dekket alle - seksjonene). Ekte typesjekket build, rullet ut, `teeoff.no` upåvirket. -- **Notert i FEATURE_BACKLOG.md, IKKE bygget:** brukeren ba om et notat - om et femte fremtidig turneringsformat, "Flaggturnering" (utover de - fire fra 2026-07-19-runden) -- krever en visning av GJENSTÅENDE slag - for spilleren, oppdatert etter hvert hull, pluss en fremtidig idé om - å bruke GPS (når/hvis integrert) til å markere hvor langt spilleren - faktisk kom. Lagt til i samme seksjon som de fire andre formatene. - -- **Tre punkter fra brukeren, ALLE BYGGET, SCRATCH-VERIFISERT OG LIVE - 2026-07-25:** (1) enkeltbane-anlegg (f.eks. Tjøme Golfklubb) dropper nå - det overflødige banenavnet ved import/runde-opprettelse — kun "Tjøme - Golfklubb", ikke "Tjøme Golfklubb – Hovedbanen" (flerbane-anlegg som - Ålesund beholder uendret "Anlegg – Bane"-navn). (2) Runder kan nå - navngis (`round.name`, migrasjon `024_round_name.sql`, valgfritt, - redigerbart i etterkant) — vises på tvers av rundeliste/rundeside/ - scorekort/statistikk, faller tilbake til banenavn når ikke satt. (3) - Profilens "Land"-felt er nå en nedtrekksliste (kun "Norge" foreløpig, - klargjort for flere), plassert FØR "Hjemmeklubb", som selv ble - omgjort til en søkbar liste mot teeoffs klubbregister (gjenbruker - eksisterende `/rounds/official-search`, ingen ny backend-kode). Full - detalj i ARCHITECTURE_DECISIONS.md. Bruker bekreftet eksplisitt: - migrasjon 024 kjørt mot ekte `teecup_db`, `test_isolation.sql` fortsatt - 12/12, begge containere redeployet, `/health`/`/dashboard`/`/my-rounds`/ - `/account` → 200, `teeoff.no` upåvirket. - -- **Refleksjonsrunde 2026-07-25: hvorfor organisasjon? + venner/deling — - DESIGNET, IKKE bygget.** Brukeren spurte hvorfor organisasjon i det - hele tatt trengs, gitt at ADR-033 nå gjør en enkelt bruker fullt - selvstendig. Veide A (bruker-eide turneringer, som runder) mot B - (organisasjon beholdt, men opprettelsen gjøres usynlig/automatisk) — - A avvist (ville krevd duplisering av HELE turnering-apparatet som er - bygget rundt RLS/`organization_id`, og er ikke reversibelt i noen - retning), B valgt (rører verken skjema eller de ni eksisterende - routerne, kun frontend-orkestrering — `POST /orgs` krever allerede kun - `name`). Skrevet som **ADR-035** (organisasjon-B-beslutningen + et - konkret 7-blokks dashbord-forslag: Hurtighandlinger/Kommende runder/ - Kommende turneringer/Statistikk/Spilte baner/Venner/Organisasjoner — - sistnevnte nedtonet, kun synlig ved reelt flere org-er) og **ADR-036** - (nytt vennekonsept — gjensidig forespørsel/aksept, privat - kategorisering i faste grupper som Make/Nær familie/Golfvenner/osv., - ny rundevisibilitet `public`/`private`/`friends` med eksplisitt - gruppevalg, og et tiered personsøk — venner→samme klubb→samme - land→globalt, navnerekkefølge-uavhengig — delt mellom "finn venn" og - en fremtidig "legg til ekte medspiller"-utvidelse av - `round_participant.user_id`, som allerede lå ubrukt i skjemaet som en - eksplisitt notert "v1-avgrensning" i ADR-033). V0-prompt for det nye - dashbordet skrevet (i FEATURE_BACKLOG.md), IKKE sendt til V0 ennå. - **Ingen kode skrevet** — bevisst en design-/dokumentasjonsrunde, ikke - en byggerunde, på brukerens eksplisitte instruks. Full detalj i - ARCHITECTURE_DECISIONS.md (ADR-035/036) og FEATURE_BACKLOG.md (åpne - spørsmål, foreslått 3-fase byggerekkefølge for venner-delen). -- **Oppfølging samme dag:** bruker bekreftet at en lagt-til ekte - medspiller SKAL se runden i sin egen "Egne runder"-liste (ADR-036 fase - 3, tidligere bevisst uavklart). Presiserte samtidig en ny teknisk - konsekvens i ARCHITECTURE_DECISIONS.md: `RoundOut` sine - `owner_*`-statistikkfelt må bli viewer-relative, ikke alltid eierens, - og et NYTT åpent spørsmål dukket opp — skal medspilleren også kunne - SKRIVE egne hull-tall, ikke bare lese? Ikke avgjort. Fortsatt ingen - kode skrevet. - -- **Dashbord-redesign (ADR-035) BYGGET, IKKE ENNÅ RULLET UT (2026-07-25):** - zip 18 mottatt og integrert — kun `dashboard.tsx` var reelt nytt (samme - full-reeksport-mønster som alltid), pluss en genuin MERGE (ikke revert) - i `tournament-card.tsx`: V0s nye `organizer`-merkelapp lagt til SIDE OM - SIDE med den eksisterende `orgId`-baserte lenkelogikken (offentlig vs. - innlogget kontekst) som V0 ikke kjente til. Datalag skrevet fra bunnen: - "Kommende turneringer" slår sammen deltaker- (`me.my_tournaments`) og - arrangør-turneringer (hentet per org, alle organisasjoner brukeren er - medlem i) til ÉN tidssortert liste, filtrert til `draft`/`active`-status. - "Statistikk"/"Spilte baner" er REN klientside-utledning fra eksisterende - `GET /rounds` + `GET /auth/profile/handicap-history` — ingen nye - endepunkter trengt. "Ny turnering"-hurtighandlingen implementerer selve - ADR-035-mekanismen: oppretter/gjenbruker organisasjon USYNLIG først - (kun ved faktisk innsending, ikke når skjemaet åpnes — unngår en - foreldreløs org ved avbrutt skjema), deretter turneringen, deretter - navigerer rett inn i den. "Bli med med kode" gjenbruker samme - `by-code`-oppslag som `login-form.tsx` sin `JoinByCode`, nå som en - dashbord-lokal variant. "Venner"-seksjonen vises med en ærlig - tom-tilstand (ADR-036 er ikke bygget ennå) — CTA-en er bevisst uten - href/onClick, ikke en lenke til noe som ikke finnes. "Spilte baner" - fikk sine listeelementer gjort om til ren visning (ikke lenker) siden - API-et ikke eksponerer noen stabil bane-id å lenke til per runde. - Ekte typesjekket produksjonsbuild kompilerte rent, alle 21 ruter - listet. **Rullet ut live 2026-07-25**, bruker bekreftet eksplisitt: - `docker compose up -d --build teecup_frontend` (gjenskapte også - `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge - containere boot-et rent, `/health`/`/dashboard`/`/my-rounds` → 200, - `teeoff.no` upåvirket. -- **Oppfølging samme runde, avklart med bruker:** medspillere skal kunne - registrere score for HELE flighten (ikke bare egen rad) når ADR-036 - fase 3 bygges — oppdatert i ARCHITECTURE_DECISIONS.md/FEATURE_BACKLOG.md. - To nye notater lagt i FEATURE_BACKLOG.md (ingen design/bygging ennå, - kun fanget opp): (1) turneringsoppsett mangler en vei til å FLYTTE en - rostret spiller til et annet lag (kun fjerning finnes i dag), (2) et - reelt modelleringsspørsmål om flere flighter i én frittstående runde - (f.eks. "min flight + vennenes flight bak oss samme dag") — to - prinsipielt ulike retninger skissert, ingen valgt. - -- **ADR-036 fase 1 (venner-kjernen) BYGGET OG SCRATCH-VERIFISERT, IKKE - ENNÅ RULLET UT (2026-07-25):** ny migrasjon `025_friends.sql` - (`friendship` + `friend_categorization`, ingen RLS — samme - `plain_connection()`-mønster som runder) og nytt `app/routers/ - friends.py`: `GET /people/search` (tiered venn→klubb→land→globalt, - navnerekkefølge-uavhengig token-matching, min. 2 tegn før noe - returneres), `POST /friends`/`POST /friends/{id}/accept`/ - `DELETE /friends/{id}` (forespørsel/aksept/avslå-kanseller-avvenn — én - DELETE dekker alle tre), `GET /friends`, `PUT /friends/{friend_user_id}/ - categories` (erstatter hele settet, krever akseptert vennskap). Router - registrert i `main.py`, `/people`+`/friends`-rewrites lagt til i - `next.config.mjs` (ny frontend-rute vil hete `/my-friends`, IKKE - `/friends` — unngår samme kollisjonsfelle som `/rounds` fra før). - **Scratch-verifisert grundig** (isolert rolle+MinIO+engangs API- - container, 5 syntetiske brukere): 31 sjekker, inkl. presist bevist - tiered rangering for 4 distinkte brukere samtidig, og at - kategorisering er EKTE PRIVAT (bekreftet: B ser aldri kategoriene A - satte B i). `test_isolation.sql` fortsatt 12/12. Ekte typesjekket - produksjonsbuild kompilerte rent. **Rullet ut live 2026-07-25**, bruker - bekreftet eksplisitt: migrasjon 025 kjørt mot ekte `teecup_db` (begge - tabeller 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. Verifisert presist at ruten faktisk når FastAPI (ikke bare - at Next.js svarte): anonymt `GET /people/search`/`GET /friends` over - ekte https ga korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404. Frontend - bevisst IKKE hånd-kodet denne gangen (V0-prompt skrevet i - FEATURE_BACKLOG.md i stedet, matcher etablert mønster/tidligere - korrigering) — venter på at brukeren kjører den i v0.app. - -- **ADR-036 fase 1 FRONTEND BYGGET, IKKE ENNÅ RULLET UT (2026-07-25):** - zip 19 mottatt og integrert — kun `components/friends.tsx` og - `app/friends/page.tsx` var reelt nye fra V0 (samme full-reeksport- - mønster som alltid, resten forventede reverts av allerede tilpassede - filer, hoppet over). **V0s egen rute (`/friends`) BEVISST IKKE brukt** - — flyttet til `/my-friends`, siden `/friends` nå er API-prefikset - (samme kollisjonsklasse som `/rounds` → `/my-rounds` tidligere, unngått - fra start denne gangen i stedet for oppdaget i produksjon). - Datalag skrevet fullstendig om fra V0s mock til ekte fetch mot - `/people/search`+`/friends`-endepunktene — TS-typene speiler Pydantic- - modellene i `app/routers/friends.py` felt-for-felt. Kategori-koder - (`spouse`, `golf_friends`, osv.) mappet mot V0s norske visningsnavn via - en delt `CATEGORY_OPTIONS`-liste i SAMME rekkefølge som backend sin - `Category`-type. Søkefeltet håndhever samme 2-tegns-minimum som - backend (viser en forklarende tekst i stedet for å bare returnere - tomt). "Send forespørsel"/"Godta"/"Avslå"/"Kanseller"/"Fjern venn" - trigger alle en full refetch av `GET /friends` etterpå (samme - refetch-etter-mutasjon-mønster som resten av appen) — kun kategori- - avkrysning er lokalt optimistisk (matcher PUT-endepunktets - erstatt-hele-settet-kontrakt). - **Dashbordets "Venner"-blokk koblet til ekte data i samme runde:** - root-komponenten henter nå `GET /friends`, viser ekte avatar-initialer - + antall venner + ventende forespørsler (samme visuelle design som - opprinnelig i zip 18, som ble bevisst forenklet til en inert tom- - tilstand forrige runde siden backend ikke fantes ennå) — begge - knappene ("Se venner"/"Søk etter venner") lenker nå til `/my-friends`. - Ekte typesjekket produksjonsbuild kompilerte rent, `/my-friends` listet - blant 22 ruter. **Rullet ut live 2026-07-25**, bruker bekreftet - eksplisitt: `docker compose up -d --build teecup_frontend` (gjenskapte - også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). - Begge containere boot-et rent, `/health`/`/dashboard`/`/my-friends` → - 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. - -- **Oppfølging samme dag: e-post-fallback for varsler + PWA- - installasjon vurdert, IKKE bygget.** Varsel-V0-prompten sendt til - bruker. Bruker foreslo en betinget e-post-fallback ved venneforespørsel - (kun hvis mottaker har samtykket til e-post fra TeeCup) — sjekket at - INGEN generell kommunikasjons-samtykke-flagg finnes i dag (kun - ADR-017s turnering-registrerings-samtykke, noe annet), foreslo nytt - opt-in `app_user.notification_emails_enabled` + en sjette - `send_friend_request_email()`-funksjon i `app/email.py` (samme mønster - som de fem eksisterende). Bruker spurte også hvordan få brukere til å - installere PWA-en "nærmest umiddelbart" — vurdert grundig: Android kan - fange `beforeinstallprompt` og vise egen timing, iOS Safari har INGEN - programmatisk installasjonsvei (kun instruksjonsoverlegg mulig, hard - Apple-begrensning) — anbefalte å vise oppfordringen rett etter - obligatorisk profil-fullføring (universelt sjekkpunkt alle nye brukere - allerede går gjennom) fremfor bokstavelig "umiddelbart". Begge kun - vurdert/skissert i FEATURE_BACKLOG.md, ingen kode skrevet, intet - V0-prompt for PWA-delen ennå. - -- **To nye drøftingspunkter, IKKE besluttet eller bygget (2026-07-25):** - bruker lastet opp to skjermbilder av Golf GameBooks leaderboard-løsning - (egen "Leaderboards"-fane, rangert liste, trykk-ut til fullt - scorekort) og spurte om (1) et tilsvarende leaderboard for - runder/turneringer, usikker på plassering, og (2) om TeeCup burde få - 1-2 nye designfarger. Drøftet grundig i FEATURE_BACKLOG.md, ikke - konkludert: fant at "runder" kan få dette NÅ (flere-deltakere-støtte - finnes allerede, ingen ADR-036-avhengighet) mens "turneringer" sitt - EKSISTERENDE leaderboard er lag-poeng (Ryder Cup-matchplay), strukturelt - noe annet enn Golf GameBooks individuelle rangering — åpent - avklaringsspørsmål. For fargespørsmålet: fant at en blåtone ALLEREDE - finnes i `--chart-3` (bare ikke løftet til en kjerne-designtoken) — - anbefalte å gjenbruke DEN i stedet for å finne på en helt ny, og - anbefalte mot flere enn én ekstra farge. Ingen kode skrevet, ingen - V0-prompt — ren refleksjonsrunde på brukerens eksplisitte instruks. - -- **In-app varslingssenter + rundeleaderboard-backend BYGGET OG SCRATCH- - VERIFISERT (2026-07-26), IKKE ENNÅ RULLET UT:** to ting i samme runde. - (1) Brukeren lastet opp zip 20 — resultatet av varsel-V0-prompten fra - 2026-07-25 (bjelle-ikon i dashbord-header, egen varslingsside). Bygget - matchende backend: migrasjon `026_notifications.sql` (tabell - `notification`, `plain_connection()`-mønster som resten av bruker-eid - data), `app/routers/notifications.py` (fire endepunkter + delt - `create_notification()`-hjelpefunksjon), to trigger-punkter i - `friends.py` (venneforespørsel sendt/akseptert — de eneste hendelsene - som faktisk finnes i dag). Frontend integrert kirurgisk (kun de nye - filene + et uttrekk fra V0s dashbord-eksport, ikke en full revert): - `components/notifications.tsx` skrevet om fra V0s mock-scenario til - ekte fetch, ny rute `/my-notifications` (IKKE `/notifications` — samme - kollisjonsklasse unngått fra start som `/rounds`/`/friends`), - bjelle-komponenten portert inn i den LIVE `dashboard.tsx` sin header, - koblet til et ekte `GET /notifications/unread-count`-kall. - (2) Brukeren ba samtidig om et leaderboard for frittstående runder - (opptil 13+ spillere mulig, flere flighter). Bygget ny - `GET /rounds/{round_id}/leaderboard` i `app/routers/rounds.py` — brutto - OG netto score-til-par + "thru"-antall PER deltaker, sortert stigende - på brutto. Netto bruker samme `allocate_strokes_by_index`-algoritme som - resten av appen (`strokes_received` i `list_holes`), denne gangen - summert over KUN de faktisk spilte hullene. - **Scratch-verifisert grundig, 39/39 sjekker i to testløp** (isolert - `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API- - container, samme mønster som hele prosjektet): full varsel-syklus begge - retninger + `mark-all-read` + kryss-bruker-isolasjon (bruker B kan ikke - markere bruker A sitt varsel som lest via id-gjetting), full - leaderboard-runde (3 deltakere, ulik fremdrift/score, riktig rangering/ - thru/to-par for alle), kryss-bruker-autorisasjon (403), ukjent - runde-id (404) — OG en dedikert netto-kryssjekk der resultatet ble - sammenlignet mot en HELT UAVHENGIG beregning via - `handicap_engine.allocate_strokes_by_index` kalt direkte fra - testskriptet (ikke bare "endepunktet svarte 200") — stemte eksakt, - inkl. et bevisst vekslende stroke-index-mønster og kun 9 av 18 hull - spilt, for å teste allokeringen over et REELT delvis spilt sett, ikke - et trivielt sammenfallende tilfelle. `test_isolation.sql` fortsatt - 12/12. Ekte typesjekket produksjonsbuild av frontend kompilerte rent, - `/my-notifications` listet blant rutene. - **V0-prompt for selve leaderboard-VISNINGEN skrevet og sendt til - bruker** (se FEATURE_BACKLOG.md for hele prompten) — rangering med - delt plassering, brutto/netto-veksling, "thru X"/"Ferdig"-tilstander, - kompakt mini-variant til rundens detaljside, alltid form+farge for - over/under par. Ikke kjørt i v0.app ennå — leaderboardets FRONTEND - kommer i en senere runde. - **Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: migrasjon - 026 kjørt mot ekte `teecup_db` (tabell `notification` bekreftet, - `test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d - --build teecup_api teecup_frontend`. Begge containere boot-et rent, - `/health`/`/dashboard`/`/my-notifications` → 200, `teeoff.no` - upåvirket. Verifisert presist at `/notifications`-ruten faktisk når - FastAPI (ikke bare at Next.js svarte): anonymt `GET /notifications` - over ekte https ga korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404. - -- **Navneformat-regel + turnering-scope avklart, IKKE bygget (2026-07-26):** - brukeren instruerte at direkte adressering (hilsener, e-post/varsler - rettet til mottakeren) alltid skal bruke KUN fornavn, mens vanlige - visninger (lister, roster, chat) fortsatt bruker fullt navn/initialer — - lagt til som ny stående regel (se egen seksjon over). Kjent brudd - notert: dashbord-hilsenen bruker i dag fullt navn, ikke rettet ennå. - Deretter bedt om analyse av .md-filene for prioritering — landet på å - avklare turnering-leaderboard-spørsmålet fra 2026-07-25 først. Svaret - avdekket et vesentlig STØRRE ønske enn antatt: TeeCup skal etter hvert - støtte ekte INDIVIDUELLE turneringer (ikke bare lag), disse skal kunne - gå over flere runder, og det skal være mulig med et Order of Merit - (sesong-sammenlagt på tvers av flere arrangementer, brukerens eksempel: - "klubbdager" gjennom en sesong). Grundig notert i FEATURE_BACKLOG.md og - ARCHITECTURE_DECISIONS.md (ADR-011s eget notat + ny "Åpne spørsmål"- - post 6) — inkl. en reell strukturell kollisjon identifisert: en - flerrunde-individuell-turnering vil kollidere med ADR-033 sitt bevisst - org-UAVHENGIGE frittstående rundesystem, må avklares eksplisitt før - bygging. **Ren notat-runde, ingen ADR skrevet, ingen kode** — brukeren - ba eksplisitt kun om at dette dokumenteres nå. -- **Flere flighter i én frittstående runde — presisert videre, fortsatt - IKKE besluttet (2026-07-26):** oppfølgende avklaring samme runde. - Brukeren presiserte: oppsett av flere flighter er ÉN handling utført av - én person (samme gjest-mønster som i dag, bare flere flight-grupper), - scoreregistrering per flight er et SENERE, separat ansvar (forventet - løst av ekte medspillere, ADR-036 fase 3) — og viktigst: leaderboardet - skal KUN dekke flightene som ble satt opp SAMMEN i én handling, ikke - "alle som spilte samme bane samme dag". Det siste peker sterkt mot - retning 1 (løs gruppering av separate `round`-rader) fra forrige - runde, men er ikke formelt bekreftet som byggeretning. Brukeren - påpekte selv at dette ligger i grenselandet mot "individuelle - turneringer"-punktet rett over — notert eksplisitt som en mulig - forening av de to, ikke to separate systemer. Se FEATURE_BACKLOG.md - for full detalj. Ren notat-runde, ingen kode, ingen ADR. - -- **ADR-037 skrevet: grunnstruktur for individuelle/flerrunde-turneringer - (2026-07-26), samme dag som de to foregående notat-rundene.** Bruker ba - om å starte ADR-runden på strukturspørsmålet direkte (ikke bare - notere). Fire load-bærende beslutninger avklart eksplisitt - (AskUserQuestion), i rekkefølge: (1) ny, PARALLELL org-scopet - datamodell for individuelle turneringer — IKKE en utvidelse av - ADR-033s frittstående `round`-tabeller, bevisst for å unngå hybrid/ - betinget RLS (samme risikoklasse som den tidligere RLS-tomstreng- - bugen) og for å bevare ADR-033 Beslutning A urørt; (2) SAMME - `tournament`-tabell med en ny `format_type`-diskriminator - (`'team'`/`'individual'`), ikke en helt ny toppnivå-entitet — - gjenbruker synlighet/join-kode/status (ADR-018/020) uendret; (3) - flerrunde-støtte fra START via en ny `tournament_round`-tabell - (økt-lignende), sammenlagt resultat summert ved lesing på ekte - deltaker-id (samme prinsipp som dagens lag-leaderboard); (4) rå - brutto slagtall lagres OG et ferdig utregnet poengtall CACHES per - scoringsmetode — samme etablerte mønster som `match.status_text`/ - `points_side_a/b` — nye formater (Københavner m.fl.) blir dermed i - hovedsak én ny motorfunksjon + én ny CHECK-verdi, ikke en - skjemaendring. - **Viktig presisering som oppsto underveis, endrer forrige rundes - antakelse:** "flight" i en formell org-turnering er KUN en - tee-tid-gruppering — leaderboardet spenner alltid HELE feltet. - Dette er strukturelt ULIKT den ad hoc "flere flighter i en - frittstående runde"-ideen (der leaderboardet bevisst avgrenses til - det man selv satte opp) — de to holdes derfor bevisst ADSKILT, ikke - forent slik forrige runde antydet. Begge berørte FEATURE_BACKLOG.md- - seksjoner oppdatert med denne presiseringen. - **Fortsatt IKKE avgjort:** de fem konkrete formatenes egne poengregler - (Københavner m.fl., egne mindre design-runder oppå denne strukturen), - og Order of Merit (sesong-sammenlagt, bekreftet som naturlig SISTE - steg). **Ingen migrasjon eller kode skrevet** — ADR-037 er ren - struktur-beslutning; neste steg er et konkret migrasjonsutkast - (nye tabeller + `tournament.format_type`) lagt frem til gjennomgang - før noe kjøres. - -- **"Fiks alle kjente små bugs"-runde, BEGGE FIKSET OG SCRATCH-VERIFISERT - (2026-07-26), samme dag som ADR-037:** brukeren ba eksplisitt om å - rydde opp i alle dokumenterte, fortsatt åpne små bugs. Systematisk - gjennomgang av CLAUDE.md/FEATURE_BACKLOG.md (søk på "IKKE fikset"/ - "urelatert bug" m.fl.) fant at nesten alt tidligere flagget som - "ikke fikset" faktisk ble fikset i en SENERE runde lenger ned i loggen - (stale seksjonsoverskrifter, ikke reelt åpne bugs) — kun to genuint - fortsatt åpne: - 1. **Dashbord-hilsen brukte fullt navn** (nettopp etablert - navneformat-regel, se egen seksjon over) — `me.first_name ?? - me.display_name` i stedet for `me.display_name` alene. - `/auth/me` eksponerte allerede `first_name`, ingen backend-endring. - 2. **Stroke-modus-scoreinnsending på en bane UTEN registrerte hull - krasjet rått (500 IndexError)** i stedet for en ren - `VALIDATION_FAILED` — `scoring.py` sin `_compute_hole_results` - kalte `allocate_over_played_holes` med en TOM stroke-indeks-liste - når `hole`-tabellen var tom for banen (f.eks. en egendefinert bane - der noen glemte å legge til de 18 hullene). Fikset med en eksplisitt - `len(all_18_si) != 18`-sjekk RETT FØR beregningen — samme mønster - som den allerede kjente/fikset manglende-handicap-indeks-krasjen - fra blind draw-runden (2026-07-18). - **Scratch-verifisert grundig (5/5 sjekker)** for backend-fiksen - (isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs - API-container, samme mønster som ellers i prosjektet): en bane UTEN - hull ga korrekt `400 VALIDATION_FAILED` ved scoreinnsending (ikke - 500), OG en egen regresjonssjekk bekreftet at en NORMAL bane (alle 18 - hull) fortsatt scorer helt uendret (begge sider, full - scorekort-henting via `GET .../scorecard`). `test_isolation.sql` - 12/12 uendret — ren Python-logikk-fiks, ingen migrasjon. Ekte - typesjekket produksjonsbuild av frontend kompilerte rent (dashbord- - fiksen). To stale FEATURE_BACKLOG.md-seksjonsoverskrifter rettet - samtidig ("Rapporterte UI-/UX-hull" og selve bug-notatet), siden de - fortsatt sa "IKKE fikset" lenge etter at innholdet faktisk var - fikset. - **Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: ingen - migrasjon, `docker compose up -d --build teecup_api teecup_frontend`, - begge containere boot-et rent (`Application startup complete`, Next.js - `Ready`), `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. - -- **ADR-036 fase 3 (delvis): søk+legg til ekte medspiller på en - frittstående runde, BYGGET OG SCRATCH-VERIFISERT (2026-07-26), IKKE - ENNÅ RULLET UT:** brukeren rapporterte at "+ Gjest"-skjemaet ikke - søkte etter spillere når man skrev et navn — bekreftet reelt (rent - tekstfelt, `/people/search` var aldri koblet på). Spurte om - omfang før bygging: brukeren ville ha "full tilgang nå" (medspilleren - kan selv registrere score), ikke bare rask utfylling — et bevisst - større valg enn det anbefalte minimum. - **Backend:** `POST /rounds/{id}/participants` tar nå ENTEN `user_id` - (funnet via tiered `/people/search`, samme algoritme som vennesøket) - ELLER `guest_name` (uendret) — kjønn/HCP hentes automatisk fra den - valgte personens egen profil. Ny migrasjon `027_round_participant_ - user_unique.sql` (partiell unik indeks). Ny `_get_accessible_round_ - or_404` (eier ELLER lenket medspiller) 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 (regnes nå - fra den spørrende brukerens egen deltaker-rad). - **Reell latent bug funnet OG fikset i SAMME runde, før den nådde - produksjon:** leaderboard-endepunktet (bygget tidligere samme dag, - se over) ville vist et TOMT navn for enhver lenket medspiller, siden - dets `display_name`-utledning kun sjekket `is_owner`, ikke om - `user_id` var satt i det hele tatt. Fanget under scratch-testing av - DENNE runden, fikset før utrulling av noen av delene. - **Frontend:** "+ Gjest" omdøpt til "+ Medspiller" i `round-detail.tsx`, - nytt søk-som-du-skriver-felt (avatar-initialer, hjemmeklubb) med - "Legg til uten konto"-fallback til det gamle tekstfeltet. Viewer- - relativ "Deg"-visning (ny `/auth/me`-bruk for viewerId) — samme fiks - portert til `round-stats.tsx`/`round-scorecard.tsx`, som hadde - identisk latent bug (ville vist eieren som "Deg" for en medspiller - som så på). Rediger/slett/legg-til/fjern-knappene skjules nå for en - ikke-eier-viewer. - **Scratch-verifisert grundig, 39/39 sjekker i to testløp:** søk-og- - legg-til med auto-utfylt kjønn/HCP, avvist duplikat/selv-tillegg/ - ukjent bruker/ufullstendig profil, lenket medspiller kan lese runden - + registrere score for BÅDE egen OG andres rad (whole-flight- - regelen bekreftet), men nektes å forvalte runden (alle - forvaltningskall 403), lenket medspiller KAN fullføre runden, - `/rounds`-listen viser nå runden for en lenket medspiller med DERES - EGEN fremdrift (ikke eierens), urelatert bruker fortsatt 403/ikke i - listen, leaderboard-navn stemmer for alle tre deltakertyper. - `test_isolation.sql` 12/12 uendret. Ekte typesjekket produksjonsbuild - kompilerte rent. - **Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: migrasjon - 027 kjørt mot ekte `teecup_db` (unik indeks bekreftet, - `test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d - --build teecup_api teecup_frontend`. Begge containere boot-et rent, - `/health`/`/dashboard`/`/my-rounds` → 200, `teeoff.no` upåvirket. - -- **Sanntid for frittstående runder, BYGGET OG SCRATCH-VERIFISERT - (2026-07-26), samme dag som medspiller-søket over:** brukeren spurte om - spillere ser i sanntid at en annen har registrert en score — svaret var - nei (kun engangs-henting ved lasting), og brukeren ba om at det bygges, - gjenbruk av det etablerte "noe endret seg, hent på nytt"-WebSocket- - mønsteret fra ADR-027 ("Følg live" for turneringer). - `app/realtime.py` utvidet (fortsatt rutefri, se moduldoc) med en andre, - parallell kringkastings-registry for runder (`live_sockets_for_round`/ - `broadcast_round_update`, delt `_broadcast()`-hjelpefunksjon for å unngå - duplisert kringkastingslogikk). Nytt `@router.websocket("/ws/rounds/ - {round_id}/live")` i `rounds.py` — ALDRI anonym tilgang (ulikt - turnering-live), krever eier ELLER lenket medspiller - (`_get_accessible_round_or_404`, samme sjekk som resten av - medspiller-utvidelsen). Kringkasting lagt inn i `update_hole`, - `complete_round`, `add_participant`, `remove_guest_participant`, - `update_round` og `delete_round` — alt som endrer noe de andre på - skjermen bør få vite om. Ingen ny Caddy-endring nødvendig (`/ws/*` er - allerede en wildcard-rute fra ADR-025). - **Reelt funn under scratch-testing, ikke en bug, men verdt å dokumentere:** - Starlette avviser en WebSocket FØR `.accept()` alltid som en bar HTTP - 403 under selve håndtrykket — de tre distinkte lukkekodene (4401/4403/ - 4404) jeg satte når til `websocket.close()` server-side, men skiller seg - IKKE fra hverandre i klientens håndtrykk-avvisning (alle tre ga HTTP 403 - i en ekte WS-klienttest, ikke bare curl). Selve sikkerheten (tilkobling - korrekt avvist i alle tre tilfeller) er upåvirket — samme underliggende - Starlette-oppførsel gjelder trolig også den eksisterende turnering-live- - ruten (ADR-027), bare ikke tidligere testet med en ekte WS-klient på - dette presisjonsnivået. - **Frontend:** `round-detail.tsx` åpner en WebSocket ved montering, - refetcher runden ved signal og henter aktiv spillers hull DIREKTE på - nytt (ingen mellomsteg med tom stat som ville blinket for spilleren som - selv nettopp registrerte et slag) — andre spilleres hull-cache droppes - i stedet, hentes friskt ved neste fanebytte. Bruker en ref for å unngå - at socket-en kobles til/fra ved hvert fanebytte. `round-stats.tsx`/ - `round-scorecard.tsx` (rene lesevisninger) fikk en enklere - `refreshKey`-basert variant, samme mønster som `public-live.tsx` fra - ADR-027. - **Scratch-verifisert grundig, 10/10 sjekker, ekte WebSocket-klient (ikke - bare REST):** ekte kringkasting bekreftet begge veier — eierens åpne - socket mottok et signal da medspilleren registrerte et slag via REST, - OG medspillerens åpne socket mottok et signal da eieren fullførte runden - — ikke bare at REST-svarene så riktige ut. Alle tre avvisningstilfellene - (uautentisert, urelatert fremmed, ukjent runde-id) korrekt avvist. - `test_isolation.sql` 12/12 uendret (ingen migrasjon). Ekte typesjekket - produksjonsbuild kompilerte rent. - **Rullet ut live 2026-07-26**, 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. Verifisert presist at `/ws/rounds/*`-ruten faktisk når - FastAPI: et ekte WS-håndtrykk-forsøk mot en ukjent runde-id over - produksjons-https ga FastAPI sin egen JSON-`{"detail":"Not Found"}`, - ikke Next.js sin HTML-404 — samme verifiseringsmønster som ADR-027s - tournament-live-rute. -- **HCP i medspiller-søk + rediger utslag/HCP per deltaker, BYGGET OG - SCRATCH-VERIFISERT (2026-07-26), IKKE ENNÅ RULLET UT:** brukeren viste et - skjermbilde av "+ Medspiller"-søket og påpekte at spillerens HCP burde - vises der (kun navn+klubb vistes), og ba samtidig om å kunne endre - utslagssted og HCP for medspillere -- "det er ikke sikkert de spiller fra - samme utslagssted som meg, og det er ikke sikkert HCP er riktig", eksplisitt - presisert som en endring KUN for denne runden/turneringen, ikke spillerens - faktiske profil. - **Reelt hull bekreftet ved kodegjennomgang FØR bygging:** ALLE deltakere - på en runde har i dag brukt SAMME `round.tee_name_snapshot` -- ingen - per-deltaker-tee har noensinne eksistert (rating-tallene har vært per - deltaker siden migrasjon 021, men selve utslags-NAVNET var aldri - eksplisitt per rad). - **Bygget:** `PersonMatch` (`app/routers/friends.py`, delt av vennesøk OG - medspiller-søk) fikk `handicap_index: float | None`. Ny migrasjon - `028_round_participant_tee.sql` -- `round_participant.tee_name_snapshot`, - backfylt fra rundens eksisterende tee for alle eksisterende rader. `_create_ - participant` skriver nå tee-navnet eksplisitt; `ParticipantCreate` (add_ - participant) fikk et valgfritt `tee_name` som faller tilbake til rundens - tee hvis utelatt. Ny `GET /rounds/{id}/tee-options` (gjenbruker - `_ResolvedCourse` sin rating-dict via en ny `tee_options()`-metode) -- - brukt av "rediger spiller"-panelet til å liste banens faktiske utslag. - `ParticipantUpdate` skrevet om til vanlig `exclude_unset`-PATCH-semantikk - (var tidligere kun `stat_level`, alltid påkrevd) -- nye valgfrie - `tee_name`/`handicap_index`-felt trigger en full re-beregning av - rating-snapshottene + `course_handicap_snapshot` for AKKURAT den - deltakeren, uten å røre spillerens egen `app_user.handicap_index`. - Eksplisitt `null` på `handicap_index` fjerner HCP-sporing for denne - deltakeren i denne runden (samme mønster som profil-PATCH). **Bevisst - avvist etter at runden er fullført** (409 `ALREADY_COMPLETED`, samme - presedens som `RoundUpdate` sitt bane-bytte -- differensialen er da - allerede beregnet fra det gamle grunnlaget); `stat_level` alene er - fortsatt tillatt uansett fullført-status. `RoundUpdate` sitt eksisterende - bane-bytte (ADR-033-oppfølging 2026-07-24) nullstiller nå eksplisitt ALLE - deltakeres `tee_name_snapshot` til rundens nye utslag (et bane-bytte gjør - individuelle tee-valg fra den gamle banen meningsløse). - **Frontend (`round-detail.tsx`):** søkeresultatene i "+ Medspiller" viser - nå HCP ved siden av hjemmeklubb. Ny "Utslag: X · HCP: Y"-rad under - statistikknivå-velgeren for aktiv spiller, med en "Rediger for denne - runden"-lenke (kun eier, kun før fullført) som åpner en ny - `EditParticipantPanel` -- utslagssted som en `<select>` fylt fra det nye - tee-options-endepunktet (filtrert på spillerens kjønn), HCP som fritekst, - forklarende "endrer ikke profilen"-tekst. Gjelder likt for eierens egen - rad som for medspillere (ingen spesialtilfelle). - **Scratch-verifisert, 27/27 sjekker** (isolert `teecup_app_scratch`- - rolle + isolert scratch-MinIO + engangs API-container): HCP i søk, legg - til medspiller med eksplisitt AVVIKENDE utslag fra eieren, gjest uten - eksplisitt tee faller korrekt tilbake til rundens tee, utslag uten rating - for kjønn avvist (400) både ved tilføyelse og redigering, tee-options - lister riktige utslag, rediger tee+HCP for medspiller lykkes og - course_handicap regnes om, **medspillerens egen profil-HCP forblir - UENDRET** (den kritiske sjekken -- bekrefter runde-scoping), delvis PATCH - (kun stat_level) lar tee/HCP stå urørt, eksplisitt `null` fjerner HCP - round-scoped, lenket medspiller (ikke eier) nektes å redigere deltakere - (403, forvaltning fortsatt eier-only), tom PATCH avvist (400), redigering - blokkert etter fullføring (409) mens stat_level fortsatt tillates. Full - regresjonskjøring av forrige rundes 35-punkts medspiller-søk-testsuite - (`test_round_coplayer.py`) mot samme friske scratch-database, alle 35 - fortsatt grønne. `test_isolation.sql` 12/12. Ekte typesjekket - produksjonsbuild kompilerte rent. - **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt: - migrasjon 028 kjørt mot ekte `teecup_db` (kolonne bekreftet `NOT NULL`, - 2 eksisterende rader backfylt korrekt, `test_isolation.sql` fortsatt - 12/12), deretter `docker compose up -d --build teecup_api - teecup_frontend`. Begge containere boot-et rent (`Application startup - complete`, Next.js `Ready`), `/health`/`/dashboard` → 200, `teeoff.no` - upåvirket. -- **V0-prompt for rundeleaderboardet rettet (2026-07-26), FØR den ble - kjørt i v0.app:** samme runde som over, brukeren ba eksplisitt om å få - leaderboardet for frittstående runder på plass. Backend - (`GET /rounds/{id}/leaderboard`) var allerede live siden tidligere samme - dag; V0-prompten (skrevet samme dag, se FEATURE_BACKLOG.md) hadde - derimot en utdatert antakelse -- den ba om et "Deg"-merke basert på at - kun eieren noensinne ser sin egen runde, som ikke lenger stemmer etter - ADR-036 fase 3 (medspillere kan nå også se runden). Rettet til to - 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. -- **Spillerliste-redesign HÅNDKODET (2026-07-26), IKKE ENNÅ RULLET UT:** - brukeren gikk tom for V0-credits rett etter at prompten over ble sendt. - Spurt eksplisitt (AskUserQuestion): bygg direkte, eller vent på fornyede - credits — svar: bygg direkte. Bygget nøyaktig etter samme prompt/data- - kontrakt som allerede var skrevet (se FEATURE_BACKLOG.md for full - detalj), i samme Tailwind/shadcn-stil som resten av `round-detail.tsx`. - Ny `PlayerList` (erstatter `PlayerTabs`) — vertikal liste av spillerkort, - hvert kort en stor "velg som aktiv spiller"-knapp + separate Rediger-/ - fjern-knapper (unngår nestede interaktive elementer), "Deg"/"Eier" som - `Badge`-komponenter. `EditParticipantPanel` utvidet med statistikknivå - (den frittstående `StatLevelPicker` er nå død kode, fjernet) og — kun - for gjester — Navn/Kjønn/E-post, med reaktivt utslagsfilter ved - kjønnsendring. "+ Medspiller"-flyten flyttet inn i selve `PlayerList`. - **Verifisert:** ekte typesjekket produksjonsbuild kompilerte rent, pluss - en engangs `next dev`-container mot scratch-backend (ekte runde med en - gjest med avvikende utslag/kjønn/e-post) — siden ga 200, ingen Next.js- - feilside. **Ærlig begrensning:** siden er en klient-komponent (data - hentes etter hydrering), så server-HTML-en viser kun last-skjelettet — - INGEN ekte nettleser-interaksjonstest utført (intet nettleserverktøy - tilgjengelig). - **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt: - ingen ny migrasjon, `docker compose up -d --build teecup_api - teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard` - → 200, `teeoff.no` upåvirket. **Bruker bør selv klikke gjennom flyten** - (spesielt gjeste-kjønnsendringens reaktive utslagsfilter og "velg vs. - rediger"-trykkflatene) før full tillit. -- **Tildelte slag + jevn korthøyde + rundeleaderboard, HÅNDKODET, IKKE ENNÅ - RULLET UT (2026-07-26):** to oppfølgingspunkter samme dag, full detalj i - FEATURE_BACKLOG.md. (1) Spillerkortet manglet "tildelte slag" (course - handicap for runden) og hadde variabel høyde — rettet: `Player` fikk - `courseHandicap`, kortet fikk fast `min-h-[68px]` med begge tekstlinjer - trunkert til én linje (ikke wrap), alle kort like høye uansett innhold. - (2) Brukeren spurte om jeg "med designerbrillene på" trodde jeg kunne få - rundeleaderboardet til å se like profesjonelt ut som V0 — svarte ja - (gjenbruk av etablerte mønstre, ikke fri utforskning) og bygget det: ny - `components/round-leaderboard.tsx` + rute `/my-rounds/[id]/leaderboard`, - gjenbruker `ScoreMark`s "form + farge"-språk fra `round-scorecard.tsx` - (som `ToParMark`), samme WS-sanntid-mønster som `round-stats.tsx`, delt - plassering ("T-N"), brutto/netto-veksling, mini-variant på rundens egen - side. Rangeringslogikken VERIFISERT UAVHENGIG i et frittstående - Node-script (19/19, inkl. tie-håndtering og uspilte spillere), pluss en - fersk scratch-runde med ekte API-kall som bekreftet leaderboard-JSON-en - stemte. Ekte typesjekket produksjonsbuild kompilerte rent. Samme ærlige - begrensning som over: ingen ekte nettleser-interaksjonstest. - **Rullet ut mot ekte systemer 2026-07-26**, 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. -- **Hullscorer i leaderboardet + lenke fra rundelisten, BYGGET, IKKE ENNÅ - RULLET UT (2026-07-26):** brukeren presiserte rett etter forrige rulling - tre krav: leaderboardet skal ha en egen visning (allerede tilfellet, se - over), med lenke fra scoreføringssiden (allerede der, `RoundLeaderboardMini`) - OG fra rundelisten (`/my-rounds`, manglet), og skal vise hullscorer - (manglet helt -- kun aggregerte tall fantes). - **Backend:** `LeaderboardEntryOut` (`GET /rounds/{id}/leaderboard`) fikk - et nytt `holes: list[LeaderboardHoleOut]`-felt (hole_number/par/ - stroke_index/played/score) -- data var ALLEREDE hentet per deltaker for - å beregne total_score/net_score_to_par, bare aldri eksponert rått før nå. - Ren tilføyelse, ingen migrasjon. - **Frontend:** `round-leaderboard.tsx` sine rader er nå klikkbare - (utvider/kollapser, default kollapset -- samme "trekkspill starter - lukket"-konvensjon som `round-stats.tsx`) og viser en horisontal strip - med per-hull-merker (`HoleMark`/`HoleStrip`, egen lokal variant av - `ScoreMark`s "form + farge"-språk fra `round-scorecard.tsx`, tilpasset en - kompakt flerspiller-liste i stedet for én tabell). `round-card.tsx` - (brukt av BÅDE `/my-rounds`-listen og dashbordets "Kommende runder") fikk - en ny "Se leaderboard"-lenke for runder med mer enn én deltaker -- - krevde å gjøre om kortets ytre element fra selve lenken til en `<div>` - med lenken som ETT av to barn (unngår nestet `<a>`, samme feilklasse som - tidligere nestet-form-/knapp-feller i prosjektet). - **Verifisert:** backend-endringen bekreftet mot en fersk scratch-runde - (ekte API-kall, `holes`-arrayet inneholder riktige 18 rader per deltaker - inkl. korrekt `played`/`score` for både spilte og uspilte hull), full - regresjon av forrige rundes 35-punkts co-player-testsuite fortsatt grønn, - `test_isolation.sql` 12/12 (uendret, ingen migrasjon). Ekte typesjekket - produksjonsbuild kompilerte rent (ingen nye ruter, kun endrede - komponenter). Samme engangs `next dev`-container-sjekk som tidligere -- - `/my-rounds`, `/my-rounds/[id]` og `/my-rounds/[id]/leaderboard` ga alle - 200, ingen Next.js-feilside. - **Samme ærlige begrensning som tidligere håndkodede runder:** ingen ekte - nettleser-interaksjonstest (klikk for å utvide en rad, se hull-stripen, - se "Se leaderboard"-lenken i rundelisten) er utført. - **Rullet ut mot ekte systemer 2026-07-26**, 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. -- **Leaderboard-oppfølging: poeng-modus + minimalistiske rader, BYGGET, IKKE - ENNÅ RULLET UT (2026-07-26):** brukeren påpekte at brutto/netto ble - presentert forskjellig (bryteren byttet BÅDE hvilket tall som vises OG - hele rangeringsrekkefølgen -- forvirrende hopp), og spurte om et eget - stableford-alternativ burde vært med. Foreslo og fikk bekreftet: gi alle - tre modiene (nå: Brutto/Netto/**Poeng**) IDENTISK radform (rangering, - navn, ETT tall, ferdig) i stedet for å prøve å vise flere tall samtidig. - Samtidig ba brukeren om å fjerne alt fra den kollapsede raden utover - navn+tall (Deg/Eier/pokal/hull-spilt-tekst) og fjerne selve - "kort"-følelsen -- radene skal flyte sømløst sammen, kun ekspandere ved - klikk. - **Backend:** `LeaderboardHoleOut` fikk `strokes_received` (samme - allerede-beregnede allokering som `net_score_to_par`, nå eksponert per - hull også). `LeaderboardEntryOut` fikk `total_points` (stableford-total, - samme formel/betingelse som `net_score_to_par` -- null uten HCP). Ingen - migrasjon. - **Frontend (`round-leaderboard.tsx`):** `Mode` utvidet til tre verdier, - poeng rangeres SYNKENDE (motsatt av til-par). Ny `PointsMark`/`ValueMark` - (poeng har ingen retning, kun fylt/uthevet for lederen). Listen er nå ÉN - sammenhengende `<ul>` (`divide-y`, kun ytterkanten avrundet/rammet) i - stedet for separate kort med mellomrom mellom hver rad -- ingen - per-rad-bakgrunn utenom en svak tone på en UTVIDET rad. Kollapset rad: - kun rangering+navn+tall+pil. Deg/Eier/pokal/fremdrift FLYTTET (ikke - fjernet) inn i den utvidbare seksjonen sammen med hull-stripen. - **Verifisert:** rangeringslogikken for poeng-modus (synkende sortering, - delt plassering, uten-HCP sortert nederst) UAVHENGIG testet i Node - (10/10). Selve stableford-formelen verifisert mot en fersk scratch-runde - med HÅNDREGNET forventet resultat (course handicap 9 over 18 hull → - slag KUN på de 9 laveste stroke-index-hullene, ikke jevnt fordelt -- - fanget en feilaktig antakelse i testens FØRSTE versjon, rettet før - den ble rapportert som bestått) -- 11/11, inkl. kryssjekk av - `total_points`/`net_score_to_par` mot uavhengig rekalkulering fra de - samme rå hull-dataene API-et returnerte. Full regresjon (35-punkts - co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12. Ekte - typesjekket produksjonsbuild kompilerte rent. Samme engangs `next - dev`-container-sjekk som tidligere -- alle tre rutene ga 200. - **Samme ærlige begrensning:** ingen ekte nettleser-interaksjonstest av - det faktiske "sømløse rader"-uttrykket eller poeng-modusen. - **Rullet ut mot ekte systemer 2026-07-26**, 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. -- **Hullscorer i leaderboardet: netto/poeng under brutto, BYGGET, IKKE ENNÅ - RULLET UT (2026-07-26):** brukeren viste to referansebilder fra en - konkurrentapp (Golf GameBook) sin leaderboard-visning -- brutto fremhevet - (fylt/farget merke) på én rad, netto/stableford i vanlig tekst RETT under, - eksplisitt presisert at det skal se likt STRUKTURELT ut men IKKE være en - kopi visuelt. Ren frontend-endring, ingen backend-endring (all data -- - `strokes_received` per hull -- var allerede lagt til forrige runde samme - dag). - **`HoleMark`** (`round-leaderboard.tsx`) fikk en tredje, umerket tekstlinje - RETT under det fremhevede brutto-merket, som viser netto eller - stableford-poeng for AKKURAT det hullet -- kun i netto-/poeng-modus (ingen - ny linje i brutto-modus, ingenting nytt å vise der). Nye lokale - `netForHole()`/`pointsForHole()`-hjelpefunksjoner (samme formel som - backend sin `total_points`, men per hull -- bruker `strokes_received` - direkte fra API-et, regner ALDRI ut egen slagfordeling). `HoleStrip` fikk - en liten "Slag · Netto"/"Slag · Poeng"-bildetekst over stripen når en - sekundærrad faktisk vises. - **Bevisst IKKE en kopi:** beholder TeeCups egne farger/former (sirkel/ - firkant, grønn/oransje) fra `ScoreMark`-språket som allerede var etablert - -- endret ikke fargevalg for å etterligne referansebildets blåtoner, kun - gjenskapte det STRUKTURELLE prinsippet (fremhevet brutto øverst, rolig - sekundærtall under). - **Verifisert:** netto/poeng-per-hull-formelen testet UAVHENGIG i Node - (9/9, inkl. et gulv-på-0-tilfelle og manglende-HCP/uspilt-hull), samme - tall som det håndregnede scratch-scenarioet fra forrige runde samme dag - (hull med slag: netto 3/poeng 3, hull uten slag: netto 5/poeng 1). Ekte - typesjekket produksjonsbuild kompilerte rent. Samme engangs `next - dev`-container-sjekk som tidligere -- leaderboard-ruten ga 200. - **Samme ærlige begrensning som resten av de håndkodede rundene:** ingen - ekte nettleser-interaksjonstest av selve det visuelle uttrykket. - **Rullet ut mot ekte systemer 2026-07-26**, 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. -- **Scoringsflyt: samlebånd-fremdrift + golf-term-taltastatur, BYGGET, IKKE - ENNÅ RULLET UT (2026-07-26), inspirert av en konkurrentapp (Golf - GameBook):** brukeren delte en skjermopptaksvideo av GameBooks - score-registrering og spurte om jeg forstod HVORFOR den flyten fungerer - bedre enn TeeCups, og om jeg kunne bygge det selv (ingen V0-credits - igjen). Video analysert bilde for bilde (ffmpeg i en engangs Docker- - container, samme mønster som en tidligere videoanalyse i prosjektet) -- - identifiserte presist samlebånd-mønsteret: taltastatur med kontekstuelle - golf-termer per tall (Eagle/Birdie/Par/Bogey ut fra hullets par, ikke - bare "par"-knappen merket), og at fullført registrering for én spiller - automatisk åpner NESTE spillers registrering for samme hull -- uten å - måtte navigere manuelt tilbake til en spillerliste. - **Bevisst IKKE en full skjermovertakende steg-for-steg-modal** (GameBooks - egen løsning) -- vurdert som unødvendig risikofylt å bygge korrekt uten - visuell testing. I stedet: samme etablerte inline-side beholdt, men to - konkrete forbedringer lagt til: - 1. `NumberPicker` sin Slag-instans fikk en ny `showGolfTerms`-modus -- - HVERT synlig tall viser nå Albatross/Eagle/Birdie/Par/Bogey/Dobbel - bogey relativt til hullets par (ikke bare selve par-knappen som før). - Ny lokal `golfTermForScore()`-hjelpefunksjon. - 2. Ny `isEntryComplete()`-sjekk (krever kun det `stat_level` faktisk gjør - obligatorisk -- slag alene, eller slag+putter; "full"-nivåets ekstra - detaljer forblir valgfrie og blokkerer ALDRI fremdrift) + ny - `advanceToNextPlayerOrHole()`. Bunnknappraden "Forrige/Neste hull" - endret til "Forrige hull" (uendret) + en kontekstsensitiv primærknapp - som enten viser "Neste: {navn på neste spiller}" (bytter aktiv - spiller på SAMME hull) eller "Neste hull" (er aktiv spiller den - siste, går videre til neste hull OG starter på spiller 1 igjen) -- - disabled med forklarende hjelpetekst til de påkrevde feltene er fylt. - **Verifisert:** golf-term-tabellen og fullført-sjekken UAVHENGIG testet - i Node (matcher videoens egne eksempler nøyaktig, f.eks. par 4 + slag - 6 = "Dobbel bogey"), samt selve samlebånds-syklusen simulert for 3 - spillere over flere hull-grenser OG for en solo-runde (ingen spillerbytte, - kun hull-fremgang) -- 21/21 sjekker. Full regresjon (35-punkts - co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12 (ingen - backend-endring denne runden). Ekte typesjekket produksjonsbuild - kompilerte rent, samme engangs `next dev`-container-sjekk som tidligere - ga 200. - **Samme ærlige begrensning som resten av de håndkodede rundene:** ingen - ekte nettleser-interaksjonstest av selve fremdrifts-følelsen (kun logikk - + server-render bekreftet). - **Rullet ut mot ekte systemer 2026-07-26**, 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. -- **Scoringsflyt v2: full skjermovertagende veiviser, BYGGET, IKKE ENNÅ - RULLET UT (2026-07-26) -- ERSTATTER forrige rundes forsøk.** Brukeren - testet forrige rundes "auto-fremdrift-knapp"-versjon live og var tydelig: - "ingen forbedring i det hele tatt", "visuelt like overveldende og - rotete" -- den inline-baserte tilnærmingen var IKKE nok, en ekte - skjermovertagende veiviser (som opprinnelig vurdert og lagt til side pga. - risiko uten visuell testing) var det som faktisk kreves. Bygget nå for - ekte, pluss et eksplisitt nytt krav: akkumulert score-så-langt for RUNDEN - synlig for HVER spiller samtidig (ikke bare aktiv), matchende - konkurrentappens vedvarende "E"/"+1"-visning ved siden av hvert navn. - **Datalasting endret** (nødvendig for punktet over): laster nå hull for - ALLE deltakere med det samme rundén lastes (ikke lenger lat lasting kun - for aktiv spiller), og sanntid-signalet (WS) henter nå alles hull på - nytt, ikke bare énes. - **Ny `ScoringWizard`-komponent** (fullskjerm, `fixed inset-0 z-50`, egen - stack utenfor `<main>`): tre steg maks, drevet av `stat_level` -- - "strokes_only" (kun Slag), "strokes_and_putts" (+ Putter/avstand første - putt), "full" (+ ett samlet detalj-steg: kølle/utslag/innspill/chip/ - bunker/straffeslag/anywayslag). Bevisst FÆRRE, grovere steg enn - konkurrentens egne 5-6 skjermer (risikoreduksjon uten visuell testing, - og TeeCups stat_level-modell gjør en så fin oppdeling mindre naturlig). - Alle spillerne vises som en fast, ikke-trykkbar kontekst-rad øverst i - veiviseren (aktiv fremhevet) -- speiler konkurrentappens "mist aldri - oversikten"-prinsipp. "Forrige"/"Neste" beveger seg gjennom stegene; - siste steg for siste spiller blir "Ferdig" (lukker + går til neste hull, - starter på spiller 1 igjen), ellers "Neste: {navn}" (bytter spiller i - SAMME veiviser, nullstiller til steg 1). - **Hovedsiden forenklet radikalt:** den gamle Slag/Putter/"flere - detaljer"-inline-blokken er FJERNET -- erstattet med én kompakt liste, - ett kort per spiller: navn, "HCP X · {til-par så langt} ({N} hull)", og - en stor rund knapp som viser gjeldende hulls slagtall (eller "–") og - åpner veiviseren ved trykk. `NumberPicker`s `showGolfTerms`-funksjon fra - forrige runde gjenbrukes uendret inni veiviserens Slag-steg (det arbeidet - var ikke bortkastet). - **Verifisert:** stegmaskinen (steg-antall per stat_level, fremover/ - bakover-navigasjon, disabled-gating per steg, "avbryt på steg 1 lukker", - "siste steg for siste spiller fullfører") og akkumulert-score- - beregningen UAVHENGIG simulert i Node (16/16). Et ekte API-rundtur- - script som sender NØYAKTIG samme felt-kombinasjon som veiviseren ville - sendt (fullt detalj-steg for en "full"-spiller, kun slag for en - "strokes_only"-gjest) bekreftet begge lagres korrekt. Full regresjon - (35-punkts co-player-testsuite) fortsatt grønn, `test_isolation.sql` - 12/12 (ingen migrasjon). Ekte typesjekket produksjonsbuild kompilerte - rent, samme engangs `next dev`-container-sjekk ga 200. - **Samme ærlige begrensning som alle håndkodede runder denne uken:** ingen - ekte nettleser-interaksjonstest -- gitt at FORRIGE runde ble avvist - nettopp fordi den så gal ut i praksis til tross for at logikken var - korrekt, er dette IKKE en ubetydelig forbehold denne gangen. Bruker bør - teste grundig før tillit. - **Rullet ut mot ekte systemer 2026-07-26**, 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. -- **Scoringsflyt v3: administrasjon og scoring adskilt i to faner, BYGGET, - IKKE ENNÅ RULLET UT (2026-07-26) -- direkte svar på at bruker fortsatt - fant siden "bråkete og lite intuitiv" etter v2.** Brukeren viste et NYTT - sidestilt skjermbilde-par (samme mønster som første gang) og påpekte at - TeeCup fortsatt hadde mye synlig FØR selve scoringslisten (leaderboard- - forhåndsvisning, Rediger/Fullfør/Slett, hele spillerlisten med Rediger- - ikoner, "+Medspiller") -- nøyaktig det jeg selv identifiserte som GameBooks - kjerneprinsipp i den aller første analysen ("administrasjon og scoring er - ADSKILTE tabs"), men aldri fullt ut gjennomførte i v2 (kun `ScoreSoFar` - ble flyttet forrige runde, resten av det administrative innholdet ble - stående igjen øverst). - **Fikset denne gangen for ekte:** ny `pageTab`-state (`"score" | "manage"`, - default `"score"`), en enkel fanevelger rett under feilmeldingen. "Score" - (default) inneholder nå KUN: fullført-banner (hvis relevant), hull- - navigasjon, og selve hull-panelet (header + scoringslisten fra v2 + - Forrige/Neste hull) -- ingenting annet. "Spillere og runde" samler - leaderboard-forhåndsvisning, Rediger/Fullfør/Slett, hele spillerlisten - (`PlayerList`), OG `ScoreSoFar` (flyttet HIT fra forrige rundes - "under scoringslisten"-plassering, siden den er detaljert stats-innsyn, - ikke selve registreringsoppgaven). - **Verifisert:** ekte typesjekket produksjonsbuild kompilerte rent, samme - engangs `next dev`-container-sjekk ga 200, full regresjon (35-punkts - co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12 (ren - frontend-omrokkering, ingen migrasjon). - **Samme ærlige begrensning som v1/v2:** ingen ekte nettleser- - interaksjonstest av selve fane-følelsen. - **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt: - ingen migrasjon, `docker compose up -d --build teecup_frontend` - (gjenskapte også `teecup_api` som vanlig bivirkning). Begge containere - boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. -- **`DESIGN_SYSTEM.md` opprettet (2026-07-27):** brukeren ba om en ny fil - som viser designsystemet arbeidet tar utgangspunkt i. Skrevet fra bunnen, - DESKRIPTIVT (hva som ER i koden, ikke et mål) — grunnet direkte i - `globals.css`/`components/ui/*.tsx` og etablerte tvers-fil-mønstre: - fargetoken (inkl. OKLCH-opprinnelsen til `primary`/`brand-orange` fra - ADR-009/016), typografi, avstand/hjørner/lag (inkl. "én sammenhengende - liste med `divide-y`, ikke separate kort"-regelen fra 2026-07-26s - leaderboard-fiks), lokale komponentmønstre (NumberPicker/Stepper/ - ChoiceRow/DirectionCross — bevisst per-fil, ikke delte importer), - golfscore-språket (`ScoreMark`/`ToParMark`/`PointsMark`/`HoleMark`), - ikonografi, tilbakemelding/tilstander, og navneformat (pekt til CLAUDE.md). -- **`teecup-scorekort-og-entry-spec.md` lest og evaluert, delvis BYGGET SOM - COMPLIANCE-PASS SAMME DAG (2026-07-27):** brukeren lastet opp et - PRESKRIPTIVT motstykke til DESIGN_SYSTEM.md (5-app-sammenligning: Golf - GameBook/Golf Pad/Golfshot/Hole 19/18Birdies) og spurte om det ga mening - og kunne forbedre TeeCup. Vurdert som reelt verdifullt — sylskarpeste nye - innsikt: "grønt-på-grønt"-diagnosen (§0.2) forklarer noe av "fortsatt - rotete"-følelsen fra v1-v3-rundene bedre enn tetthet alene: `primary` - (grønn) var brukt om hverandre for BÅDE "aktiv tilstand" og "generisk - fylt/positiv", uten at begge betydde det samme sted. Foreslo (og - brukeren bekreftet) å fikse spec-dokumentets §3 "Compliance-pass" FØR den - større strukturelle §1-omleggingen (scorekort som grid) vurderes som egen, - senere runde. - **To av sjekklistens fem punkter var KONKRETE, VERIFISERBARE bugs, bekreftet - direkte i koden (ikke antatt fra spec-teksten alene) FØR de ble fikset:** - 1. **"Deg Deg"-duplikat:** `playerLabel()` (`round-detail.tsx`) erstattet - tidligere selve navnet med "Deg" for viewer-relativ egen rad, OG en - separat `<Badge>Deg</Badge>` sto ved siden av samme sted (spillerkort, - scoringslisten) — bekreftet duplikat, matchet et tidligere skjermbilde - brukeren delte. **Fikset:** `playerLabel()` returnerer nå alltid det - faktiske `display_name` (aldri "Deg") — Badge-en er nå ENESTE - selv-indikator. Dette retter samtidig et videre, ikke tidligere flagget - avvik: spec-dokumentet krever eksplisitt "roster-kontekst → fullt navn" - for BÅDE scorekort-gridet og score-entry-headeren — veiviserens header/ - kontekst-rad/"Neste: {navn}"/fullført-banneret viste tidligere "Deg" i - stedet for et fullt navn der også, uten noen badge til å disambiguere - (reelt forvirrende på en delt telefon som sendes rundt en flight). - Det nå overflødige `rawName`-feltet (var identisk med `name` etter - fiksen) fjernet, tre kallsteder oppdatert. - 2. **Score-knappen fulgte ikke `§Golfscore-språket`:** den store runde - knappen i den kompakte scoringslisten (`round-detail.tsx`, bygget i - v2-runden) var `rounded-full`+grønn UANSETT om resultatet var under, - over eller på par — bogey og eagle så identiske ut. **Fikset:** ny - `scoreMarkClasses(diff)`-hjelpefunksjon som gjenbruker EKSAKT samme - `primary`/`brand-orange`-fargespråk og fylt-vs-border+10%-tint-omfangs- - regel som `ScoreMark` i `round-scorecard.tsx` (bekreftet ved å lese - `ScoreMark` sin kildekode direkte, ikke gjettet) — sirkel under par, - nøytral sirkel på par, `rounded-2xl` (bevisst mildere enn scorekortets - `rounded-[4px]`, for å matche denne skjermens øvrige 56px-trykkflate- - avrunding) over par, fylt ved 2+ slag fra par. - **Resten av sjekklisten (44px-trykkgulv, `tabular-nums`) auditert - systematisk mot AKKURAT denne filen** (samme fil brukerens skjermbilder - viste) — 2 manglende `tabular-nums` (HCP/tildelte slag i spillerkortet) - og 11 knapper/lenker under 44px (fane-bryteren `min-h-10`→`min-h-11`, - `EditRoundPanel`s lukk-ikon `size-8`→`size-11`, feilside-tilbakelenken, - og åtte knapper i bane-bytte-/legg-til-medspiller-skjemaene) rettet. - **Bevisst UTENFOR omfang denne runden:** spec-dokumentets §1 (scorekort - som fullt grid, celle åpner veiviseren) — en STØRRE strukturell endring - som fortjener et eget, bevisst ja fra brukeren, ikke bygget stille inn i - en "fiks kjente bugs"-runde. - **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` som - deployes) kompilerte rent, alle 24 ruter listet. **Samme ærlige - begrensning som ALLE håndkodede runder denne uken:** ingen ekte - nettleser-interaksjonstest av det faktiske visuelle resultatet (kun kode- - lesing + build-verifisering + bevisst gjenbruk av en allerede lest, - eksisterende komponents fargespråk for å holde risikoen lav). - **Rullet ut live 2026-07-27**, bruker bekreftet eksplisitt: ingen - migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte - også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge - containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` - upåvirket. -- **Spec-dokumentets §1 (scorekort som fullt grid) BYGGET OG LIVE - (2026-07-27), samme dag rett etter compliance-passet:** brukeren - bekreftet eksplisitt "JA" på at grid-redesignet er en egen, bevisst - runde, egen informasjonsarkitektur enn ett-hull-om-gangen-listen - bygget dagen før. `round-detail.tsx` sin "Score"-fane fikk hull-strip - + ett-hull-panel ERSTATTET av en ny `ScorecardGrid`: spillere som rader - (sticky venstre navnekolonne, navn+HCP, samme tap-target åpner - veiviseren for gjeldende hull), hull som horisontalt scrollbare - kolonner, `Hcp`/`Par`-referanserader over spillerradene (samme - konvensjon som den allerede shippede `round-scorecard.tsx`), sticky - `Ut`/`Inn`/`Sum` til høyre (Ut/Inn gruppert på FYSISK hullnummer 1-9/ - 10-18, uavhengig av øktens starthull — riktig konvensjon uansett - spillerekkefølge; kun vist for 18-hulls runder, 9-hulls runder får - én samlet Sum). Ny `ScorecardCell` gjenbruker EKSAKT samme klassifisering - og Tailwind-klasser som `ScoreMark`/`classify` i `round-scorecard.tsx` - (lest direkte, ikke gjettet) — ulikt compliance-passets softere - `rounded-2xl`-knapp-variant (fortsatt riktig der, egen visuell kontekst), - siden dette er en LITEN tabellcelle som spec eksplisitt ber om å følge - språket "UBRYTELIG". - **Interaksjon:** tapp en score-celle ELLER spillerens navnecelle ELLER - en hull-kolonneoverskrift åpner `ScoringWizard` (uendret komponent fra - v2-runden) for akkurat den (spiller, hull)-kombinasjonen — kolonne- - overskrift alene (uten å tappe en celle) setter kun "gjeldende hull" - uten å åpne veiviseren, samme jobb som den fjernede `HoleNav` gjorde. - Beholdt en kompakt "Forrige/Neste hull"-knapperad under gridet for - rask sekvensiell registrering uten bred scrolling. - **Dødt kode fjernet i samme runde:** `HoleNav`, `holeIsPlayed`, - GIR-merket/`showGir` (ga ikke lenger mening i en multi-hull-visning), - og OGSÅ compliance-passets `scoreMarkClasses`-hjelpefunksjon (var kun - brukt av den nå fjernede ett-hull-listens store runde knapp). - **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` - som deployes) kompilerte rent, alle 24 ruter listet. I TILLEGG en - engangs full runner-image bygget og kjørt i en isolert container (ikke - bare `--target builder`) — `/my-rounds/[id]` for en ukjent runde-id - ga 200, ingen React-feilgrense/krasj-markup i responsen. **Samme - ærlige begrensning som ALT håndkodet arbeid denne uken, men STØRRE - konsekvens denne gangen siden dette er en vesentlig strukturell endring - (ny informasjonsarkitektur), ikke en liten fiks:** ingen ekte - nettleser-interaksjonstest (scrolling, tapping av celler/hull- - overskrifter/navnerad, faktisk visuelt resultat av sticky-kolonnene) - er utført — flagget eksplisitt til bruker FØR utrulling, bruker bør - selv klikke seg grundig gjennom før full tillit. - **Rullet ut live 2026-07-27**, bruker bekreftet eksplisitt: ingen - migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte - også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge - containere boot-et rent, `/health`/`/dashboard`/`/my-rounds` → 200, - `teeoff.no` upåvirket. -- **Reell produksjonsbug funnet OG fikset SAMME DAG via ekte nettleser- - testing (2026-07-27) — FØRSTE gang denne økten en Chrome DevTools MCP - har vært tilgjengelig.** Brukeren ba meg selv åpne gridet - (`localhost:3000/my-rounds/{id}`), logget meg inn (passord feilet først - — kontoen mangler passord/miljøet pekte annerledes; løst med en ekte - magic-link brukeren limte inn), og jeg tok et ekte skjermbilde av det - nettopp bygde scorekort-gridet på en mobil viewport (390×844) mot ekte - produksjonsdata (runden `c5db2e31-...`, 2 spillere, 4/18 hull spilt). - **Fant umiddelbart en alvorlig, reell rendering-bug** som ALDRI ble - fanget av typesjekking/container-boot-verifisering: `position: sticky` - på `<td>`/`<th>` inni en `<table>` med `border-collapse` rendret - fullstendig ødelagt i Chrome — de sticky `Ut`/`Inn`/`Sum`-kolonnene - overlappet/utvisket hull-kolonnene bak seg (synlige sammenblandede - siffer, f.eks. "436"/"472" der Par-radens tall lå oppå hverandre). - Nøyaktig den typen feil den gjentatte "ingen ekte nettleser-test - utført"-forbeholdet advarte mot hele uken. - **Første fiks-forsøk (kun `border-collapse` → `border-separate - border-spacing-0`) løste IKKE problemet** — re-skjermbilde etter - redeploy viste samme overlapp. **Rot-årsaken var strukturell, ikke - syntaktisk:** å kombinere en sticky VENSTRE-kolonne MED sticky HØYRE- - kolonner i en tabell som er mye bredere enn viewporten er i seg selv - et ustabilt mønster — ved scroll-posisjon 0 blir de sticky høyre- - kolonnene umiddelbart trukket til synlig høyre kant, og alt som - "egentlig" befinner seg der i normal dokument-flyt (hull 3+) blir - liggende RETT BAK dem, delvis synlig gjennom `bg-muted/60`s - delvise gjennomsiktighet. **Fikset ved å fjerne sticky-posisjonering - fra `Ut`/`Inn`/`Sum`-kolonnene helt** (de scroller nå med resten av - hullene i normal flyt, samme velprøvde, veletablerte mønster som den - gjenværende sticky VENSTRE navnekolonnen, som fungerte korrekt hele - tiden) — droppet også `/60`-gjennomsiktigheten til fordel for en helt - opak `bg-muted`. - **Verifisert presist, ekte, etter fiksen** (ikke bare "ser bedre ut"): - nytt skjermbilde ved scroll-posisjon 0 viste alle tall rene og lesbare - (Hcp/Par-radene, begge spilleres scoringsceller, korrekt sirkel/firkant- - form for bogey/dobbel bogey/par), OG et script som scrollet gridet helt - til høyre (`scrollLeft = scrollWidth`) bekreftet `Ut`/`Inn`/`Sum` også - rene der (`Ut 36/Inn 36/Sum 72` på Par-raden — stemmer eksakt med en - 18-hulls par-72-bane), med navnekolonnen fortsatt korrekt pinnet til - venstre gjennom hele scrollingen. Tilgjengelighetstreet (`take_snapshot`) - bekreftet også at aria-labels med golftermer ("Bogey", "Dobbel bogey", - "Par") faktisk leses ut korrekt — `§Golfscore-språket`s "aldri farge - alene"-regel holder i praksis, ikke bare i teorien. - **Rullet ut live 2026-07-27**, samme dag: `docker compose up -d --build - teecup_frontend` kjørt to ganger (én for det mislykkede første forsøket, - én for den faktiske fiksen), `/health` 200 begge ganger, `teeoff.no` - upåvirket. - **Lærdom:** Chrome DevTools MCP-tilgangen endrer risikobildet for alt - fremtidig håndkodet frontend-arbeid denne økten — bruk den til å - FAKTISK se resultatet før noe rapporteres som ferdig, i stedet for kun - typesjekk+container-boot+`curl`-baserte proxyer for "det virker". -- **Full nettleser-gjennomgang av ALLE 22 skjermer, 2026-07-27 — - systematisk browsersjekk av alt som IKKE var reelt nettleser-testet - tidligere.** Brukeren spurte først hvilke visninger som faktisk finnes - (svart med en gruppert oversikt over `frontend/app/`s 22 `page.tsx`- - ruter), deretter ba om at ALLE de ikke-browsersjekkede skjermene faktisk - ble sjekket. Logget inn som `hei@erol.no` (spiller-konto, egne runder) - og — etter en egen runde med å spore opp riktig konto (magic-link fra - brukeren landet først på feil konto to ganger: `erol.haagenrud@gmail.com` - og kontoen manglet 2FA — satt opp TOTP for ekte ved å hente ut den rå - base32-secreten fra oppsett-skjermen og regne ut en gyldig 6-sifret kode - selv med et frittstående RFC 6238-script, ingen autentisator-app - involvert) — som `erol.haagenrud@envide.no` (org-eier for «Tjøme - Gents») for de org-/turnering-scopede skjermene. Gikk gjennom alle 22 - ruter med ekte skjermbilder + konsoll-feil-sjekk (`list_console_ - messages`) på hver. - **To reelle funn:** - 1. Scorekort-gridet (§1, bygget dagen før) hadde EN NY sticky-kolonne- - overlapp-bug som IKKE fantes i utgangspunktet -- se eget punkt over, - fikset samme økt. - 2. **Ny, ekte bug funnet i `round-scorecard.tsx`** (post-runde- - scorekortet, bygget i en tidligere økt) -- "til par" i BÅDE - header-hero-tallet og bunn-"TIL PAR"-brikken regnet - `totalGross - totalPar` der `totalPar` var summen av ALLE 18 hulls - par, ikke bare de faktisk spilte -- ga en absurd "−53 til par" for - en runde med 19 slag på kun 4 hull (skulle vært "+2"). Bekreftet - ved at `/my-rounds/[id]/stats` (en ANNEN komponent) viste riktig - "+2,00 til par" for SAMME runde, som isolerte feilen presist til - `round-scorecard.tsx`. Fikset med en ny `playedPar`-variabel - (paret for KUN spilte hull) brukt i de to til-par-utregningene -- - "Par"-brikken nederst beholdt bevisst `totalPar` (hele rundens - par, riktig som statisk referanse). Bunn-"Par"/øvre "Hcp"/"Par"- - referanseradene i `ScoreBlock` ble sjekket og bekreftet IKKE - rammet (brukes kun til den statiske referansen, aldri til en - til-par-utregning). - **For å nå de org-/turnering-scopede skjermene: satt org «Tjøme - Gents» sin `public_profile`/`slug` MIDLERTIDIG (bekreftet med bruker - FØR endring) for å teste `/clubs/[slug]`, deretter revertert - eksplisitt til nøyaktig opprinnelig tilstand (`slug=null`, - `public_profile=false`) — bekreftet med en direkte databasespørring - etterpå at reverten var eksakt. - **Alle 22 skjermer bekreftet uten krasj/konsoll-feil** (utenom - forventede 401/403 på steder som SKAL avvise — feil passord-forsøk, - privat lagchat, ugyldig verify-token). Full liste med status i - chat-loggen denne runden. - **Rullet ut live 2026-07-27**, ingen migrasjon for til-par-fiksen, - `docker compose up -d --build teecup_frontend`, verifisert direkte i - nettleseren mot den samme runden som viste bugen (nå "+2 til par", - korrekt). -- **Dashboard-runde: bane-navn-fiks, to nye designfarger, bane-detaljvisning - og aggregert statistikk — BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE - (2026-07-28):** fem punkter reist av brukeren i samme runde, alle - bekreftet eksplisitt før bygging (AskUserQuestion) unntatt fargevalget, - der brukeren ba om at jeg selv "tok på designerbrillene" og foreslo. - 1. **"Kommende runder" → "Runder"** på dashbordet (`dashboard.tsx`, - `UpcomingRounds`) — ren tekstendring, ingen annen forekomst i kodebasen. - 2. **Tjøme-bane-duplikatet rettet i ekte `teecup_db`:** presist - diagnostisert FØR noe ble kjørt — alle tre av `hei@erol.no` sine - runder pekte til nøyaktig samme `teeoff_facility_slug`/ - `teeoff_course_id` (`tjome-golfklubb`/140), kun `round. - course_name_snapshot`-TEKSTEN differerte (én runde fra FØR - enkeltbane-navnefiksen 25.07 hadde "Tjøme Golfklubb – Hovedbanen"). - Ingen delt banetabell å slå sammen — frittstående runder har (bevisst, - ADR-033 Beslutning C) ingen egen `course`-rad, kun et navn-snapshot per - runde. Fiksen var én presist scopet `UPDATE round SET - course_name_snapshot = 'Tjøme Golfklubb' WHERE id = 'fa6e528f-...'` - (1 rad), kjørt etter eksplisitt bekreftelse, verifisert med en - read-only spørring rett etterpå. - 3. **To nye kjernefarger** — brukeren ba eksplisitt om minst én, "kanskje - to", etter først å ha fått presentert (og avvist "kun én") en - tidligere anbefaling fra 25.07 om å løfte den allerede eksisterende - `--chart-3`-blåtonen. Løftet BEGGE allerede validerte statistikk- - fargene i stedet for å finne opp nye OKLCH-verdier: `--info` - (fra `--chart-3`, blå) og `--gold` (fra `--chart-4`, gul/gull) — - nye tokens i `globals.css` (`:root`/`.dark`/media-dark-blokken, alle - tre synkronisert som vanlig). Konkret, begrunnet førstebruk for - begge, ikke bare dekorativt: `--info` på et nytt "Hcp spilt til - X"-merke (før: ren grå tekst) på rundekort (`round-card.tsx`), - `--gold` på et nytt "Personlig rekord"-merke (`Medal`-ikon) som vises - når en fullført runde er brukerens laveste til-par blant MINST to - fullførte runder (unngår at den eneste fullførte runden feilaktig - kalles en "rekord"). Beregnet i `dashboard.tsx`/`own-rounds.tsx`/ - `course-rounds.tsx` sine respektive `findPersonalBestRoundId()`. - 4. **Bane-detaljvisning:** "Spilte baner" på dashbordet er nå klikkbar - (`PlayedCourses`, `dashboard.tsx`) — ny rute - `/my-rounds/course/[name]` (`components/course-rounds.tsx`), gjenbruker - eksisterende `GET /rounds` (ingen nytt backend-endepunkt), filtrerer - client-side på nøyaktig samme `course_name_snapshot`-nøkkel dashbordets - egen gruppering allerede bruker. "Personlig rekord" regnes likevel over - HELE rundelisten, ikke bare denne banens, for at merket skal bety det - samme uansett hvor et rundekort vises. - **Reell bug funnet OG fikset UNDER browserverifisering, ikke antatt - riktig fra kildekoden alene:** første versjon leste `params.name` rått - uten `decodeURIComponent` (matchet et eksisterende mønster i - `/clubs/[slug]/page.tsx` som aldri hadde blitt testet med et navn som - inneholder mellomrom/æøå) — et ekte skjermbilde viste tittelen som - `Tj%C3%B8me%20G...` og "0 runder funnet", siden matchen skjedde mot den - RÅ URL-kodede strengen. Rettet med et eksplisitt `decodeURIComponent`, - bekreftet med et nytt skjermbilde: riktig tittel og alle tre Tjøme- - rundene listet, inkl. både `--info`- og `--gold`-merkene rendret - korrekt. - 5. **Aggregert statistikk, klikkbar fra dashbordet:** ny - `GET /rounds/stats/summary` (`app/routers/rounds.py`), ny side - `/my-rounds/stats` (`components/rounds-stats-summary.tsx`), lenket - fra dashbordets "Statistikk"-seksjon ("Se full statistikk"). Bruker - valgte det BREDESTE av tre foreslåtte omfang (utover kun runder/snitt- - til-par/putt: fairwaytreff, GIR, én-putt, scrambling, sand save, - snitt chip/bunker/straffeslag/anywayslag per runde) — portert fra de - allerede R&A/manuelt verifiserte formlene i `round-stats.tsx` sin - `computeStats()` for ÉN runde, generalisert til å pole alle kvalifiserte - hull på tvers av ALLE fullførte runder (ikke gjennomsnitt av per-runde- - prosenter, som ville vektet små utvalg feil). - **Putt/18-hull-regelen** (brukerens eksplisitte instruks, presist - bekreftet tolkning FØR bygging via AskUserQuestion): `round_hole` har - alltid nøyaktig 18 rader per deltaker uansett `holes_planned` (9 eller - 18) — verifisert i `_create_participant`. For hver fullført runde der - puttsporing faktisk var på (`stat_level != 'strokes_only'`) telles - derfor alle 18 lagrede rader, et hull uten registrert putt-verdi - (uspilt, eller utenfor et 9-hulls spilleomfang) telles som 2 putter — - runder UTEN puttsporing holdes helt utenfor tallet (ellers ville alle - 18 hull feilaktig blitt padded). Kun putt-tallet padder — alle andre - andelstall bruker KUN faktisk registrerte hull, som instruert. - **Verifisert i to lag:** (a) en frittstående Python-simulering av - nøyaktig samme aggregeringslogikk mot et hånd-konstruert 3-runde- - datasett (én `strokes_only`-runde padding-ekskludert, én - `strokes_and_putts`-runde med 9 av 18 hull padded) — alle hånd-regnede - forventninger stemte eksakt, inkl. det kritiske tilfellet (padded runde - ga nøyaktig 36 putt/18, strokes_only-runden talte 0 mot totalen); (b) - ekte typesjekket produksjonsbuild + import-sjekk av hele FastAPI-appen - i det faktiske prod-imaget (ikke bare syntaks) + et ekte browserbesøk - som viste reelle, korrekt utregnede tall for `hei@erol.no` sine 2 - fullførte runder. - **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt for - databaseskrivingen (punkt 2) og fargevalget (punkt 3, "to farger i stedet" - for anbefalt én); resten bygget direkte på brukerens egen presise - instruks. Ingen migrasjon. `docker compose up -d --build teecup_api - teecup_frontend` (kjørt to ganger — én gang for hovedleveransen, én gang - for `decodeURIComponent`-fiksen over). `/health`/`/dashboard`/ - `/my-rounds/stats`/`/my-rounds/course/...` alle bekreftet 200 og - konsoll-feilfrie i en ekte innlogget nettleser-sesjon, `teeoff.no` - upåvirket. -- **Statistikk-siden utvidet: miss-retning, tidsvindu + forrige-periode- - sammenligning — BYGGET, TESTET OG LIVE (2026-07-28), samme dag, rett - etter forrige punkt:** brukeren reiste tre ting samtidig — ønsket - miss-RETNING (ikke bare treff%), trender, og et presist spørsmål om - hvorvidt GIR-fra-score-og-putt-inferens faktisk var forstått/utnyttet. - **Siste punkt avklart, ikke en bug:** bekreftet at `isGir = score - putts - <= par - 2` (allerede i `round-stats.tsx`, portert uendret til det nye - aggregerte endepunktet dagen før) ER akkurat denne generelle inferensen — - fungerer for ALLE score/putt/par-kombinasjoner, ikke bare brukerens - eksempel. Eneste reelle begrensning (forklart, ikke fikset): krever - putt-tall, så `strokes_only`-runder får aldri en GIR-verdi — score alene - er nesten aldri nok til å BEVISE GIR (en scrambling-birdie fra utenfor - green gir identisk score som en ekte GIR+1-putt). - **Miss-retning bygget:** `_summarize_rounds` i `app/routers/rounds.py` - utvidet med `fairway_left_pct`/`fairway_right_pct` (samme - `fairway_tracked`-pool som treff%) og `green_miss_long/short/left/ - right_pct` (egen pool — hull der `approach_result` er registrert og ≠ - "hit", UAVHENGIG av om putt er kjent, samme adskilte spor som - `round-stats.tsx` sin `missedGreen`/`missDir`). Ny `MissBar`-komponent i - `rounds-stats-summary.tsx` (fordelingsbar, samme visuelle idé som - `SegmentedBar`/`DistributionBar` andre steder i appen). - **Tidsvindu + forrige-periode bygget:** ny `StatsWindow`-type (`Literal`) - og `_resolve_stats_window()` — brukeren ba eksplisitt om BÅDE en full - velger (Siste runde/5/10/Denne måneden/I år/Siste år/Alltid) OG - sammenligningspiler mot forrige periode (utover det opprinnelig - anbefalte "kun siste 5 vs. alltid, ingen piler"). Én ENSARTET regel for - "forrige periode" på tvers av alle vindutyper — samme LENGDE (antall - runder for rullerende antall-vinduer, antall DAGER for dato-vinduer) - rett før gjeldende vindus start — i stedet for å måtte spesialdefinere - "forrige måned"/"forrige år" ulikt for hver kalenderbasert type. - `GET /rounds/stats/summary?window=...` returnerer nå `{window, current, - previous}` (`RoundStatsWindowSummary`) — samme `_summarize_rounds()`- - funksjon kalt to ganger på to ulike round_id-mengder, ingen duplisert - aggregeringslogikk. Frontend fikk en ny `Delta`-komponent (pil + farge, - farget etter en eksplisitt `goodDirection`-per-tall — lavere er bedre - for til-par/putt/chip/bunker/straffeslag/anywayslag, høyere er bedre for - treff-/rednings-prosentene, INGEN farge på de rene miss-retnings-tallene - siden venstre/høyre ikke er "bedre/verre"). - **Verifisert i to lag:** (a) en frittstående Python-simulering av - `_resolve_stats_window()` mot syntetiske datasett — rullerende antalls- - vindu (riktig current/previous-splitt, riktig tomt `previous` når for få - runder finnes), dato-vindu (kalender-til-dato + rullerende forrige - periode), og en isolert miss-retning-poolingstest (`None`-verdier - korrekt ekskludert fra nevneren); (b) ekte typesjekket produksjonsbuild - (måtte rette en TypeScript-nullbarhets-feil underveis — `current`/ - `previous` pakket ut via en IIFE inni JSX for at TS skulle smalne begge - riktig), full import-sjekk av hele FastAPI-appen i prod-imaget, OG et - ekte browserbesøk som viste reelle tall (fairway-miss 36/43/21, - green-miss-retning 0/75/13/13) + bekreftet vindu-velgeren fungerer - (klikket "Siste 10", pillen ble grønn, tallene oppdaterte seg) + et - direkte `fetch()`-kall fra siden selv som bekreftet `{window, current, - previous}`-formen (2 runder i `current`, korrekt tomt `previous` siden - brukeren kun har 2 fullførte runder totalt). - **Rullet ut live 2026-07-28**, ingen migrasjon, `docker compose up -d - --build teecup_api teecup_frontend`. Begge containere boot-et rent, - `/health` → 200, anonymt `GET /rounds/stats/summary?window=last_5` → 401 - (bekrefter query-parameteren ruter riktig gjennom Caddy/rewrites), - `teeoff.no` upåvirket. -- **Statistikk-siden: visuell v2 (kompass-diagram + donuter), HÅNDKODET og - BROWSERVERIFISERT MED EKTE ITERASJON, LIVE (2026-07-28), samme dag:** - brukeren delte et referansebilde av en konkurrent-app sin statistikk- - skjerm (donuter med sentertall, kompass for miss-retning) og ba om noe - tilsvarende. Skrev først et fullt V0-prompt (data-kontrakt, komponent- - for-komponent) — brukeren var tom for V0-credits og spurte eksplisitt om - jeg kunne "gi promptet til meg selv" og bygge det direkte, siden Chrome - DevTools MCP nå gjør det mulig å faktisk SE resultatet underveis (ulikt - tidligere håndkodede runder denne uken som kun var typesjekket). - **Backend:** tre nye felt i `RoundStatsSummary`/`_summarize_rounds` - (`app/routers/rounds.py`) — `putt_dist_one_pct`/`two_pct`/ - `three_plus_pct`, EGNE eksakte bøtter (`putts==1`/`==2`/`>=3`, samme som - `round-stats.tsx` sin `puttCategories`), bevisst forskjellig fra det - allerede eksisterende `one_putt_pct` (som bruker `<=1` — en annen, - allerede etablert rate, ikke en distribusjon). - **Frontend, `rounds-stats-summary.tsx` skrevet om betydelig:** ny - `GirCompass` — greentreff-prosenten i midten (grønn fremheving), fire - retningsceller rundt (Langt=topp, Kort=bunn, Venstre=venstre, - Høyre=høyre), samme visuelle språk som den allerede etablerte - `DirectionCross`/`DirButton` fra `round-detail.tsx` (plusstegn-rutenett, - `min-h-16 rounded-2xl border`-celler) — bevisst IKKE fargekodet - grønn/oransje på retningscellene (retning er beskrivende, ikke god/ - dårlig), kun sentercellen. Ny gjenbrukbar `Donut`-komponent - (conic-gradient, sentertall + valgfri forklaringsliste via - `showLegend`) brukt til BÅDE puttfordelingen (3 segmenter: 1-putt/ - 2-putt/3-putt+) og to enkle rednings-gauger (scrambling/sand save, ett - segment). Bevisst IKKE et flyt-/beslutningstre-diagram for scrambling/ - sand save (som referansebildet hadde) — det er to uavhengige prosenter i - datamodellen, ikke en forgrening, et flytdiagram ville antydet en - struktur som ikke finnes. Bevisst INGEN trendlinje (referansebildets - fjerde element) — for lite rundehistorikk til å vise noe meningsfullt - ennå, samme vurdering som tidligere samme dag. - **Reelt funn UNDER selve visuell iterasjon, ikke bare kodegjennomgang:** - første versjon av "Redning"-kortet viste samme tall TO GANGER per gauge - (`Donut` sin egen auto-genererte forklaringsliste "● Scrambling 9 %" RETT - OVER en egen, manuelt lagt til "Scrambling"-bildetekst) — sett direkte i - et ekte skjermbilde, ikke antatt. Fikset ved å legge til en - `showLegend`-prop på `Donut` og slå den av for de to gaugene, beholdt kun - min egen kompakte bildetekst+delta under ringen. - **Verifisert:** ekte typesjekket produksjonsbuild (to runder — én for - hovedversjonen, én for legend-fiksen), full backend-import-sjekk i - prod-imaget, OG ekte skjermbilder tatt FØR og ETTER legend-fiksen i en - innlogget nettleser-sesjon (samme mønster som sticky-kolonne-bug-fiksen - tidligere denne uken) — kompasset viser reelle tall (39 % greentreff, - 75 % kort, 13/13 % venstre/høyre, 0 % langt for `hei@erol.no` sine 2 - runder), puttfordelings-donuten viser korrekt fargede segmenter (17/61/ - 22 %), vindu-velgeren fungerer fortsatt uendret (klikket "Siste 5" etter - redesignet, ingen konsoll-feil). Ingen migrasjon. - **Rullet ut live 2026-07-28**, `docker compose up -d --build teecup_api - teecup_frontend` (kjørt to ganger). `/health` → 200 begge ganger, - `teeoff.no` upåvirket. - **Oppfølging, samme dag:** brukeren ba om at fairway-baren sin venstre/ - høyre-bom bruker SAMME farge (i stedet for to ulike chart-farger) -- - begge er tross alt bare "bom", ingen grunn til å skille dem visuelt. - Byttet begge til `--brand-orange` (appens etablerte "bom/over par"-farge), - beholdt grønn kun for selve fairwaytreffet. Verifisert med et nytt - skjermbilde. Rullet ut, ingen migrasjon. -- **"Til par: med vs. uten"-splitt, BYGGET, TESTET OG LIVE (2026-07-28), - samme dag:** brukeren ba eksplisitt om snitt-til-par splittet på om en - hull-hendelse inntraff eller ikke -- greentreff, fairwaytreff, bunker, - OG anywayslag (eksplisitt fremhevet). Fire nye par felt i - `RoundStatsSummary`/`_summarize_rounds` (`app/routers/rounds.py`): - `avg_to_par_with/without_gir`, `_fairway_hit/miss`, `_with/without_bunker`, - `_with/without_anyway` -- pooler ENKELTHULL på tvers av alle runder i - perioden (ikke per-runde-snitt, siden dette er hull-nivå-betingelser), - samme `diff()`-formel som `round-stats.tsx` sin `avgToParWithGir`/ - `avgToParFairwayHit`/`avgToParWithBunker` bruker for én runde (portert - uendret) -- anywayslag-splitten er en ny, konsekvent utvidelse av - akkurat samme mønster (fantes ikke fra før for enkeltrunder heller). - Ny `CompareToPar`-komponent i `rounds-stats-summary.tsx`: to bokser side - om side, den med FAKTISK lavest til-par denne perioden fremheves grønn - -- ingen hardkodet antakelse om hvilken side som "skal" vinne (bekreftet - reelt i data: "Med anywayslag" var faktisk verre enn "Uten anywayslag" - som forventet, men "I bunker" var marginalt BEDRE enn "Ikke i bunker" - for denne brukerens 2 runder -- fremhevingen fulgte automatisk det - virkelige tallet, ikke en antakelse). - **Verifisert:** en frittstående Python-simulering av alle fire splittene - mot et hånd-konstruert 5-hulls datasett (eksakte forventede gjennomsnitt - regnet ut for hånd og sammenlignet), full backend-import-sjekk i - prod-imaget, ekte typesjekket build, OG et ekte skjermbilde i innlogget - nettleser som viste reelle, korrekt utregnede og korrekt fargede tall - for alle fire kategoriene. Ingen migrasjon. - **Rullet ut live 2026-07-28**, `docker compose up -d --build teecup_api - teecup_frontend`, `/health` → 200, `teeoff.no` upåvirket. - -- **Gjennomgang av frittstående runder (ADR-033) + det viktigste hullet - lukket: faktisk (beregnet) HCP, BYGGET, SCRATCH-/BROWSERVERIFISERT OG - LIVE (2026-07-28, ADR-038):** brukeren ba om en vurdering av om alt var - tenkt gjennom for single-runder. Fant ved grep at hele WHS-indeksmotoren - (`handicap_index_from_differentials`/`low_handicap_index`/ - `apply_index_caps` i `handicap_engine.py`, 41/41 testet siden ADR-033) - ALDRI ble kalt fra noe API-endepunkt — `round_participant. - score_differential` ble regnet og lagret per runde, men `app_user. - handicap_index` endret seg kun manuelt. Bekreftet eksplisitt i - `rounds.py` sin egen moduldoc ("skjer IKKE automatisk her -- eksplisitt - uavklart punkt i ADR-033"). Sekundære, mindre hull notert samtidig - (offline-kø kun for turnering-scorekortet, ikke frittstående runder; - ingen Stableford; ingen rundedeling/visibility) — brukeren valgte å ta - tak i HCP-hullet. - **To load-bærende design-avklaringer** (AskUserQuestion, se ADR-038): - (1) hver INNLOGGET deltaker (ikke bare eieren) styrer sin egen - eksklusjon fra faktisk HCP; (2) faktisk HCP designes NÅ for å kunne - inkludere begge kilder (frittstående runder OG en fremtidig turnering- - kilde), men v1 bygger kun runder-delen — løst med ett bevisst tynt, - navngitt skjøtepunkt (`_gather_qualifying_differentials`), IKKE en ny - generell "scoring record"-tabell (for tidlig abstraksjon). - **Ny migrasjon `030_actual_handicap_index.sql`:** `app_user. - computed_handicap_index`/`computed_handicap_index_updated_at` (den - faktiske, beregnede WHS-indeksen — ALDRI direkte redigerbar, kun - avledet), `round_participant.exclude_from_handicap` (manuell opt-out, - uavhengig av den automatiske `counts_for_handicap`-kvalifiseringen), - `round.play_format` (`stroke`/`match`, selvdeklarert — ingen egen - match-motor for frittstående runder), `handicap_history.source` - (`manual`/`computed` — gjenbruker EKSISTERENDE tabell fra ADR-031- - oppfølgingen i stedet for en parallell historikk-tabell, siden Low - Handicap Index/cap (Rule 5.7/5.8) trenger nøyaktig samme - dato+indeks-form som allerede fantes der). - **WHS-kilde lest og lagt til grunn** (`WHS_Rules_of_Handicapping_2024. - pdf`, Rule 3.3): matchspill-scorer ER teknisk et gyldig HCP-grunnlag - under WHS, MEN et konsedert/ikke-utspilt hull krever en subjektiv "most - likely score" TeeCups rene slagregistrering ikke har noen vei til å - representere presist — begrunner "spør (med anbefalt eksklusjon), ikke - tving"-designet i stedet for et hardkodet forbud mot matchspill i - HCP-grunnlaget. - **Backend (`app/routers/rounds.py`):** ny `_recompute_computed_ - handicap_index(conn, user_id)` — henter de ≤20 nyeste kvalifiserende - differensialene, kaller den allerede-testede motoren, henter tidligere - `computed`-historikk for Low HI-cap (hopper bevisst over capping ved - FØRSTE beregning noensinne — Low HI er udefinert før en indeks er - etablert). Kalt fra `complete_round` (alle deltakere med `user_id`), - `update_participant` (eksklusjon endret på en ALLEREDE fullført runde), - `remove_guest_participant` (dekker faktisk enhver ikke-eier-fjerning, - til tross for navnet) og `delete_round` (fanger berørte brukere FØR - kaskade-slettingen fjerner radene). `update_participant` sin - autorisasjon utvidet presist: en ikke-eier kan KUN sende - `exclude_from_handicap`, KUN på sin egen rad (`_OWNER_ONLY_ - PARTICIPANT_FIELDS`-sjekk + eksplisitt eier-eller-selv-gate) — alle - andre felt forblir strengt eier-only, uendret. - **Backend (`app/routers/auth.py`):** `Me` fikk `computed_handicap_ - index`/`computed_handicap_index_updated_at`. Ny `POST /auth/profile/ - handicap/apply-computed` — kopierer gjeldende beregnet verdi inn i det - manuelt satte HCP-et (samme skrivevei/historikk-logging som en vanlig - manuell PATCH, kun `source='manual'`). `GET /auth/profile/handicap- - history` eksponerer nå `source` også. - **Frontend:** `/my-rounds/new` fikk en "Spilleform"-bryter - (Slagspill/Matchspill) — velges Matchspill, forhåndsutfylles (ikke - tvinges) en eksklusjons-avkrysning med forklarende tekst. - `round-detail.tsx` fikk en "Matchspill"-badge i headeren, en - spilleform-bryter i `EditRoundPanel` (ren metadata, redigerbar uansett - fullført-status), og `EditParticipantPanel` fikk en ny `restricted`- - modus: en ikke-eier som redigerer SIN EGEN rad, ELLER EIEREN etter at - runden er fullført, ser KUN eksklusjons-toggelen (ikke tee/HCP/navn/ - statistikk) — `PlayerList` sin "Rediger"-knapp vises nå også for en - ikke-eiers egen rad, ikke bare for eieren. `/account` fikk et nytt - "Faktisk HCP (beregnet)"-kort (verdi + sist-beregnet-dato + - "Bruk som mitt HCP →"-knapp), og HCP-historikk-listen viser nå - "beregnet"/"manuelt" per rad. - **Scratch-verifisert grundig, 111/111 sjekker** (isolert - `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API- - container via ekte HTTP, samme mønster som hele prosjektet): - hånd-utregnet WHS-matte bekreftet PRESIST (tre runder med kjente - differensialer [10.0, 12.0, 14.0] ga nøyaktig 8.0 — beste-1-av-3 med - -2.0-justering fra Rule 5.2a-tabellen), <3 tellende runder gir fortsatt - `null` (ikke en feil), sletting av en tellende runde regner faktisk HCP - på nytt (tilbake til `null` under 3), matchspill-runde eksplisitt - ekskludert ved opprettelse telles korrekt IKKE med, "Bruk som mitt - HCP"-overføring bekreftet (+ `handicap_history` bærer nå begge kilder), - og hele autorisasjonsmatrisen for eksklusjons-feltet (medspiller nektes - andre felt og eierens rad, men kan endre EGEN eksklusjon; eieren kan - fortsatt overstyre medspillerens). `test_isolation.sql` 12/12 uendret. - **Reelt funn UNDER selve scratch-oppsettet, ikke i produksjon:** første - forsøk på en engangs API-container monterte kildekoden til feil sti - (`/app/app` i stedet for `/srv/app`, som er `Dockerfile` sin faktiske - `WORKDIR`) — containeren boot-et rent, men kjørte stille det GAMLE, - innbakte imagekoden uendret. Fanget FØR noe ble stolt på, ved at - `/auth/me` manglet det nye feltet helt i et faktisk API-svar — rettet - ved å montere til riktig `/srv`-sti, deretter bekreftet på nytt. - **Ekte nettleser-verifisert** (Chrome DevTools MCP, engangs `next dev`- - container mot scratch-backend — samme "sett resultatet, ikke bare - typesjekk det"-arbeidsmåte som resten av uken): logget inn via ekte - magic-link, opprettet en matchspill-runde → bekreftet - forhåndsutfylt-men-overstyrbar eksklusjonsavkrysning i skjemaet, - "Matchspill"-badge i rundens header, fullførte runden → bekreftet - `EditParticipantPanel` automatisk bytter til restriktert modus (kun - eksklusjons-toggel, ingen av de andre feltene), lagret en endring → - bekreftet ekte `PATCH .../participants/{id}` 200 i nettverksfanen. - Ekte typesjekket produksjonsbuild (alle 24 ruter) kjørt og bekreftet. - **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: migrasjon - 030 kjørt mot ekte `teecup_db` (alle fire nye kolonnesettene bekreftet, - `test_isolation.sql` fortsatt 12/12 mot ekte database), deretter - `docker compose up -d --build teecup_api teecup_frontend`. Begge - containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` - upåvirket. Verifisert presist at den nye ruten faktisk når FastAPI: - anonymt `POST /auth/profile/handicap/apply-computed` over ekte https ga - korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404. - **Bevisst utenfor omfang, notert i ADR-038:** turnering-/organisasjons- - scoring teller fortsatt ikke mot faktisk HCP (venter på videre - ADR-037-arbeid), ingen egen "aging"-bakgrunnsjobb utover at spørringen - alltid henter kun de 20 nyeste differensialene. - -- **Ekte spillformer for frittstående runder (match/skins/fourball/ - foursome/greensome/scramble), BACKEND BYGGET OG SCRATCH-VERIFISERT, - IKKE ENNÅ RULLET UT MOT EKTE SYSTEMER (2026-07-28, ADR-039):** reist av - brukeren rett etter ADR-038: "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." Fire load-bærende avklaringer (AskUserQuestion, - to runder) FØR bygging — bruker valgte det bredeste omfanget i alle - runder: to sider (Beslutning A), ALLE fire par-/lag-underformater fra - start inkl. de to delt-ball-krevende (Beslutning B), skins med BEGGE - akser konfigurerbare av oppsetteren (netto/brutto OG rullerer/deles, - Beslutning D), og gå rett til migrasjon+motor samme økt (ikke bare - dokumentere). - **Kjerneinnsikt som gjorde dette trygt å bygge fort:** hele match-play- - motoren (`handicap_engine.py` sin `Format`/`AllowanceStrategy`-familie/ - `match_play_strokes`/`compute_match_state`) og hele "delt-ball vs. - individuell"-mønsteret (`hole_score.match_participant_id` NULLABLE, - delt-ball-rader identifisert av `team_side` alene) fantes ALLEREDE, - bygget og produksjonskjørt for org-scopede turnering-matcher. Denne - runden PORTERER dette mønsteret til frittstående runder (nye - `round_side`/`round_participant.round_side_id`/`round_participant. - playing_handicap`/`round_hole.round_side_id`) i stedet for å finne opp - noe nytt — kun skins (ingen turnering-motstykke) fikk EKTE ny - motorkode (`compute_skins`, 7 nye tester, 50/50 i `test_handicap_ - engine.py`). `app/handicap.py` sin `_SIDE_IS_UNIT` omdøpt til - `SIDE_IS_UNIT` (gjort delt for gjenbruk, samme "fjern understrek når - et andre bruksted dukker opp"-mønster som tidligere runder). - **Skjema, migrasjon `031_round_play_formats.sql`:** `round.play_format` - utvidet til 8 verdier, nye `round.skins_scoring`/`skins_tie_handling`, - ny `round_side`-tabell (nøyaktig to per runde, håndhevet i app-laget - som ADR-011s to-lags-grense), `round_participant.round_side_id`/ - `playing_handicap` (sistnevnte ALDRI det samme som det eksisterende - `course_handicap_snapshot` — den absolutte WHS-verdien rørt av INGEN - av denne rundens kode), `round_hole.round_participant_id` gjort - NULLABLE + ny `round_hole.round_side_id` + XOR-CHECK + to partielle - unike indekser (samme mønster som org-scopet `hole_score`, migrasjon - 001). - **Reelt, bekreftet funn UNDER selve designet (ikke antatt), avklart - eksplisitt med bruker FØR bygging:** foursome/greensome/scramble har - ÉN kombinert score per SIDE per hull -- INGEN individuell score - finnes i det hele tatt å bygge en Score Differential fra. Løst - (Beslutning E, bekreftet av bruker): disse deltakerne får - `counts_for_handicap` ALDRI sann for disse rundene -- og viste seg, - presist verifisert i scratch, å følge HELT AUTOMATISK av den - eksisterende `complete_round`-logikken uten noen kodeendring i det - hele tatt (en delt-ball-deltaker har null individuelle `round_hole`- - rader, så `played_count` blir alltid 0, som `round_counts_for_ - handicap` allerede tolker som "teller ikke" for både 9- og - 18-hulls-intensjon). - **API (`app/routers/rounds.py`):** `POST/DELETE .../sides` (eier-only, - maks to, avvist for slagspill/skins), `ParticipantCreate`/ - `ParticipantUpdate` fikk `round_side_id` (eier-only reassignment, - validerer siden hører til samme runde), `_recompute_side_handicaps` - (porterer `compute_and_store_side_handicaps` — venter på at siden når - forventet spillerantall FØR den skriver noe, samme - "ikke komplett ennå = ikke skriv"-filosofi som originalen), - `_relative_strokes_for_round` (porterer `relative_strokes_for_match`), - nye `GET/PATCH .../sides/{id}/holes/{n}` (delt-ball-scoring, kun - slagtall — ingen av de andre detalj-feltene gir mening for en delt - ball), og et nytt lese-endepunkt `GET .../format-result` som regner - løpende matchstatus (gjenbruker `compute_match_state`/`HoleResult` - uendret) for de to-sidede formatene, eller en skins-tavle - (`compute_skins`) for skins — aldri lagret, alltid avledet ved lesing. - **To reelle bugs funnet OG fikset UNDER scratch-testing, ingen nådde - produksjon:** (1) side-tildelings-recompute kjørte FØR responsen ble - hentet i stedet for ETTER — testen fanget dette presist (forventet - `playing_handicap` i responsen, fikk `null` fra FØR omregningen); (2) - individuell-ball-gren i format-result-spørringen nøkkel-forvekslet - "enhet" (satte `round_side_id` som nøkkel i stedet for - `round_participant_id`, mens `side_net()`-oppslaget forventet - deltaker-id) — ga et tomt `hole_results` til tross for gyldige - registrerte scorer, fanget da `match_holes_played` kom ut som 0 i - stedet for det forventede 3. - **Scratch-verifisert grundig, 96/96 sjekker** (isolert - `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API- - container via ekte HTTP, samme mønster som hele prosjektet): en - hånd-utregnet 3-hulls singles-match (hcp 5 vs. 10, riktig slagmottak - på de fem vanskeligste hullene) ga eksakt `lead=0`/"AS"/riktig - hole-for-hole-mønster; fourball bekreftet INDIVIDUELL (ikke kombinert) - 90 %-beregning per spiller, korrekt "ikke komplett ennå" før andre - spiller på siden var tildelt; foursome bekreftet 18 round_hole-rader - opprettet på SIDEN ved side-opprettelse (før noen deltaker lagt til), - korrekt kombinert 50 %-Playing-Handicap først når begge var tildelt, - ekte delt-ball-scoring via det nye sub-endepunktet, OG at - `counts_for_handicap` ble `False` for begge etter fullføring; skins - (netto+carry) ga eksakt `{sk3: 2.0}` for et 2-hulls scenario med et - bevisst konstruert uavgjort-så-carry-så-outright-vinn-mønster, OG - bekreftet skins teller NORMALT mot faktisk HCP etter full fullføring - (ulikt match). `test_isolation.sql` 12/12 uendret (additiv migrasjon). - Alle 50 handicap_engine-tester (43 eksisterende + 7 nye skins) grønne. - **IKKE bygget i denne runden, bevisst utsatt (Beslutning F):** - frontend — ingen skjerm for å opprette sider, tildele deltakere, - konfigurere skins, eller vise løpende matchstatus/skins-tavle. Backend - er fullt funksjonelt og testet, men ubrukelig fra selve appen inntil - frontend bygges i en egen, senere runde (samme lagdelings-mønster som - ADR-038: motor/skjema/API FØR frontend). - **Rullet ut mot ekte systemer 2026-07-28**, bruker bekreftet - eksplisitt: migrasjon 031 kjørt mot ekte `teecup_db` (nye kolonner/ - `round_side`-tabell bekreftet, `test_isolation.sql` fortsatt 12/12), - deretter `docker compose up -d --build teecup_api`. Ren boot, - `/health`/`/dashboard` → 200, ny rute bekreftet nåbar (anonymt - `POST .../sides` → 401, ikke en rå 404), `teeoff.no` upåvirket. - -- **Oppfølging samme dag: manglende minimums-spiller-håndhevelse fanget - og fikset, BYGGET OG SCRATCH-VERIFISERT, RULLET UT LIVE (2026-07-28):** - brukeren påpekte presist et hull ADR-039 selv ikke fanget opp: "Er det - match-spill, skins eller lagspill så MÅ det jo være flere spillere. - Dette fanges ikke opp." Riktig — ingenting hindret å fullføre en - "match" med kun eieren, eller la flere spillere enn formatet tillater - havne på samme side. Bekreftet med bruker (AskUserQuestion): skins - krever minst 3 spillere (2 gjør skins i praksis identisk med en vanlig - match — 3+ er der en oppsamlet, uavgjort pott faktisk gir mening). - **Bygget, ren Python-logikk, INGEN migrasjon:** ny - `_format_setup_status()` — slagspill alltid klar; skins krever minst - 3 deltakere; to-sidede formater (match/fourball/foursome/greensome/ - scramble) krever NØYAKTIG to sider, INGEN uassignerte deltakere, og - hver side nøyaktig riktig spillerantall for formatet - (`_SIDE_PLAYER_COUNT`, allerede definert). Ny `setup_complete`/ - `setup_message` på `RoundOut` (alltid synlig, uansett format/status). - `complete_round` avviser nå (409 `SETUP_INCOMPLETE`) hvis oppsettet - ikke er komplett — FØR noe regnes ut. Ny `_check_side_capacity()` - avviser (409 `SIDE_FULL`) proaktivt ved SELVE tildelingen (både - `POST .../participants` med `round_side_id` og `PATCH .../ - participants/{id}`), i stedet for å først oppdage overtallet ved - fullføring — med eksplisitt unntak for en no-op-reassignment til - samme side (en deltaker teller ikke seg selv ut av plassen sin egen - side har). - **Scratch-verifisert grundig, 122/122 sjekker** (samme isolerte - scratch-oppsett som resten av runden — full regresjon av alle - tidligere 96 sjekker PLUSS 26 nye): 3. spiller avvist på en full - match-/foursome-side (409 SIDE_FULL), `setup_complete` korrekt False - ved kun 1 av 2 sider / uassignerte deltakere / for få skins-spillere, - fullføring korrekt avvist (409 SETUP_INCOMPLETE) i alle disse - tilstandene, og korrekt True (+ vellykket fullføring) først når - oppsettet faktisk er komplett for formatet. `test_isolation.sql` - uendret (ingen skjemaendring). - **Rullet ut live 2026-07-28**, ingen migrasjon, kun - `docker compose up -d --build teecup_api`. Ren boot, `/health`/ - `/dashboard` → 200, `teeoff.no` upåvirket. -- **Frontend for ADR-039 (sider/skins/delt-ball-scoring) BYGGET, BROWSER- - VERIFISERT OG LIVE (2026-07-28), samme dag:** ingen backend-endring i - denne runden (alt allerede live) — ren frontend-jobb, testet reelt i - nettleser mot en isolert scratch-backend (Chrome DevTools), ikke bare - typesjekk. - **`new-round.tsx`:** spilleform-velgeren utvidet fra to (Slagspill/ - Match) til alle åtte format (Skins/Fourball/Foursome/Greensome/ - Scramble 2/4 lagt til), med en ny skins-konfigurasjonsseksjon (netto/ - brutto, rullerer/deles) som kun vises for `play_format="skins"` og et - forklarende "du setter opp sidene inni runden etterpå"-notat for de - to-sidede formatene (ADR-039 Beslutning A -- sider kan ikke opprettes - før deltakerne finnes). - **`round-detail.tsx` (hoveddelen):** ny `SidesPanel` (manage-fanen) -- - opprett/slett de to sidene, tildel/fjern deltakere (kompakte - "→ Side"-hurtigknapper, deaktivert når siden er full), viser - `playing_handicap` per side. Ny `FormatResultPanel` -- henter - `GET .../format-result`, viser løpende matchstatus (oversetter - motorens bokstavelige "(A)"/"(B)" til faktiske side-navn via - `round.sides[0]/[1]`, samme sorteringsrekkefølge som backend) eller en - skins-tavle (sortert synkende). `setup_complete`/`setup_message` - gater nå "Fullfør runde"-knappen klientside også (server er fortsatt - autoritativ). For delt-ball-formatene (foursome/greensome/scramble): - ny `SideScorecardGrid` (rader = sider, ikke spillere) + ny, forenklet - `SideScoreWizard` (kun slagtall, ingen putt/detalj-steg) mot de - eksisterende `GET/PATCH .../sides/{id}/holes/{n}`-endepunktene. - **To reelle stale-state-bugs funnet UNDER selve browserverifiseringen - (ikke i kodegjennomgang), begge fikset før utrulling:** - 1. `SidesPanel` sin `assign()` oppdaterte kun deltaker-listen lokalt - (via `onPatchParticipant`) -- `setup_complete`/`setup_message` - (server-beregnet) ble stående utdatert etter en vellykket - side-tildeling ("ikke tildelt en side" fortsatte å vises til tross - for at begge var tildelt). Fikset: `assign()` kaller nå - `onSidesChanged()` (full runde-refetch) etter en vellykket PATCH. - 2. `FormatResultPanel` sin refetch var kun koblet til hull-registrering - og WebSocket-signaler, ikke til side-/deltaker-tildeling -- - matchstatus ble stående på "venter..." selv etter at oppsettet var - komplett og "Fullfør runde" allerede var aktivert. Fikset ved å - bumpe `formatResultRefreshTick` ved HVER vellykket `loadRound()` - (enklere og mer robust enn å spore hvert enkelt kallsted som kan - påvirke handicap-beregningen). - **Verifisert grundig i en isolert scratch-nettleserøkt** (fersk - `teecup_scratch`-database, isolert scratch-MinIO, engangs API- - container, ekte `next dev` mot scratch-backend, Chrome DevTools MCP -- - ekte innlogging via magic-link, ekte profil-fullføring): tre komplette - runder bygget og spilt gjennom UI-et alene, ende-til-ende: - - **Match:** opprettet med eksklusjons-avkrysning forhåndshuket, - opprettet to sider, tildelte eier+gjest, bekreftet `playing_handicap` - (60/20) vist riktig, scoret hull 1 (4 mot 6) via `ScoringWizard` - (ubrørt komponent), bekreftet `FormatResultPanel` viste "1 UP - (Erol)" + riktig fargede hull-merker -- kryssjekket med - `aria-label`-attributtet direkte via `evaluate_script` for å bekrefte - semantisk korrekt side-navn bak den rå A/B-bokstaven. - - **Skins:** konfigurasjons-UI-et (netto/brutto, rullerer/deles) bekreftet - visuelt, opprettet med kun 1 spiller (satte-message "krever minst 3"), - la til to gjester til (meldingen forsvant idet den tredje ble lagt til, - "Fullfør runde" aktivert), scoret hull 1 for alle tre (via direkte - API-kall for hastighet, samme kontrakt som UI-et bruker), bekreftet - skins-tavlen -- **hånd-regnet og kryssjekket eksakt**: netto 1/4/3 for - de tre spillerne (course handicap 60/12/6, alle mottar 1 slag på - hull 1 unntatt eieren som mottar 4) ga korrekt "1 skin" til laveste - netto. - - **Foursome:** bekreftet "Opprett begge sidene..."-meldingen i Score- - fanen FØR sidene fantes (ingen krasj), opprettet to sider, la til tre - gjester, scoret hull 1 via `SideScorecardGrid`/`SideScoreWizard` - (5 mot 5 -- observerte LIVE at gridet oppdaterte seg bak selve - veiviseren), fant OG fikset de to stale-state-bugene over midt i - denne runden (glemte først å tildele spillerne til sider -- avdekket - nettopp fordi UI-et da IKKE viste feil tilstand, men en ekte utdatert - en), bekreftet til slutt `playing_handicap` kombinert riktig per side - (38/38 og 17/17) og at `FormatResultPanel` viste "1 UP (Rødt lag)" - -- kryssjekket for hånd at Rødt lag (høyere kombinert CH) mottar - slag på det vanskeligste hullet og derfor vinner nettoduellen 5 mot 5. - Ekte typesjekket + full produksjonsbuild kjørt på nytt ETTER - bug-fiksene (ikke bare før), alle 24 ruter listet. - **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen - migrasjon (ren frontend), `docker compose up -d --build - teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning, - ingen backend-kode rørt). Begge containere boot-et rent, `/health`/ - `/dashboard` → 200, `teeoff.no` upåvirket. **ADR-039 er dermed - fullstendig ferdig, backend og frontend, live.** -- **Auto-hopp i scoringsveiviserne, LIVE (2026-07-28), samme dag:** - brukeren ba om at "i det øyeblikket [scoren] nå registreres" skal - veiviseren hoppe videre av seg selv, uten å måtte trykke "Neste"/ - "Ferdig". Presiserende avklaring FØR bygging (AskUserQuestion): "Avstand - første putt" lå tidligere PÅ SAMME steg som selve putt-tallet i - `ScoringWizard` -- auto-hopp idet putt-tallet velges ville gjort - avstandsfeltet uoppnåelig (ingen annen inngang finnes). Løst ved å - splitte putt-steget i to (bekreftet anbefalt løsning): `WizardStep` - utvidet med et eget `"puttDistance"`-steg, `wizardStepsFor()` gir nå - `["strokes","putts","puttDistance"]`/`["strokes","putts","puttDistance", - "details"]` for de to høyere statistikknivåene. - **Mekanisme (samme mønster i `ScoringWizard` og den enklere - `SideScoreWizard` for delt-ball-formater):** to refs -- `enteredWithValueRef` - fanger om steget sitt eget felt ALLEREDE hadde en verdi idet steget ble - vist (et allerede utfylt hull skal ikke hoppe videre bare fordi - veiviseren åpnes, og "Forrige" tilbake til et allerede besvart steg skal - ikke re-trigge et nytt hopp), `firedRef` hindrer dobbelt-triggering. - Kun steg med ETT entydig felt (Slag, Putter, Avstand, samt hele - `SideScoreWizard` sitt eneste Slag-felt) auto-hopper -- "flere - detaljer"-steget (kølle/retning/chip/bunker/straffeslag/anywayslag) har - ingen enkelt "dette er ferdig"-verdi og beholder derfor "Neste"/ - "Ferdig"-knappen som manuell handling, bevisst uendret. - **Verifisert grundig i en isolert scratch-nettleserøkt** (fersk - database/MinIO/API-container, ekte `next dev`, Chrome DevTools): - full "full"-nivå-runde spilt gjennom Slag→Putter→Avstand (alle tre - auto-hoppet uten et eneste "Neste"-trykk) →detaljer (korrekt IKKE - auto-hoppet, krevde et bevisst "Ferdig"-trykk, som deretter gikk videre - til neste hull av seg selv). "Forrige" fra Putter tilbake til Slag - bekreftet trygt (viste den allerede valgte verdien, hoppet IKKE - automatisk fremover igjen). Delt-ball (`SideScoreWizard`, foursome) - bekreftet separat: valgt slagtall for "Rødt" hoppet umiddelbart til - "Blått" uten trykk. Ekte typesjekket + full produksjonsbuild kjørt før - utrulling, alle 24 ruter listet. - **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen - migrasjon (ren frontend), `docker compose up -d --build - teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard` - → 200, `teeoff.no` upåvirket. -- **Scorekort for spillformater (match/skins/fourball/foursome/greensome/ - scramble): to reelle bugs bekreftet og fikset, BYGGET, SCRATCH-/ - BROWSERVERIFISERT OG LIVE (2026-07-28):** brukeren ba om at scorekortene - faktisk sjekkes ved å simulere 3-4 spilte hull i hvert spillformat, med - et konkret forventningsbilde (en match bør vise hvem som vant hvilket - hull og HVORFOR -- brutto vs. netto, match-hcp). Simulert systematisk mot - en isolert scratch-backend (Python/urllib-testskript) FØR noe ble antatt - riktig -- fant to distinkte, bekreftede problemer, presentert til - brukeren som fikk velge omfang (AskUserQuestion) og valgte full løsning: - 1. **Ekte blokkerende bug:** foursome/greensome/scramble (ADR-039, - delt-ball -- score lagres PER SIDE, `round_hole.round_participant_id` - settes ALDRI for disse) viste 0 spilte hull/ingen score på - `/my-rounds/[id]/scorecard`, `/leaderboard` OG `/stats`, uansett - faktisk fremdrift -- disse tre sidene spurte alle kun mot - deltaker-endepunktet (`round_participant_id`), som strukturelt aldri - kan ha data for disse formatene. - 2. **Reell designmangel:** for match/fourball/skins var rå brutto/netto/ - stableford riktig, men "hvem vant hvilket hull, og hvorfor" fantes - KUN i `FormatResultPanel` (manage-fanen i `round-detail.tsx`) som et - rent vinn/tap-merke -- ingen synlige tall (brutto vs. netto, slag - mottatt) noe sted, og ingenting av dette på de dedikerte Scorekort-/ - Leaderboard-sidene brukeren faktisk testet. - **Backend:** ny `compute_skins_detail()` i `handicap_engine.py` - (hull-for-hull-forløp -- verdier/pott-før/tildelt/carried per hull), - `compute_skins()` omskrevet til en tynn wrapper rundt den (uendret - signatur/oppførsel, alle 50 eksisterende tester fortsatt grønne + 5 nye). - `GET /rounds/{id}/format-result` (ADR-039) utvidet med et nytt - `holes`-felt -- full hull-for-hull-oppløsning (brutto/netto/slag mottatt - PER enhet PER hull, pluss for fourball hvilken av de to partnernes netto - som faktisk talte for siden det hullet, R&A-regelen gjort synlig i - stedet for skjult). **Reell refactor-bug funnet OG fikset UNDER egen - scratch-verifisering, før noe ble stolt på:** en samlet `side_net()` for - BEGGE gren-typene (individuell-ball og delt-ball) brukte format-nivåets - `expected_players` (spiller-ANTALL, f.eks. 2 for foursome) som - fullstendighetssjekk også for delt-ball, der en side alltid er NØYAKTIG - ÉN enhet uansett spillerantall -- ga `match_holes_played=0`/tom - `holes`-liste for ALLE delt-ball-formater til tross for korrekt lagrede - side-scorer. Rettet med en egen `required_units`-variabel (1 for - delt-ball, `expected_players` for individuell-ball). `GET .../sides/ - {id}/holes` fikk samtidig et nytt `strokes_received`-felt (samme - allokeringsalgoritme, nå basert på sidens kombinerte `playing_handicap`). - **Frontend:** `round-scorecard.tsx` bruker nå SIDER (ikke deltakere) som - "enhet" for delt-ball-formater -- samme visuelle `ScoreBlock`-tabell, - bare mot `/sides/{id}/holes`, med en forklarende melding hvis sidene - ikke er opprettet ennå. Ny `MatchProgressTable`-seksjon (to-sidede - formater: Hull/Par/Side A/Side B/Resultat, brutto→netto per enhet, ikke- - tellende fourball-partner tonet ned i stedet for fjernet) og - `SkinsProgressTable` (skins: hull-for-hull med hvem som vant/hvilket - hull som rullet videre, netto med brutto i parentes). `round- - leaderboard.tsx`: to-sidede formater viser nå en `MatchStatusSection` - (status + hull-merker + lenke til scorekortets fulle oppløsning) i - stedet for en individuell rangering som uansett ikke gir mening for et - 1v1/lag-format (og alltid var tom for delt-ball) -- `RoundLeaderboardMini` - returnerer `null` for disse formatene i `round-detail.tsx` sin manage- - fane, siden `FormatResultPanel` allerede dekker akkurat det der. `round- - stats.tsx` viser en tydelig forklarende melding for delt-ball-formater - (individuell slag-for-slag-statistikk er strukturelt umulig der) i - stedet for en stille tom/misvisende side. - **Scratch-verifisert grundig, flere lag:** isolert `teecup_scratch`- - database + `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs - API-container (samme mønster som hele prosjektet), 55/55 - `handicap_engine`-tester, et Python/urllib-simuleringsskript som spilte - 4 hull i alle 6 ikke-trivielle formater og sammenlignet rå API-svar før/ - etter fiksen. **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome - DevTools MCP, engangs `next dev` mot scratch-backend, ekte innlogging): - opprettet alle 7 formatene (inkl. `stroke` som regresjonssjekk) via - ekte API-kall fra en innlogget nettleserøkt, besøkte deretter - scorekort/leaderboard/stats-sidene for hver -- foursome sitt tidligere - BLANKE scorekort viste nå korrekte side-tabs + reelle score + riktig - `Matchforløp`-tabell; fourball sin `Matchforløp` viste presist BEGGE - partnernes brutto/netto med den ikke-tellende partneren korrekt tonet - ned; skins sin hull-for-hull-tabell viste riktig vinner-navn og - "Uavgjort — rullet videre" nøyaktig der forventet; leaderboardets - matchstatus stemte hull-for-hull med scorekortets egen utregning (bevisst - kryssjekket for hånd); fanebytte mellom sider (Side A/Side B) bekreftet - å faktisk refetche og re-rendre riktig data. Ekte typesjekket + - produksjonsbuild (alle 24 ruter) kjørt både før og etter refactor-bug- - fiksen. `test_isolation.sql` uendret (ingen migrasjon, ren kode-endring). - **Rullet ut live 2026-07-28**, 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. -- **Offline-kø (ADR-028) utvidet til frittstående runder, BYGGET, - BROWSERVERIFISERT OG LIVE (2026-07-28):** brukeren ba om det som ble - identifisert som viktigste gjenstående hull etter forrige runde -- - ADR-028s offline-skrivekø var kun koblet til turnering-scorekortet - (`session-scorecard.tsx`), IKKE frittstående runder - (`round-detail.tsx`), nettopp der man oftest står alene ute på banen - uten dekning, og nå som frittstående runder er bekreftet appens - hovedfokus var dette et reelt hull i kjerneflyten. - **Enklere port enn originalen, ikke bare en kopi:** turnering- - scorekortets PATCH-endepunkter er en append-historie (krever egne - `pendingStrokes`/`pendingResults`-verdi-overlays for å vise queued - verdier). Frittstående runders `PATCH .../participants/{id}/holes/{n}` - og `PATCH .../sides/{id}/holes/{n}` erstatter derimot HELE hull-raden - per kall, og `statToPatchBody()` sine feltnavn matcher `ApiHole` 1:1 -- - en køet skriving kan dermed speiles direkte inn i - `holesByParticipant`/`holesBySide` med en enkel `{...h, ...body}`-spread, - ingen egen verdi-overlay nødvendig. Kun to lette `Set<string>` (nøkkel - `id:hullnummer`) beholdt, utelukkende til selve "lagret lokalt"- - indikatoren i veiviseren. - Samme kø/synk-mønster som originalen ellers: `navigator.onLine`-sjekk - FØR forsøk, `try/catch` rundt selve fetch-kallet som queuer ved en EKTE - nettverksfeil (ikke ved et avvist HTTP-svar -- det vises fortsatt som - vanlig feiltekst), auto-synk ved `window`s `online`-event, manuell - "Synkroniser nå"-knapp, og en engangs-sjekk for allerede køede - skrivinger fra en TIDLIGERE økt (f.eks. siden ble lukket mens offline) - ved mount. `flushPending()` matcher URL-mønsteret på hver synkronisert - kø-oppføring (`/participants/{id}/holes/{n}` vs. `/sides/{id}/holes/ - {n}`) for å vite hvilke deltakere/sider som trenger en ekte refetch - etterpå (reconciles bl.a. `strokes_received`, som den optimistiske - speilingen ikke kan regne ut selv). Banner ("Du er offline"/"N - endringer venter") lagt til rett under fane-velgeren, synlig uansett - fane. "Lagret lokalt · venter på synk"-indikator lagt til i BÅDE - `ScoringWizard` (vanlig scoring) og `SideScoreWizard` (delt-ball). - **Browserverifisert grundig, IKKE bare kodegjennomgang/build denne - gangen** (i motsetning til den opprinnelige ADR-028-runden, som - brukeren selv måtte teste manuelt siden intet nettleserverktøy var - tilgjengelig da) -- Chrome DevTools MCP sin ekte nettverks-emulering - (`Offline`, bekreftet at `navigator.onLine` faktisk flippet til - `false`) mot en isolert scratch-backend: registrerte slag+putter - offline for en vanlig runde -- bekreftet 2 kø-oppføringer i ekte - IndexedDB, optimistisk oppdatert scorekort-grid, "1 endring venter"- - banner, "Lagret lokalt"-indikator i veiviseren; koblet til nett igjen - -- bekreftet AUTOMATISK synk (ingen manuelt trykk), kø tom etterpå, OG - et direkte API-kall som bekreftet serveren faktisk hadde de riktige - verdiene (score=5, putts=1, strokes_received=1). Gjentok hele syklusen - for en ny foursome-runde via `SideScoreWizard` (delt-ball) -- samme - resultat, kø tom og server bekreftet score=4 for siden etterpå. Ingen - konsollfeil i noen av rundene. Ekte typesjekket + full produksjonsbuild - (alle 24 ruter) kjørt før utrulling. - **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen - migrasjon (ren frontend), `docker compose up -d --build - teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard` - → 200, `teeoff.no` upåvirket. -- **Turnering-scorekortets offline-flyt (den OPPRINNELIGE ADR-028, session- - scorecard.tsx) FAKTISK browserverifisert, samme dag (2026-07-28):** - eneste reelt gjenstående punkt fra forrige runde — offline-koden i - turnering-scorekortet er over ett år gammel (funksjonelt), men var ALDRI - browser-testet, kun kodegjennomgang/build (se opprinnelig ADR-028-notat). - Bygget en full isolert scratch-turnering fra bunnen via API for å nå - frem til selve scorekortet (organisasjon → turnering → to lag → to - spillere rostret som kapteiner → egendefinert bane med 18 hull + tee → - økt (`format=singles`, `scoring_mode=stroke`) → match → to - match-deltakere → begge lag låst) -- ingen slik full turnering-scaffold - fantes fra før i noe testskript denne uken (frittstående runder trenger - ikke dette apparatet i det hele tatt, ADR-033 Beslutning A), måtte bygges - fra grunnen ved å lese `tournaments.py`/`matches.py`/`courses.py` sine - faktiske endepunkt-kontrakter direkte. - **Verifisert identisk mønster som frittstående runder samme dag:** ekte - DevTools-nettverksemulering (`navigator.onLine` bekreftet `false`), - registrerte et slag (5, Spiller B, hull 1) offline -- bekreftet ÉN - kø-oppføring i ekte IndexedDB (`POST .../hole-scores`), "1 endring - venter"-banner, "Lagret lokalt · venter på synk"-tekst under - tallvelgeren, hull-navigasjonens sjekkmerke. Koblet til nett igjen -- - bekreftet AUTOMATISK synk (ingen manuelt trykk på "Synkroniser nå" - nødvendig), kø tom etterpå, "Bogey"-etiketten dukket opp (bevis på at - `refetchScorecard()` faktisk hentet det avledede resultatet fra - serveren), OG et direkte API-kall mot `.../scorecard` som bekreftet - serveren faktisk hadde `gross_strokes=5` lagret. Ingen konsollfeil. - **Ingen kodeendring i denne runden** — ren verifisering av allerede - levert funksjonalitet, ingen utrulling nødvendig. -- **Undersøkt: grensesnitt for å følge en venns runde live — BEKREFTET AT - DET IKKE FINNES (2026-07-28), ren undersøkelse, ingen kode skrevet.** - Brukeren spurte om en spiller kan gå inn på en venns profil og følge en - pågående frittstående runde live, gitt at rettighetene er gitt. Lest - direkte i koden (ikke antatt): (1) `frontend/components/friends.tsx` har - INGEN lenke/rute til en vennprofil-side i det hele tatt — kun - send/aksepter/avvis-knapper og kategorisering, ingen `/friends/[id]`- - eller lignende rute finnes noe sted i `frontend/app/`. (2) `round`- - tabellen har INGEN `visibility`-kolonne (bekreftet med grep over ALLE - migrasjoner — `visibility` finnes kun på `tournament`, fra - `009_landing_pages_and_visibility.sql`); `020_personal_rounds.sql` sin - egen kommentar (linje 39-42) slår eksplisitt fast v1-avgrensningen: en - runde er kun synlig for `owner_user_id`. (3) `app/routers/rounds.py` sin - `_get_accessible_round_or_404` (linje 1220-1240, lest direkte) gir - tilgang KUN til eieren ELLER en lenket `round_participant.user_id` - (ADR-036 fase 3) — ingen sjekk mot `friendship`-tabellen noe sted, - bekreftet med `grep -n -i "friend" app/routers/rounds.py` → null treff. - (4) `GET /ws/rounds/{id}/live` bruker NØYAKTIG samme - `_get_accessible_round_or_404`-sjekk FØR `websocket.accept()` (samme - fil, linje ~2763) — en venn som ikke er lenket deltaker får - websocketen lukket med kode 4403, ulikt turnering-live (ADR-027), som - bevisst TILLATER anonym tilgang. Ingen forberedt/påbegynt kode for - "følg venns runde" funnet noe sted (grep etter "follow"/"følg"/"friend" - i både `rounds.py` og `friends.py` — null relevante treff). - **Konklusjon: dette er nøyaktig ADR-036 fase 2 (rundevisibilitet - public/private/friends), som lenge har stått notert som IKKE bygget i - FEATURE_BACKLOG.md/CLAUDE.md** — bekreftet nå med presis kode-evidens i - stedet for bare et notat om at det mangler. Ingen ny beslutning tatt, - ingen kode skrevet — dette var en ren undersøkelse på brukerens - eksplisitte forespørsel. Se FEATURE_BACKLOG.md for ADR-036 fase 2 sitt - design (public/private/friends, eksplisitt gruppevalg). -- **ADR-036 fase 2 (rundevisibilitet) BYGGET, GRUNDIG SCRATCH-/ - BROWSERVERIFISERT OG LIVE (2026-07-28), samme dag:** direkte oppfølging - av undersøkelsen over — brukeren bekreftet eksplisitt retningen fra - Beslutning B (allerede fullt designet i ADR-036, ikke funnet opp på - nytt): tre nivåer (`public`/`private`/`friends`), der `friends` krever - et EKSPLISITT kategori-valg (ikke "alle venner"). - **Migrasjon `032_round_visibility.sql`:** `round.visibility_mode` - (`public`/`private`/`friends`, default `private`), ny tabell - `round_visible_category` (samme faste kategori-sett som - `friend_categorization`, migrasjon 025, bevisst duplisert CHECK fremfor - delt ENUM — samme pragmatiske mønster som resten av skjemaet). - **Kjernestykket, `app/routers/rounds.py`:** ny `_can_view_round()` — - eier ELLER lenket medspiller ser alltid; ellers `public`→alle (også - anonyme), `private`→ingen, `friends`→krever et AKSEPTERT vennskap MED - eieren OG at EIERENS kategorisering av viewer (retningen er bevisst - omvendt av hva man skulle tro — det er eieren som begrenser, basert på - egen gruppering) treffer minst én av rundens synlige kategorier. - **Reelt funn under selve designarbeidet:** `_can_view_round()` viste - seg å være en STRIKT SUPERSETT av den eksisterende - `_get_accessible_round_or_404()` sin logikk (dens to første grener ER - nøyaktig eier+medspiller-sjekken) — i stedet for å bygge en helt - parallell endepunkt-familie fra bunnen, ble fire eksisterende - autentiserte lese-endepunkters KROPP ekstrahert til delte - hjelpefunksjoner (`_build_participant_holes`/`_build_side_holes`/ - `_build_leaderboard`/`_build_format_result` — mekanisk gjort med et - Python-script for presis inndenting fremfor manuell redigering av - ~270+95 linjer, verifisert med `ast.parse` + full regresjonskjøring - etterpå), gjenbrukt av BÅDE de originale autentiserte endepunktene OG - syv nye `/public/rounds/*`-endepunkter (samme `get_current_user_ - optional`-mønster som turnering sin offentlige side, ADR-018/026/027) - pluss et nytt offentlig WS-endepunkt `/ws/public/rounds/{id}/live` - (ulikt det eksisterende PRIVATE `/ws/rounds/{id}/live`, som fortsatt - krever ekte autentisert eier/medspiller-sesjon, uendret). Egen, leaner - `PublicRoundOut` (aldri `guest_email`, som er PII, aldri `my_*`/ - `setup_*`, som kun gir mening for eier/deltaker) i stedet for å - gjenbruke den fulle `RoundOut` med etterhånds-redigering. - Ny `GET /people/{id}` (friends.py, samme lavsensitive felt-sett som - `/people/search`, bevisst OPTIONALT autentisert — en offentlig runde - må kunne nås via en delt lenke selv av en anonym leser, og - vennprofil-siden som leder dit må da fungere anonymt også) og - `GET /public/people/{id}/rounds` (rounds.py, lister eierens runder - filtrert gjennom `_can_view_round()` per rad — "pågår nå" alltid øverst). - **Scratch-verifisert grundig, 191 automatiserte sjekker i tre testløp** - (isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs - API-container, samme mønster som hele prosjektet): et NYTT 34-punkts - skript som dekket hele synlighetsmatrisen presist (tre brukere A/B/C + - en helt anonym opener) — B/C/anonym nektet privat OG venner-runde FØR - vennskap, alle ser offentlig (inkl. anonym), venner-runde fortsatt - nektet RETT ETTER vennskap men FØR kategorisering, tilgjengelig - UMIDDELBART etter riktig kategorisering, feil kategori (close_family - mot golf_friends) fortsatt nektet, PATCH kan endre synlighet i - etterkant (bekreftet begge retninger), offentlig+autentisert - leaderboard ga BEVIST IDENTISK resultat (kryssjekket), ukjent - runde-id ga 404 (ikke 403). PLUSS en full regresjonskjøring av to - eksisterende testsuiter fra tidligere runder denne uken (35-punkts - co-player-flyt, 122-punkts spillformat-flyt) — begge 100 % grønne, - bekrefter at ekstraheringen av de fire delte funksjonene ikke endret - noen eksisterende oppførsel. - **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP, - isolert browser-kontekst for en helt anonym tredje "bruker" ved siden - av to ekte innloggede faner): (1) synlighetsvelgeren i opprett-runde- - veiviseren fungerte som designet (tre knapper, kategori-multiselect - dukker kun opp ved "Venner", advarselstekst ved 0 valgte kategorier) — - runde opprettet med `friends`+`golf_friends`, bekreftet via API; - (2) rediger-runde-panelet viste korrekt FORHÅNDSUTFYLT synlighet, - PATCH til `public` bekreftet lagret; (3) en HELT ANONYM - nettleserkontekst (ingen cookies i det hele tatt) kunne se den - offentlige runden direkte på `/watch/{id}` (live-puls, leaderboard, - riktig eiernavn) OG via `/my-friends/{eier-id}`-profilsiden (navn/ - avatar/HCP/hjemmeklubb + rundeliste med lenke inn); (4) en ekte andre - bruker sendte venneforespørsel, eieren aksepterte og kategoriserte - vedkommende som "Golfvenner" VIA DEN FAKTISKE UI-EN (ikke bare API) — - satte deretter runden til `friends`+`golf_friends`, bekreftet vennen - fikk tilgang UMIDDELBART via profilsiden; (5) **negativ kontroll, - samme venn**: byttet runden til `friends`+`close_family` (en kategori - vennen IKKE var satt i) — profilsiden viste korrekt INGEN runder - lenger, og et direkte `/watch/{id}`-forsøk ga en tydelig "Du har ikke - tilgang"-melding (403), atskilt fra en egen "Denne runden finnes - ikke"-melding for en ukjent id (404) — begge bekreftet med ekte - skjermbilder. Ekte typesjekket + full produksjonsbuild (26 ruter, inkl. - de to nye `/my-friends/[id]` og `/watch/[id]`) kjørt før utrulling. - **Rullet ut mot ekte systemer 2026-07-28**, bruker bekreftet - eksplisitt (viste frem full plan for migrasjon FØR den kjørte, per - CLAUDE.md sin ufravikelige regel): migrasjon 032 kjørt mot ekte - `teecup_db` (kun additivt — ny kolonne med default, ny tabell, ingen - eksisterende rader rørt), `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. Verifisert presist at de nye rutene faktisk når FastAPI - (ikke bare at Next.js svarte): et ukjent person-id mot - `/public/people/{id}/rounds` over ekte https ga korrekt JSON-formet - `404 NOT_FOUND` (ikke en rå Next.js-404-side). - **ADR-036 er dermed HELT ferdig, alle tre faser** (venner-kjernen, - rundevisibilitet, ekte medspillere) — backend + frontend, live. - -- **Rundevarsler koblet til det eksisterende varslingssenteret, BYGGET, - SCRATCH-VERIFISERT OG LIVE (2026-07-28), samme dag:** direkte oppfølging - av ADR-036 fase 2 — varslingssenteret (2026-07-26) hadde fra start - reservert `type` for `"round"`/`"result"` (skjema OG frontendens - `KIND_ICON`/`KIND_LABEL`), men INGENTING skrev noensinne en slik rad — - kun venneforespørsler trigget et varsel. Bruker bekreftet omfang - eksplisitt (AskUserQuestion, alle tre valgt): (A) lagt til som ekte - medspiller på en runde, (B) en venn starter en runde du kan se - (offentlig, ELLER `friends`-synlig med treffende kategori), (C) en - runde du er koblet til (eier/medspiller/tredjeparts-venn fra B) blir - fullført. - **Ren backend-endring, INGEN frontend-endring nødvendig** — bekreftet - ved lesing av `notifications.tsx` FØR noe ble bygget: `NotificationKind`/ - `KIND_ICON`/`KIND_LABEL` dekker allerede `"round"`/`"result"` fullt ut, - siden de ble reservert med akkurat dette for øye i den opprinnelige - runden. - **Bygget i `app/routers/rounds.py`:** ny delt `_friends_who_can_see_ - round(conn, round_id, owner_user_id)` — GJENBEREGNER (ikke en lagret - mottakerliste) nøyaktig samme regel som `_can_view_round` (offentlig = - alle aksepterte venner, `friends` = kun de hvis EGEN kategorisering av - venn treffer rundens synlige kategorier), brukt BÅDE ved opprettelse og - ved fullføring — aldri ute av synk med selve tilgangskontrollen. - `create_round` sender (B) rett etter transaksjonen (kun for - `public`/`friends`, aldri `private`), lenke til `/watch/{id}`. - `add_participant` sender (A) inni transaksjonen når `user_id` er satt - (aldri for gjester — de har ingen konto å varsle), lenke til - `/my-rounds/{id}` (full tilgang, ikke tredjeparts-visningen). - `complete_round` sender (C) til to ATSKILTE mottakergrupper med ulik - lenke, siden de har ulik tilgang: lenkede medspillere (unntatt den som - selv fullførte) → `/my-rounds/{id}`; tredjeparts-venner fra (B), - gjenberegnet på nytt → `/watch/{id}` — ingen dobbel-varsling hvis noen - skulle være i begge grupper (settoperasjon), og aldri et varsel til - personen som selv utførte handlingen. - **Scratch-verifisert grundig, 28/28 nye sjekker** (isolert - `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API- - container, samme mønster som hele prosjektet, alle 32 migrasjoner kjørt - friskt): venn fikk `round`-varsel ved synlig opprettelse, fremmed fikk - det IKKE, privat runde ga INGEN varsel til noen, medspiller fikk - `round`-varsel med riktig `/my-rounds/`-lenke ved tilføyelse, fullføring - ga `result`-varsel til BÅDE medspiller (`/my-rounds/`) og venn - (`/watch/`) men ALDRI til den som selv fullførte, en privat solorunde - sin fullføring ga fortsatt ingen varsler, mark-as-read uendret. PLUSS - full regresjon av tre eksisterende testsuiter (co-player-flyt 35/35, - spillformat-flyt 122/122, ADR-036 fase 2-synlighetsmatrise 34/34) — alle - fortsatt 100 % grønne, ingen utilsiktet bivirkning av de nye - `create_notification()`-kallene inni eksisterende transaksjoner. - `test_isolation.sql` 12/12 uendret (ingen migrasjon, ren Python-logikk). - **Rullet ut live 2026-07-28**, ingen migrasjon, kun - `docker compose up -d --build teecup_api`. Ren boot - (`Application startup complete`), `/health`/`/dashboard` → 200, - `teeoff.no` upåvirket. - -- **E-post-fallback for varsler, per type, BYGGET OG SCRATCH-VERIFISERT - (2026-07-28), samme dag, rett etter rundevarsel-runden:** brukeren ba - eksplisitt om at mottakeren selv skal kunne velge HVILKE varseltyper som - skal utløse e-post — ikke en enkelt global av/på-bryter. Ny migrasjon - `033_notification_email_prefs.sql`: `user_notification_email_pref` - (`user_id`, `type`, samme CHECK-sett som `notification.type`) — samme - "tilstedeværelse = valgt"-mønster som `round_visible_category`/ - `friend_categorization`, TRYGG STANDARD ingen rad = ingen e-post for - noen type (samme "se ingenting til noen har valgt"-filosofi som resten - av appen). - **Bevisst ÉN felles innsnevring, ikke ett kallsted per varseltrigger:** - e-post-utsendingen ligger INNI `create_notification()` selv (etter selve - INSERT-en) — modul-docstringen kalte den allerede "den eneste - skrivevegen inn", så alle nåværende OG fremtidige varsel-triggere (i dag - 2 i friends.py, 4 i rounds.py) får e-post-støtte helt uten å røres, - ingen risiko for at et fremtidig kallsted glemmer det. Samme - `SMTP_CONFIGURED`-sjekk + try/except + `traceback.print_exc()`-mønster - som all annen e-postutsending i appen (routers/auth.py, - organizations.py) — en driftsfeil i selve SMTP-en skal ALDRI hindre at - in-app-varselet (allerede skrevet FØR e-post-forsøket) består. - Ny `send_notification_email()` i `app/email.py` — bevisst tospråklig KUN - i ramme-teksten (emne/hilsen/lenkeforklaring); selve `message`-teksten - er allerede en ferdig norsk snapshot-tekst (samme prinsipp som - in-app-varselet), ingen full i18n av selve varselinnholdet i denne - runden. - Nye `GET`/`PUT /notifications/email-prefs` — PUT er en FULL erstatning - (samme kontrakt som `PUT /friends/{id}/categories`), ikke en delvis - PATCH. - **Liten, men reell ryddejobb funnet FØR e-post-laget ble lagt på:** - forrige rundes `add_participant`-varsel (medspiller lagt til) lå INNI - den åpne DB-transaksjonen for selve deltaker-innsettingen — flyttet til - RETT ETTER (samme mønster som `create_round`/`complete_round` allerede - fulgte), slik at en fremtidig blokkerende SMTP-utsending aldri skjer - mens en transaksjon holder låser. - **Frontend:** ny seksjon "Varsler på e-post" i `/account` - (`NotificationEmailPrefsSection`, `account-settings.tsx`) — fire - avkrysningsbokser (Venneforespørsler/Runder/Resultater/Turneringer, sistnevnte - reservert for fremtidig bruk), samme avkrysningsboks-mønster som - kølle-bag-listen lenger opp i samme fil, lagrer umiddelbart ved hvert - klikk (ingen egen "lagre"-knapp). - **Scratch-verifisert grundig, 19/19 nye sjekker** (isolert - `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API- - container, alle 33 migrasjoner kjørt friskt): trygg standard bekreftet - (ingen typer valgt fra start), full-erstatning bekreftet (PUT med ett - sett fjerner det forrige, legger ikke til), e-post-fallback FAKTISK - logget (dev-log-varianten av `SMTP_CONFIGURED`-grenen) for en bruker med - `round`/`result` valgt inn — BÅDE ved medspiller-tilføyelse og ved - fullføring — INGEN e-post til brukeren som selv utførte handlingen, - INGEN e-post til en bruker (eieren) som ikke har valgt inn NOE, og etter - å ha slått AV `result` igjen: ingen ny e-post ved neste fullføring MENS - in-app-varselet fortsatt opprettes helt uendret (de to er reelt - atskilte, ikke koblet). PLUSS full regresjon av fire eksisterende - testsuiter (rundevarsler 28/28, co-player-flyt 35/35, spillformat-flyt - 122/122, ADR-036 fase 2-synlighetsmatrise 34/34) — alle fortsatt 100 % - grønne, ingen bivirkning av at `add_participant`s varsel flyttet utenfor - transaksjonen. `test_isolation.sql` 12/12 uendret. Ekte typesjekket - produksjonsbuild (samme `Dockerfile` som deployes) kompilerte rent, - alle 24 ruter listet uendret. - -- **Tre organisator-oppfølgingspunkter fra FEATURE_BACKLOG.md, ALLE - BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE (2026-07-28), samme dag:** - brukeren ba om at alle tre gjenstående, godt avgrensede oppfølgings- - punkter fra en tidligere gjennomgang av `.md`-filene tas i én runde, - inkludert oppdatering av de relevante backlog-/beslutningsfilene. - 1. **Flytte spiller mellom lag:** ny - `POST /orgs/{id}/teams/{team_id}/roster/{roster_id}/move` - (`{"target_team_id": ...}`, `app/routers/tournaments.py`) — - atomisk `UPDATE team_roster SET team_id = ...`, avviser 409 - `ALREADY_IN_MATCH` hvis spilleren allerede er lagt til i en match - (roster-raden er referert av `match_participant.team_roster_id` - med `ON DELETE RESTRICT` -- en flytting ville da gjort matchens - `team_side` inkonsistent med spillerens faktiske lag), 400 ved - flytting til samme lag, 404 ved ukjent mållag/roster-id. - Kapteinmerket nullstilles eksplisitt ved flytting (følger ikke med - til det nye laget). Frontend: ny "Flytt til {annet lag}"-handling i - `TeamPanel` sin per-spiller-meny (`tournament-detail.tsx`) — v1s - to-lags-grense (ADR-011) gjør målet entydig, ingen dropdown - nødvendig. Ingen migrasjon. - 2. **Individuell rangering PER ØKT i en org-turnering:** ny - `GET /orgs/{id}/sessions/{id}/individual-leaderboard` — bevisst - AVGRENSET til én økt (ikke summert på tvers av turneringen, samme - avklaring som rundeleaderboardets omfang tidligere), og KUN - meningsfull for `scoring_mode='stroke'`-økter i individuell-ball- - format (singles/fourball; delt-ball-formater og hole_result-modus - avvist med 400, ikke krasj, siden de ikke har noen individuell - brutto-score å rangere fra). Gjenbruker `mp.playing_handicap` - (allerede beregnet, allowance-justert) + `allocate_over_played_ - holes` (samme mønster som eksisterende match-play-scoring i - `scoring.py`, som fikk sin `_played_hole_numbers` omdøpt til - `played_hole_numbers` -- "fjern understrek når et andre bruksted - dukker opp"-mønsteret, samme som `SIDE_IS_UNIT` tidligere) — ingen - ny regnelogikk. Respekterer samme reveal-gating (`locked_team_ids`/ - `own_team_ids`, ADR-013/026) som den eksisterende matchlisten. - Frontend: ny side `/tournaments/[id]/sessions/[sessionId]/ - individual-leaderboard` (`session-individual-leaderboard.tsx`, - egen enklere lokal variant av `round-leaderboard.tsx` sitt - rangerings-/mode-toggle-mønster -- brutto/netto/poeng, delt - plassering "T-N"), lenket fra blind draw-skjermen for - kvalifiserende økter. Ingen migrasjon. - 3. **Midlertidige spillere + automatisk etter-runde-invitasjon - (økt-nivå):** de tre tidligere åpne spørsmålene avklart eksplisitt - (AskUserQuestion) -- nivå ØKT (ikke turnering), dobbel-utsending- - sperre JA, locale bevisst alltid `nb`. Ny migrasjon - `034_session_scorecard_invitations.sql` - (`match_participant.invitation_sent_at`, samme "tidsstempel = - skjedd"-mønster som `round.started_at` m.fl.). Ny - `POST /orgs/{id}/sessions/{id}/send-scorecard-invitations` — - sender KUN til spillere uten konto (`player.user_id IS NULL`) OG - med registrert e-post OG uten en tidligere sendt invitasjon for - akkurat denne (økt, deltaker)-kombinasjonen; responsen skiller - `sent`/`skipped_has_account`/`skipped_no_email`/ - `skipped_already_sent` for full gjennomsiktighet. Gjenbruker - SAMME magic-link-token-mekanisme som vanlig innlogging (ikke bare - en "logg inn senere"-henvisning) -- ny `send_session_result_email()` - i `app/email.py` (begge nb/en-maler klare, kun `nb` faktisk brukt). - E-postens innhold: matchresultat (`status_text`) + individuelt - slagtotal når tilgjengelig (individuell-ball-formater -- `SUM - gross_strokes` er naturlig `NULL` for delt-ball-formater uten noen - egen format-sjekk, siden `match_participant_id` aldri settes i - `hole_score` der). Frontend: ny "Send scorekort til alle med - e-post"-knapp i blind draw-skjermen (`session-blind-draw.tsx`), - synlig når økten har minst én match. - **Liten, men reell ryddejobb funnet FØR e-post-laget ble lagt på** - (delt med forrige rundes rundevarsel-mønster): dev-log-grenen for den - nye invitasjons-e-posten skrev først KUN en oppsummeringstekst, ikke - selve token-en -- umulig å teste innloggingslenken i et scratch-/dev- - miljø uten SMTP. Rettet til å skrive to linjer: én i SAMME format som - `request_magic_link` sin etablerte `[DEV] Magic link for ...`-linje - (så eksisterende dev-verktøy som harvester token derfra fungerer - uendret), pluss en egen lesbar oppsummeringslinje. - **Scratch-verifisert grundig, 54/54 nye sjekker** (isolert - `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API- - container, alle 34 migrasjoner kjørt friskt, full org-turnering- - scaffold bygget fra bunnen via API -- org/bane/18 hull/tee/turnering/ - to lag/seks spillere/roster/singles-stroke-økt/to matcher): flytting - happy-path + kaptein-nullstilling + alle tre feilveier (409/400/404) - bekreftet, individuell rangering bekreftet tom→fylt→korrekt brutto/ - netto/poeng for et hånd-utregnet 4-spiller-scenario (kryssjekket at - netto-til-par alltid ≤ brutto-til-par for alle fire), begge ikke- - kvalifiserende økt-typer (hole_result, foursome) avvist rent, - invitasjons-utsending bekreftet presist (1 sendt/2 manglet e-post/1 - hadde allerede konto via en EKTE innlogget "linket" bruker), andre - kall bekreftet idempotent (0 nye, riktig `skipped_already_sent`), OG - den utstedte lenken bekreftet FAKTISK brukbar (spilleren logget inn - med den, kontoen ble koblet til spiller-profilen, samme ADR-017- - mekanisme uendret). PLUSS regresjon av scoring.py-omdøpingen - (`test_holeless_course_crash.py`, 5/5) og to notifikasjons-/e-post- - testsuiter fra forrige runde (28/28, 19/19) — alle fortsatt grønne. - `test_isolation.sql` 12/12. Ekte typesjekket produksjonsbuild - kompilerte rent, ny rute `/tournaments/[id]/sessions/[sessionId]/ - individual-leaderboard` listet. - **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP, - isolert scratch-backend, ekte innlogging inkl. tvungen 2FA-oppsett for - en fersk organisasjonseier): flyttet en spiller mellom lag direkte i - UI-et og bekreftet begge lags roster-lister/-antall oppdatert - umiddelbart; åpnet individuell rangering og bekreftet alle tre - visningsmodiene (Brutto/Netto/Poeng) viste korrekte, ulike tall for de - samme to spillerne; trykket "Send scorekort til alle med e-post" og - bekreftet resultatteksten "1 invitasjon sendt, 1 mangler registrert - e-post" -- trykket samme knapp igjen og bekreftet "0 invitasjoner - sendt, 1 allerede sendt tidligere, …" (dobbel-sperren synlig direkte i - UI-et, ikke bare i et API-svar). Ingen konsollfeil i noen av de tre - rundene. - **Rullet ut mot ekte systemer 2026-07-28**, bruker bekreftet - eksplisitt: migrasjon 034 kjørt mot ekte `teecup_db` - (`invitation_sent_at`-kolonnen 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. - **Samtidig, ren dokumentasjonshygiene:** et par steder i - `FEATURE_BACKLOG.md`/`ARCHITECTURE_DECISIONS.md` hadde blitt hengende - etter `CLAUDE.md` (ADR-039-utrulling/frontend og PWA-offline- - browsertesting fremstod fortsatt som "ikke gjort" til tross for at - begge var fullført i tidligere økter denne uken) — rettet til å - stemme med den faktiske, allerede leverte tilstanden. - -- **Fire gjenstående forslag fra en tidligere "hva nå?"-runde, ALLE FIRE - BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-07-28, samme dag:** - brukeren ba eksplisitt om å ferdigstille alle fire samtidig - (PWA-installasjon, flere flighter i frittstående runder, scramble/ - greensome-statistikk, push-varsler til telefonens OS) — se - FEATURE_BACKLOG.md for full detalj per punkt, kort oppsummert her. - Tre nye migrasjoner (`035_round_flight_group.sql`, - `036_round_hole_selected_participant.sql`, `037_push_subscriptions.sql`), - alle rent additive. - 1. **PWA-installasjonsoppfordring** — `lib/pwa-install.ts` (globalt - fanget `beforeinstallprompt`, mountet via `sw-register.tsx` på alle - sider siden eventet kun fyres én gang per side-liv) + ny - `components/install-prompt.tsx`, vist på dashbordet rett under - hilsenen. Android/Chrome-familien får en ekte "Installer"-knapp, - iOS Safari et instruksjonsbanner (ingen programmatisk vei finnes - der), andre nettlesere uten reell installasjonsvei viser ingenting. - 2. **Flere flighter i én frittstående runde** (retning 1, løs - gruppering av separate `round`-rader bundet sammen av en klient- - generert delt UUID, `round.flight_group_id`) — ny - `GET /rounds/{id}/flight-group` + `GET .../flight-group/leaderboard` - (slår sammen hver tilgjengelig søsken-flights EGET leaderboard til - én rangert liste, `score_to_par` allerede normalisert og dermed - sammenlignbart på tvers av baner). Ny `FlightGroupPanel` i - `round-detail.tsx` ("+ Legg til en flight til" gjenbruker HELE - opprett-runde-flyten, forhåndsutfylt via URL-parametre — banen - velges på nytt per flight, bevisst), ny side - `/my-rounds/[id]/flights`. - 3. **Scramble/greensome: valgt utslag per spiller** — avgrenset til - frittstående runder (ADR-039 sitt delt-ball-format), IKKE - org-scopede turneringer i denne runden (bevisst scope-kutt, se - FEATURE_BACKLOG.md). Ny `round_hole.selected_participant_id`, - `PATCH .../sides/{id}/holes/{n}` fikk et nytt valgfritt felt (samme - "full overwrite hvert kall"-kontrakt som `played`/`score`). - `SideScoreWizard` fikk en ny valgfri "Hvem sitt utslag ble brukt?"- - seksjon (auto-hopp bevisst slått av her, ellers ville brukeren blitt - revet videre før valget kunne gjøres); `round-stats.tsx` fikk en ny - "Utslag brukt"-oppsummering per side. - 4. **Push-varsler til telefonens OS** (Web Push/VAPID) — ny - `push_subscription`-tabell, nytt `app/push.py` - (`send_push_to_user`, `pywebpush`), kalt fra `create_notification()` - for ALLE fire varseltyper. Bevisst INGEN egen per-type opt-in - (ulikt e-post-fallbacken) — å abonnere ER samtykket. VAPID-nøkler - valgfrie (`PUSH_CONFIGURED`), samme grasiøs-degraderings-mønster som - SMTP. `public/sw.js` fikk `push`/`notificationclick`-håndtering, ny - `lib/push-subscribe.ts` + seksjon i `/account` - (`PushNotificationSection`). - **VAPID-nøkkelpar generert lokalt** (Python `cryptography`, EC P-256, - rå base64url-kodet privat/offentlig nøkkel — samme format `pywebpush` - og nettleserens `applicationServerKey` forventer), ALDRI vist i - klartekst i chatten (skrevet direkte til en midlertidig fil med 600- - rettigheter, lest inn i ekte `.env` ved utrulling, slettet fra - scratchpad etterpå) — samme regel som alle andre hemmeligheter i - prosjektet. - **Scratch-verifisert grundig, 27/27 sjekker i ett delt testløp** - (isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs - API-container med `pywebpush` installert, alle 37 migrasjoner kjørt - friskt): flight-group happy-path + ugyldig UUID avvist + kryss-bruker- - isolasjon (403/404, ingen lekkasje) + slått-sammen leaderboard rangerer - korrekt på tvers av to flighter; scramble-valg PATCH lykkes + feil side - avvist (400) + eksplisitt null nullstiller; VAPID-nøkkel eksponert + - abonnement lagret + anonymt abonnement avvist (401) + en EKTE - `webpush()`-utsendelse forsøkt mot en syntaktisk ugyldig test-nøkkel - (`WebPushException` fanget og logget, in-app-varselet ble uansett - opprettet normalt — beviser feil-isolasjonen fungerer i praksis, ikke - bare i teorien). `test_isolation.sql` 12/12 uendret. - **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP, - isolert scratch-backend, ekte innlogging): PWA-knappen rendret og var - klikkbar (Chrome fyrte faktisk `beforeinstallprompt` i testøkten), ingen - konsollfeil; hele "+ Legg til en flight til"-flyten klikket gjennom FRA - KNAPPETRYKK TIL FERDIG RUNDE (riktig forhåndsutfylt skjema, korrekt - `flightGroupId` i URL-en, DIREKTE DATABASE-bekreftelse at begge rundene - delte samme gruppe-id etterpå), kombinert leaderboard-siden bekreftet - visuelt med korrekt rangering; scramble-valget satt i veiviseren, - bekreftet DIREKTE I DATABASEN, OG "Utslag brukt"-oppsummeringen - bekreftet visuelt på statistikksiden; push-UI-et rendret korrekt og - håndterte avslått tillatelse med en forklarende tekst uten konsollfeil - (denne automatiserte nettleserøkten hadde `Notification.permission` - forhåndssatt til "denied" av selve miljøet — en ekte innvilget - tillatelse → ekte levert OS-varsel er derfor IKKE bevist, ærlig - begrensning, bruker bør selv teste dette på en ekte enhet før full - tillit). Ekte typesjekket produksjonsbuild (`docker build --target - builder`) kompilerte rent, alle 27 ruter listet inkl. den nye - `/my-rounds/[id]/flights`. - **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt (plan vist - FØR kjøring, per CLAUDE.md sin ufravikelige regel): migrasjon 035-037 - kjørt mot ekte `teecup_db` (alle tre bekreftet med direkte spørring, - `test_isolation.sql` fortsatt 12/12), VAPID-nøkler lagt inn i ekte - `.env`, `docker compose up -d --build teecup_api teecup_frontend`. - Begge containere boot-et rent, `/health`/`/dashboard` → 200, - `GET /push/vapid-public-key` over ekte https ga korrekt offentlig - nøkkel, `GET /rounds/{ukjent-id}/flight-group` ga korrekt - `401 NOT_AUTHENTICATED` (ikke en rå 404 — bekrefter ruten faktisk når - FastAPI), `teeoff.no` upåvirket. -- **Designinstruksen erstattet/formalisert + seks brukerpunkter, ALLE - BYGGET OG LIVE (2026-07-29):** `alternativ designinstruks.md` (fra - dagen før) erstattet med en revidert versjon fra bruker (allerede - forankret i det eksisterende token-systemet, ikke lenger i konflikt med - oransje/hardkodet-slate-spørsmålene) — INNHOLDET er nå slått sammen inn - i `DESIGN_SYSTEM.md` som den gjeldende fasiten (8-punkts rutenett, - `shadow-md shadow-black/8`, `font-normal` OK ved `text-base`+god - kontrast, `:active`-tilstander, safe-area, 16px input, `transition-all - duration-200`). `alternativ designinstruks.md` selv beholdt kun som - arkiv/historikk. **Konsistens-sveip:** det gamle svake skygge-mønsteret - (`shadow-sm shadow-black/5`) erstattet mekanisk på tvers av 20 filer - (`sed`, verifisert 0 gjenværende treff) — IKKE en fullstendig - strukturell gjennomgang av alle 27 skjermer, kun dette ene, trygge, - mekaniske mønsteret. - **Hurtighandlinger:** dashbordets tre knapper er nå `grid-cols-3` også - på mobil (var `grid-cols-1 sm:grid-cols-3`, tok unødvendig mye plass) — - mindre ikon/tekst/høyde for å fortsatt være lesbare i smalere kolonner. - **Par/stroke-index manglet på rundeleaderboardet** (`/my-rounds/{id}/ - leaderboard`) — ny `HoleReferenceStrip` viser Hull/Par/Hcp ÉN gang - (banedata, identisk for alle deltakere) rett over den rangerte listen. - **Venner: kategorisering nå OBLIGATORISK fra vennskapet inngås** - (presisert av bruker: "det skal ikke finnes ukategoriserte venner") — - `POST /friends` og `POST /friends/{id}/accept` krever begge minst én - kategori (`Field(min_length=1)`), satt AV BEGGE PARTER på hvert sitt - naturlige tidspunkt (avsender ved sending, mottaker ved aksept) — ikke - en frivillig senere handling. `PUT .../categories` nekter også å sette - et tomt sett. Ny delt `CategoryPicker`-komponent (friends.tsx, alle - forhåndsvalgt — "man må heller velge bort", presisert av bruker) brukt - tre steder (send/godta/rediger), nekter å fjerne SISTE avkrysning. - Selv-helbredende sikkerhetsnett for venner fra FØR denne regelen: ny - advarselsbanner øverst i `/my-friends` ("N venner mangler kategori"), - tvinger vedkommendes kategori-panel åpent til det er løst — bekreftet - reelt nødvendig i produksjon (Erol hadde aldri kategorisert Tore - Morell, sin faste matchspill-motstander — vil nå bli fanget opp og - tvunget løst neste gang `/my-friends` besøkes). - **Synlighetsregelen for "Venner"-runder endret fra "minst én treffende - kategori" til "ALLE vennens kategorier må være i det synlige settet"** - (presisert av bruker: "'ikke vise' overstyrer 'vise'") — en venn med - ÉN ikke-valgt kategori ekskluderes nå helt, selv om en annen av - kategoriene deres er valgt. En venn med NULL kategorier vises ALDRI - (avklart eksplisitt med bruker via spørsmål — skal i praksis aldri - forekomme lenger pga. regelen over, kun en igjenværende tilstand fra - FØR den ble håndhevet). `_can_view_round`/`_friends_who_can_see_round` - (rounds.py) omskrevet til denne AND-semantikken (fra tidligere OR). - `new-round.tsx` sin kategori-multiselect for "Venner"-synlighet starter - nå med ALLE forhåndsvalgt (samme "velg heller bort"-prinsipp). - **"Match leaderboard" — undersøkt grundig, IKKE en backend-bug:** - bekreftet direkte mot ekte `teecup_db` (kun lesing) at brukerens egen - Tjøme-matchrunde (`d857289c-...`) hadde begge sider korrekt satt opp - med beregnet `playing_handicap` — `format-result`-endepunktet ville - altså allerede regnet ut riktig "1 UP (A)"-status. Det reelle hullet - var at `FormatResultPanel` (matchstatus/skins-tavle, med hull-for-hull - vinner/AS-visning) KUN lå under "Spillere og runde"-fanen, usynlig fra - "Score"-fanen der scoring naturlig skjer. Fikset ved å vise samme - komponent (ikke duplisert logikk) øverst på BEGGE faner. - **Scratch-verifisert grundig** (isolert rolle+MinIO+engangs API- - container, samme mønster som hele prosjektet): full kategori-håndheving - (422 uten kategorier ved både send/godta/rediger, 200 med), presis - eksklusjon-overstyrer-inklusjon-test (venn med golf_friends+close_family - ekskludert når kun golf_friends er synlig, inkludert når begge er - synlige — bekreftet mot BÅDE rå SQL og de faktiske API-endepunktene), - match-runde med sider satt opp fra bunnen ga korrekt "1 UP (A)". - Deretter en FULL, ekte nettleser-gjennomgang (Chrome DevTools, mobil - 390×844): 3-kolonners hurtighandlinger, matchstatus-banner synlig på - Score-fanen, Hull/Par/Hcp-referanserad på leaderboardet, hele venne- - søk→kategorivelger(alle forhåndsvalgt, avkrysning fungerer)→send- - flyten, og selv-helbredende-banner-flyten (simulerte en gammel - ukategorisert venn direkte i databasen, bekreftet banneret dukket opp - OG forsvant igjen etter kategorisering via UI-et). - **Rullet ut live 2026-07-29**, ingen migrasjon (kun eksisterende felt/ - logikk endret), `docker compose up -d --build teecup_api - teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard` - → 200, anonymt `POST /friends` ga korrekt `401` (ikke en rå 404 — - bekrefter ruten når FastAPI), `teeoff.no` upåvirket. - -- **Match-scorekort redesignet på tvers av appen (frittstående runde + - turnering), BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT OG LIVE - (2026-07-29):** brukeren delte et referansebilde av en konkurrentapp sitt - 1v1-matchscorekort (horisontalt rutenett, farget etter hvem som vant - hvert hull, løpende "Stilling"-rad AS/X UP/X&Y, navn+HCP-banner) og ba om - det tilsvarende for ALLE match-scorekort i TeeCup, uten direkte plagiat. - **Bevisst IKKE en kopi:** beholdt appens egne, allerede etablerte - fargespråk i stedet for referansens røde/blå -- frittstående runder bruker - primær/oransje (samme "side A/side B"-konvensjon som `ResultChip`/ - `FormatResultPanel` fra tidligere runder), turnering-matcher bruker lagets - faktiske `team.color` (samme konvensjon som resten av turnering-UI-et). - Droppet bevisst referansens "Matchplay NET/Stableford NET"-fane (ga ikke - entydig mening for delt-ball-formater) og "Lik/Kommentar til - spillfeeden/Spillere"-ikonraden (en helt ny sosial funksjon, ikke en - scorekort-redesign -- utenfor denne rundens omfang). - **Kjernemekanisme, ny og delt idé (separat implementert i begge filer, - ikke faktisk delt kode siden filene allerede har egne lokale typer per - etablert konvensjon):** `computeRunning()` speiler - `handicap_engine.py` sin `compute_match_state()`/`describe()` presist, - men regnet ETT PREFIKS om gangen client-side (backend cacher i dag kun - SLUTT-tilstanden) -- gir en løpende AS/X UP/dormie/X&Y-status per hull i - stedet for kun et sluttresultat. - **`round-scorecard.tsx`:** den generiske `ScoreBlock`-tabellen erstattet med `MatchScorecardGrid` -- horisontalt rutenett (Hull/ - liste) erstattet med `MatchScorecardGrid` -- horisontalt rutenett (Hull/ - Par/en rad per spiller-ELLER-side/Stilling), identitetsbanner (navn+HCP, - farget dot, løpende sentral status + "Ferdig"-merke når avgjort). Fargen - på scorecellen følger HVEM SOM VANT hullet (fylt sirkel), ikke over/under - par -- egen semantikk fra `ScoreMark` med vilje, siden dette er en match, - ikke en individuell runde. `ApiParticipant` fikk `playing_handicap` - (allerede eksponert av backend, kun en frontend-typeutvidelse). - **`session-scorecard.tsx`:** `HoleSummaryTable`/`OutcomeBadge`/ - `grossPairLabel` erstattet med `TournamentMatchGrid` (samme struktur, - brukt som HOVEDVISNING for pågående matcher, og i `DecidedView`s "Hull - for hull" -- én komponent, ikke to). `Unit`-typen fikk `playingHandicap`. - **Ekte, nødvendig backend-endring:** `match_participant.playing_handicap` - ble beregnet og lagret siden ADR-039, men var ALDRI eksponert i - `MatchParticipantOut` (matches.py) -- lagt til i modellen og i begge - SELECT-spørringene som populerer den (`fetch_matches` og - `add_participant`). **Reelt funn, fikset FØR utrulling:** - `add_participant` sin `row` hentes FØR - `compute_and_store_side_handicaps()` kjører -- en naiv - `MatchParticipantOut(**dict(row))` ville derfor alltid returnert - `playing_handicap: null` for en NYLIG lagt til deltaker, selv når verdien - faktisk ble beregnet et øyeblikk senere i samme kall. Fikset med et - eksplisitt re-oppslag av `playing_handicap` RETT FØR responsen bygges. - **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle + - isolert scratch-MinIO + engangs API-container, alle 37 migrasjoner kjørt - friskt, samme mønster som resten av prosjektet): en full match bygget fra - bunnen for BEGGE surfacene (en frittstående match-runde med to sider, OG - en full turnering-scaffold -- org/bane-import/tee/tournament/to lag/ - roster/singel-økt/match/deltakere -- bygget fra API-et for FØRSTE gang i - et testskript denne uken, siden frittstående runder aldri har trengt det - apparatet før). 8-9 hull registrert med et bevisst blandet vinn/tap/delt- - mønster, bekreftet at `format-result`/`scorecard` sine per-hull - resultater matchet forventet handicap-justert utfall. - **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP): - et REELT, tidkrevende miljøproblem ble diagnostisert og løst underveis -- - React-komponentenes egne `useEffect`-datahentinger kjørte ALDRI (evig - lastespinner på HVER side, ikke bare de nye) når dev-serveren ble besøkt - via `127.0.0.1:3100` i stedet for `localhost:3100`; Next.js 16 sin - `allowedDevOrigins`-beskyttelse blokkerer stille dev-ressurser (HMR- - websocket m.m.) for det som oppfattes som et fremmed opphav, og dette - fikk hele klient-hydreringen til å henge uten en eneste konsollfeil. - Løst ved å konsekvent bruke `localhost` i stedet for `127.0.0.1` -- - ren miljø-lærdom for fremtidige scratch-frontend-økter, ikke en bug i - selve appen. Etter fiksen: bekreftet BEGGE match-scorekortene visuelt, - i BÅDE lys og mørk modus, i BÅDE pågående (løpende "2 UP"/farget - Stilling-rad) og avgjort tilstand ("8 UP"/"Ferdig"-merke, cachet - banner), inkl. et helt nytt 2FA-oppsett fullført på fersk konto via en - engangs e-post-2FA-kode lest fra dev-loggen (org-eier/admin krever 2FA, - ADR-021) for å nå frem til turnering-siden. Ingen konsollfeil i noen av - rundene. - **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt: ingen - migrasjon, `docker compose up -d --build teecup_api teecup_frontend`. - Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` - → 200 upåvirket. - -- **Match-scorekort-redesignet også på selve LIVE scoringssiden - (`/my-rounds/{id}` sin Score-fane), BYGGET, GRUNDIG SCRATCH-/ - BROWSERVERIFISERT OG LIVE (2026-07-29), samme dag rett etter forrige - punkt:** brukeren viste tre skjermbilder (referansen på nytt, samt et - mobil- OG et PC-skjermbilde av "hvordan du har løst det nå") og spurte - direkte hvorfor det ikke så riktig ut ennå. **Presis diagnose FØR noe ble - bygget:** PC-skjermbildets URL (`teecup.teeoff.no/my-rounds/{id}`, uten - `/scorecard`) avslørte at forrige runde kun traff `round-scorecard.tsx` - (en egen, separat LESE-visning) og `session-scorecard.tsx` (turnering) -- - aldri `round-detail.tsx` sin egen, ELDRE `FormatResultPanel`+ - `ScorecardGrid`/`SideScorecardGrid`, som er det brukeren faktisk ser og - bruker under selve scoringen. `FormatResultPanel` viste dessuten en - reell, synlig svakhet: falt tilbake til det generiske "Side B" i stedet - for spillerens faktiske navn når siden ikke hadde et eget satt navn. - **Bygget:** `ApiFormatResult` utvidet med samme `holes`-per-hull- - oppløsning som round-scorecard.tsx allerede har (ingen backend-endring - -- feltet fantes allerede i API-svaret, bare ikke lest her). Selve - fetch-en LØFTET fra `FormatResultPanel` opp til hovedkomponenten (`Round - Detail`) som ny delt `formatResult`-state, siden BÅDE banneret OG de to - scorekort-gridene nå trenger den samme dataen (én henting, ikke to). - `FormatResultPanel` gjort om til en ren presentasjonskomponent (tar - `result` som prop) med et nytt identitetsbanner (navn+HCP, farget prikk, - stor sentrert løpende status, "Ferdig"-merke) -- samme visuelle språk - som `MatchScorecardGrid`/`TournamentMatchGrid` fra forrige runde, egen - lokal kopi per prosjektets "ett sted, én fil"-konvensjon. Den tidligere - hull-for-hull-sirkel-listen under banneret FJERNET (overflødig nå som - selve gridet under viser dette per hull). - `ScorecardGrid` (individuell-ball: match/fourball) og `SideScorecardGrid` - (delt-ball: foursome/greensome/scramble) fikk begge: en farget prikk ved - siden av spiller-/side-navnet (grønn=side A, oransje=side B), en ny - `MatchScorecardCell` som farger scorecellen etter HVEM SOM VANT hullet - (fylt sirkel) i stedet for over/under par -- men KUN når formatet - faktisk er to-sidet (`isTwoSided`-prop, gate på `TWO_SIDED_FORMATS`) -- - slagspill og skins beholder uendret over/under-par-fargelegging siden de - ikke har noe "vant hullet"-konsept. Fourball sin ikke-tellende - partner-score tones ned (samme "laveste netto teller"-nedtoning som - round-scorecard.tsx). Ny "Stilling"-rad nederst i BEGGE grid, regnet med - en lokal `matchStillingByHole()` (samme prefiks-vise - `compute_match_state()`-speiling som forrige rundes `computeRunning()`, - egen kopi her siden denne trenger å være justert til `holeOrder`, ikke - bare en flat liste). Alt dette er BEVISST lagt OPPÅ den eksisterende - klikk-for-å-registrere-interaksjonen (ScoringWizard/SideScoreWizard) - uendret -- ingen endring i selve registreringsflyten, kun presentasjonen - av allerede registrerte hull. - **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle + - isolert scratch-MinIO + engangs API-container, alle 37 migrasjoner kjørt - friskt): tre nye runder bygget fra bunnen via API for å dekke alle tre - strukturelle grenene -- en MATCH-runde (Erol vs. en gjest, ulik HCP), - en FOURBALL-runde (2v2, individuell netto, bevisst konstruert med et - hull der "feil" partner sin lavere brutto ikke telte pga. handicap- - justering), og en FOURSOME-runde (delt ball). Ekte typesjekket - produksjonsbuild kompilerte rent. - **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP, - samme `localhost`-ikke-`127.0.0.1`-lærdom fra forrige runde anvendt fra - start denne gangen): alle tre rundene bekreftet visuelt -- identitets- - banneret viste ekte spillernavn+HCP (ikke lenger "Side B"), gridcellene - fargela nøyaktig riktig hull som vunnet (kryssjekket presist mot de - faktiske innsendte tallene og aria-labelene, inkl. et hull der en - tilsynelatende brutto-uavgjort (4-4) faktisk ble avgjort på netto pga. - et stort HCP-gap -- bekreftet korrekt, ikke en bug), fourball sin - nedtonede ikke-tellende partner-celle bekreftet nøyaktig på det - konstruerte hullet, Stilling-raden fulgte riktig gjennom AS/1 UP/2 UP- - mønsteret for alle tre rundene. Bekreftet at et klikk på en scorecelle - FORTSATT åpner riktig veiviser (`ScoringWizard`/`SideScoreWizard`) - uendret, i begge grid-typer. Kjørte i tillegg en ren REGRESJONSSJEKK - mot en vanlig `stroke`-formatert runde -- bekreftet ingen Stilling-rad, - ingen fargede prikker, ingen bytte av cellespråk (uendret over/under- - par-styling som før). Ingen konsollfeil i noen av rundene, i verken lys - eller mørk modus. - **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt (ba om fiksen - rett etter diagnosen): ingen migrasjon, `docker compose up -d --build - teecup_api teecup_frontend`. Begge containere boot-et rent, - `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. **Alle tre stedene - et match-scorekort vises i appen (frittstående runde sin live Score- - fane, frittstående runde sin egen lesevisning, turnering-match) bruker - nå samme konsistente visuelle språk.** - -- **Dashbordets rundeboks: viewer-relativt matchresultat + netto til-par, - pluss mottatte slag vist FØR hullet fylles ut, BYGGET, SCRATCH-/ - BROWSERVERIFISERT OG LIVE (2026-07-29), samme dag:** brukeren ba om to - ting samtidig -- at rundeboksen (`round-card.tsx`, brukt av dashbordet, - "Egne runder" og "Spilte baner") viser spillerens eget resultat i - matchen/runden ("1 UP"/"+4 netto"/"-2 brutto" osv.), og at det er - tydelig hvor mange slag en spiller MOTTAR på et hull -- kommunisert på - scorekortet, allerede FØR hullet er fylt ut (ikke bare etterpå som - netto-tallet i dag). Fire load-bærende avklaringer bekreftet eksplisitt - (AskUserQuestion, alle anbefalte valg): vis resultat BÅDE for pågående - og fullførte runder (ikke bare fullførte som før), samme "UP"/"AS"/ - "X&Y"-golfkonvensjon som scorekortets Stilling-rad (ikke et eget - dagligdags språk kun for dashbordkortet), vis BÅDE netto og brutto - til-par for slagspill når HCP spores, og vis slag-indikatoren INNI selve - den tomme score-ruten (ikke en egen ny rad). - **Backend (`app/routers/rounds.py`):** `RoundOut`/`_load_round_out` - (delt av BÅDE `GET /rounds` (liste) OG `GET /rounds/{id}`, ingen egen - kode for listen) fikk tre nye felt: `my_net_score_to_par` (kun - `play_format=="stroke"` OG `course_handicap_snapshot` satt, samme - `allocate_strokes_by_index`-algoritme som list_holes/update_hole - allerede bruker -- ingen ny utregningsmåte), og `my_match_status`/ - `my_match_lead` (to-sidede formater KUN, ALLTID viewer-relativt -- - ny `_viewer_match_status_text()`-formatter tar en fortegns-FLIPPET - `lead` fra `_build_format_result` sitt allerede eksisterende, testede - resultat -- gjenbrukt UENDRET, ikke en ny match-motor -- slik at - positivt alltid betyr "spørrende bruker leder", uansett hvilken side de - faktisk sitter på). Et avgjort resultat vises som "Vunnet 2&1"/"Tapt - 2&1" (eksplisitt prefiks, siden "2&1" alene ikke lenger sier hvem sin - side det gjaldt når visningen ikke er side-A/B-basert). - **Frontend (`round-card.tsx`):** ny `Round.matchStatus`/`matchLead`/ - `netToPar`. To-sidede formater viser ett "Resultat"-felt med - matchstatusen (farget grønn ved ledelse, oransje ved etterslep, samme - "form+farge"-språk som resten av appen -- ALDRI backend sin bokstavelige - side A/B). Slagspill beholder "Resultat"+"Til par" (brutto) og fikk et - nytt "Netto"-felt ved siden av, kun når tilgjengelig. Resultatraden er - nå IKKE lenger gatet til kun fullførte runder -- vises også mens runden - pågår. Alle tre kallstedene (`dashboard.tsx`, `own-rounds.tsx`, - `course-rounds.tsx`) oppdatert til å lese og videresende de tre nye - API-feltene, samme mønster i alle tre (ingen egen logikk per fil). - **Slag-indikator FØR utfylling (`round-detail.tsx`):** `ScorecardGrid` - (individuell-ball) og `SideScorecardGrid` (delt-ball) viser nå en liten - blå "−N"-tekst inni den tomme score-ruten når spilleren/siden faktisk - mottar minst ett slag på det hullet -- samme `strokes_received`-verdi - som allerede ble hentet (kun aldri vist før noe var registrert). Egen - farge (`text-info`, den etablerte blåtonen) for å skille denne FØR- - visningen tydelig fra den eksisterende grå netto-visningen ETTER - utfylling. **Reelt, tidligere ubrukt hull funnet underveis:** - `SideScorecardGrid` sin egen frontend-type `ApiSideHole` manglet - `strokes_received` helt (backend har alltid eksponert feltet i - `RoundSideHoleOut`, bare aldri lest her) -- lagt til. - **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle + - isolert scratch-MinIO + engangs API-container, alle 37 migrasjoner kjørt - friskt): en slagspill-runde ga korrekt brutto til-par 4 / netto til-par - 2 (både i enkelt-GET og i listen), en matchrunde der spilleren vant - begge registrerte hull ga korrekt `"2 UP"`/`lead=2`. Deretter en FULL, - ekte nettleser-gjennomgang (samme `localhost`-lærdom anvendt fra start): - rundeboksen viste "2 UP" i grønt for en pågående matchrunde og "Resultat - 17 / Til par +4 / Netto +2" (oransje) for en pågående slagspill-runde, - i BÅDE lys og mørk modus; scorekortet viste "−1"-hint i blått på - nøyaktig de tomme rutene der spilleren/siden faktisk mottar et slag, - bekreftet i BÅDE individuell-ball- (match) og delt-ball-grid (foursome). - Ingen konsollfeil i noen av rundene. - **Rullet ut live 2026-07-29**, 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. - -- **Match-identitetsbanneret: territorium-bar i stedet for flat 50/50-boks, - BYGGET, GRUNDIG BROWSERVERIFISERT (inkl. en reell overlapp-bug funnet OG - fikset) OG LIVE (2026-07-29), samme dag:** brukeren lastet opp to bilder - av dagens banner (samme identitetskort som ble bygget dagen før -- navn+ - HCP i hver ende, en sentrert statusboks) og en "VELDIG DÅRLIG, kun - illustrerende" skisse av eget forslag: den ledende sidens halvdel bør - markeres OVER på motstanderens halvdel (ikke en statisk 50/50-boks), og - ved AS skal begge sider markeres likt (nøytralt). - **Design:** ny `leadZoneFraction(lead, totalHoles)` -- `0.5 + (lead / - totalHoles) * 0.5`, klippet til `[0.18, 0.82]` for å alltid holde begge - navn lesbare selv ved en ekstrem ledelse. Dominant side får en solid, - mettet farge (`bg-primary`/`bg-brand-orange`, hvit/kontrastfarget tekst - via allerede eksisterende `--primary-foreground`/`--brand-orange- - foreground`-tokens) og strekker seg proporsjonalt forbi midtlinjen; - underlegne side beholder samme fargetone som en svak, lys tint - (`/12`-opacity) med vanlig temafarget tekst. Ved AS (eller ingen hull - spilt ennå): begge soner nøyaktig 50/50 og nøytralt `bg-muted` -- ingen - side ser ut til å lede. Egen lokal kopi i alle tre filer der et match- - identitetsbanner finnes (samme "én fil, én kopi"-konvensjon som - MatchScorecardCell-arbeidet dagen før): `round-detail.tsx` sin - `FormatResultPanel` (primary/brand-orange-tokens), `round-scorecard.tsx` - sin nye delte `LeadBar`+`LeadZone` (samme tokens, portert fra den - tidligere `IdentitySide`+`CenterBadge`-duoen), `session-scorecard.tsx` - sin `IdentityBlock` (FAKTISK lagets `team.color`-hex i stedet for faste - tokens -- dominant sone `backgroundColor: color` + hvit tekst, svak sone - `color-mix(in srgb, ${color} 15%, transparent)`, samme kontrastvalg som - den eksisterende `SegmentedBar` i tournament-leaderboard.tsx). - **Reell overlapp-bug funnet OG fikset UNDER selve browserverifiseringen, - ikke antatt riktig fra kodegjennomgang alene:** første forsøk brukte en - `position:absolute`-sentrert statusboks OPPÅ to `width:X%`-delte soner -- - et ekte skjermbilde på en smal (390px) mobil-viewport viste at et langt - navn ("Erol Haagenrud") ble delvis SKJULT bak statusboksen ("l Haagenrud" - vist i stedet, ikke en ellipse-trunkering, et reelt visuelt overlapp). - Første fiksforsøk (CSS Grid med `minmax(0,X fr) auto minmax(0,Y fr)` i - stedet for absolutt posisjonering) løste IKKE problemet alene -- fortsatt - samme overlapp ved re-test. Rot-årsaken var dypere: identitetsboksen - brukte `items-start`/`items-end` på sin YTRE flex-kolonne, som sizer - boksen etter INNHOLDETS egen bredde (shrink-to-fit) i stedet for sonens - faktiske tildelte bredde -- innholdet kunne dermed visuelt strekke seg - utover sin egen sone og inn i midt-kolonnen uansett hvor korrekt selve - grid-/bredde-fordelingen var. Fikset ved at boksen alltid STREKKER SEG - (fjernet items-start/items-end helt), med venstre/høyre-justering i - stedet løst via `justify-start`/`justify-end` på navne-raden og - `text-left`/`text-right` på HCP-teksten -- INNI en boks som nå alltid har - sonens fulle, korrekte bredde, slik at `truncate` faktisk virker presist. - **Verifisert grundig i en isolert scratch-nettleserøkt** (fersk - `teecup_scratch`-database + isolert scratch-MinIO + engangs API- - container, ekte `next dev` mot scratch-backend, Chrome DevTools MCP, 390× - 844 mobil-viewport): bygget en ekte match-runde fra bunnen via API (egen- - definert 18-hulls bane, Erol HCP 10 vs. gjest "Tore Morell" HCP 54, - samme HCP-mønster som brukerens skjermbilde) og testet re-render etter - HVER kodeendring, ikke bare én gang til slutt -- fanget nettopp DERFOR - både at CSS Grid-fiksen alene ikke var nok, og at stretch-fiksen - faktisk løste det. Bekreftet: normal ledelse ("1 UP", dominant sone - moderat bredere), en EKSTREM ledelse (avgjort "9&7", dominant sone - nesten fyller hele baren, underlegne navn korrekt trunkert "T…"/"HCP…" - i stedet for å overlappe), AS/ingen-hull-spilt (nøytral 50/50, `bg- - muted`), BÅDE lys og mørk modus (kontrast bekreftet i begge), og samme - fiks bekreftet å fungere identisk i `round-scorecard.tsx` sin - `MatchScorecardGrid`-visning (samme runde, `/my-rounds/{id}/scorecard`). - `session-scorecard.tsx` sin tournament-variant IKKE egen nettleser- - testet denne runden (identisk kodemønster, kun `team.color`-hex i stedet - for faste tokens -- lavere risiko, men flagget ærlig som ikke eget - bevist). Ekte typesjekket produksjonsbuild kjørt (to runder -- én etter - første, mislykkede CSS Grid-only-forsøk, én etter den faktiske stretch- - fiksen), alle 24 ruter listet begge ganger. - **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt: ingen - migrasjon (ren frontend), `docker compose up -d --build - teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning, - ingen backend-kode rørt). Begge containere boot-et rent, `/health`/ - `/dashboard` → 200, `teeoff.no` upåvirket. - -- **Territorium-bar-språket fullført på de to siste "Matchstatus"-boksene, - BYGGET, BROWSERVERIFISERT OG LIVE (2026-07-29), samme dag -- brukeren ba - eksplisitt om "visuell konsistens ferdig helt ut" etter at jeg flagget - disse to som gjenstående:** `round-leaderboard.tsx` sin - `MatchStatusSection` (den innloggede rundens leaderboard-side) og - `watch-round.tsx` sin `MatchStatus` (den offentlige "Følger live"-siden - for en delt/synlig runde, ADR-036 fase 2) hadde begge fortsatt den gamle - flate boksen (kun stor tekst, ingen fargesone). Disse to har INGEN - navn å vise (deltakernavnene vises allerede andre steder på samme side) - -- lagt til KUN de to fargesonene + den sentrerte statuspillen (samme - `leadZoneFraction`+CSS-Grid-mønster som identitetsbanneret dagen før, - egen lokal kopi i hver fil), ikke selve `LeadZone`-identitetsboksen. - Begge filenes lokale `ApiFormatResult`-type manglet `match_lead` helt - (aldri lest her før, selv om backend alltid har eksponert det) -- lagt - til i begge. - **Verifisert i en isolert scratch-nettleserøkt** (fersk `teecup_scratch`- - database + isolert scratch-MinIO + engangs API-container, ekte `next - dev`, Chrome DevTools MCP, 390×844 mobil-viewport): bygget en ekte - OFFENTLIG (`visibility_mode:"public"`) match-runde fra bunnen via API, - bekreftet BEGGE sider (innlogget leaderboard OG anonym `/watch/{id}`) - viser identisk, korrekt fargesatt "1 UP (Side B)"-tilstand med riktig - dominant/lys sone -- OG en egen AS/0-hull-spilt-test (nøytral 50/50, - ingen farge). Begge bekreftet i BÅDE lys og mørk modus. Ingen - konsollfeil (kun en godartet, urelatert PWA-install-loggmelding). Ekte - typesjekket produksjonsbuild kjørt og bekreftet. - **Rullet ut live 2026-07-29**, ingen migrasjon (ren frontend), `docker - compose up -d --build teecup_frontend` (gjenskapte også `teecup_api` - som vanlig bivirkning, ingen backend-kode rørt). Begge containere - boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. - **Territorium-bar-språket er dermed konsistent på ALLE fem stedene en - matchstatus vises i appen** (tre fulle identitetsbannere + disse to - navnløse variantene) -- listevisningene (blind draw-reveal, offentlig - turnering-live) beholder bevisst sin egen, ulike kort-per-match-stil - (farget topplinje + statuschip), siden de viser MANGE matcher samtidig - og ikke er en fokusert enkelt-match-visning. - -- **Stableford som ekte spilleform + "plukket opp"-tilstand for frittstående - runder, BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT (inkl. TO reelle bugs - funnet OG fikset UNDER browserverifiseringen) OG LIVE (2026-07-29), samme - dag:** brukeren ba om å ta fatt på det siste, klart avgrensede hullet fra - ADR-038-gjennomgangen -- Stableford-POENGENE var allerede beregnet og vist - flere steder (round-detail.tsx/round-scorecard.tsx/round-leaderboard.tsx - sin `stablefordPoints()`/`total_points`, bygget 2026-07-25/26), men to - ting manglet reelt: (1) `'stableford'` fantes ikke som et faktisk - selvdeklarert `play_format` (kun "stroke"/"match" fantes som individuelle - format-etiketter), (2) ingen vei til å registrere at en spiller plukket - opp ballen fordi hullet uansett var klart 0 poeng. - **Design, ny migrasjon `038_stableford_and_pickup.sql`:** `round_hole. - picked_up boolean DEFAULT false`, `'stableford'` lagt til - `round_play_format_check`. "Plukket opp" lagres IKKE som en NULL-score -- - serveren skriver eksplisitt Net Double Bogey (par + 2 + mottatte slag, - `handicap_engine.py` sin allerede eksisterende og testede `max_hole_ - score_for_handicap()`, Rule 3.1b) som selve `score`-verdien når - `picked_up=true` sendes til `PATCH .../holes/{n}` -- dette gir automatisk - presis 0 Stableford-poeng (samme formel som ellers, ingen spesialkoding) - OG et korrekt AGS-bidrag for faktisk-HCP (ADR-038) helt uendret -- INGEN - ny motorlogikk trengs, kun ett nytt felt for VISNING ("PU"-merke i stedet - for det underliggende tallet, samme "form+farge, aldri farge alene"-språk - som resten av golfscore-visningen). `HoleUpdate` beregner strokes_received - for AKKURAT det aktuelle hullet FØR selve UPDATE-en (omstrukturert fra - tidligere "beregn etterpå"-rekkefølge) for å kunne skrive riktig NDB-verdi - i samme kall. Avvist tydelig (400 `VALIDATION_FAILED`) hvis deltakeren - ikke har noen beregnet course handicap ennå. - **To reelle bugs funnet OG fikset UNDER selve browserverifiseringen, ikke - antatt riktig fra kodegjennomgang alene** -- begge samme underliggende - mønster: eksisterende kode som spesialsjekket `play_format === "stroke"` - (for å bety "individuelt, ikke to-sidet format") måtte utvides til - eksplisitt å ekskludere `"stableford"` også, ellers falt det nye formatet - feilaktig gjennom til den TO-SIDEDE grenen: - 1. `new-round.tsx` sin `needsSidesSetup = playFormat !== "stroke" && - playFormat !== "skins"` -- et ekte skjermbilde viste "Du setter opp - sidene..."-teksten under Stableford-valget, til tross for at formatet - aldri bruker sider. Rettet ved å legge til `&& playFormat !== - "stableford"`. - 2. **Alvorligere:** `round-detail.tsx` sin `FormatResultPanel` (`if - (round.play_format === "stroke" || !result) return null`) -- et ekte - skjermbilde av en fersk Stableford-runde viste en fullstendig - meningsløs "Side A"/"Side B"-territorium-bar (samme komponent bygget - tidligere samme dag for ekte to-sidede formater) med "Ingen hull - spilt ennå", siden backend sin `_build_format_result()` returnerer en - harmløs tom `ready:true`-respons for ethvert format utenfor - `_TWO_SIDED_FORMATS`/`"skins"` (inkl. det nye "stableford"), som - frontend-sjekken ikke fanget opp. Rettet samme sted (lagt til - `round.play_format === "stableford"` i den samme betingelsen), pluss - den tilhørende `useEffect` som utløser selve HTTP-kallet (unødvendig - nettverkskall for Stableford, samme fiks som for "stroke"). - **Systematisk oppfølging etter de to funnene, IKKE bare disse to - stedene:** grep'et gjennom HELE frontend-kodebasen etter samme - `"stroke"`-spesialsjekk-mønster og fant tre til ekte hull i - `watch-round.tsx` (den offentlige "Følger live"-siden) -- leaderboard- - henting, `StrokeLeaderboard`-visning, og "ingen live-visning - tilgjengelig"-fallback-meldingen ville alle feilaktig behandlet en - offentlig Stableford-runde som enten to-sidet eller "ukjent format". - Alle tre rettet samme runde, FØR de kunne bli oppdaget i produksjon. - Backend-siden av samme klasse spesialsjekk (`app/routers/rounds.py`) - var allerede korrekt fra første forsøk (`_format_setup_status`, - `my_net_score_to_par`-gaten, `RoundCreate`/`RoundUpdate` sine - `Literal`-typer) -- kun frontend hadde det gjentatte hullet. - `EditRoundPanel` sin spilleform-brytar (rediger-runde-panelet) utvidet - fra to til tre valg (Slagspill/Stableford/Matchspill), samme mønster - som `new-round.tsx`. - **Scratch-verifisert grundig i to lag** (isolert `teecup_app_scratch`- - rolle + isolert scratch-MinIO + engangs API-container, alle 38 - migrasjoner kjørt friskt, samme mønster som hele prosjektet): et - hånd-utregnet scenario (course handicap 18, slope 113/rating lik par, - 1 slag mottatt per hull) bekreftet "plukket opp" på et par-4-hull med - 1 mottatt slag ga eksakt score 7 (4+2+1, identisk med håndregning), - leaderboardets `total_points` stemte eksakt (0 for plukket-opp-hullet + - 3 for et normalt netto-birdie-hull = 3), reversering (fjern plukket- - opp, sett ekte score, plukk opp igjen) fungerte, avvist tydelig for en - deltaker uten HCP (400), `_format_setup_status` bekreftet IKKE lenger - krasjer (KeyError) for stableford (`setup_complete:true` uten - sider), `RoundUpdate.play_format` PATCH til "stableford" i etterkant - bekreftet, runde fullført uten krasj. - **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP, - samme `localhost`-ikke-`127.0.0.1`-lærdom fra tidligere runder): hele - opprett-runde-flyten klikket gjennom i UI-et (Stableford-valg, riktig - beskrivelsestekst, INGEN sider-notis etter fiksen), "Plukket opp (0 - poeng)"-knappen i selve `ScoringWizard` bekreftet å auto-hoppe videre - akkurat som et vanlig slagtall, og "PU"-merket bekreftet konsekvent på - ALLE FIRE stedene det vises: scorekort-gridet på selve Score-fanen, - det post-runde `round-scorecard.tsx`-scorekortet (med korrekt "Poeng - 0"/netto 6), `round-leaderboard.tsx` (både i hull-stripen og i - Poeng-modus, total "3" korrekt), og "Så langt i runden"-tabellen. En - separat REGRESJONSSJEKK av et eksisterende `match`-format (to sider, - territorium-bar, "1 UP") bekreftet UENDRET oppførsel -- de to bug- - fiksene rørte kun stableford-grenen. Ingen konsollfeil i noen av - rundene. - **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt (plan vist - FØR migrasjonen, per CLAUDE.md sin ufravikelige regel): migrasjon 038 - kjørt mot ekte `teecup_db` (kolonne + utvidet CHECK-constraint - 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. - -- **Scramble/greensome: "utslag brukt"-statistikk NÅ OGSÅ for org-scopede - turneringer, BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT OG LIVE - (2026-07-29), samme dag:** direkte oppfølging av brukerens eget forslag - fra "hva nå?"-runden rett etter Stableford-arbeidet -- funksjonen ble - bevisst holdt utenfor migrasjon 036 (frittstående runder, 2026-07-25), - se dens egen kommentar; dette er den avgrensede oppfølgeren. - **Design, ny migrasjon `039_hole_score_selected_participant.sql`:** - `hole_score.selected_participant_id uuid` (nullable), composite FK - `(organization_id, selected_participant_id) REFERENCES match_ - participant(organization_id, id) ON DELETE SET NULL` -- samme mønster - som `hole_score.match_participant_id` sin egen FK, men SET NULL (ikke - CASCADE): fjernes en deltaker fra matchen (mulig FØR lås) skal ikke - slette allerede registrerte hull-scorer, kun nullstille selve valget. - Kun meningsfullt på en DELT-BALL-hull-rad (`match_participant_id IS - NULL`) -- håndhevet i app-laget (`submit_hole_score`), ikke en CHECK, - samme mønster som migrasjon 036. - **Backend (`app/routers/scoring.py`):** `HoleScoreCreate`/`HoleScoreOut` - fikk `selected_participant_id`. Validering: avvist (400 - `WRONG_PARTICIPANT_MODE`) for individuell ball (`match_participant_id` - satt) -- gir ingen mening der. For delt ball: validert å tilhøre - matchen OG samme `team_side` som selve scoren (400 `VALIDATION_FAILED` - ellers), speiler `round.py` sin identiske sjekk for frittstående runder. - Begge INSERT/UPSERT-setningene i `submit_hole_score` oppdatert (delt- - ball-grenen skriver/oppdaterer feltet, individuell-ball-grenen inkluderer - det aldri i det hele tatt -- alltid NULL der). `get_scorecard` sin - `stroke_entries`-SELECT utvidet -- feltet flyter automatisk gjennom - siden den allerede delte `HoleScoreOut`-modellen gjenbrukes. - **Frontend (`session-scorecard.tsx`):** ny "Hvem sitt utslag ble - brukt?"-knapperad (samme mønster som round-detail.tsx sin - `SideScoreWizard` fikk 2025-07-25) under `StrokePicker` for delt-ball- - enheter -- vises KUN når et slagtall allerede er registrert for hullet - (ulikt frittstående runders `round_hole`, som alltid forhåndsopprettes - med nullbar score, krever `hole_score`-raden faktisk å EKSISTERE først, - siden `gross_strokes` er `NOT NULL` i skjemaet -- en reell strukturell - forskjell fra migrasjon 036s mønster, løst ved å gjenbruke samme - `submitStroke`-funksjon med et nytt valgfritt fjerde argument som - enten beholder gjeldende valg (utelatt) eller setter et nytt (inkl. - eksplisitt `null` for å fjerne). Ny `SelectedDriverSummary`-komponent - (samme presentasjon som round-stats.tsx sin tilsvarende, tilpasset - denne sidens `teams`/`match.participants`/`scorecard.stroke_entries`- - datform) lagt inn i den eksisterende "Vis full oversikt"-seksjonen. - **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle + - isolert scratch-MinIO + engangs API-container, alle 39 migrasjoner - kjørt friskt): en FULL org-turnering-scaffold bygget fra bunnen via API - for FØRSTE gang i en test for akkurat denne funksjonen (org/egendefinert - bane/18 hull/tee/turnering/to lag/fire spillere/roster/foursome-økt+ - match/fire deltakere/lås) -- 51/51 sjekker: skriving uten valg, skriving - med valg (gross_strokes bevart), feil-side-valg avvist (400 - VALIDATION_FAILED), individuell-ball+valg avvist (400 - WRONG_PARTICIPANT_MODE), persistens bekreftet via `GET .../scorecard`, - korrekt opptelling (2 vs. 1 utslag), eksplisitt `null` fjerner valget. - `test_isolation.sql` 12/12 uendret (additiv migrasjon). Ekte typesjekket - produksjonsbuild kjørt og bekreftet. - **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP, - isolert scratch-backend, inkl. et ekte 2FA-oppsett for en fersk - organisasjonseier siden ADR-021 krever det): naviger til foursome- - matchens scorekort, bekreftet at allerede-registrerte valg (satt via - API) vises korrekt som trykte knapper for RIKTIG spiller på RIKTIG - hull -- OG et ekte klikk i UI-et som byttet valgt spiller for et hull, - bekreftet persistert direkte i databasen etterpå (ikke bare at UI-et - så riktig ut). "Vis full oversikt" sin nye "Utslag brukt"-seksjon - bekreftet med nøyaktig riktig opptelling for begge lag (2/1 og 1/0, - inkl. "Registrert på N av M spilte hull"-teksten). Ingen konsollfeil. - **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt (plan vist - FØR migrasjonen): migrasjon 039 kjørt mot ekte `teecup_db` (kolonne + - FK-constraint 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. **"Utslag brukt"-statistikken finnes dermed nå for BEGGE - domener** (frittstående runder siden 2026-07-25, org-scopede - turneringer siden i dag) -- se ARCHITECTURE_DECISIONS.md sitt åpne - spørsmål 4 (Scramble-grensesnitt), som dermed er helt avsluttet. - -- **ADR-037 (individuelle/flerrunde-turneringer): migrasjon + motor + API - BYGGET OG SCRATCH-VERIFISERT, IKKE ENNÅ RULLET UT (2026-07-30):** bruker - ba om å gå videre med anbefalingen fra en dypdykk-gjennomgang av - .md-filene -- det eneste gjenværende punktet med en ferdig, load-bærende - strukturbeslutning (ADR-037, 2026-07-26) uten kode. Bygget i tre lag, - samme "test i isolasjon FØR resten"-rekkefølge som ADR-005/033/038/039. - **Migrasjon `040_individual_tournaments.sql`:** `tournament.format_type` - (`team`/`individual`, default `team` -- alle eksisterende rader uendret) - + `tournament.scoring_method` (`stroke_gross`/`stroke_net`/`stableford`, - nullable). Fem nye, RLS-beskyttede tabeller: `tournament_round` (samme - rolle som `session`, peker til org-ens EGEN bane -- ingen snapshot, - ulikt ADR-033), `tournament_participant` (samme rolle som `team_roster`, - fryser `handicap_index_snapshot`), `tournament_round_participant` - (tee + cachet course/playing-handicap PER runde), `tournament_round_hole` - (rå brutto slag, kilde-sannhet), `tournament_round_score` (ferdig - utregnet brutto/netto/Stableford-total PER deltaker PER runde, cachet -- - samme mønster som `match.status_text`/`points_side_a/b`; sammenlagt over - flere runder summeres VED LESING i leaderboardet, ingen egen tredje - cache-tabell, Beslutning C). - **Scratch-verifisert alene FØR API-et ble bygget:** alle 40 migrasjoner - kjørte rent i rekkefølge, `test_isolation.sql` fortsatt 12/12, 10 egne - funksjonelle sjekker (kryss-org-isolasjon på BÅDE lesing og skriving, - `format_type`-CHECK+default, unik-constraints, `gross_strokes`-CHECK, - kaskade-sletting). - **Motor** (`handicap_engine.py`, ny seksjon rett etter - `allocate_over_played_holes`): `stroke_play_gross_total`/ - `stroke_play_net_total`/`stableford_points_for_hole`/`stableford_total` - -- rene funksjoner, ingen ny slagfordeling (bruker samme - `allocate_over_played_holes`-output som resten av motoren). 8 nye - tester, alle 63 (55 eksisterende + 8 nye) bestått i - `test_handicap_engine.py`. - **API** (nytt `app/routers/individual_tournaments.py`, registrert i - `main.py`): CRUD for runder/turnering-deltakere/rundedeltakere (med - handicap-beregning ved tilføyelse -- v1 har INGEN allowance-prosent for - individuelle turneringer, `playing_handicap` er alltid identisk med - avrundet `course_handicap`, ulikt lagturneringenes komplekse relative - `AllowanceStrategy`-familie, som er bygget for et to-siders oppgjør og - ikke gir mening for et flatt felt), hull-for-hull-scoring - (`PATCH .../holes/{n}`, cacher totalen på nytt ved hver innsending), - leaderboard som summerer på tvers av runder ved lesing. - `tournaments.py` sin `TournamentCreate`/`TournamentUpdate`/`Tournament` - utvidet med `format_type`/`scoring_method` (samme `exclude_unset`-PATCH- - mønster som resten av filen). - **To reelle funn, begge fikset FØR utrulling:** - 1. Leaderboard-endepunktet kunne IKKE hete - `/orgs/{id}/tournaments/{id}/leaderboard` -- den stien er allerede - `tournaments.py` sitt LAG-leaderboard, og siden `tournaments.router` - registreres FØR `individual_tournaments.router` i `main.py`, ville - det stille skygget for det nye endepunktet (funnet presist ved en - ekte API-test som krasjet på feil responsform). Løst med et eget - navn, `/individual-leaderboard` -- samme kollisjonsklasse som - `/rounds` vs. `/my-rounds` tidligere, denne gangen unngått fra start. - 2. `list_rounds`/`list_tournament_participants` manglet en eksplisitt - "finnes turneringen"-sjekk (samme mønster `list_sessions` allerede - har) -- ga stille en tom liste under RLS for en fremmed - turnering-id i stedet for 404 (ingen sikkerhetslekkasje, RLS - blokkerte fortsatt all faktisk data, men inkonsistent med resten av - API-et). Rettet til å matche `list_sessions` presist. - **Scratch-API-verifisert grundig, 52/52 sjekker** (isolert - `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs - API-container, ekte HTTP via `requests`, ekte magic-link-innlogging via - dev-log): full happy path fra org til leaderboard, hånd-utregnet netto- - kryssjekk for to spillere (course handicap 11/20, stemte eksakt), - `front_9`-runde avviser hull utenfor omfang, kryss-org-isolasjon - (bekreftet BÅDE lesing og en FK-basert skrivesperre), slette-vern (runde - MED deltakere avvist 409, turnering-deltaker fortsatt referert av en - rundedeltaker avvist 400 RESTRICT-FK, løst opp igjen etter fjerning). - `test_isolation.sql` 12/12 uendret (additiv migrasjon). - **IKKE bygget i denne runden, bevisst neste steg:** frontend (ingen - skjerm ennå -- samme lagdelings-rekkefølge som ADR-033/038/039). - Autorisasjon er bredt org-medlemskap for ALT i denne runden, inkl. selve - scoreregistreringen -- en senere innstramming (analogt ADR-023s - kaptein-only) er en naturlig, separat oppfølger. De fem konkrete - formatene (Københavner m.fl.) og Order of Merit fortsatt ikke designet. - **Rullet ut mot ekte systemer 2026-07-30**, bruker bekreftet eksplisitt - (plan vist FØR migrasjonen, per CLAUDE.md sin ufravikelige regel): - migrasjon 040 kjørt mot ekte `teecup_db` (alle fem nye tabeller + de to - nye `tournament`-kolonnene bekreftet, eksisterende turnering fikk - korrekt default `format_type='team'`, `test_isolation.sql` fortsatt - 12/12 mot ekte database), deretter `docker compose up -d --build - teecup_api` (kun backend, ingen frontend-kode denne runden). Containeren - boot-et rent (`Application startup complete`), `/health`/`/dashboard` - → 200, `teeoff.no` upåvirket. Verifisert presist at den nye ruten - faktisk når FastAPI gjennom hele produksjonsstacken (Caddy → Next.js - rewrite → `teecup_api`): et anonymt kall mot - `/orgs/.../tournaments/.../rounds` ga korrekt `401 NOT_AUTHENTICATED`, - ikke en rå 404. - -- **ADR-037 frontend HÅNDKODET, GRUNDIG BROWSERVERIFISERT (inkl. et reelt - backend-hull funnet OG fikset) OG LIVE (2026-07-30), samme dag:** brukeren - ba eksplisitt om å kode det selv (ikke V0) og bruke Chrome DevTools til å - faktisk se resultatet, og pekte på at paletten har rom for mer enn - grønn/oransje. - **Bygget:** ny `components/individual-tournament-detail.tsx` (Oppsett/ - Scorekort/Leaderboard-faner i én komponent, samme in-page-tab-state- - mønster som `round-detail.tsx`) + ny `components/tournament-router.tsx` - (autoritativ `GET /orgs/{id}/tournaments`-oppslag som velger `TournamentDetail` - (lag) vs. `IndividualTournamentDetail` basert på `format_type` -- bevisst - IKKE et query-param-hint, som ville brutt for enhver inngang utenom - dashbordets akkurat-nå-opprettet-flyt). `app/tournaments/[id]/page.tsx` - peker nå til routeren. `dashboard.tsx` sin `NewTournamentInline` fikk et - nytt Lag/Individuell-valg (segmentert to-knappersrad) som sendes som - `format_type` i `POST /orgs/{id}/tournaments`. - **Ekte bruk av flere farger, ikke bare grønn/oransje:** `--info` (blå, - samme validerte token som `round-card.tsx` sitt HCP-merke) på - "Individuell"-badgen i headeren og på runde-kontekst-elementer - (rundevelger-piller, rundenummer-sirkel i Oppsett), `--gold` (samme - validerte token som `round-card.tsx` sitt "Personlig rekord"-merke) på - leaderboardets 1.-plass-rad (medaljeikon + gullbakgrunn). - **Scorekortet** gjenbruker `round-scorecard.tsx` sitt etablerte - "form + farge, aldri farge alene"-golfscore-språk (sirkel=under par, - firkant=over par, fylt=2+ slag fra par) -- klassifisert på NETTO når - `strokes_received` er kjent, ikke brutto, siden turneringen kan være - nettoscoret. Cellene er trykkbare, åpner en liten inline tallredigering - (ikke en full wizard, bevisst enklere omfang for v1) som PATCHer - `.../holes/{n}` direkte. - **Ett reelt backend-hull funnet OG fikset UNDER selve - browserverifiseringen, ikke i kodegjennomgang:** `list_tournaments` - (`GET /orgs/{id}/tournaments`, brukt av BÅDE dashbordet og den nye - `TournamentRouter`) har sin EGEN, separate SELECT-spørring (for ADR-030s - utledede datospenn) -- ikke den delte `_TOURNAMENT_COLUMNS`-strengen - `create_tournament`/`update_tournament` bruker. Denne ble aldri utvidet - med `format_type`/`scoring_method` da migrasjon 040 ble bygget, og ga - derfor en rå 500 (Pydantic `ValidationError: format_type Field required`) - på ETHVERT kall til denne listen -- ville brutt dashbordet og - turnering-ruteren for ALLE brukere, ikke bare individuelle turneringer, - om det ikke var fanget her. Rettet med to nye kolonner i SELECT-en. - **Ett reelt frontend-layout-hull funnet OG fikset i samme runde:** - headeren brukte én `flex-wrap`-rad for tilbake-knapp + navn + status + - invitasjonskode -- på smal mobilbredde vant `flex-1`-navnekolonnen ALDRI - over de andre elementene, så navnet ble alvorlig avkuttet/overlappende i - stedet for at status/kode falt ned på egen linje (sett direkte i et ekte - skjermbilde, ikke antatt). Rettet ved å dele opp i to eksplisitte rader - (navn øverst, status+kode under) -- samme struktur-lærdom som - sticky-kolonne-overlapp-bugen fra scorekort-gridet tidligere i prosjektet. - **Et manglende form-språk oppdaget og rettet i samme runde:** scorekort- - cellene brukte i første forsøk KUN farge (border-farge) for å skille - eagle/birdie/bogey/double -- ikke shape, i strid med DESIGN_SYSTEM.md sin - "aldri farge alene"-regel og `round-scorecard.tsx` sin egen etablerte - `ScoreMark`. Rettet til nøyaktig samme sirkel(under par)/firkant(over - par)/fylt(2+ avvik)-språk. - **Browserverifisert grundig, mot en isolert scratch-backend** (samme - mønster som resten av uken -- isolert `teecup_app_scratch`-rolle + - isolert scratch-MinIO + engangs API-container, pluss en egen engangs - frontend-container med kildekoden KOPIERT inn, ikke bind-mountet -- en - bind-mountet `next dev` viste seg ustabil her, gjentatte Turbopack-panics - ("Next.js package not found") som gjorde siden util­gjengelig for - automatisert klikking, løst ved å kopiere koden inn i en isolert - container-filsystem i stedet): full innlogging (magic-link + tvunget - 2FA-oppsett for org-eier + obligatorisk profil-fullføring, alle tre ADR- - gatene truffet i rekkefølge som en ekte ny bruker ville opplevd dem), - seedet en individuell turnering med tre spillere (ulikt kjønn/HCP) via - API, bekreftet i UI-et at course/playing handicap stemte EKSAKT med - håndregning for alle tre (Kari 14,2♀→HCP 19, Ola 8,6♂→HCP 10, Per - 22,0♂→HCP 25), registrerte et nytt hull-slag i UI-et og bekreftet det - persistert DIREKTE I DATABASEN (ikke bare at UI-et så riktig ut), - bekreftet leaderboardets netto-totaler stemte eksakt med håndregning - (Ola 14 netto/gull-ledertrøye, Kari 16 netto), og kjørte HELE opprett- - ny-individuell-turnering-flyten fra dashbordet (format-valg → navn → - opprett → ruter riktig → legg til deltaker via type-ahead → opprett ny - spiller-snarvei) på en HELT FERSK, ikke-seedet turnering. Ingen - konsollfeil (`list_console_messages`) gjennom hele økten. Ekte - typesjekket produksjonsbuild (samme `Dockerfile` som deployes) kompilerte - rent til slutt, med begge fiksene inne. - **Rullet ut mot ekte systemer 2026-07-30**, 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, `GET /orgs/.../tournaments` (den fikset - ruten) bekreftet nåbar med korrekt `401` gjennom hele produksjonsstacken. - **ADR-037 er dermed live, backend + frontend** (grunnstruktur — de fem - konkrete formatene og Order of Merit fortsatt ikke designet, se - FEATURE_BACKLOG.md). - -- **To reelle punkter fra bruker, BEGGE FIKSET OG LIVE, samme dag - (2026-07-30):** rapportert med et vedlagt skjermbilde av - Chip/Bunker/Straffeslag-stepperne som gled over i hverandre på - `/my-rounds/{id}`, pluss en forespørsel om et TeeCup-relatert favikon. - 1. **Stepper-overlapp, root cause presist identifisert:** `ScoringWizard` - sin "flere detaljer"-seksjon (`round-detail.tsx`) er fast begrenset - til `max-w-sm` (384px) UANSETT hvor bred selve viewporten er -- men - Chip/Bunker/Straffeslag-gridet brukte et VIEWPORT-basert - `sm:grid-cols-3`-brudd (640px). Enhver skjerm bredere enn 640px - (dvs. de fleste telefoner i liggende modus, nettbrett, og skjermbildet - brukeren delte) trigget dermed 3-kolonne-modus INNI en 384px-bred - kolonne -- hver Stepper har to faste 44px-knapper (tilgjengelighets- - kravet, kan ikke krympes) + verdi + padding, som aldri kan bli - smalere enn ca. 190px, så tre av dem kolliderte alltid. Samme - klasse feil som de tidligere sticky-kolonne- og match-identitets- - boks-overlappene i prosjektet (fast innholdsbredde møter et - viewport-basert, ikke container-basert, brudd). Fikset ved å fjerne - `sm:grid-cols-3` helt -- alltid stablet i én kolonne, som uansett var - den eneste bredden som noensinne hadde plass i en 384px-container. - 2. **Favikon var faktisk V0s egen logo, ikke TeeCup:** `icon.svg` og - `icon-light-32x32.png`/`icon-dark-32x32.png` (selve nettleser-fane- - ikonet, styrt av `layout.tsx` sin `metadata.icons`) viste seg -- ved - faktisk å åpne og se på filene, ikke anta -- å fortsatt være en - ubrukt "V0"-logo (sort/hvit v0.app-merke) helt siden V0-eksporten, - aldri erstattet. `apple-icon.png` og selve PWA-ikonsettet - (`public/icons/icon-192.png` m.fl.) var derimot ALLEREDE korrekt - TeeCup-merket (grønt golf-flagg, fra PWA-runden 2026-07-19) -- kun - favikon-stien var glemt. Fikset ved å gjenbruke SAMME etablerte - golf-flagg-design: `icon-light-32x32.png`/`icon-dark-32x32.png` - regenerert (nedskalert fra `icon-512.png` via en engangs - `sharp`-scratch, samme verktøy som PWA-runden brukte) og `icon.svg` - skrevet på nytt som en ren vektor av samme flagg (grønn `#8BC24A`- - bakgrunn, hvit flaggstang+flagg -- appens egen merkevarefarge, ikke - funnet på). - **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` - som deployes) kompilerte rent BEGGE ganger (én mislykket 404-test mot - kun `--target builder`-imaget, som IKKE har `public/`-mappen kopiert inn - ved siden av `server.js` ennå -- forventet byggestrenge-artefakt, ikke en - reell bug -- rettet ved å bygge og teste det FULLE, faktiske - produksjonsimaget i stedet, som ga korrekt `200`/riktig `content-type` - for alle tre ikonfilene). Stepper-fiksen browserverifisert grundig i en - isolert scratch-økt (ekte innlogging, en fersk `full`-stat_level-runde - seedet via API, veiviseren kjørt gjennom Slag→Putter→Avstand→Detaljer): - bekreftet INGEN overlapp ved 800px viewport (der bugen reprodusertes - presist, matcher brukerens skjermbilde) OG ingen regresjon ved 390px - (alltid stablet der uansett, uendret oppførsel). Ingen konsollfeil. - **Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt (samme - "redeploy begge"-godkjenning som ADR-037): ingen migrasjon, - `docker compose up -d --build teecup_frontend` (gjenskapte også - `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge - containere boot-et rent, `/health`/`/dashboard` → 200, favikon-filene - bekreftet nåbare over ekte https med riktig `content-type`, `teeoff.no` - upåvirket. - -- **Individuelle turneringer (ADR-037): scoring-autorisasjon strammet inn, - BYGGET, SCRATCH-VERIFISERT OG LIVE (2026-07-30), samme dag som frontend- - runden:** direkte oppfølging av det noterte hullet fra byggerunden - tidligere samme dag — ethvert org-medlem kunne skrive score for HVEM SOM - HELST i en individuell turnering, ikke bare sin egen. - Ny `user_is_own_tournament_participant` i `app/team_authz.py` — samme - mønster som `user_is_match_participant` (ADR-023): krever at brukeren ER - spilleren bak `tournament_participant`-raden (via `player.user_id`), - eller er org-eier/admin. Brukt av `individual_tournaments.py` sin - `update_hole` (eneste endepunkt strammet inn). Runde-/deltaker-OPPSETT - (opprett/slett runde, legg til/fjern turnering-/rundedeltaker) forblir - bevisst på vanlig org-medlemsnivå — matcher presedensen fra `session`- - opprettelse og `team_roster`-tilføyelse i tournaments.py, som heller - aldri har vært captain-/admin-gatet. - **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle + - isolert scratch-MinIO + engangs API-container, alle 40 migrasjoner kjørt - friskt): full regresjon av den eksisterende 52-punkts testsuiten (uendret - grønn — org-eier brukes gjennomgående der, rammes ikke av innstrammingen), - pluss 13 nye målrettede sjekker: et fremmed org-medlem (uten kobling til - noen av deltakerne) NEKTES å score for både en annen deltaker OG en - tredje deltaker (403 `NOT_TOURNAMENT_PARTICIPANT`), en spiller KOBLET til - sin egen `tournament_participant` (via `player.email` + ADR-017s - kontokobling ved innlogging) FÅR score seg selv men NEKTES å score for en - annen, org-eier beholder uendret admin-fallback for begge, og lesing - (`GET .../holes`) er bekreftet uendret tilgjengelig for et vanlig - org-medlem (kun skriving er strammet inn, ikke lesing). - **Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ingen - migrasjon (ren Python-logikk), `docker compose up -d --build - teecup_api`. Containeren boot-et rent (`Application startup complete`), - `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. - -- **To notater fra brukeren, BEGGE BYGGET, GRUNDIG BROWSERVERIFISERT OG - LIVE (2026-07-30), samme dag som ADR-037-autorisasjonsfiksen:** - 1. **GIR-auto-inferens for "Innspill: Traff":** ny `useEffect` i - `ScoringWizard` (`round-detail.tsx`) -- idet "flere detaljer"-steget - nås, settes `stat.approach = "hit"` automatisk når - `stat.strokes - stat.putts <= hole.par - 2` (samme formel som den - allerede eksisterende GIR-STATISTIKK-inferensen i `round-stats.tsx` - sin `isGir`, nå også koblet til selve REGISTRERINGEN) -- men KUN når - `stat.approach` fortsatt er `null` (rører aldri et allerede satt - manuelt ELLER tidligere auto-satt valg). - 2. **Auto-prompt "Fullfør runde":** ny `allHolesEnteredForEveryone`- - beregning + `AllHolesEnteredBanner`-komponent i `RoundDetail` -- viser - en tydelig CTA øverst på Score-fanen ("Alle hull er ført — Klar til å - fullføre runden?") så snart ALLE spillere (eller BEGGE sider for - delt-ball-formater) har `played=true` på alle hull i `holeOrder`, - gatet på `round.setup_complete`. Kaller samme `finishRound()` som den - eksisterende, tidligere passive knappen. - **Browserverifisert grundig i en isolert scratch-nettleserøkt** (fersk - `teecup_scratch`-database + isolert scratch-MinIO + engangs API- - container + en isolert `next dev`-frontend-container med KOPIERT, ikke - bind-mountet, kildekode -- bind-mount ga gjentatte Turbopack-panics i - denne økten, løst ved å `tar`-kopiere kildetreet inn i en isolert - container-filsystem i stedet): GIR-auto-inferens bekreftet BÅDE positivt - (birdie+1-putt -> "Traff" auto-merket umiddelbart, uten klikk, bekreftet - visuelt OG direkte i databasen) og negativt (bogey+1-putt -> "Traff" - korrekt IKKE forhåndsmerket). Auto-prompt-banneret bekreftet å dukke opp - automatisk idet siste hull ble fylt for begge spillerne i en 18-hulls - 2-spiller-runde, og "Fullfør runden"-knappen i banneret bekreftet å - fullføre runden korrekt (håndterte den native `confirm()`-dialogen via - Chrome DevTools -- HCP-differensialer beregnet og vist etterpå). Ingen - konsollfeil i noen av rundene. - **Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ingen - migrasjon (ren frontend), `docker compose up -d --build - teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning, - ingen backend-kode rørt). Begge containere boot-et rent, `/health`/ - `/dashboard` → 200, `teeoff.no` upåvirket. - -- **Symbolforklaringen fjernet fra scorekortet + to design-notater fanget - opp (2026-07-30), samme dag:** brukeren viste et skjermbilde av - `round-scorecard.tsx` med "Under par (sirkel)/Over par (firkant)/Fylt - symbol..."-forklaringen sirklet inn og ba om at den fjernes — `Legend`- - komponenten (kun ett bruksted) fjernet fullstendig. Samtidig reist: - (1) spilleren bør kunne se historikk/statistikk for NØYAKTIG hullet som - spilles/er spilt (ikke bygget — krever en ny, ikke-triviell "aggreger på - tvers av runder, filtrert til banenavn+hullnummer"-spørring, notert i - FEATURE_BACKLOG.md); (2) manglende lenke scorekort→statistikk, og et - åpent spørsmål om scorekort/statistikk/live-registrering burde slås - sammen til én visning, med mottatte slag vist som prikker. Svarte - direkte (ikke bygget): behold scorekort og statistikk som to separate - sider (bevisst skilt 2026-07-25 nettopp for å unngå én lang/rotete - side) — legg heller til den manglende lenken; behold også selve - live-registrerings-gridet uendret (bygget 2026-07-27 som en AKTIV - data-entry-flate, ikke en lesevisning — et vertikalt front9/back9-delt - format ville svekket registreringsergonomikken). "Prikker for mottatte - slag" vurdert som en god, uavhengig senere polish-oppgave. Full - begrunnelse i FEATURE_BACKLOG.md. - **Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ekte - typesjekket produksjonsbuild kompilerte rent, `docker compose up -d - --build teecup_frontend` (gjenskapte også `teecup_api` som vanlig - bivirkning). Begge containere boot-et rent, `/health` → 200, - scorekort-siden bekreftet 200, `teeoff.no` upåvirket. - -- **De to avgrensede scorekort-oppfølgerne fra samme dag BYGGET, - BROWSERVERIFISERT OG LIVE (2026-07-30):** (1) ny "Se full - rundestatistikk"-lenke nederst på `round-scorecard.tsx` (symmetrisk med - den eksisterende motsatte lenken på statistikksiden); (2) mottatte - slag-hintet (vist FØR et hull er fylt ut) endret fra "−N"-tekst til - prikker (`StrokeDots`, `round-detail.tsx`, i BÅDE `ScorecardGrid` og - `SideScorecardGrid`) — selve tallet bevart i `aria-label` for - tilgjengelighet. Selve spørsmålet om å slå sammen scorekort/statistikk/ - live-registrering til én visning er BEVISST IKKE avgjort — bruker - ønsker en fremtidig brukertest først. - **Browserverifisert grundig i en isolert scratch-nettleserøkt** (samme - mønster som resten av uken): en HCP 28-spiller (course handicap 30) - bekreftet å vise nøyaktig to prikker på hull 1-12 i live- - registreringsgridet (riktig ut fra 30-18=12 ekstra slag), den nye - lenken bekreftet klikkbar og navigerte korrekt til `/stats`. Ekte - typesjekket produksjonsbuild kompilerte rent. Ingen konsollfeil (kun en - godartet, urelatert WebSocket-advarsel fra rask sidenavigasjon). - **Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ingen - migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte - også `teecup_api` som vanlig bivirkning). Begge containere boot-et - rent, `/health`/`/dashboard`/scorekort-siden → 200, `teeoff.no` - upåvirket. - -- **Åtte nye turneringsformater: BACKEND FERDIG, SCRATCH-VERIFISERT OG - RULLET UT LIVE (2026-07-30):** brukeren ba om å ta fatt på det - lenge noterte "flere turneringsformater"-punktet fra 2026-07-19. Listen - ble utvidet fra fire til åtte (Shamble, Chapman/Pinehurst, Bingo Bango - Bongo, Money Ball/Lone Ranger, Nassau Match Play lagt til; "High-low- - high"/"Try all" presist avklart av bruker med et fullt utregnet - eksempel; "Robbins" droppet). Full plan skrevet og godkjent (plan- - modus) FØR bygging, deretter bygget ETT format om gangen i rekkefølgen - Chapman → Nassau → Københavner → Bingo Bango Bongo → Flaggturnering → - Shamble → Money Ball → High-low-high, motor→migrasjon→API→scratch- - verifisering per format FØR neste startet. - **Arkitektonisk hjem** (bekreftet av bruker "begge, fra start"): flatt- - felt-formater (Københavner/BBB/Flag) → frittstående runder OG den - individuelle org-turnering-modellen (ADR-037), ALDRI org-lagturneringer - (ADR-011s to-lags-modell passer strukturelt ikke). To-siders-formater - (Chapman/Nassau/Shamble/Money Ball/High-low-high) → frittstående runder - OG org-lagturneringer, ALDRI den individuelle org-modellen. Shamble/ - Money Ball i frittstående runder: ETT LAG = HELE RUNDENS deltakersett - (bekreftet av bruker, INGEN `round_side`) -- flere lag kobles via - eksisterende `flight_group_id`-leaderboard. - **Ny delt `ENGINE_FORMAT_ALIASES`-mekanisme** i `app/handicap.py` -- - Chapman/Shamble/Money Ball/High-low-high aliaserer til - foursome/singles/singles/fourball for HCP-beregning (ingen dupliser - allowance-logikk), løst opp FØRST i `parse_allowance_config`/ - `compute_and_store_side_handicaps`/`relative_strokes_for_match`. - **Reelle funn/presiseringer underveis, alle rettet FØR utrulling:** - Nassau kan hindres av org-matchers eksisterende ALREADY_DECIDED-sperre - (ADR-012) hvis overall-matchen avgjøres tidlig -- dokumentert v1- - begrensning, ikke fikset. Københavner-poeng må regnes om for ALLE tre - deltakerne samtidig (eneste scoring_method som bryter "regn om for ÉN - deltaker"-mønsteret). Money Ball-rotasjon er basert på POSISJON i spilt - rekkefølge, ikke rått hullnummer. High-low-high passer ikke inn i den - ternære `HoleResult`-cachen -- egen dedikert leseendepunkt per system - (som Nassau), generisk `status_text` viser en ufarlig statisk "AS"- - plassholder for dette formatet. To reelle "manglende SELECT-kolonne"- - krasj (bbb_sweep_bonus_enabled, shamble_best_n) fanget og rettet under - scratch-testing, før noe nådde en antatt-ferdig tilstand. - **Verifisert grundig:** `handicap_engine.py`-enhetstester 98/98 (opp fra - 63 FØR denne runden), full scratch-API-verifisering i begge relevante - systemer per format (isolert `teecup_app_scratch`-rolle + isolert - scratch-MinIO + engangs API-container) -- over 400 sjekker totalt på - tvers av de åtte formatenes testskript, inkl. High-low-high sin - eksakte gjenskaping av brukerens eget håndregnede eksempel ("1-1 etter - hull 1", "2-1 til lag 2"). `test_isolation.sql` 12/12 uendret gjennom - hele runden (migrasjonene 041-050 er rent additive). - **IKKE bygget ennå, bevisst neste steg:** frontend for samtlige åtte - formater (nye format-valg i opprett-runde/opprett-økt, nye - resultatvisninger) — backend er fullt funksjonelt og testet, men - ubrukelig fra selve appen inntil frontend bygges, samme lagdelings- - mønster som ADR-037/038/039. - **Rullet ut mot ekte systemer 2026-07-30**, bruker bekreftet eksplisitt - ("Ja takk"): migrasjonene 041-050 kjørt i rekkefølge mot ekte - `teecup_db` (alle 10 rene, additive `DROP/ADD CONSTRAINT`+nye - tabeller/kolonner, ingen eksisterende rader rørt), `test_isolation.sql` - fortsatt 12/12, deretter `docker compose up -d --build teecup_api`. - Containeren boot-et rent (`Application startup complete`), `/health`/ - `/dashboard` → 200, `teeoff.no` upåvirket. Verifisert presist at det nye - Nassau-endepunktet faktisk når FastAPI gjennom hele produksjonsstacken: - et anonymt kall ga korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404. - Scratch-miljøet (isolert rolle/database/MinIO/API-container, alle - test-skript) ryddet opp etterpå, som vanlig. Se FEATURE_BACKLOG.md for - full detalj per format. - -- **Driftsvarsel ved ny konto, BYGGET, SCRATCH-VERIFISERT OG LIVE - (2026-07-30):** brukeren ba om en e-post til seg selv - (`hei@erol.no`) hver gang noen oppretter en HELT NY TeeCup-konto — - avklart eksplisitt (AskUserQuestion) at dette gjelder kontoopprettelse - generelt (ikke turnering-selvregistrering, ADR-017, som var det andre - alternativet). Ingen migrasjon — ren kode-endring. - Ny `settings.NEW_ACCOUNT_ALERT_EMAIL` (`app/config.py`, env-variabel - `TEECUP_NEW_ACCOUNT_ALERT_EMAIL`, default `hei@erol.no` — egen setting - fremfor hardkodet adresse, kan endres uten ny utrulling). Ny - `send_new_account_alert_email()` i `app/email.py` (alltid norsk — internt - driftsvarsel til én fast, kjent mottaker, ikke brukervendt i18n-tekst). - **Kjernestykket:** `verify_magic_link` (`app/routers/auth.py`) er den - ENESTE plassen en `app_user`-rad noensinne settes inn (bekreftet med - `grep -rn "INSERT INTO app_user"` — null andre treff) — utvidet til å - fange `is_new_account` (sann KUN når selve INSERT-en faktisk vant, ikke - ved en samtidig konflikt ELLER en sekundær-e-post-innlogging som løses - til en eksisterende konto, `FEATURE_BACKLOG.md` "Én person, flere - e-postadresser"). Selve e-postutsendingen skjer BEVISST ETTER at - `plain_connection()`-blokken er lukket (samme "e-post skal aldri sendes - mens en tilkobling/transaksjon holdes åpen"-prinsipp som resten av - appen), med samme `DEV_LOG_MAGIC_LINKS`/`SMTP_CONFIGURED`/try-except- - mønster som all annen e-postutsending — en driftsfeil i selve - sendingen kan aldri endre innloggingsresponsen. - **Scratch-verifisert, 8/8 sjekker:** varsel logget nøyaktig én gang ved - aller første innlogging for en ny e-post, INGEN nytt varsel ved en - påfølgende innlogging for SAMME konto, og et helt nytt, eget varsel for - en ANNEN ny e-post (uendret telling for den første). `test_isolation.sql` - 12/12 uendret (ingen skjemaendring). - **Rullet ut live 2026-07-30**, sammen med migrasjonene 041-050 over (én - samlet utrulling, bruker bekreftet eksplisitt): ingen migrasjon for - denne delen isolert, dekket av samme `docker compose up -d --build - teecup_api`-kjøring, `/health`/`/dashboard` → 200, `teeoff.no` - upåvirket. - -- **Login-skjermen redesignet ("Forest Green", inspirert av et eksternt - design-verktøy kalt "Stitch"), BYGGET OG LIVE (2026-08-01/02):** - brukeren ba eksplisitt om et V0-prompt basert på Stitchs skisse, kjørte - det selv, sendte zip-eksporten tilbake. `frontend/components/login- - form.tsx` skrevet fullstendig om — V0s nye visuelle skall (låst - fargepalett `const C = {...}`, WCAG-kontrast verifisert presist, ikke - anslått, for hvert fargevalg) flettet med 100 % ekte logikk fra den - gamle komponenten uendret: `sendLink()` (`POST /auth/request-link`), - passord-innlogging (`POST /auth/login-password`), invitasjonskode- - oppslag (`GET /public/tournaments/by-code/{code}` + navigasjon). - `TwoFactorVerifyForm`/`TwoFactorSetupForm` (ADR-021) beholdt HELT - uendret, kun rendret inni det nye kortskallet. **Reell bug funnet under - integrering, browserverifisert:** `app/page.tsx` hadde sin egen - `<Wordmark/>`+tagline-header OVER selve `LoginForm`, som nå OGSÅ hadde - fått sin egen header inni det nye kortet — duplikat. Fjernet den ytre - headeren + det nå ubrukte `Wordmark`-importet fra `page.tsx`. - **Rullet ut live**, bruker bekreftet eksplisitt ("Rull ut live. Jeg har - comitted til git og kan reversere om nødvendig."): ren frontend-endring, - ingen migrasjon. - -- **Dashbordet redesignet i samme visuelle retning, BYGGET OG LIVE - (2026-08-01/02), to runder samme dag:** første runde (V0-eksport) - skrev om `dashboard.tsx` fra bunnen — flettet ekte ADR-035-datalag - (organisasjoner/turneringer/runder/HCP-historikk/venner/varsler) med - V0s nye visuelle seksjoner, la til en NY «Venner på banen»-seksjon - (bekreftet av bruker at den skal ligge RETT UNDER hurtighandlingene, - jf. Stitchs egen skisse — jeg hadde ikke selv sjekket dette FØR - brukeren spurte eksplisitt, rettet før prompten ble sendt). - **Ny backend-endepunkt bygget for dette:** `GET /friends/on-course` - (`app/routers/rounds.py`, ny `FriendOnCourseEntry`-modell) — venner med - en PÅGÅENDE runde, eller en fullført innen siste 24 timer (bekreftet - av bruker), filtrert gjennom SAMME synlighets-SQL som `_can_view_ - round` sin venner-gren (ADR-036 fase 2) — ingen egen, parallell - synlighetsregel. - **Andre runde, presise fargekorreksjoner mot Stitchs FAKTISKE - skjermbilde** (ikke bare beskrivelse — brukeren lastet opp det ekte - bildet etter at jeg først måtte innrømme jeg hadde slettet den - opprinnelige zip-en og derfor sammenlignet blindt): pikselverdier - hentet presist via PIL i en engangs Docker-container (Stitchs egen - knappegrønn viste seg å være `#2d950c`, kontrast mot hvit tekst kun - 3,88:1 — FEILER WCAG AA, flagget proaktivt FØR den ble brukt). - `const C` fikk `primary:"#1f6b08"` (samme fargefamilie, kontrast - ~6,6:1, verifisert med samme relative-luminans-formel), `primaryInk: - "#ffffff"`, `borderSoft:"#e4ebe3"`, ny `AVATAR_HUES`-array (pastell- - par til venne-avatarer). Header endret fra glassmorfisme til flat - opak bakgrunn, flagg-ikonet fikk lys grønn bakgrunn i stedet for - oransje, `QuickAction`/`ShortcutButton` og `LiveFriends`-kortene - fargekorrigert tilsvarende. - **Ny fast bunn-fanerad** (`BottomTabBar`, eksportert fra - `dashboard.tsx`, brukt av flere sider): fem faner (Hjem/Runder/ - Turneringer/Profil/Mer). **Reell rute-kollisjon funnet og rettet FØR - utrulling** (samme klasse feil som tidligere i prosjektet, `/rounds` - vs. API-prefikset) — V0s genererte `href="/rounds"`/`href="/tournaments"` - pekte begge feil (ingen slik side finnes) — rettet til `/my-rounds` og - et ankerpunkt `/dashboard#kommende-turneringer` (ingen egen turnering- - liste-side finnes). Ny, tidligere ikke-eksisterende `/more`-side bygget - (`components/more-menu.tsx`, `app/more/page.tsx`) som femte fanes reelle - mål — samler Konto/Venner/Varsler/organisasjoner ett sted, ekte - `handleLogout()`. `<main>`-padding økt (`pb-24`) for å ikke overlappe - den nye faste bunnraden. - `install-prompt.tsx` fikk kun ny JSX (mørk grønn gradient-banner), - 100 % av den ekte iOS/Android-deteksjons-/`beforeinstallprompt`-/ - 14-dagers-utsettelseslogikken uendret. - **Rullet ut live**, bruker bekreftet eksplisitt: ingen migrasjon for - visuelle deler, ny `/friends/on-course`-ruten dekket av samme - `docker compose up -d --build teecup_api teecup_frontend`. Begge - containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` - upåvirket. - -- **«Det store grepet»: sammenhengende rundeoppsett + delt Score/ - Scorekort/Leaderboard-navigasjon — steg 1+2 av 5 BYGGET, SCRATCH-/ - BROWSERVERIFISERT OG LIVE (2026-08-02), se ADR-040 for full - beslutningslogg:** direkte oppfølging av login-/dashbord-redesignet — - brukeren pekte på to strukturelle hull utover selve fargespråket - (rundeoppsettet spredt over to skjermer, Score/Scorekort/Leaderboard - deler ingen fast navigasjon). Fem beslutninger bekreftet (ADR-040 - Beslutning A-E), plan skrevet som en levende Artifact-skisse før - bygging. - **Steg 1 (backend, HCP-prosent alltid justerbar + Match-HCP-bryter):** - ny migrasjon `051_round_allowance_override.sql` (`round.allowance_ - override jsonb`), koblet inn i `_recompute_side_handicaps`/`_relative_ - strokes_for_round`/`POST`+`PATCH /rounds` (`app/routers/rounds.py`). - Gjenbruker ADR-014s allerede ferdigbygde motor uendret (`match_play_ - strokes()`, `parse_allowance_config`) — frittstående runder manglet - bare selve kolonnen. - **Steg 2 (ren kode-fiks, ikke V0): spillere i scorekortet var ALDRI - gruppert lagvis** — reelt, tidligere udokumentert funn rapportert av - bruker med en konkret lenke (fourball-runde der to kjente lagkamerater - ble vist interleaved). Bekreftet i koden: `ScorecardGrid` (`round- - detail.tsx`) og `MatchScorecardGrid` (`round-scorecard.tsx`) sorterte - ingen av dem på `round_side_id`. Fikset med stabil sortering (side A - samlet, deretter side B) i begge. - **Scratch-verifisert presist:** 100 %→50 %-prosent ga `playing_ - handicap` 24/10 → 12/5 (nøyaktig som beregnet for hånd), `use_ - matchplay_handicap:false` ga rå 12/5 i stedet for differensial 7/0. - Lag-grupperingsfiksen browserverifisert mot en fersk scratch-fourball- - runde som gjenskapte brukerens eget scenario nøyaktig (interleaved - tilføyelsesrekkefølge A→Rødt, B→Blått, C→Rødt, D→Blått) — bekreftet - visuelt i BEGGE visningene at lagene nå vises samlet. - **Rullet ut mot ekte systemer 2026-08-02**, bruker bekreftet eksplisitt - (plan vist FØR migrasjonen): migrasjon 051 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, anonym - `PATCH /rounds/{ukjent-id}` ga korrekt `401` (ikke en rå 404), `teeoff.no` - upåvirket. - **Gjenstår (steg 3-5 av 5, ingen V0-prompt skrevet/sendt ennå):** - V0-prompt 1 (den samlede rundeoppsett-veiviseren), V0-prompt 2 (den - delte fane-raden + spørsmålet om lag-markering utover sortering), - deretter integrering/browserverifisering/utrulling av begge. Se - FEATURE_BACKLOG.md for detaljert arbeidsnotat. - -- **«Det store grepet»: steg 3 av 5 (V0-prompt 1, rundeoppsett- - veiviseren) BYGGET, GRUNDIG VERIFISERT OG LIVE (2026-08-02), samme - dag:** V0-prompten (Beslutning A/B) skrevet, kjørt av bruker (zip 25), - `frontend/components/new-round.tsx` skrevet fullstendig om — - V0s 5-stegs veiviser-skall flettet med ekte logikk (offisiell/egen - bane-søk inkl. nearby-geolokasjon, ekte egen-bane-opprettelse portert - fra tidligere versjon, ekte `/people/search`, ny `allowance_override`- - konstruksjon i steg 2 samme mønster som `CreateSessionCard`, og en helt - ny innsendingsorkestrering — runden+deltakere+sider/lineup finnes - FØRST når hele veiviseren er fullført, ulikt den gamle "opprett runden - først"-flyten). - **Reelle feil funnet og rettet under integrering:** V0s egen - `Tee`-type manglet kjønnsfelt (ville vist ufiltrerte utslag) — lagt til - `genders`, filtrert riktig. To TS-feil (prop-spredning som overskrev - `course`/`ownGender` tilbake til `null`, en `"x"`-kjønn-mismatch i - tee-filtreringen). - **Verifisert grundig i isolert scratch:** full produksjonsbuild (alle - ruter listet), to komplette nettleser-gjennomkjøringer mot en fersk - scratch-backend (én Fourball-runde med egen bane/gjest/to sider/90 % - HCP-prosent, én Slagspill-runde med rene standardvalg) — bekreftet - direkte mot databasen at `allowance_override` ble bygget nøyaktig - riktig i begge tilfeller (`per_player` for Fourball siden det ikke er - en side-enhet, `null` for Slagspill), sidene/tildelingene stemte, og - HCP ble beregnet korrekt fra ekte profildata. - **Rullet ut live 2026-08-02**, bruker bekreftet eksplisitt: ingen - migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte - også `teecup_api` som vanlig bivirkning). Begge containere boot-et - rent, `/health`/`/dashboard`/`/my-rounds/new` → 200, `teeoff.no` - upåvirket. Full detalj i ARCHITECTURE_DECISIONS.md (ADR-040). - -- **«Det store grepet»: steg 4-5 av 5 (V0-prompt 2, den delte Score/ - Scorekort/Leaderboard-fane-raden + integrering) BYGGET, GRUNDIG - VERIFISERT OG LIVE (2026-08-02), samme dag — ADR-040 dermed HELT - FERDIG:** V0-prompten (Beslutning C/D) bevisst smalere i omfang enn - prompt 1 — kun selve header/fane-chrome-en, ikke en redesign av - Scorekort-sidens innhold (den sammenslåingen med Statistikk er ren - kode-sammenstabling, løst direkte uten V0). Kjørt av bruker (zip 26) - — ny `components/round-header.tsx` (tatt inn uendret: kontekstblokk - + tre-fanet segmentert kontroll + en "Administrer"-knapp som åpner en - bunnsheet-dialog med "Spillere og runde"-lenke + "Fullfør runde" + - to-stegs "Slett runde"). - **Ekte datalag:** ny `components/round-page-shell.tsx` — henter - rundedata for headeren, implementerer `onFinishRound`/`onDeleteRound` - som egne, selvstendige kall (samme `POST .../complete`/`DELETE ...` - som før, men uavhengig siden komponenten deles av alle tre sidene). - **Wiret inn:** Score (`round-detail.tsx` sin gamle egne header - fjernet, erstattet med `RoundPageShell` — den interne "Score"/ - "Spillere og runde"-fanevekslingen bevisst BEHOLDT som lavrisiko-valg, - nås nå i tillegg via headerens dialog med en ny `?tab=manage`-URL- - parameter), Scorekort (`round-scorecard.tsx`+`round-stats.tsx` slått - sammen til ÉN side via en ny `embedded`-prop på begge -- statistikk - RETT UNDER scorekortet, gammel `/stats`-rute er nå en redirect dit), - Leaderboard. - **Reelt funn og fiks under integrering:** to `<main>`-landemerker på - samme side etter sammenslåingen (ugyldig HTML/a11y) -- rettet med en - `ContentTag`-switch (`div` når embedded) i `round-stats.tsx`. Fjernet - nå overflødige kryss-lenker, kollapset "Se scorekort"/"Se full - rundestatistikk" til én knapp i `round-detail.tsx` sitt fullført- - banner. - **Verifisert grundig i isolert scratch:** full produksjonsbuild, full - nettleser-gjennomkjøring av hele "Administrer"-flyten -- headeren - konsistent på tvers av alle tre fanene, dialogens lenke til "Spillere - og runde" bekreftet å faktisk åpne riktig intern fane, ekte "Fullfør - runde" (bekreftet `completed_at` satt via API etterpå) og ekte - to-stegs "Slett runde" (bekreftet navigerte korrekt til tom-tilstand - etterpå). Traff en Turbopack-flakighet i scratch-dev-serveren - underveis (stuck spinner) -- løst med frisk containerstart, bekreftet - IKKE en kodefeil. - **Rullet ut live 2026-08-02**, bruker bekreftet eksplisitt: ingen - migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte - også `teecup_api` som vanlig bivirkning). Begge containere boot-et - rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. - **Bevisst IKKE dekket:** Beslutning E sitt spørsmål om lag- - gruppering trenger mer enn celle-fargelegging (sortering i seg selv - ble fikset i steg 2) -- prompt 2 ble bevisst avgrenset til kun - header-chrome-en, dette spørsmålet er derfor fortsatt åpent, ikke - stilt til V0 ennå. Se FEATURE_BACKLOG.md. - -- **Rundeoppsett-veiviseren: dato/klokkeslett forhåndsutfylt, LIVE - (2026-08-02), samme dag:** brukeren ba om at dagens dato og gjeldende - klokkeslett skal være default når man setter opp en runde, i stedet - for tomme felt. `frontend/components/new-round.tsx` sitt Steg 1 fikk - to nye lokale hjelpefunksjoner (`todayIso()` -- lokal tidssone, ikke - UTC, portert fra en tidligere versjon av filen, samme presisjonshensyn - som `<input type="date">` alltid har krevd i dette prosjektet; - `nowTimeString()`) — `date`/`time`-state initialiseres nå med disse i - stedet for tomme strenger (dato respekterer fortsatt `prefillPlayedAt` - fra "legg til en flight til"-flyten der den er satt). Browserverifisert - i isolert scratch: begge feltene viste korrekt dagens dato/klokkeslett - ved første besøk til Steg 1s felt-visning. Rullet ut live, ingen - migrasjon, kun `docker compose up -d --build teecup_frontend`, `/health`/ - `/my-rounds/new` → 200, `teeoff.no` upåvirket. - -Neste steg: -0a. **Spillerliste-redesign — nå FAKTISK nettleser-bekreftet - (2026-07-27, full 22-skjerms gjennomgang):** rendrer korrekt, ingen - konsoll-feil. Ikke hvert enkelt interaksjonsdetalj (f.eks. gjeste- - kjønnsendringens reaktive utslagsfilter) klikket gjennom stykke for - stykke, men grunnleggende rendring/lasting er bevist, ikke lenger - bare typesjekket. -0b. **Rundeleaderboard — nå FAKTISK nettleser-bekreftet (2026-07-27):** - brutto/netto/poeng-veksling testet direkte i nettleseren, viste - korrekte tall og riktig form/farge-språk. -1. **Ferdig, kun for historikk:** dashbord-redesign (ADR-035) og - venner/kategorisert deling fase 1 (ADR-036) — begge designet - 2026-07-25 og siden BYGGET, SCRATCH-VERIFISERT OG RULLET UT LIVE - samme dag (se status over). Venner fase 2 (rundevisibilitet) og - fase 3 (ekte medspillere) er fortsatt ikke bygget. -3. **Frittstående rundeføring + detaljert statistikk — ADR-033, skrevet - 2026-07-22 og siden BYGGET/ITERERT KONTINUERLIG hver dag fram til - 2026-07-29 (se hele status-loggen over -- dette punktet er nå appens - klart mest utviklede område, ikke lenger "ikke bygget").** Beholdt her - UENDRET som historisk kontekst for selve grunnbeslutningen fra - 2026-07-22 (hvorfor/hvordan ADR-033 ble designet) -- ikke som en - påstand om at arbeidet fortsatt gjenstår. Brukeren avklarte 2026-07-22 - at dette skulle bli appens HOVEDFOKUS (turneringsoppsett skulle bli - ekstremt enkelt ETTER dette var på plass) — den største enkeltbeslutningen - i prosjektet siden ADR-001, og det har stemt. Fire load-bærende - delbeslutninger avklart eksplisitt - (AskUserQuestion): nytt parallelt eierskapsmønster keyet på `user_id` - (gjenbruker det allerede beviste `plain_connection()`-mønsteret fra - personlig profil/HCP-historikk/sekundær e-post — IKKE en skjult - personlig organisasjon), fast sett navngitte statistikk-felt per hull - (ikke fri slag-for-slag-logg), full WHS HCP-indeksberegning bygges NÅ - (ikke utsatt), shotgun-start blir egen separat ADR-034. De tre - HCP-PDF-ene lest i sin helhet — Score Differential/Course - Handicap/Net Double Bogey-formlene er hentet derfra (kilde, ikke - hukommelse). - **Oppdatert samme dag:** brukeren bekreftet banedata-spørsmålet (LIVE - oppslag mot teeoff, ikke import — Beslutning C) og lastet opp en - FJERDE PDF, den offisielle "WHS Rules of Handicapping 2024" - (USGA/R&A) — lest i sin helhet, løste presist det som manglet: 9- - hulls-minimumsregelen (Rule 2.2, IKKE bare "minst 9 hull" — en - 9-hulls-runde krever ALLE 9 av et faktisk ratet sett, en 18-hulls- - runde krever minst 10 av 18), full HCP-indeks-pipeline (Score - Differential, Net Double Bogey, expected-score for uspilte hull, beste - 8-av-20, Low Handicap Index, soft/hard cap), OG en ny, presis - 9-hulls-Course-Handicap-formel som HALVERER indeksen først (Rule - 6.1b) — bevisst holdt atskilt fra det eksisterende front_9/back_9- - øktoppsettet i turnering-flyten (ADR-008), som løser et annet problem - av andre grunner. ADR-033 er dermed fullt kildebelagt, ingen store - åpne HCP-regelspørsmål gjenstår. - **Bygging påbegynt samme dag:** brukeren instruerte at "Expected - Score" (Rule 3.2b, upublisert WHS-formel) erstattes gjennomgående av - WHS sin egen "Net Par"-term for uspilte hull — løser samtidig hele - 9-hulls-runde-spørsmålet uten en egen separat formel (Rule 5.1b droppet - bevisst). Hele HCP-indeks-motor-komponenten (Net Double Bogey/Net Par, - Adjusted Gross Score, Score Differential, Handicap Index fra beste- - 8-av-20 m/opptrappingstabell for <20 runder, Low Handicap Index, soft/ - hard cap, 9-hulls Course Handicap) er BYGGET og TESTET i - `handicap_engine.py` — 41/41 tester (`test_handicap_engine.py`, kjørt - uten pytest da det ikke er installert i miljøet), flere verifisert mot - regelbokens egne tallregneeksempler (Rule 5.2a, Rule 5.1c, Diagram 5.8, - Diagram 3.1b). Ren Python, ingen DB/API/frontend rørt ennå — matcher - ADR-005s "test i isolasjon FØR resten". - **Deretter, samme dag:** full databasemigrasjon skrevet - (`020_personal_rounds.sql`, sju nye tabeller: `round`/ - `round_participant`/`round_hole` + fire for en global egendefinert- - bane-katalog) og scratch-verifisert (9 sjekker som - `teecup_app_scratch`, ikke superbruker — CHECK-constraints, XOR - user_id/guest_name, kaskade-sletting, GIR-derivering bekreftet mot - ekte data). INGEN RLS på disse tabellene (Beslutning A). `test_ - isolation.sql` fortsatt 12/12. - **Rullet ut mot ekte `teecup_db` 2026-07-22,** bruker bekreftet - eksplisitt: alle sju tabeller bekreftet opprettet, `test_isolation.sql` - fortsatt 12/12 mot ekte database. - **Deretter, samme dag: fullt API-lag bygget** (`app/routers/ - rounds.py` — opprett/liste/hent/slett runde, legg til/fjern gjest- - deltaker, PATCH hull-for-hull-stats, fullfør-runde som kjører hele - Adjusted-Gross-Score→Score-Differential-kjeden). Fant og fikset et - reelt hull underveis: `round_participant` manglet rating-tall- - kolonner (kun tee-NAVN var snapshotet, ikke selve Course/Slope/Par) — - ny migrasjon `021_round_participant_rating_snapshot.sql`. 26 scratch- - sjekker + en egen, ekte teeoff-live-oppslag-test (Beslutning C - bekreftet: ingen `course`-rad skrives noe sted). Rullet ut mot ekte - `teecup_db`/`teecup_api` 2026-07-22, `/health`/`/dashboard` → 200, - `teeoff.no` upåvirket. `frontend/next.config.mjs` fikk `/rounds`/ - `/personal-courses` lagt til i `rewrites()` proaktivt (ikke deployet - ennå, ingen frontend bruker den før neste runde). - **Notat fra bruker, IKKE designet:** planer om å måle lengde på slag + - opplyse avstand til ulike punkter på banen (golf-GPS/rangefinder-type - funksjonalitet) — krever geografiske/GPS-data ingen kilde har i dag - (verken teeoff eller `personal_course`). Se FEATURE_BACKLOG.md for - full detalj. - **Frontend bygget og rullet ut 2026-07-23, samme rekkefølge-prinsipp - (engine → skjema → API → frontend) fullført:** tre nye, - organisasjonsuavhengige endepunkter lagt til i `rounds.py` - (`GET /rounds/official-search`/`{slug}` — samme mønster som - `courses.py` sitt org-scopede søk, men uten org-kontekst, siden - frittstående runder ikke har noen; `GET /personal-courses/{id}` — - detalj med utslag+kjønn, manglet fra søk-runden) og en fjerde, - nødvendig tilføyelse oppdaget UNDER frontend-designet: `RoundOut` bar - aldri hull-nivå-data i det hele tatt — ny - `GET .../participants/{id}/holes`. Hull-PATCH-endepunktet endret til å - returnere hele den oppdaterte raden i stedet for `{"ok": true}`. - **Reelt kontraktsfunn, bekreftet i scratch FØR frontend stolte på det:** - hull-PATCH er IKKE et ekte delvis-PATCH — den skriver ALLE felt ved - hvert kall, så et utelatt felt (f.eks. putts) nullstilles stille hvis - frontend ikke sender det. Løst ved at `round-detail.tsx` alltid slår - sammen med gjeldende hull-data før hver PATCH, aldri sender et isolert - feltnavn alene — verifisert eksplisitt med en egen scratch-test som - FØRST beviste nullstillings-oppførselen, DERETTER beviste at - merge-mønsteret unngår den. - Nye sider: `/rounds` (liste), `/rounds/new` (bane-kilde teeoff/egen, - søk-før-opprett for egen bane samme idé som org-banene, utslag filtrert - på brukerens registrerte kjønn, dato/starthull/hull-antall), `/rounds/ - [id]` (deltaker-faner, hull-navigasjon fra starthull, slag/putt- - tallvelgere i samme stil som `session-scorecard.tsx` sin `StrokePicker`, - kølle/retning/innspill/chip/bunker/straffeslag bak en «flere detaljer»- - utvidelse, GIR utledet og vist klientside — aldri lagret, kun beregnet - fra `approach_result`+slag+putt ved lesing, fullfør-runde med HCP- - differensial-sammendrag). Lenket fra dashbordet som «Egne runder» - (bevisst adskilt navn fra det eksisterende «Mine runder», som gjelder - turnering-deltakelse — samme ord, to ulike konsepter). - **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle + - isolert scratch-MinIO + engangs API-container, samme mønster som hele - økten ellers): 22 sjekker som dekker egendefinert-bane-opprettelse med - to utslag/kjønn, søk, detalj, PATCH-kontrakten (inkl. det bevisste - nullstillings-beviset over), GIR-derivering, gjest-fjerning, - kryss-bruker-autorisasjon (403), og full fullføring med differensial - — PLUSS en egen, separat test av hele teeoff-baserte opprettelsesløpet - mot den ekte kjørende `teeoff_api`-containeren (Borregaard Golfklubb), - som bekreftet course_handicap ble beregnet riktig. Ekte typesjekket - PRODUKSJONSBUILD (`docker build --target builder`, samme steg som - `Dockerfile` faktisk bruker) kjørt og bekreftet — alle nye ruter listet. - **Rullet ut live 2026-07-23**, bruker bekreftet eksplisitt: ingen - migrasjon, `docker compose up -d --build teecup_api teecup_frontend`, - begge containere boot-et rent, `/health`/`/dashboard`/`/rounds` → 200, - `teeoff.no` upåvirket. **Frittstående rundeføring har dermed backend - OG frontend live** — se ADR-033 i ARCHITECTURE_DECISIONS.md for full - detalj om alle fire lagene (engine/skjema/API/frontend). - **Frontend ERSTATTET med V0-designet versjon, samme dag:** brukeren - påpekte berettiget at de tre skjermene over var hånd-kodet av meg i - stedet for designet i V0, som er prosjektets etablerte mønster for - ALL øvrig frontend. Bekreftet arbeidsmåten uendret (jeg skriver - V0-prompten, brukeren kjører den og sender koden tilbake, jeg - integrerer). Tre prompter skrevet (liste/opprett/hull-registrering, - inkl. eksplisitt tilgjengelighetskrav i hver), tre zip-eksporter - mottatt og diffet mot levende tre (samme "full re-eksport"-mønster som - alltid — kun `round-card.tsx`/`own-rounds.tsx`/`new-round.tsx`/ - `round-detail.tsx` + rutene var reelt nye). Samme kjente V0-feil dukket - opp igjen og ble hoppet over (`app/clubs/[id]/page.tsx`, feilnavngitt - slug-parameter — identisk feil som ble rettet under ADR-018). Mine - hånd-bygde komponenter ERSTATTET (ikke supplert), datalag skrevet om - fra V0s mock til ekte fetch — samme mønster som enhver annen skjerm. - Full detalj (inkl. de fire reelle tilpasningene utover ren om-kabling) - i ADR-033. Ekte typesjekket produksjonsbuild kjørt og bekreftet, ingen - ny interaktiv nettleser-test (intet slikt verktøy tilgjengelig, - flagget eksplisitt). **Rullet ut live 2026-07-23**, bruker bekreftet - eksplisitt: `docker compose up -d --build teecup_frontend` (gjenskapte - også `teecup_api` som vanlig bivirkning), begge containere boot-et - rent, `/health`/`/dashboard`/`/rounds`/`/rounds/new` → 200, `teeoff.no` - upåvirket. V0-zip-ene slettet fra prosjektroten. - **Reell produksjonsbug rapportert av bruker (2 skjermbilder) og - FIKSET samme dag:** `/rounds` var samtidig frontend-listesidens sti OG - backend-APIets ressursprefiks — en verre, begge-veier-variant av - ADR-016s medlemsside-felle. Statisk side vant over rewrite for det - eksakte `/rounds`-treffet (klientens `fetch("/rounds")`/`POST /rounds` - traff aldri backend, fikk Next sin egen HTML tilbake — derav "Klarte - ikke å hente rundene dine"/"Klarte ikke å opprette runden"), mens - rewriten vant over den DYNAMISKE `/rounds/[id]`-siden i motsatt - retning (selve rundedetalj-siden var dermed fullstendig uoppnåelig, - bekreftet direkte med `curl` før fiksen). Skjermbildets andre detalj — - utslagsnavn "55/50/44/32" — ble sjekket direkte mot ekte `teeoff_api` - og bekreftet Å IKKE VÆRE EN BUG (Tjøme Golfklubb sine faktiske - utslagsnavn, lengde i hundremeter). Fikset ved å flytte alle tre - frontend-rutene til et nytt, ikke-overlappende prefiks `/my-rounds/*` - — API-et uendret på `/rounds`. `next.config.mjs` sin advarsel utvidet - med denne nye varianten. Verifisert med `curl` mot ekte produksjon - BÅDE før og etter (anonymt `GET /rounds` → nå korrekt backend-JSON, - anonymt `GET /my-rounds/<uuid>` → nå korrekt `text/html`, altså - endelig oppnåelig). Rullet ut live 2026-07-23, bruker bekreftet - eksplisitt, kun `teecup_frontend` (+ vanlig `teecup_api`-bivirkning), - `teeoff.no` upåvirket. Full detalj i ADR-033. -4. **Del 1 (fri, ukrevd sekundær-e-post) er nå BYGGET OG LIVE** (2026-07-21, - se status over). **Del 2 (ekte konto-sammenslåing) fortsatt IKKE - designet:** hva skjer hvis den ønskede adressen ALLEREDE tilhører en - annen, eksisterende konto (i dag avvist tydelig med 409 DUPLICATE i - stedet for gjettet på)? Trenger egen, separat designrunde — se - FEATURE_BACKLOG.md for full analyse av hvorfor dette er vesentlig - vanskeligere enn del 1. -5. **Deltaker-tilgang til lag-chat/scorekort OG HCP-historikk er nå BEGGE - BYGGET OG LIVE** (2026-07-21, se status over) — ADR-031s tidligere - noterte "naturlig neste steg"-punkter er dermed alle tettet, unntatt - notifikasjons-/aktivitetsfeed og "Mine runder" for rene påmeldinger - (begge fortsatt IKKE bygget, se FEATURE_BACKLOG.md). -6. ~~Oppfølgingspunkt fra PWA-runden: offline-scoreregistrering er ALDRI - browser-testet i praksis~~ — **ferdig 2026-07-28** (se status-punkt - samme dag, "Turnering-scorekortets offline-flyt (ADR-028) FAKTISK - browserverifisert"). Beholdt her kun for historikk. -7. Fortsatt åpne beslutninger fra FEATURE_BACKLOG.md: kode-regenerering for - ADR-020, korrigering-godkjenning fra motpart, video/1-til-1-meldinger - (bevisst utsatt i ADR-025). -8. Flere turneringsformater utover Ryder Cup (Københavner/High-low-high/ - Robbins/Try all, notert 2026-07-19) — ingen ADR-runde startet ennå. -9. Ved fremtidige nye V0-skjermer/-design i V0 (fortsett i samme prosjekt), - FORVENT en full re-eksport hver gang — diff mot live-treet i et - scratch-område før noe pakkes ut over eksisterende filer, og sjekk om - V0-skjermen bygger inn handlinger backend ikke støtter ennå FØR - integrering. -10. **Resterende hull fra 2026-07-28-gjennomgangen av frittstående runder** - (det viktigste — faktisk HCP — er tettet, se ADR-038 over; offline-kø, - punkt (a), er nå OGSÅ tettet, se status 2026-07-28 over — punktet - beholdes her kun for historikk): (a) ~~offline-kø (ADR-028) er kun - koblet til turnering-scorekortet~~ — ferdig, browserverifisert og - live; (b) ~~ingen Stableford-poengberegning for frittstående runder - (kun rå slag/differensial), inkl. en uløst "plukket opp ballen"- - tilstand~~ — ferdig, scratch-/browserverifisert og live 2026-07-29, - se status over (`play_format='stableford'` + `round_hole.picked_up`, - migrasjon 038); (c) ~~rundedeling/visibility (ADR-036 fase 2, public/private/friends) - fortsatt ikke bygget~~ — ferdig, browser-/scratch-verifisert og live - 2026-07-28, se status over (ADR-036 er dermed HELT ferdig, alle tre - faser); (d) ~~flere flighter i én frittstående runde fortsatt kun - drøftet~~ — ferdig, retning 1 (løs gruppering) bygget, scratch-/ - browserverifisert og live 2026-07-28, se status over; (e) ~~varsler - koblet til venneforespørsler, ikke til rundehendelser ennå~~ — - ferdig, scratch-verifisert og live 2026-07-28, se status over - (medspiller lagt til/venn ser synlig runde/tilkoblet runde fullført). - Alle fem punktene (a)-(e) i denne listen er dermed ferdig. -11. **Ferdig, kun for historikk:** ekte spillformer (match/skins/fourball/ - foursome/greensome/scramble) for frittstående runder — backend - (ADR-039 + minimums-spiller-håndhevelse) OG frontend (sideoppsett, - skins-konfig i `/my-rounds/new`, matchstatus-/skins-tavle-visning, - delt-ball-scorekort/-veiviser, `setup_complete`-gating) er nå BEGGE - bygget, browserverifisert og live (se status 2026-07-28). Mulig - fremtidig finpuss (ikke bedt om ennå): redigere et sidenavn i - etterkant (kun opprett/slett finnes i dag), en tydeligere - skins-poeng-forklaring i UI-et. -12. **Ferdig, kun for historikk:** de tre organisator-oppfølgingspunktene - (flytte spiller mellom lag, individuell rangering per økt, midlertidige - spillere + etter-runde-invitasjon) — alle bygget, scratch-/ - browserverifisert og live 2026-07-28, se status over. Ingen nye åpne - spørsmål igjen fra denne runden. -13. **Ferdig, kun for historikk:** PWA-installasjonsoppfordring, flere - flighter i én frittstående runde (retning 1), scramble/greensome- - statistikk over valgt utslag (frittstående runder), og push-varsler - til telefonens OS (Web Push/VAPID) — alle fire bygget, scratch-/ - browserverifisert og live 2026-07-28, se status over for full detalj. - Gjenstående, IKKE bygget: samme scramble-utslagsstatistikk for - org-scopede turneringer (bevisst utenfor omfang, se - FEATURE_BACKLOG.md), ekte OS-nivå push-levering ikke bevist med en - reell innvilget tillatelse (automatisert testmiljø hadde - `Notification.permission` forhåndssatt til "denied") — bruker bør - selv teste push på en ekte enhet. -14. **Åtte nye turneringsformater (Chapman/Nassau/Københavner/Bingo Bango - Bongo/Flaggturnering/Shamble/Money Ball/High-low-high) — BACKEND - FERDIG, SCRATCH-VERIFISERT OG LIVE (2026-07-30, migrasjoner 041-050), - se status over.** Eneste gjenstående steg: FRONTEND for samtlige åtte - — nye spilleform-valg i opprett-runde/opprett-økt (frittstående - runder + org-lag + org-individuell, avhengig av format), nye - resultatvisninger (Nassau-vinduer, Københavner/BBB-poengtabeller, - Flag-nedtelling, Shamble/Money Ball-lagvisning, High-low-high-løpende - poeng). Backend er fullt funksjonelt og testet, men ubrukelig fra - selve appen inntil dette bygges — samme lagdelings-rekkefølge - (motor→skjema→API→frontend) som ADR-037/038/039. -15. **Ferdig, kun for historikk:** «Det store grepet» (ADR-040) — alle - 5 steg (backend/allowance_override, lag-sortering, rundeoppsett- - veiviseren, delt Score/Scorekort/Leaderboard-fane-rad, integrering) - BYGGET, VERIFISERT OG LIVE 2026-08-02, se status over. **Ett bevisst - utsatt, fortsatt åpent spørsmål:** trenger lag-grupperingen i - scorekortet mer enn celle-fargelegging for å skille lagene tydelig - nok (utover selve sorteringen, som allerede er fikset) — ikke stilt - til V0 ennå, egen liten vurdering om ønskelig. Se FEATURE_BACKLOG.md. diff --git a/DESIGN_SYSTEM.md b/DESIGN_SYSTEM.md index 1b609f3..8f80450 100644 --- a/DESIGN_SYSTEM.md +++ b/DESIGN_SYSTEM.md @@ -19,7 +19,7 @@ > "alternativ designinstruks.md" (brukerens eget forslag, forsøkt først på > dashbordet, deretter godkjent som ny fasit). Innholdet under ER derfor nå > den gjeldende regelen — men er ikke nødvendigvis rullet ut på ALLE -> skjermer ennå (se `CLAUDE.md`-status for hvor langt konsistens-sjekken har +> skjermer ennå (se `CHANGELOG.md` for hvor langt konsistens-sjekken har > kommet). Rett opportunistisk opp eldre skjermer mot dette dokumentet når > de likevel røres, samme mønster som tilgjengelighetsregelen over. @@ -50,7 +50,7 @@ ellers virker helt av seg selv så lenge token-klassene brukes. | Token | Lys modus | Rolle | |---|---|---| -| `background` / `foreground` | nesten hvit / mørk grå | sideflate / brødtekst | +| `background` / `foreground` | ren hvit (identisk med `card`) / mørk grå | sideflate / brødtekst | | `card` / `card-foreground` | hvit / mørk grå | kort, paneler, seksjoner | | `primary` / `primary-foreground` | `oklch(0.7512 0.1613 130.33)` (grønn) / mørk | hovedhandling, valgt tilstand, "under par"/positiv | | `secondary` / `secondary-foreground` | lys grå | sekundær vekt, sjeldent brukt alene | diff --git a/FEATURE_BACKLOG.md b/FEATURE_BACKLOG.md index eb17f55..ba5304b 100644 --- a/FEATURE_BACKLOG.md +++ b/FEATURE_BACKLOG.md @@ -78,7 +78,7 @@ 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 + direkte — se ADR-016 for hvorfor, og CHANGELOG.md for driftsdetaljene (samme stale-Caddy-inode-hendelse som containeriseringsrunden, løst likt). - **Verifisert med FAKTISK e-postlevering, ikke bare curl:** ekte @@ -144,7 +144,7 @@ **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 + `docker-compose.yml` oppdatert tilsvarende. Se CHANGELOG.md for full driftsdetalj, inkl. at dette krevde et fullt `docker compose up -d --build --force-recreate` (ikke bare `--force-recreate` — imaget bygger inn `app/` ved build-tid). Verifisert @@ -339,7 +339,7 @@ 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 + (tilskuer) for alle delbeslutningene, og CHANGELOG.md for scratch-verifiseringen (15 + 16 automatiserte sjekker). ### Scoring-autorisasjon: hvem fører, hvem korrigerer, hvem lukker (rejst 2026-07-16) @@ -409,7 +409,7 @@ 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 + CHANGELOG.md for full runde, inkl. en tredje, urelatert 500-bug (manglende handicap-indeks) funnet og fikset samtidig. - Kapteinene låser oppstillingen skjult; matchene avsløres samtidig når begge er ferdige. @@ -867,7 +867,7 @@ som ADR-023s `user_is_match_participant`) brukt av `update_hole` — kun deltakeren selv eller org-admin kan nå skrive score for en gitt rundedeltaker. Runde-/deltaker-oppsett forblir bevisst på org-medlemsnivå (samme presedens som `session`/`team_roster` i tournaments.py). 13 nye -scratch-sjekker + full 52-punkts regresjon, se CLAUDE.md-status +scratch-sjekker + full 52-punkts regresjon, se CHANGELOG.md 2026-07-30 for full detalj. De fem konkrete formatene (Københavner m.fl.) og Order of Merit fortsatt ikke designet. @@ -891,7 +891,7 @@ egen SELECT (ADR-030s utledede datospenn) som aldri ble utvidet med turneringer. Rettet. Et layout-hull i headeren (navn ble avkuttet på smal mobil) og et manglende form-språk i scorekort-cellene (kun farge, ikke sirkel/firkant som `round-scorecard.tsx` sin `ScoreMark`) ble også -funnet og rettet samme runde. Se CLAUDE.md-status 2026-07-30 for full +funnet og rettet samme runde. Se CHANGELOG.md 2026-07-30 for full verifiseringsdetalj (håndregnet HCP-kryssjekk, databasebekreftet persistens, full opprett-ny-turnering-flyt testet fra bunnen). **IKKE rullet ut mot ekte systemer ennå** — venter på brukerens @@ -910,7 +910,7 @@ CLAUDE.md sin ufravikelige regel. triggerpunkter (venneforespørsel sendt/akseptert, medspiller lagt til på en runde, en venn ser en synlig runde, en tilkoblet runde fullført) OG en e-post-fallback PER TYPE (`user_notification_email_pref`, trygg standard -= ingen e-post). Se CLAUDE.md-status for full detalj. Kun push til += ingen e-post). Se CHANGELOG.md for full detalj. Kun push til telefonens eget OS-varslingssystem (under) gjenstår av det opprinnelige forslaget. @@ -1065,7 +1065,7 @@ utsendingen ligger ETT sted (inni `create_notification()` selv, OG fremtidige varseltyper (`friend`/`round`/`result`/`tournament`) uten at hvert kallsted må huske det selv. Ny `send_notification_email()` i `app/email.py`, ny seksjon "Varsler på e-post" i `/account`. Se -CLAUDE.md-status 2026-07-28 for full detalj (19/19 scratch-sjekker). +CHANGELOG.md 2026-07-28 for full detalj (19/19 scratch-sjekker). Opprinnelig forslag, for historikkens skyld: brukeren foreslo at TeeCup i tillegg sender en e-post til mottakeren av en venneforespørsel ("du har @@ -1173,7 +1173,7 @@ ble kalt uten konsollfeil (selve den native installasjonsdialogen kan ikke utløses i en automatisert nettleserøkt -- ærlig, forventet begrensning, samme klasse som all annen "ekte OS-installasjon"-testing i prosjektet). **Rullet ut live 2026-07-28** sammen med de tre andre punktene samme dag, -se CLAUDE.md-status for felles utrullingsdetalj. +se CHANGELOG.md for felles utrullingsdetalj. --- @@ -1374,7 +1374,7 @@ 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 +Full verifiserings- og utrullingsdetalj i CHANGELOG.md (2026-07-26) -- ikke gjentatt her for å unngå duplisering. ### Oppdatering 2026-07-28: turnering-halvparten av spørsmålet AVKLART OG BYGGET @@ -1398,7 +1398,7 @@ matchlisten. Frontend: ny side (`session-individual-leaderboard.tsx`, gjenbruker rangerings-/ mode-toggle-mønsteret fra `round-leaderboard.tsx` i en enklere, sesjonsscopet variant), lenket fra blind draw-skjermen for kvalifiserende -økter. Scratch- og browserverifisert 2026-07-28, se CLAUDE.md-status. +økter. Scratch- og browserverifisert 2026-07-28, se CHANGELOG.md. --- @@ -1651,7 +1651,7 @@ rapportere "ingen synlig endring i det hele tatt" (bekreftet presist ved 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 +verifiserings-/utrullingsdetalj i CHANGELOG.md — ikke duplisert her. **Oppfølging 2026-07-27 — `teecup-scorekort-og-entry-spec.md` (nytt, @@ -1687,7 +1687,7 @@ 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. +i CHANGELOG.md (2026-07-27) — ikke duplisert her. --- @@ -1825,7 +1825,7 @@ bare den anbefalte ene). Løftet BEGGE `--chart-3` (blå → `--info`) OG 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 +blant minst to fullførte runder). Full detalj i CHANGELOG.md (2026-07-28) — ikke duplisert her. --- @@ -1903,7 +1903,7 @@ strukturell forskjell fra `round_hole`, som alltid forhåndsoppretter en nullbar rad). Ny `SelectedDriverSummary` i "Vis full oversikt"- seksjonen. 51/51 scratch-sjekker (full org-turnering-scaffold bygget fra bunnen) + full nettleser-gjennomgang (ekte klikk, persistens bekreftet -direkte i databasen, riktig opptelling). Se CLAUDE.md-status 2026-07-29 +direkte i databasen, riktig opptelling). Se CHANGELOG.md 2026-07-29 for full detalj. **Begge domener (frittstående runder og org-scopede turneringer) dekker nå samme funksjon** -- ingen kjent gjenstående forskjell mellom scramble og greensome i noen av domenene. @@ -1923,7 +1923,7 @@ forskjell mellom scramble og greensome i noen av domenene. | 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- +CHANGELOG.md for full byggerunde (datamodell, autorisasjon, scratch- verifisering med 20 automatiserte sjekker inkl. reell WebSocket-sanntid, og utrulling). @@ -1943,7 +1943,7 @@ dra-og-slipp-opplastingsskjermen i frontend (V0) gjenstår. | "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. | +| Lesbar URL (slug) for organisasjon | ✅ | Fantes faktisk allerede i skjemaet siden migrasjon 001 (oversett, funnet da migrasjon 009 feilet mot scratch — se CHANGELOG.md). Kun `CHECK`-constraints lagt til i 009. | | Organisator kan faktisk SETTE disse feltene | ✅ | Implisitt hull fylt under bygging: `PATCH /orgs/{id}/tournaments/{id}` (visibility/description/registrering), `PATCH /orgs/{id}` (slug/public_profile), full sponsor-CRUD. | | Bilder (hero, sponsorlogoer), backend | ✅ | Ny `teecup-minio`-tjeneste, ekte multipart-opplasting → AVIF-konvertering (Pillow) → lagring, live på `teecup.teeoff.no/teecup-media/*`. Selve opplastingsskjermen i frontend (V0) gjenstår. | | Del-metadata (Open Graph: og:title/og:description) | ✅ | `generateMetadata()` på `/t/[id]`+`/clubs/[slug]`, ekte data fra API-et, verifisert mot produksjonsimaget. `og:image` gjenstår (MinIO). | @@ -2029,7 +2029,7 @@ 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 +CHANGELOG.md for det fulle scenarioet (verifisert med et 10-hulls avgjort-match-eksempel) og et urelatert funn (`front_9`/`back_9` + `stroke`-modus kan aldri få handicap i dag, siden `tee_rating` alltid kun lages med `full_18`-omfang). @@ -2037,7 +2037,7 @@ lages med `full_18`-omfang). 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 +ble hentet inn, det ekte opprett-skjemaet urørt. Se CHANGELOG.md for detaljer. --- @@ -2084,7 +2084,7 @@ 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. +stedet). Se CHANGELOG.md for full root cause. **Nok en reell bug funnet og fikset samme uke, rapportert fra ekte bruk mot `teecup.teeoff.no`:** import av samme teeoff-bane til flere økter (helt @@ -2095,7 +2095,7 @@ 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). +CHANGELOG.md for full hendelse og rotårsak). **Åpent, ikke løst i denne runden:** de to bane-søkeflatene (øverste felt = organisasjonens egne baner + "opprett ny"-snarvei, "Hent bane fra teeoff"-knappen lenger ned = offisielt søk) er lette å forveksle — det var @@ -2121,7 +2121,7 @@ Full design i ARCHITECTURE_DECISIONS.md ADR-020. | 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 +del-ønsker levert samme dag. Se CHANGELOG.md for full runde inkl. en reell (og transparent håndtert) passord-eksponeringshendelse underveis. --- @@ -2131,13 +2131,13 @@ reell (og transparent håndtert) passord-eksponeringshendelse underveis. | 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. | +| Sesjon holder seg ikke — ny magic-link-kode kreves ved hvert besøk | ✅ FIKSET og LIVE 2026-07-19 | Ikke en cookie-bug (brukerens ekte cookie var korrekt satt, 30 dager, `Secure`/`HttpOnly`). Root cause: `app/page.tsx` sjekket aldri om en gyldig sesjon allerede fantes før den viste innloggingsskjemaet. Fikset med server-side sesjonssjekk + redirect til `/dashboard`. Se CHANGELOG.md for full diagnose og verifisering. | | Passord som valgfritt tillegg til magic-link | ✅ LIVE 2026-07-19 | Argon2id-hashing (ikke bcrypt — unngår 72-byte-trunkering). Verifisert med et ekte passord med mellomrom+æøå+spesialtegn. `POST /auth/login-password`, `/auth/set-password`, `/auth/remove-password`. Passord er ALDRI påkrevd. | | 2FA: TOTP eller e-post-engangskode, brukerens eget valg | ✅ LIVE 2026-07-19 | SMS bevisst utenfor omfang (krever betalt leverandør). `POST /auth/2fa/setup/start`+`/confirm`, `/auth/2fa/verify`, `/auth/2fa/disable`. | | 2FA påkrevd for org-eier/admin, valgfritt for medlemmer | ✅ LIVE 2026-07-19 | Håndheves ved hver innlogging via en `stage: "pending_2fa"`/`"must_enroll_2fa"`-mellomtilstand i sesjons-JWT-en. Verifisert: en fersk org-eier uten 2FA ble korrekt tvunget inn i oppsett ved neste innlogging. | | Frontend: passord-innlogging, 2FA-verifisering/-oppsett, kontoinnstillinger | ✅ LIVE 2026-07-19 | `login-form.tsx` (passord-modus), `two-factor-flow.tsx` (delt mellom login/verify), `/account`. | -**ADR-021 er dermed helt ferdig.** Se CLAUDE.md-status for full byggerunde, +**ADR-021 er dermed helt ferdig.** Se CHANGELOG.md for full byggerunde, inkl. tre reelle bugs funnet og fikset under scratch-testing. ### Organisasjonseierskap: dele, invitere, frasi seg, superadmin — ADR-022 @@ -2252,7 +2252,7 @@ en reell skjemamigrasjon (`014_tee_gender_to_rating.sql`). (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. + dashbord-hilsen-fiksen under, se CHANGELOG.md. **Designspørsmålene for punkt 2 er avklart** (bruker valgte det anbefalte alternativet på begge, se ADR-029 Beslutning B/C): helautomatisk tee-valg @@ -2514,7 +2514,7 @@ koblingen skal skje). en ekte innlogget nettleser (Chrome DevTools) — knappen viste korrekt «1 invitasjon sendt, 1 mangler registrert e-post» første gang, «0 invitasjoner sendt, 1 allerede sendt tidligere, …» andre gang. Se - CLAUDE.md-status 2026-07-28 for full detalj. + CHANGELOG.md 2026-07-28 for full detalj. --- @@ -2675,7 +2675,7 @@ obligatorisk**, før noe annet i appen (inkl. dashbordet) er tilgjengelig. 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 +**Verifisert:** se full detalj i CHANGELOG.md 2026-07-22 — 16/16 scratch-backend-sjekker, `test_isolation.sql` 12/12, ekte typesjekket produksjonsbuild, og et ekte HTTP-nivå-bevis mot en kjørende produksjonscontainer (anonym → 200 innloggingsskjema, ekte innlogget-men- @@ -2693,7 +2693,7 @@ og vil derfor begge se profil-fullførings-skjemaet ved neste innlogging. **Bygget som beskrevet i "2026-07-25"-forslaget under** (de sju blokkene: Hurtighandlinger/Kommende runder/Kommende turneringer/Statistikk/Spilte baner/Venner/Organisasjoner) — se ADR-035 i ARCHITECTURE_DECISIONS.md og -CLAUDE.md-status 2026-07-25 for full bygge-/verifiseringsdetalj. +CHANGELOG.md 2026-07-25 for full bygge-/verifiseringsdetalj. Organisasjon er ikke lenger første/eneste synlige vei inn; opprettes nå usynlig i bakgrunnen når en bruker trykker "Ny turnering". @@ -2721,7 +2721,7 @@ inn. 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å +beskrivelse) — se CHANGELOG.md, ✅ BYGGET OG LIVE. Dette svarer på "hva møter en fersk bruker aller først", men IKKE på det opprinnelige spørsmålet i denne seksjonen: hva skal dashbordets tom-tilstand vise for en bruker som HAR fullført profilen, men ennå ikke har noen organisasjon/ @@ -2883,7 +2883,7 @@ fritekst-versjonen. bygget, scratch-/browserverifisert og live 2026-07-28 (`round.visibility_mode`, migrasjon 032, `_can_view_round`/nye `/public/rounds/*`-endepunkter, ny `/watch/[id]`-side og - `/my-friends/[id]`-profilside). Se CLAUDE.md-status 2026-07-28 for + `/my-friends/[id]`-profilside). Se CHANGELOG.md 2026-07-28 for full detalj (191 automatiserte sjekker + full nettleser-gjennomgang med tre reelle nettleserkontekster). 3. **✅ Ekte medspillere** (ikke bare gjester) — utvider @@ -3060,7 +3060,7 @@ entydig, ingen dropdown nødvendig. Scratch-verifisert (happy path, kaptein-nullstilling, 409/400/404 alle bekreftet) OG bekreftet direkte i en ekte innlogget nettleser (Chrome DevTools): flytting oppdaterte begge lags roster-lister og -antall -umiddelbart, ingen konsollfeil. Ingen migrasjon. Se CLAUDE.md-status +umiddelbart, ingen konsollfeil. Ingen migrasjon. Se CHANGELOG.md 2026-07-28. --- @@ -3187,7 +3187,7 @@ fullførte oppretting, bekreftet DIREKTE I DATABASEN at begge rundene nå delte samme `flight_group_id`. Kombinert leaderboard-siden bekreftet visuelt (skjermbilde) med korrekt rangering/farger, ingen konsollfeil noe sted i hele flyten. -**Rullet ut live 2026-07-28**, se CLAUDE.md-status for felles +**Rullet ut live 2026-07-28**, se CHANGELOG.md for felles utrullingsdetalj med de tre andre punktene samme dag. --- @@ -3204,7 +3204,7 @@ 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 +matchspill. Se CHANGELOG.md 2026-07-28 for full detalj (migrasjon `030`, backend/frontend-endringer, verifisering). **Fortsatt IKKE tettet, notert samme runde:** offline-kø kun for turnering-scorekortet (ikke frittstående runder), ingen Stableford, @@ -3275,7 +3275,7 @@ Løst nøyaktig som antatt her: `'stableford'` lagt til som eget "plukket opp" lagres som en cap på (netto) score -- Net Double Bogey, samme prinsipp som WHS allerede bruker, via den eksisterende `max_hole_score_for_handicap()` i `handicap_engine.py` (ingen ny -motorlogikk). Full detalj i CLAUDE.md-status 2026-07-29, inkl. to +motorlogikk). Full detalj i CHANGELOG.md 2026-07-29, inkl. to reelle frontend-bugs funnet og fikset under browserverifisering (`play_format === "stroke"`-spesialsjekker som ikke ekskluderte det nye "stableford"-formatet, samme mønster funnet og rettet fem steder @@ -3465,7 +3465,7 @@ hvilke "første handling"-alternativer dashbordet bør vise i fremtiden. **Oppdatering 2026-07-28 (ADR-039):** design + backend bygget samme dag som punktet ble reist, frontend (sideoppsett/skins-konfig/matchstatus- visning/delt-ball-scorekort) og selve utrullingen mot ekte `teecup_db`/ -`teecup_api` fulgte rett etter, samme dag — se CLAUDE.md-status +`teecup_api` fulgte rett etter, samme dag — se CHANGELOG.md 2026-07-28 for full detalj (migrasjon `031`, nye endepunkter, 96+26 backend-scratch-sjekker, full nettleser-verifisering av alle seks formater). Kort: alle fire load-bærende spørsmål (sider/gruppering, @@ -3721,7 +3721,7 @@ Full design i ARCHITECTURE_DECISIONS.md ADR-028. Kort: | Service worker: cache app-navigasjon + `/orgs/*`-GET-er | ✅ bygget | `public/sw.js`, nettverk-først/cache-fallback (bevisst IKKE stale-while-revalidate, se ADR-028). `public/offline.html` som siste utvei. | | Offline scoreregistrering (hole-scores/hole-results) | ✅ bygget | `lib/offline-queue.ts` (IndexedDB-kø) + `components/session-scorecard.tsx`. Synker automatisk ved `window`s `online`-event, pluss manuell "Synkroniser nå"-knapp. Bevisst IKKE Background Sync API (iOS Safari støtter den ikke). | | Andre skrivehandlinger offline (walkover, chat/feed, oppsett) | 💤 bevisst utenfor omfang | Kun de to scoreregistrerings-endepunktene er køet — se ADR-028 Beslutning B for begrunnelse per type. | -| Faktisk browser-testet (DevTools Offline-modus) | ✅ **FERDIG 2026-07-28** | Både turnering-scorekortet (den opprinnelige ADR-028-flyten) og den senere utvidelsen til frittstående runder er nå bevist med ekte Chrome DevTools-nettverksemulering (offline → registrer slag → tilbake online → automatisk synk bekreftet server-side), ikke bare kodegjennomgang. Se CLAUDE.md-status 2026-07-28. | +| Faktisk browser-testet (DevTools Offline-modus) | ✅ **FERDIG 2026-07-28** | Både turnering-scorekortet (den opprinnelige ADR-028-flyten) og den senere utvidelsen til frittstående runder er nå bevist med ekte Chrome DevTools-nettverksemulering (offline → registrer slag → tilbake online → automatisk synk bekreftet server-side), ikke bare kodegjennomgang. Se CHANGELOG.md 2026-07-28. | **Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: `docker compose up -d --build teecup_frontend` (ingen migrasjon). Verifisert: diff --git a/app/email.py b/app/email.py index 35e0f9c..a4017a9 100644 --- a/app/email.py +++ b/app/email.py @@ -14,17 +14,26 @@ vanlig tilkobling (typisk port 587/25). import smtplib from asyncio import to_thread +from dataclasses import dataclass from email.message import EmailMessage +from html import escape as _esc from .config import settings -def _send_sync(to_email: str, subject: str, body: str) -> None: +def _send_sync(to_email: str, subject: str, body: str, html_body: str | None = None) -> None: msg = EmailMessage() msg["Subject"] = subject msg["From"] = settings.FROM_EMAIL msg["To"] = to_email msg.set_content(body) + # html_body er valgfritt (2026-08-03, rundeoppsummering til midlertidige + # spillere -- FØRSTE HTML-e-post i appen). add_alternative() gjør + # meldingen multipart/alternative: body over forblir en ekte tekst- + # fallback for klienter uten HTML-støtte, html_body er det klienter + # faktisk viser når de kan. + if html_body is not None: + msg.add_alternative(html_body, subtype="html") if settings.SMTP_PORT == 465: with smtplib.SMTP_SSL(settings.SMTP_SERVER, settings.SMTP_PORT) as smtp: @@ -336,3 +345,121 @@ async def send_new_account_alert_email(to_email: str, new_user_email: str, displ f"Navn: {display_name}\n" ) await to_thread(_send_sync, to_email, subject, body) + + +@dataclass(frozen=True) +class RoundSummaryHole: + hole_number: int + par: int + score: int | None + net: int | None + + +async def send_round_summary_email( + to_email: str, + guest_first_name: str, + round_label: str, + holes: list[RoundSummaryHole], + stat_lines: list[tuple[str, str]], + raw_token: str, +) -> None: + """Rundeoppsummering til en midlertidig spiller (gjest) med registrert + e-post, sendt ved fullføring (app/routers/rounds.py sin complete_round, + 2026-08-03). FØRSTE HTML-e-post i appen -- multipart/alternative via + _send_sync sin nye html_body-parameter, med en ekte tekst-fallback. + + Navneformat (CLAUDE.md, ufravikelig): dette ER direkte adressering til + mottakeren -- KUN fornavn i hilsenen, aldri fullt navn. + + `stat_lines` er allerede ferdig utledet og formatert av kalleren (samme + "bygg teksten i routeren, ikke i e-post-modulen"-mønster som + send_scorecard_invitations/send_session_result_email) -- graderer seg + naturlig etter hva som faktisk ble spilt (stat_level, individuell-ball + vs. delt-ball) siden kalleren rett og slett utelater linjer det ikke + finnes data for. + + Alltid norsk, samme presedens som send_scorecard_invitations (gjesten + har ingen lagret språkpreferanse -- ingen konto ennå).""" + link = f"{settings.PUBLIC_BASE_URL}/verify?token={raw_token}" + subject = f"Scorekortet ditt fra {round_label}" + + text_lines = [ + f"Hei {guest_first_name},", + "", + f"Her er scorekortet ditt fra {round_label}:", + "", + ] + for h in holes: + score_txt = str(h.score) if h.score is not None else "–" + net_txt = str(h.net) if h.net is not None else "–" + text_lines.append(f"Hull {h.hole_number} (par {h.par}): {score_txt} slag (netto {net_txt})") + text_lines.append("") + for label, value in stat_lines: + text_lines.append(f"{label}: {value}") + text_lines += [ + "", + "Noen registrerte deg som spiller i TeeCup med denne e-postadressen -- " + "du trenger ingen konto for å se dette scorekortet, men med én kan du " + "føre score selv, følge egen statistikk og handicap-utvikling over tid.", + "", + f"Logg inn og se hele runden ({settings.MAGIC_LINK_MAX_AGE_MINUTES} minutter):", + link, + "", + "Ikke interessert? Ignorer denne e-posten -- den brukes ikke til noe annet.", + ] + body = "\n".join(text_lines) + + hole_rows_html = "\n".join( + f'<tr><td style="padding:6px 10px;border-bottom:1px solid #e4ebe3;">{h.hole_number}</td>' + f'<td style="padding:6px 10px;border-bottom:1px solid #e4ebe3;">{h.par}</td>' + f'<td style="padding:6px 10px;border-bottom:1px solid #e4ebe3;">{h.score if h.score is not None else "–"}</td>' + f'<td style="padding:6px 10px;border-bottom:1px solid #e4ebe3;">{h.net if h.net is not None else "–"}</td></tr>' + for h in holes + ) + stat_rows_html = "\n".join( + f'<tr><td style="padding:6px 10px;border-bottom:1px solid #e4ebe3;color:#424941;">{_esc(label)}</td>' + f'<td style="padding:6px 10px;border-bottom:1px solid #e4ebe3;font-weight:700;">{_esc(value)}</td></tr>' + for label, value in stat_lines + ) + html_body = f"""\ +<!doctype html> +<html><body style="margin:0;padding:0;background-color:#f3f6f2;font-family:Arial,Helvetica,sans-serif;color:#012c11;"> +<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color:#f3f6f2;padding:24px 0;"> +<tr><td align="center"> +<table role="presentation" width="100%" style="max-width:480px;background-color:#ffffff;border-radius:16px;overflow:hidden;"> +<tr><td style="background-color:#1a4325;padding:20px 24px;"> +<span style="color:#ffffff;font-size:20px;font-weight:800;">TeeCup</span> +</td></tr> +<tr><td style="padding:24px;"> +<p style="margin:0 0 16px;font-size:16px;">Hei {_esc(guest_first_name)},</p> +<p style="margin:0 0 20px;font-size:16px;">Her er scorekortet ditt fra <strong>{_esc(round_label)}</strong>:</p> +<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="font-size:14px;margin-bottom:20px;"> +<tr style="background-color:#edf3ef;font-weight:700;"> +<td style="padding:6px 10px;">Hull</td><td style="padding:6px 10px;">Par</td> +<td style="padding:6px 10px;">Slag</td><td style="padding:6px 10px;">Netto</td> +</tr> +{hole_rows_html} +</table> +<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="font-size:14px;margin-bottom:24px;"> +{stat_rows_html} +</table> +<p style="margin:0 0 20px;font-size:14px;color:#424941;"> +Noen registrerte deg som spiller i TeeCup med denne e-postadressen -- du trenger ingen +konto for å se dette scorekortet, men med én kan du føre score selv, følge egen +statistikk og handicap-utvikling over tid. +</p> +<table role="presentation" cellpadding="0" cellspacing="0"><tr><td style="border-radius:12px;background-color:#1f6b08;"> +<a href="{link}" style="display:inline-block;padding:14px 28px;color:#ffffff;font-size:16px; +font-weight:700;text-decoration:none;">Logg inn og se hele runden</a> +</td></tr></table> +<p style="margin:20px 0 0;font-size:12px;color:#767f75;"> +Lenken er gyldig i {settings.MAGIC_LINK_MAX_AGE_MINUTES} minutter. Ikke interessert? Ignorer denne +e-posten -- den brukes ikke til noe annet. +</p> +</td></tr> +</table> +</td></tr> +</table> +</body></html> +""" + await to_thread(_send_sync, to_email, subject, body, html_body) diff --git a/app/routers/auth.py b/app/routers/auth.py index 51a55be..e4a180f 100644 --- a/app/routers/auth.py +++ b/app/routers/auth.py @@ -110,6 +110,48 @@ def _build_qr_data_uri(data: str) -> str: return f"data:image/png;base64,{b64}" +async def _link_round_participants_by_email(conn, user_id: str, email: str) -> None: + """Kobler frittstående-runde-gjester (round_participant.guest_email) til + denne nå-innloggede kontoen (2026-08-03) -- samme "kjør trygt på HVER + innlogging"-idempotens som link_player_by_email (WHERE user_id IS NULL), + men INGEN SECURITY DEFINER-bro trengs: round/round_participant har ingen + RLS (ADR-033 Beslutning A, plain_connection()), så en rett UPDATE er nok. + + Bevisst IKKE en blind kobling -- se ADR-038-tillitsresonnementet: en + FREMMED kan i prinsippet ha registrert deg som gjest med feil/dårlig + score. `exclude_from_handicap = true` settes derfor alltid på nykoblede + rader, uansett hva raden opprinnelig hadde -- personen må selv slå den + PÅ (update_participant) for at runden skal telle mot faktisk HCP. + `NOT EXISTS`-vaktet mot en allerede eksisterende ekte deltaker-rad for + (round_id, user_id) -- ville ellers brutt UNIQUE-indeksen fra migrasjon + 027 hvis personen tilfeldigvis også er lagt til som ekte spiller på + SAMME runde. guest_name/guest_first_name/guest_last_name nullstilles + ved kobling (samme "nøyaktig ett av user_id/guest_first_name"-invariant + som CHECK-constrainten på round_participant håndhever ved INSERT) -- + `gender` derimot IKKE: den er NOT NULL og brukt til tee-rating-oppslag + for ALLE deltakere, ikke bare gjester (reelt funn under scratch- + testing -- en første versjon nullet den også, brøt NOT NULL- + constrainten). Beholder gjestens opprinnelig snapshottede kjønn + uendret; update_participant sin egen guest_only_fields-sjekk låser + uansett feltet mot videre endring så snart user_id er satt.""" + await conn.execute( + """ + UPDATE round_participant AS rp + SET user_id = $1, + exclude_from_handicap = true, + guest_name = NULL, guest_first_name = NULL, guest_last_name = NULL + WHERE rp.user_id IS NULL + AND lower(rp.guest_email) = lower($2) + AND NOT EXISTS ( + SELECT 1 FROM round_participant rp2 + WHERE rp2.round_id = rp.round_id AND rp2.user_id = $1 + ) + """, + user_id, + email, + ) + + class MagicLinkRequest(BaseModel): email: EmailStr locale: Literal["nb", "en"] = "nb" @@ -297,6 +339,7 @@ async def verify_magic_link(body: MagicLinkVerify, response: Response, request: # er idempotente, berører kun rader som ennå ikke er koblet/forbrukt. await conn.execute("SELECT link_player_by_email($1, $2)", user_id, email) await conn.execute("SELECT accept_pending_invitations_by_email($1, $2)", user_id, email) + await _link_round_participants_by_email(conn, user_id, email) # Driftsvarsel til appens eier ved en HELT NY konto (2026-07-30) -- # BEVISST etter at connection-blokken er lukket, samme "e-post skal @@ -354,6 +397,7 @@ async def login_with_password(body: PasswordLoginRequest, response: Response, re await conn.execute("SELECT link_player_by_email($1, $2)", user_row["id"], email) await conn.execute("SELECT accept_pending_invitations_by_email($1, $2)", user_row["id"], email) + await _link_round_participants_by_email(conn, user_row["id"], email) return await _issue_login_result(response, request, user_row["id"]) diff --git a/app/routers/rounds.py b/app/routers/rounds.py index 2ee788a..b0dac4d 100644 --- a/app/routers/rounds.py +++ b/app/routers/rounds.py @@ -34,8 +34,10 @@ from __future__ import annotations import json import math +import secrets +import traceback import uuid -from datetime import date, datetime, timedelta +from datetime import date, datetime, timedelta, timezone from typing import Literal from fastapi import APIRouter, Depends, HTTPException, Query, WebSocket, WebSocketDisconnect @@ -43,10 +45,13 @@ from pydantic import BaseModel, EmailStr, Field from .. import teeoff_client from ..auth import CurrentUser, get_current_user, get_current_user_from_websocket, get_current_user_optional +from ..config import settings from ..db import plain_connection +from ..email import RoundSummaryHole, send_round_summary_email from ..errors import app_error, translate_db_errors from ..handicap import SIDE_IS_UNIT, parse_allowance_config from ..realtime import broadcast_round_update, live_sockets_for_round +from .auth import _hash_secret from .notifications import create_notification from handicap_engine import ( HoleResult, @@ -767,6 +772,12 @@ class RoundParticipantOut(BaseModel): id: str user_id: str | None guest_name: str | None + # Kilden til sannhet for redigering (2026-08-03) -- guest_name over + # forblir det auto-synkroniserte, VISTE fulle navnet (samme mønster som + # app_user.display_name synkes fra first_name/last_name). guest_last_name + # er valgfritt (en gjest kan legges til med kun fornavn). + guest_first_name: str | None + guest_last_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 @@ -884,7 +895,8 @@ 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, rp.guest_email, + SELECT rp.id::text AS id, rp.user_id::text AS user_id, rp.guest_name, + rp.guest_first_name, rp.guest_last_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, @@ -1008,6 +1020,8 @@ async def _create_participant( *, user_id: str | None, guest_name: str | None, + guest_first_name: str | None = None, + guest_last_name: str | None = None, is_owner: bool, gender: str, handicap_index: float | None, @@ -1028,15 +1042,18 @@ async def _create_participant( participant_row = await conn.fetchrow( """ INSERT INTO round_participant - (round_id, user_id, guest_name, guest_email, is_owner, gender, tee_name_snapshot, handicap_index_snapshot, + (round_id, user_id, guest_name, guest_first_name, guest_last_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, exclude_from_handicap, round_side_id) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING id::text AS id """, round_id, user_id, guest_name, + guest_first_name, + guest_last_name, guest_email, is_owner, gender, @@ -1477,6 +1494,44 @@ async def get_rounds_stats_summary( ) +class KnownGuestOut(BaseModel): + guest_first_name: str + guest_last_name: str | None + gender: Literal["m", "f", "x"] | None + handicap_index: float | None + + +@router.get("/rounds/guests/known", response_model=KnownGuestOut | None) +async def find_known_guest(email: str, user: CurrentUser = Depends(get_current_user)) -> KnownGuestOut | None: + """"Gjenkjenn denne e-posten"-oppslag (2026-08-03) -- bevisst scoped til + DENNE brukerens EGNE tidligere registrerte gjester, ikke globalt på + tvers av alle organisatorer. Et globalt oppslag ville latt hvem som + helst skrive inn en tilfeldig e-post og se navn/kjønn/HCP en HELT ANNEN + organisator har registrert -- en reell personvernlekkasje. Returnerer + den NYESTE treffende gjeste-raden (spillerens opplysninger kan ha + endret seg siden forrige runde).""" + email = email.strip() + if not email: + return None + async with plain_connection() as conn: + row = await conn.fetchrow( + """ + SELECT rp.guest_first_name, rp.guest_last_name, rp.gender, + rp.handicap_index_snapshot::float AS handicap_index + FROM round_participant rp + JOIN round r ON r.id = rp.round_id + WHERE r.owner_user_id = $1 + AND rp.guest_first_name IS NOT NULL + AND lower(rp.guest_email) = lower($2) + ORDER BY r.played_at DESC + LIMIT 1 + """, + user.user_id, + email, + ) + return KnownGuestOut(**dict(row)) if row is not None else None + + async def _get_owned_round_or_404(conn, round_id: str, user_id: str): """Strengt eier-only -- for runde-forvaltning (rediger/slett metadata, legge til/fjerne deltakere). IKKE for lesing/scoreregistrering, se @@ -2067,10 +2122,13 @@ async def delete_round(round_id: str, user: CurrentUser = Depends(get_current_us # --------------------------------------------------------------------------- class ParticipantCreate(BaseModel): - # Nøyaktig én av user_id/guest_name. + # Nøyaktig én av user_id/guest_first_name. user_id: str | None = None - guest_name: str | None = Field(default=None, min_length=1, max_length=100) - # Kun brukt for guest_name-varianten -- for user_id hentes kjønn/HCP fra + # guest_last_name er bevisst valgfritt (en gjest kan legges til med kun + # fornavn) -- guest_first_name er den eneste PÅKREVDE gjeste-identiteten. + guest_first_name: str | None = Field(default=None, min_length=1, max_length=60) + guest_last_name: str | None = Field(default=None, min_length=1, max_length=60) + # Kun brukt for gjest-varianten -- for user_id hentes kjønn/HCP fra # 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) @@ -2098,8 +2156,8 @@ async def add_participant( body: ParticipantCreate, user: CurrentUser = Depends(get_current_user), ) -> 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) == (body.guest_first_name is not None): + raise app_error(400, "VALIDATION_FAILED", "Oppgi enten user_id (funnet via søk) eller guest_first_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.") @@ -2145,11 +2203,15 @@ async def add_participant( gender = target["gender"] handicap_index = target["handicap_index"] guest_name = None + guest_first_name = None + guest_last_name = None linked_user_id = body.user_id else: gender = body.gender handicap_index = body.handicap_index - guest_name = body.guest_name.strip() + guest_first_name = body.guest_first_name.strip() + guest_last_name = body.guest_last_name.strip() if body.guest_last_name else None + guest_name = f"{guest_first_name} {guest_last_name}".strip() if guest_last_name else guest_first_name linked_user_id = None if gender is None: raise app_error(400, "VALIDATION_FAILED", "gender er påkrevd for en gjest uten konto.") @@ -2164,7 +2226,8 @@ async def add_participant( async with conn.transaction(), translate_db_errors(): participant_id = await _create_participant( conn, round_id, resolved, tee_name, - user_id=linked_user_id, guest_name=guest_name, is_owner=False, + user_id=linked_user_id, guest_name=guest_name, + guest_first_name=guest_first_name, guest_last_name=guest_last_name, is_owner=False, gender=gender, handicap_index=handicap_index, stat_level=body.stat_level, guest_email=body.guest_email, @@ -2194,7 +2257,8 @@ async def add_participant( ) row = await conn.fetchrow( """ - SELECT rp.id::text AS id, rp.user_id::text AS user_id, rp.guest_name, rp.guest_email, + SELECT rp.id::text AS id, rp.user_id::text AS user_id, rp.guest_name, + rp.guest_first_name, rp.guest_last_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, @@ -2322,8 +2386,12 @@ class ParticipantUpdate(BaseModel): # 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) + # rating som er gyldig), `guest_first_name`/`guest_last_name`/ + # `guest_email` er rene metadata. `guest_name` (det VISTE fulle navnet) + # synkes automatisk av update_participant når ett av disse to endres -- + # sendes aldri direkte av klienten. + guest_first_name: str | None = Field(default=None, min_length=1, max_length=60) + guest_last_name: str | None = Field(default=None, min_length=1, max_length=60) gender: Literal["m", "f", "x"] | None = None guest_email: EmailStr | None = None # ADR-038 Beslutning C -- eneste feltet en IKKE-eier (en lenket @@ -2342,8 +2410,8 @@ class ParticipantUpdate(BaseModel): # Felt en ikke-eier ALDRI kan sende her, uansett hvilken rad det gjelder -- # eier-only, se update_participant sin autorisasjonssjekk. _OWNER_ONLY_PARTICIPANT_FIELDS = { - "stat_level", "tee_name", "handicap_index", "guest_name", "gender", "guest_email", "round_side_id", - "lineup_order", + "stat_level", "tee_name", "handicap_index", "guest_first_name", "guest_last_name", "gender", "guest_email", + "round_side_id", "lineup_order", } @@ -2366,7 +2434,7 @@ async def update_participant( """ SELECT rp.user_id::text AS user_id, rp.gender, rp.tee_name_snapshot, rp.handicap_index_snapshot::float AS handicap_index_snapshot, - rp.counts_for_handicap, + rp.counts_for_handicap, rp.guest_first_name, rp.guest_last_name, r.owner_user_id::text AS round_owner_user_id, r.completed_at, r.course_source, r.teeoff_facility_slug, r.teeoff_course_id, r.personal_course_id, r.play_format @@ -2389,7 +2457,7 @@ async def update_participant( if current["user_id"] != user.user_id: raise app_error(403, "NOT_AUTHORIZED", "Du kan kun endre dette for din egen deltakelse.") - guest_only_fields = {"guest_name", "gender", "guest_email"} + guest_only_fields = {"guest_first_name", "guest_last_name", "gender", "guest_email"} if current["user_id"] is not None and guest_only_fields & updates.keys(): raise app_error( 400, "VALIDATION_FAILED", @@ -2409,8 +2477,22 @@ 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()) + if "guest_first_name" in updates or "guest_last_name" in updates: + # guest_name (det VISTE fulle navnet) synkes automatisk her -- + # samme mønster som app_user.display_name synkes fra first_name/ + # last_name (update_profile, routers/auth.py). Slår sammen med + # det som IKKE ble sendt i dette kallet, slik at en PATCH med + # kun ett av de to feltene ikke nullstiller det andre. + new_first = ( + updates["guest_first_name"].strip() if "guest_first_name" in updates else current["guest_first_name"] + ) + new_last_raw = updates["guest_last_name"] if "guest_last_name" in updates else current["guest_last_name"] + new_last = new_last_raw.strip() if new_last_raw else None + values.append(new_first) + set_clauses.append(f"guest_first_name = ${len(values)}") + values.append(new_last) + set_clauses.append(f"guest_last_name = ${len(values)}") + values.append(f"{new_first} {new_last}".strip() if new_last else new_first) set_clauses.append(f"guest_name = ${len(values)}") if "guest_email" in updates: values.append(updates["guest_email"]) @@ -2511,7 +2593,8 @@ async def update_participant( row = await conn.fetchrow( """ - SELECT rp.id::text AS id, rp.user_id::text AS user_id, rp.guest_name, rp.guest_email, + SELECT rp.id::text AS id, rp.user_id::text AS user_id, rp.guest_name, + rp.guest_first_name, rp.guest_last_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, @@ -4302,12 +4385,106 @@ async def complete_round(round_id: str, user: CurrentUser = Depends(get_current_ conn, user_id=uid, type="result", message=message, link_path=f"/watch/{round_id}", ) + await _send_guest_round_summaries(conn, round_id, round_label) + result = await _load_round_out(conn, round_id, user.user_id) await broadcast_round_update(round_id) return result +async def _send_guest_round_summaries(conn, round_id: str, round_label: str) -> None: + """Rundeoppsummering til midlertidige spillere med registrert e-post + (2026-08-03) -- ETT ekte magic-link-innloggingstoken per gjest, samme + mønster som tournaments.py sin send_scorecard_invitations. Graderer seg + naturlig etter individuell-ball (egne slag/putter) vs. delt-ball + (kun sidens felles slag, ADR-039 Beslutning C) -- ingen egen + format-sjekk trengs, feltene som ikke finnes er rett og slett fraværende + i kildedataene.""" + guest_rows = await conn.fetch( + """ + SELECT id::text AS id, guest_email, guest_first_name, round_side_id::text AS round_side_id, stat_level + FROM round_participant + WHERE round_id = $1 AND guest_email IS NOT NULL + """, + round_id, + ) + for g in guest_rows: + stat_lines: list[tuple[str, str]] = [] + if g["round_side_id"] is not None: + side_holes = await _build_side_holes(conn, round_id, g["round_side_id"]) + summary_holes = [ + RoundSummaryHole( + hole_number=h.hole_number, + par=h.par, + score=h.score if h.played else None, + net=h.score - h.strokes_received + if h.played and h.score is not None and h.strokes_received is not None + else None, + ) + for h in side_holes + ] + else: + p_holes = await _build_participant_holes(conn, round_id, g["id"]) + summary_holes = [ + RoundSummaryHole( + hole_number=h.hole_number, + par=h.par, + score=h.score if h.played else None, + net=h.score - h.strokes_received + if h.played and h.score is not None and h.strokes_received is not None + else None, + ) + for h in p_holes + ] + if g["stat_level"] != "strokes_only": + total_putts = sum(h.putts for h in p_holes if h.played and h.putts is not None) + if total_putts: + stat_lines.append(("Putter totalt", str(total_putts))) + + played_holes = [h for h in summary_holes if h.score is not None] + if played_holes: + total_score = sum(h.score for h in played_holes) + to_par = total_score - sum(h.par for h in played_holes) + lead_lines = [("Slag totalt", str(total_score)), ("Til par", _signed(to_par))] + net_holes = [h for h in played_holes if h.net is not None] + if net_holes: + net_to_par = sum(h.net for h in net_holes) - sum(h.par for h in net_holes) + lead_lines.append(("Netto til par", _signed(net_to_par))) + stat_lines = lead_lines + stat_lines + + email = g["guest_email"].strip().lower() + raw_token = secrets.token_urlsafe(32) + expires_at = datetime.now(timezone.utc) + timedelta(minutes=settings.MAGIC_LINK_MAX_AGE_MINUTES) + # Samme "ugyldiggjør eldre uforbrukte lenker"-mønster som + # request_magic_link/send_scorecard_invitations. + await conn.execute( + "UPDATE magic_link_token SET consumed_at = now() WHERE email = $1 AND consumed_at IS NULL", email + ) + await conn.execute( + "INSERT INTO magic_link_token (email, token_hash, expires_at, locale) VALUES ($1, $2, $3, 'nb')", + email, _hash_secret(raw_token), expires_at, + ) + if settings.SMTP_CONFIGURED: + try: + await send_round_summary_email( + email, g["guest_first_name"] or "Gjest", round_label, summary_holes, stat_lines, raw_token, + ) + except Exception: + # Se app/email.py sitt mønster -- en driftsfeil i selve + # utsendingen skal aldri hindre fullføringen av runden. + traceback.print_exc() + elif settings.DEV_LOG_MAGIC_LINKS: + print(f"[DEV] Magic link for {email} (nb): {raw_token}", flush=True) + print(f"[DEV] Rundeoppsummering til {email}: {round_label}, {len(played_holes)} hull spilt", flush=True) + + +def _signed(value: int) -> str: + if value == 0: + return "E" + return f"+{value}" if value > 0 else str(value) + + def _course_handicap_from_row(p) -> int: return course_handicap( p["handicap_index_snapshot"], p["slope_rating_snapshot"], p["course_rating_snapshot"], p["tee_par_snapshot"] diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 85b157c..9b3cada 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -59,8 +59,10 @@ disse en svak grønn hue (130-145) iblandet, som ga et vedvarende grønt skjær på nesten hver bakgrunn/hover/kant i hele appen. `primary` er fortsatt den bevisste, mettede merkevare-grønnen -- kun den skal - lese som grønn. */ - --background: oklch(0.985 0 0); + lese som grønn. `--background` er ren hvit (0.985 var fortsatt en + synlig off-white, ikke "fjernet") -- identisk med `--card`, kort + skilles fra siden via border/shadow, ikke en bakgrunnsfarge-forskjell. */ + --background: oklch(1 0 0); --foreground: oklch(0.24 0 0); --card: oklch(1 0 0); --card-foreground: oklch(0.24 0 0); diff --git a/frontend/components/dashboard.tsx b/frontend/components/dashboard.tsx index 1ab6f7e..5f418ee 100644 --- a/frontend/components/dashboard.tsx +++ b/frontend/components/dashboard.tsx @@ -69,7 +69,6 @@ import { cn } from "@/lib/utils" // hvit tekst -- feiler AA for vanlig tekst) -- egen kontrastkorrigert // variant (#1f6b08, ~6,6:1) i samme fargefamilie. const C = { - bg: "#f7faf8", card: "#ffffff", glass: "rgba(255, 255, 255, 0.78)", ink: "#012c11", // primærtekst (nesten-svart grønn), IKKE lenger knappe-bakgrunn @@ -433,7 +432,7 @@ export function Dashboard() { if (loadingMe) { return ( - <div className="flex min-h-[100dvh] flex-col items-center justify-center gap-4" style={{ backgroundColor: C.bg }}> + <div className="flex min-h-[100dvh] flex-col items-center justify-center gap-4 bg-background"> <div aria-hidden="true" className="size-10 animate-spin rounded-full border-4" style={{ borderColor: `${C.accent}33`, borderTopColor: C.accent }} /> </div> ) @@ -470,10 +469,10 @@ export function Dashboard() { const playedCourses = [...coursesMap.values()].sort((a, b) => (a.lastPlayed < b.lastPlayed ? 1 : -1)) return ( - <div className="flex min-h-[100dvh] flex-col" style={{ backgroundColor: C.bg }}> + <div className="flex min-h-[100dvh] flex-col bg-background"> <header - className="sticky top-0 z-10 border-b pt-[env(safe-area-inset-top)]" - style={{ backgroundColor: C.bg, borderColor: C.border }} + className="sticky top-0 z-10 border-b bg-background pt-[env(safe-area-inset-top)]" + style={{ borderColor: C.border }} > <div className="mx-auto flex w-full max-w-3xl items-center justify-between gap-4 px-5 py-4"> <Link href="/dashboard" aria-label="TeeCup – til forsiden" className="flex items-center gap-2 rounded-xl"> @@ -624,7 +623,7 @@ function NotificationBell({ unreadCount }: { unreadCount: number }) { <span aria-hidden="true" className="absolute right-1 top-1 flex min-w-[18px] items-center justify-center rounded-full px-1 text-[11px] font-bold leading-none" - style={{ height: 18, backgroundColor: C.warmInk, color: "#ffffff", boxShadow: `0 0 0 2px ${C.bg}` }} + style={{ height: 18, backgroundColor: C.warmInk, color: "#ffffff", boxShadow: `0 0 0 2px var(--background)` }} > {badgeText} </span> diff --git a/frontend/components/more-menu.tsx b/frontend/components/more-menu.tsx index fb9dfe3..d201864 100644 --- a/frontend/components/more-menu.tsx +++ b/frontend/components/more-menu.tsx @@ -13,7 +13,6 @@ import { Bell, Building2, ChevronRight, LogOut, UserCircle, UserPlus } from "luc import { BottomTabBar } from "@/components/dashboard" const C = { - bg: "#f7faf8", card: "#ffffff", ink: "#012c11", muted: "#424941", @@ -44,7 +43,7 @@ export function MoreMenu() { } return ( - <div className="flex min-h-[100dvh] flex-col" style={{ backgroundColor: C.bg }}> + <div className="flex min-h-[100dvh] flex-col bg-background"> <header className="border-b pt-[env(safe-area-inset-top)]" style={{ borderColor: C.border }}> <div className="mx-auto w-full max-w-2xl px-5 py-4"> <h1 className="text-xl font-extrabold tracking-tight" style={{ color: C.ink }}> diff --git a/frontend/components/new-round.tsx b/frontend/components/new-round.tsx index abf5583..2498b1f 100644 --- a/frontend/components/new-round.tsx +++ b/frontend/components/new-round.tsx @@ -132,6 +132,11 @@ const TWO_SIDED = new Set<FormatId>([ // avgjør om HCP-prosenten bygges som "combined" (delt enhet) eller // "per_player" i allowance_override.strategy. const SIDE_IS_UNIT_FORMAT = new Set<FormatId>(["foursome", "greensome", "scramble2", "scramble4", "chapman"]) +// "Bruk course handicap-justering" er uten reell nytteverdi for de to +// enkleste, rene individuelle formatene (etterspurt av bruker 2026-08-02) +// -- skjules for disse, men holdes funksjonelt PÅ (aldri slettet/fjernet +// fra selve innsendingen, kun ikke eksponert som et valg i UI-et). +const ORDINARY_FORMATS = new Set<FormatId>(["slagspill", "stableford"]) function usesStep4(f: FormatId) { return TWO_SIDED.has(f) || f === "moneyball" @@ -312,6 +317,14 @@ export function NewRound() { const [useMatchplayHcp, setUseMatchplayHcp] = useState(true) const [hcpPercent, setHcpPercent] = useState("") + // "Bruk course handicap-justering" er skjult for de ordinære formatene + // (ORDINARY_FORMATS) -- siden brukeren da aldri ser bryteren, må den + // funksjonelt holdes PÅ uansett hva den måtte ha stått i tidligere (f.eks. + // slått av på et annet format før man byttet tilbake til Slagspill). + useEffect(() => { + if (ORDINARY_FORMATS.has(format)) setUseCourseHcp(true) + }, [format]) + // Step 3 const [players, setPlayers] = useState<Player[]>([]) @@ -506,7 +519,14 @@ export function NewRound() { if (p.userId) { participantBody.user_id = p.userId } else { - participantBody.guest_name = p.name + // Samme "første ord = fornavn, resten = etternavn"-heuristikk som + // round-detail.tsx sin splitName()/migrasjon 052 sin backfill + // (2026-08-03 -- ParticipantCreate godtar ikke lenger guest_name + // direkte, kun guest_first_name/guest_last_name). + const trimmedName = p.name.trim() + const spaceIdx = trimmedName.indexOf(" ") + participantBody.guest_first_name = spaceIdx === -1 ? trimmedName : trimmedName.slice(0, spaceIdx) + participantBody.guest_last_name = spaceIdx === -1 ? null : trimmedName.slice(spaceIdx + 1).trim() participantBody.gender = apiGenderOf(p.gender) participantBody.handicap_index = parseHcp(p.hcp) if (p.email && p.email.trim()) participantBody.guest_email = p.email.trim() @@ -632,7 +652,14 @@ export function NewRound() { </div> </header> - <main className="mx-auto w-full max-w-3xl flex-1 px-5 py-6 pb-32 sm:py-8"> + {/* pb-32 er reservert klaring for den faste Tilbake/Neste-linjen under -- + ALDRI en py- eller sm:py-klasse her (Tailwind emitterer sm-varianter + i en egen media-blokk etter grunnklassene, så en sm:py-8 ville slått + padding-bottom tilbake til 32px på alle skjermer 640px og bredere, + uansett rekkefølge i selve className-strengen -- reell bug funnet + 2026-08-02, se CHANGELOG.md). En pt-klasse er trygt, den påvirker + aldri padding-bottom. */} + <main className="mx-auto w-full max-w-3xl flex-1 px-5 pt-6 pb-32 sm:pt-8"> {isAdditionalFlight && stepN === 1 && s1Sub !== "fields" && ( <p className="mb-6 rounded-2xl border border-dashed border-primary/40 bg-primary/5 px-4 py-3 text-sm font-semibold text-foreground"> Du setter opp enda en flight i samme oppsett -- dato, starthull, hullantall og spilleform er @@ -1812,8 +1839,10 @@ function Step2(props: { {advOpen && ( <div className="flex flex-col gap-5 border-t border-border px-4 py-5"> <ToggleRow label="Bruk handicap" checked={useHcp} onChange={setUseHcp} /> - <ToggleRow label="Bruk course handicap-justering" checked={useCourseHcp} onChange={setUseCourseHcp} /> - {twoSided && ( + {useHcp && !ORDINARY_FORMATS.has(format) && ( + <ToggleRow label="Bruk course handicap-justering" checked={useCourseHcp} onChange={setUseCourseHcp} /> + )} + {useHcp && twoSided && ( <div className="flex flex-col gap-1.5"> <ToggleRow label="Bruk matchplay-handicap" checked={useMatchplayHcp} onChange={setUseMatchplayHcp} /> <p className="px-1 text-sm leading-relaxed text-muted-foreground text-pretty"> @@ -1822,12 +1851,14 @@ function Step2(props: { </p> </div> )} - <div className="flex flex-col gap-2"> - <Label htmlFor="hcp-percent" className="text-base font-semibold"> - HCP-prosent (valgfritt) - </Label> - <Input id="hcp-percent" inputMode="decimal" value={hcpPercent} onChange={(e) => setHcpPercent(e.target.value)} placeholder="Standard for format" className="h-14 rounded-2xl text-lg" /> - </div> + {useHcp && ( + <div className="flex flex-col gap-2"> + <Label htmlFor="hcp-percent" className="text-base font-semibold"> + HCP-prosent (valgfritt) + </Label> + <Input id="hcp-percent" inputMode="decimal" value={hcpPercent} onChange={(e) => setHcpPercent(e.target.value)} placeholder="Standard for format" className="h-14 rounded-2xl text-lg" /> + </div> + )} </div> )} </div> diff --git a/frontend/components/round-detail.tsx b/frontend/components/round-detail.tsx index d9ff620..1321221 100644 --- a/frontend/components/round-detail.tsx +++ b/frontend/components/round-detail.tsx @@ -9,7 +9,7 @@ // aldri kun det ene feltet som ble endret. import type React from "react" -import { useCallback, useEffect, useRef, useState } from "react" +import { useCallback, useEffect, useMemo, useRef, useState } from "react" import Link from "next/link" import { useRouter, useSearchParams } from "next/navigation" import { RoundPageShell } from "@/components/round-page-shell" @@ -60,6 +60,8 @@ type Player = { // "Deg Deg"-duplikat der en egen Badge også viste "Deg" ved siden av). name: string userId: string | null + guestFirstName: string | null + guestLastName: string | null guestEmail: string | null isOwner: boolean gender: Gender @@ -158,6 +160,8 @@ type ApiParticipant = { id: string user_id: string | null guest_name: string | null + guest_first_name: string | null + guest_last_name: string | null // Valgfritt kontaktfelt, KUN meningsfullt for en gjest (2026-07-26). guest_email: string | null // Alltid utfylt av API-et -- gjestens navn, eller en levende oppslått @@ -779,6 +783,8 @@ export function RoundDetail({ roundId }: { roundId: string }) { id: p.id, name: playerLabel(p), userId: p.user_id, + guestFirstName: p.guest_first_name, + guestLastName: p.guest_last_name, guestEmail: p.guest_email, isOwner: p.is_owner, gender: apiGenderToUi(p.gender), @@ -981,13 +987,22 @@ export function RoundDetail({ roundId }: { roundId: string }) { } } - async function addGuest(name: string, gender: Gender, hcp: number | null, statLevel: StatLevel) { + async function addGuest( + firstName: string, + lastName: string, + email: string, + gender: Gender, + hcp: number | null, + statLevel: StatLevel, + ) { const res = await fetch(`/rounds/${roundId}/participants`, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ - guest_name: name, + guest_first_name: firstName, + guest_last_name: lastName.trim() === "" ? null : lastName, + guest_email: email.trim() === "" ? null : email.trim(), gender: uiGenderToApi(gender), handicap_index: hcp, stat_level: statLevel, @@ -3313,7 +3328,7 @@ function PlayerList({ editingPlayerId: string | null onToggleEdit: (id: string) => void onPatchParticipant: (participantId: string, body: Record<string, unknown>) => Promise<{ ok: true } | { ok: false; message: string }> - onAddGuest: (name: string, gender: Gender, hcp: number | null, statLevel: StatLevel) => void + onAddGuest: (firstName: string, lastName: string, email: string, gender: Gender, hcp: number | null, statLevel: StatLevel) => void onAddSearched: (userId: string, statLevel: StatLevel) => Promise<string | null> }) { return ( @@ -3462,7 +3477,7 @@ function AddParticipantForm({ onAddSearched, onCancel, }: { - onAddGuest: (name: string, gender: Gender, hcp: number | null, statLevel: StatLevel) => void + onAddGuest: (firstName: string, lastName: string, email: string, gender: Gender, hcp: number | null, statLevel: StatLevel) => void onAddSearched: (userId: string, statLevel: StatLevel) => Promise<string | null> onCancel: () => void }) { @@ -3500,7 +3515,7 @@ function AddParticipantForm({ } if (mode === "guest") { - return <AddGuestForm onAdd={onAddGuest} onCancel={onCancel} onBack={() => setMode("search")} /> + return <AddGuestForm initialName={query} onAdd={onAddGuest} onCancel={onCancel} onBack={() => setMode("search")} /> } return ( @@ -3585,27 +3600,88 @@ function AddParticipantForm({ } // --- Add guest form (fallback for spillere uten TeeCup-konto) -------------- +// Navnesplitt/e-post/"gjenkjenn gjest"-utvidelse 2026-08-03 -- se +// CHANGELOG.md for full bakgrunn (retroaktiv e-post-kobling, rundeoppsummer- +// ing per e-post ved fullføring). + +// Samme "første ord = fornavn, resten = etternavn"-heuristikk som +// migrasjon 052 sin backfill -- brukt til å autofylle fra søkefeltet. +function splitName(full: string): { first: string; last: string } { + const trimmed = full.trim() + const spaceIdx = trimmed.indexOf(" ") + if (spaceIdx === -1) return { first: trimmed, last: "" } + return { first: trimmed.slice(0, spaceIdx), last: trimmed.slice(spaceIdx + 1).trim() } +} + +type KnownGuest = { + guest_first_name: string + guest_last_name: string | null + gender: ApiGender | null + handicap_index: number | null +} function AddGuestForm({ + initialName, onAdd, onCancel, onBack, }: { - onAdd: (name: string, gender: Gender, hcp: number | null, statLevel: StatLevel) => void + initialName: string + onAdd: (firstName: string, lastName: string, email: string, gender: Gender, hcp: number | null, statLevel: StatLevel) => void onCancel: () => void onBack?: () => void }) { - const [name, setName] = useState("") + const initialSplit = useMemo(() => splitName(initialName), [initialName]) + const [firstName, setFirstName] = useState(initialSplit.first) + const [lastName, setLastName] = useState(initialSplit.last) + const [email, setEmail] = useState("") const [gender, setGender] = useState<Gender>("male") const [hcp, setHcp] = useState("") const [statLevel, setStatLevel] = useState<StatLevel>("strokes_only") + const [knownGuest, setKnownGuest] = useState<KnownGuest | null>(null) + const [knownGuestDismissed, setKnownGuestDismissed] = useState(false) + + // "Vi kjenner igjen denne e-posten" -- kun søk i EGNE tidligere + // registrerte gjester (håndhevet server-side, se find_known_guest i + // app/routers/rounds.py -- aldri et globalt oppslag). + useEffect(() => { + const trimmed = email.trim() + setKnownGuestDismissed(false) + if (trimmed.length < 5 || !trimmed.includes("@")) { + setKnownGuest(null) + return + } + const handle = setTimeout(() => { + fetch(`/rounds/guests/known?email=${encodeURIComponent(trimmed)}`, { credentials: "include" }) + .then((res) => (res.ok ? res.json() : null)) + .then((data: KnownGuest | null) => setKnownGuest(data)) + .catch(() => setKnownGuest(null)) + }, 350) + return () => clearTimeout(handle) + }, [email]) + + function useKnownGuest() { + if (!knownGuest) return + setFirstName(knownGuest.guest_first_name) + setLastName(knownGuest.guest_last_name ?? "") + if (knownGuest.gender) setGender(apiGenderToUi(knownGuest.gender)) + if (knownGuest.handicap_index !== null) setHcp(String(knownGuest.handicap_index).replace(".", ",")) + setKnownGuestDismissed(true) + } function handleSubmit(e: React.FormEvent) { e.preventDefault() - const trimmed = name.trim() - if (!trimmed) return + const trimmedFirst = firstName.trim() + if (!trimmedFirst) return const parsedHcp = hcp.trim() === "" ? null : Number(hcp.replace(",", ".")) - onAdd(trimmed, gender, parsedHcp !== null && !Number.isNaN(parsedHcp) ? parsedHcp : null, statLevel) + onAdd( + trimmedFirst, + lastName.trim(), + email.trim(), + gender, + parsedHcp !== null && !Number.isNaN(parsedHcp) ? parsedHcp : null, + statLevel, + ) } return ( @@ -3619,11 +3695,57 @@ function AddGuestForm({ ← Søk i stedet </button> )} + <div className="grid grid-cols-1 gap-4 sm:grid-cols-2"> + <div className="flex flex-col gap-2"> + <Label htmlFor="guest-first-name" className="text-base font-semibold"> + Fornavn + </Label> + <Input + id="guest-first-name" + autoFocus + value={firstName} + onChange={(e) => setFirstName(e.target.value)} + placeholder="Fornavn" + className="h-12 rounded-2xl text-base" + /> + </div> + <div className="flex flex-col gap-2"> + <Label htmlFor="guest-last-name" className="text-base font-semibold"> + Etternavn <span className="font-normal text-muted-foreground">(valgfritt)</span> + </Label> + <Input + id="guest-last-name" + value={lastName} + onChange={(e) => setLastName(e.target.value)} + placeholder="Etternavn" + className="h-12 rounded-2xl text-base" + /> + </div> + </div> + <div className="flex flex-col gap-2"> - <Label htmlFor="guest-name" className="text-base font-semibold"> - Navn + <Label htmlFor="guest-email" className="text-base font-semibold"> + E-post <span className="font-normal text-muted-foreground">(valgfritt)</span> </Label> - <Input id="guest-name" autoFocus value={name} onChange={(e) => setName(e.target.value)} placeholder="Gjestens navn" className="h-12 rounded-2xl text-base" /> + <Input + id="guest-email" + type="email" + value={email} + onChange={(e) => setEmail(e.target.value)} + placeholder="For scorekort og statistikk etter runden" + className="h-12 rounded-2xl text-base" + /> + {knownGuest && !knownGuestDismissed && ( + <div className="flex flex-col gap-2 rounded-2xl border border-primary/40 bg-primary/5 p-3 sm:flex-row sm:items-center sm:justify-between"> + <p className="text-sm text-foreground"> + Vi fant <span className="font-bold">{knownGuest.guest_first_name} {knownGuest.guest_last_name}</span>{" "} + fra en tidligere runde du satte opp. + </p> + <Button type="button" size="sm" onClick={useKnownGuest} className="h-10 shrink-0 rounded-xl px-4 text-sm font-bold"> + Bruk disse opplysningene + </Button> + </div> + )} </div> <ChoiceRow @@ -4014,7 +4136,8 @@ function EditParticipantPanel({ const [teeName, setTeeName] = useState(player.teeName) const [hcp, setHcp] = useState(player.hcp !== null ? String(player.hcp).replace(".", ",") : "") const [statLevel, setStatLevel] = useState<StatLevel>(player.statLevel) - const [guestName, setGuestName] = useState(player.name) + const [guestFirstName, setGuestFirstName] = useState(player.guestFirstName ?? player.name) + const [guestLastName, setGuestLastName] = useState(player.guestLastName ?? "") const [gender, setGender] = useState<Gender>(player.gender) const [guestEmail, setGuestEmail] = useState(player.guestEmail ?? "") const [excludeFromHandicap, setExcludeFromHandicap] = useState(player.excludeFromHandicap) @@ -4054,8 +4177,11 @@ function EditParticipantPanel({ if (normalizedHcp !== player.hcp) body.handicap_index = normalizedHcp if (statLevel !== player.statLevel) body.stat_level = statLevel if (isGuest) { - const trimmedName = guestName.trim() - if (trimmedName && trimmedName !== player.name) body.guest_name = trimmedName + const trimmedFirst = guestFirstName.trim() + const trimmedLast = guestLastName.trim() + if (trimmedFirst && trimmedFirst !== (player.guestFirstName ?? "")) body.guest_first_name = trimmedFirst + const normalizedLast = trimmedLast === "" ? null : trimmedLast + if (normalizedLast !== (player.guestLastName ?? null)) body.guest_last_name = normalizedLast if (gender !== player.gender) body.gender = apiGender const trimmedEmail = guestEmail.trim() const normalizedEmail = trimmedEmail === "" ? null : trimmedEmail @@ -4095,16 +4221,29 @@ function EditParticipantPanel({ <> {isGuest && ( <> - <div className="flex flex-col gap-2"> - <Label htmlFor={`edit-guest-name-${player.id}`} className="text-sm font-semibold"> - Navn - </Label> - <Input - id={`edit-guest-name-${player.id}`} - value={guestName} - onChange={(e) => setGuestName(e.target.value)} - className="h-11 rounded-xl text-base" - /> + <div className="grid grid-cols-1 gap-3 sm:grid-cols-2"> + <div className="flex flex-col gap-2"> + <Label htmlFor={`edit-guest-first-name-${player.id}`} className="text-sm font-semibold"> + Fornavn + </Label> + <Input + id={`edit-guest-first-name-${player.id}`} + value={guestFirstName} + onChange={(e) => setGuestFirstName(e.target.value)} + className="h-11 rounded-xl text-base" + /> + </div> + <div className="flex flex-col gap-2"> + <Label htmlFor={`edit-guest-last-name-${player.id}`} className="text-sm font-semibold"> + Etternavn <span className="font-normal text-muted-foreground">(valgfritt)</span> + </Label> + <Input + id={`edit-guest-last-name-${player.id}`} + value={guestLastName} + onChange={(e) => setGuestLastName(e.target.value)} + className="h-11 rounded-xl text-base" + /> + </div> </div> <ChoiceRow diff --git a/frontend/components/tournament-program.tsx b/frontend/components/tournament-program.tsx index 6b28016..ca31f23 100644 --- a/frontend/components/tournament-program.tsx +++ b/frontend/components/tournament-program.tsx @@ -1306,29 +1306,33 @@ function CreateSessionCard({ {advancedOpen && ( <div className="flex flex-col gap-1 border-t border-border px-4 py-2"> <ToggleRow label="Bruk handicap" checked={useHandicap} onCheckedChange={setUseHandicap} /> - <ToggleRow - label="Bruk course handicap-justering" - checked={useCourseHandicap} - onCheckedChange={setUseCourseHandicap} - /> - <ToggleRow - label="Bruk matchplay-handicap" - checked={useMatchplayHandicap} - onCheckedChange={setUseMatchplayHandicap} - /> - <div className="flex flex-col gap-2 py-3"> - <Label htmlFor="hcp-percent" className="text-sm font-semibold"> - HCP-prosent <span className="font-normal text-muted-foreground">(valgfritt)</span> - </Label> - <Input - id="hcp-percent" - inputMode="decimal" - placeholder="Standard for format" - value={hcpPercent} - onChange={(e) => setHcpPercent(e.target.value)} - className="h-11 rounded-xl text-base" - /> - </div> + {useHandicap && ( + <> + <ToggleRow + label="Bruk course handicap-justering" + checked={useCourseHandicap} + onCheckedChange={setUseCourseHandicap} + /> + <ToggleRow + label="Bruk matchplay-handicap" + checked={useMatchplayHandicap} + onCheckedChange={setUseMatchplayHandicap} + /> + <div className="flex flex-col gap-2 py-3"> + <Label htmlFor="hcp-percent" className="text-sm font-semibold"> + HCP-prosent <span className="font-normal text-muted-foreground">(valgfritt)</span> + </Label> + <Input + id="hcp-percent" + inputMode="decimal" + placeholder="Standard for format" + value={hcpPercent} + onChange={(e) => setHcpPercent(e.target.value)} + className="h-11 rounded-xl text-base" + /> + </div> + </> + )} </div> )} </div> diff --git a/frontend/public/sw.js b/frontend/public/sw.js index cf98907..e4e7e33 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -24,7 +24,13 @@ // appen, se team_authz.py sin trusselmodell-begrunnelse), ingen // cache-tømming ved utlogging bygget ennå. -const RUNTIME_CACHE = "teecup-runtime-v1" +// v2 (2026-08-02): "nettverk først" var i praksis "nettleser-cache først" -- +// et rått fetch(req) her respekterer fortsatt HTTP Cache-Control på selve +// requesten, så en side lastet før en ny utrulling kunne forbli synlig +// lenge etter deploy uten at brukeren merket noe (ingen feilmelding, bare +// stille utdatert innhold). explicit cache:"reload" tvinger et ekte +// nettverksoppslag hver gang, samme prinsipp begge steder under. +const RUNTIME_CACHE = "teecup-runtime-v2" const OFFLINE_URL = "/offline.html" self.addEventListener("install", (event) => { @@ -51,7 +57,7 @@ self.addEventListener("fetch", (event) => { if (req.mode === "navigate") { event.respondWith( - fetch(req) + fetch(req, { cache: "reload" }) .then((res) => { const copy = res.clone() caches.open(RUNTIME_CACHE).then((cache) => cache.put(req, copy)) @@ -65,7 +71,7 @@ self.addEventListener("fetch", (event) => { const url = new URL(req.url) if (url.origin === self.location.origin && url.pathname.startsWith("/orgs/")) { event.respondWith( - fetch(req) + fetch(req, { cache: "reload" }) .then((res) => { if (res.ok) caches.open(RUNTIME_CACHE).then((cache) => cache.put(req, res.clone())) return res diff --git a/frontend/tsconfig.tsbuildinfo b/frontend/tsconfig.tsbuildinfo index f4938cb..27bd121 100644 --- a/frontend/tsconfig.tsbuildinfo +++ b/frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"fileNames":["./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es5.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2023.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.esnext.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.dom.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2022.regexp.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2023.array.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2023.collection.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2023.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.collection.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.object.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.promise.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.regexp.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.string.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.esnext.array.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.esnext.collection.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.esnext.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.esnext.decorators.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.esnext.iterator.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/global.d.ts","./node_modules/.pnpm/csstype@3.2.3/node_modules/csstype/index.d.ts","./node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/css.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/macro.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/style.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/global.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/get-page-files.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/globals.typedarray.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/buffer.buffer.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/globals.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/crypto.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/events.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/utility.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/header.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/readable.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/fetch.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/formdata.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/connector.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/client-stats.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/client.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/errors.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/dispatcher.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/global-dispatcher.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/global-origin.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/pool-stats.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/pool.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/handlers.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/balanced-pool.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/h2c-client.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/agent.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-interceptor.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-call-history.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-agent.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-client.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-pool.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/snapshot-agent.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-errors.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/proxy-agent.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/retry-handler.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/retry-agent.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/api.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/cache-interceptor.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/interceptors.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/util.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/cookies.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/patch.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/websocket.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/eventsource.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/content-type.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/cache.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/index.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/navigator.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/storage.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/streams.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/assert.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/assert/strict.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/async_hooks.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/buffer.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/child_process.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/cluster.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/console.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/constants.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/crypto.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/dgram.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/dns.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/dns/promises.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/domain.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/events.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/fs.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/fs/promises.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/http.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/http2.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/https.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/inspector.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/inspector.generated.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/module.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/net.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/os.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/path.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/perf_hooks.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/process.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/punycode.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/querystring.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/readline.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/readline/promises.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/repl.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/sea.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/sqlite.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream/promises.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream/consumers.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream/web.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/string_decoder.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/test.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/timers.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/timers/promises.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/tls.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/trace_events.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/tty.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/url.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/util.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/v8.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/vm.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/wasi.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/worker_threads.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/zlib.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/ts5.7/index.d.ts","./node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/canary.d.ts","./node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/experimental.d.ts","./node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/index.d.ts","./node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/canary.d.ts","./node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/experimental.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/fallback.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/webpack/webpack.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/modern-browserslist-target.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/entry-constants.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/constants.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/bundler.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/config.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/load-custom-routes.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/image-config.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/subresource-integrity-plugin.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/body-streams.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/search-params.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/segment-cache/vary-params-decoding.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/vary-params.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/params.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-kind.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/route-definition.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matches/route-match.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/app-router-headers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/cache-control.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/app-router-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/cache-handlers/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/use-cache/use-cache-wrapper.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/resume-data-cache/cache-store.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/resume-data-cache/resume-data-cache.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/constants.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/render-result.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/response-cache/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/response-cache/index.d.ts","./node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/jsx-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/userspace/pages/pages-dev-overlay-setup.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/static-paths/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/app-page-route-definition.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/adapter/setup-node-env.external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/instrumentation/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/setup-exception-listeners.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/worker.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/experimental/ppr.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/page-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/app/app-segment-config.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/pages/pages-segment-config.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/analysis/get-page-static-info.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/loaders/get-module-build-info.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/middleware-plugin.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/require-hook.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-polyfill-crypto.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-baseline.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/error-inspect.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/console-file.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/console-exit.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/console-dim.external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/unhandled-rejection.external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/random.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/date.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/web-crypto.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/node-crypto.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/fast-set-immediate.external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/page-extensions-type.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/module.compiled.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/app-route-route-definition.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/i18n-provider.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/next-url.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@edge-runtime/cookies/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/cookies.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/request.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/deep-readonly.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/incremental-cache/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/middleware-route-matcher.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/flight-manifest-plugin.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/next-font-manifest-plugin.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/locale-route-definition.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/pages-route-definition.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/mitt.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/with-router.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/router.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/route-loader.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/page-loader.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/bloom-filter.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/router.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router-context.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/loadable-context.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/loadable.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/image-config-context.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/readonly-url-search-params.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/hooks-client-context.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/head-manager-context.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/flight-data-helpers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/cache-key.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/router-reducer/fetch-server-response.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/segment-cache/segment-value-encoding.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/scheduler.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/cache-map.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/vary-path.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/cache.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/router-reducer/ppr-navigations.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/navigation.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/router-reducer/router-reducer-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/app-router-context.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/server-inserted-html.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/vendored/contexts/entrypoints.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/module.compiled.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/templates/pages.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/module.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/render.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/pages-api-route-definition.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matches/pages-api-route-match.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matchers/route-matcher.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matcher-providers/route-matcher-provider.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matcher-managers/route-matcher-manager.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/normalizer.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/locale-route-normalizer.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/pathname-normalizer.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/suffix.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/rsc.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/next-data.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/builtin-request-context.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/segment-prefix-rsc.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/builtin/_error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/load-default-error-components.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/base-server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/after.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/after-context.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/use-cache/cache-life.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-async-storage-instance.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/lazy-result.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/create-error-handler.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/action-revalidation-kind.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-async-storage.external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/async-storage/work-store.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/http.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/hooks-server-context.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-route/shared-modules.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/redirect-status-code.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/redirect-error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/adapters/request-cookies.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/async-storage/draft-mode-provider.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/adapters/headers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/cache-signal.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/boundary-tracking.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/instant-validation-error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/parse-relative-url.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/instant-samples.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/dynamic-rendering.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-unit-async-storage-instance.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/implicit-tags.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/staged-rendering.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-unit-async-storage.external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/templates/app-route.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/action-async-storage-instance.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/action-async-storage.external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-route/module.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-route/module.compiled.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/app/app-segments.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/get-supported-browsers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/utils.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/rendering-mode.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/build-prefetch-segment-data-route.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/cpu-profile.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/result.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/helpers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/export/routes/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/export/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/export/worker.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/worker.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/coalesced-function.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/trace.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/shared.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/load-jsconfig.d.ts","./node_modules/.pnpm/@next+env@16.2.6/node_modules/@next/env/dist/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/telemetry-plugin/use-cache-tracker-utils.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/telemetry-plugin/telemetry-plugin.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/telemetry/storage.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/build-context.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack-config.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/swc/generated-native.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/define-env.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/swc/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/swc/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/parse-version-info.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/shared/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/dev-indicator-server-state.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/cache-indicator.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/parse-stack.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/server/shared.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/shared/stack-frame.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/utils/get-error-by-type.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/container/runtime-error/render-error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/shared.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/debug-channel.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/hot-reloader-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/fetch-event.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/response.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/middleware/middleware-config.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/parse-url.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/base-http/node.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/async-callback-set.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/route-regex.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/route-matcher.d.ts","./node_modules/.pnpm/sharp@0.34.5/node_modules/sharp/lib/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/image-optimizer.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/next-server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/lru-cache.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/dev-bundler-service.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/static-paths-worker.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/next-dev-server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/next.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/render-server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/path-match.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/filesystem.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/router-server-context.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/route-module.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/load-components.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/adapter.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/loaders/metadata/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/loaders/next-app-loader/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/app-dir-module.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/app-render.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/vendored/contexts/entrypoints.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/error-boundary.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/layout-router.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/render-from-template-context.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/client-page.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/client-segment.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/http-access-fallback/error-boundary.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/alternative-urls-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/extra-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/metadata-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/manifest-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/opengraph-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/twitter-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/metadata-interface.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/resolvers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/icons.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/resolve-metadata.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/metadata.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/framework/boundary-components.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/rsc/preloads.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/rsc/postpone.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/rsc/taint.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/collect-segment-data.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/instant-validation.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/userspace/app/segment-explorer-node.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/entry-base.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/templates/app-page.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/helpers/prerender-manifest-matcher.d.ts","./node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/jsx-dev-runtime.d.ts","./node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/compiler-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/vendored/rsc/entrypoints.d.ts","./node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/client.d.ts","./node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/static.d.ts","./node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/vendored/ssr/entrypoints.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/module.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/fallback-params.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/image-response.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/user-agent.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/url-pattern.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/connection.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/exports/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request-meta.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/cli/next-test.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/size-limit.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/config-shared.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/base-http/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/api-utils/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/adapter/build-complete.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/html-context.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/utils.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/pages/_app.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/app.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/unstable-cache.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/revalidate.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/unstable-no-store.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/use-cache/cache-tag.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/cache.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/pages/_document.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/document.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/dynamic.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dynamic.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/pages/_error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/catch-error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/api/error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/head.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/head.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/cookies.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/headers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/draft-mode.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/headers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/get-img-props.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/image-component.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/image-external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/image.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/link.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/link.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unrecognized-action-error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/redirect.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/not-found.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/forbidden.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unauthorized.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unstable-rethrow.server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unstable-rethrow.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/navigation.react-server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/navigation.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/navigation.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/router.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/script.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/script.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@edge-runtime/primitives/url.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@vercel/og/satori/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@vercel/og/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/types/global.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/types/compiled.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/image-types/global.d.ts","./.next/types/routes.d.ts","./next-env.d.ts","./app/manifest.ts","./lib/offline-queue.ts","./lib/push-subscribe.ts","./lib/pwa-install.ts","./node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/clsx.d.mts","./node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/types.d.ts","./lib/utils.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@next/font/dist/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@next/font/dist/google/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/font/google/index.d.ts","./components/sw-register.tsx","./app/layout.tsx","./node_modules/.pnpm/lucide-react@1.17.0_react@19.2.4/node_modules/lucide-react/dist/lucide-react.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/reason-parts.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/reasons.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/createBaseUIEventDetails.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/types/index.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/types.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/button/Button.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/button/index.d.ts","./node_modules/.pnpm/class-variance-authority@0.7.1/node_modules/class-variance-authority/dist/types.d.ts","./node_modules/.pnpm/class-variance-authority@0.7.1/node_modules/class-variance-authority/dist/index.d.ts","./components/ui/button.tsx","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/form-context/FormContext.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/form/Form.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/form/index.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/root/FieldRoot.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/label/FieldLabel.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/useTransitionStatus.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/error/FieldError.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/description/FieldDescription.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/control/FieldControl.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/validity/FieldValidity.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/item/FieldItem.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/index.parts.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/index.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/input/Input.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/input/index.d.ts","./components/ui/input.tsx","./components/ui/label.tsx","./components/two-factor-flow.tsx","./components/login-form.tsx","./app/page.tsx","./components/wordmark.tsx","./components/account-settings.tsx","./app/account/page.tsx","./node_modules/.pnpm/@floating-ui+utils@0.2.11/node_modules/@floating-ui/utils/dist/floating-ui.utils.d.mts","./node_modules/.pnpm/@floating-ui+core@1.7.5/node_modules/@floating-ui/core/dist/floating-ui.core.d.mts","./node_modules/.pnpm/@floating-ui+utils@0.2.11/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.d.mts","./node_modules/.pnpm/@floating-ui+dom@1.7.6/node_modules/@floating-ui/dom/dist/floating-ui.dom.d.mts","./node_modules/.pnpm/@floating-ui+react-dom@2.1.8_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.d.mts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/components/FloatingTreeStore.d.ts","./node_modules/.pnpm/reselect@5.2.0/node_modules/reselect/dist/reselect.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/store/createSelector.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/store/createSelectorMemoized.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/fastHooks.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/store/Store.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/store/useStore.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/store/ReactStore.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/store/StoreInspector.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/store/index.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/utils/popups/inlineRect.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/useEnhancedClickHandler.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/utils/popups/popupTriggerMap.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/utils/popups/store.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/utils/popups/popupStoreUtils.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/utils/popups/index.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/components/FloatingRootStore.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/components/FloatingFocusManager.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/getStateAttributesProps.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/useRenderElement.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/components/FloatingPortal.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useClientPoint.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useDismiss.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useFocus.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/shadowDom.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/utils/element.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useHoverShared.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useHover.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useHoverFloatingInteraction.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useHoverReferenceInteraction.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useListNavigation.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useTypeahead.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useFloatingRootContext.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/safePolygon.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/components/FloatingTree.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/types.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/components/FloatingDelayGroup.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useClick.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useFloating.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useSyncedFloatingRootContext.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/index.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/utils/useAnchorPositioning.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/arrow/MenuArrow.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/backdrop/MenuBackdrop.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/store/MenuStore.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/root/MenuRootContext.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menubar/MenubarContext.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/context-menu/root/ContextMenuRoot.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/context-menu/root/ContextMenuRootContext.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/store/MenuHandle.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/root/MenuRoot.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/checkbox-item/MenuCheckboxItem.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/checkbox-item-indicator/MenuCheckboxItemIndicator.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/group/MenuGroup.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/group-label/MenuGroupLabel.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/item/MenuItem.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/link-item/MenuLinkItem.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/popup/MenuPopup.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/portal/MenuPortal.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/positioner/MenuPositioner.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/radio-group/MenuRadioGroup.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/radio-item/MenuRadioItem.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/radio-item-indicator/MenuRadioItemIndicator.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/submenu-root/MenuSubmenuRootContext.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/submenu-root/MenuSubmenuRoot.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/trigger/MenuTrigger.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/viewport/MenuViewport.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/separator/Separator.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/submenu-trigger/MenuSubmenuTrigger.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/index.parts.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/index.d.ts","./components/ui/dropdown-menu.tsx","./components/tournament-status-badge.tsx","./components/tournament-card.tsx","./components/public-club.tsx","./app/clubs/[slug]/page.tsx","./components/install-prompt.tsx","./components/round-card.tsx","./components/dashboard.tsx","./app/dashboard/page.tsx","./components/more-menu.tsx","./app/more/page.tsx","./components/friends.tsx","./app/my-friends/page.tsx","./components/friend-profile.tsx","./app/my-friends/[id]/page.tsx","./components/notifications.tsx","./app/my-notifications/page.tsx","./components/own-rounds.tsx","./app/my-rounds/page.tsx","./components/round-header.tsx","./components/round-page-shell.tsx","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/merge-props/mergeProps.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/merge-props/index.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/use-render/useRender.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/use-render/index.d.ts","./components/ui/badge.tsx","./components/round-leaderboard.tsx","./components/round-detail.tsx","./app/my-rounds/[id]/page.tsx","./components/flight-group-leaderboard.tsx","./app/my-rounds/[id]/flights/page.tsx","./app/my-rounds/[id]/leaderboard/page.tsx","./components/round-scorecard.tsx","./components/round-stats.tsx","./app/my-rounds/[id]/scorecard/page.tsx","./app/my-rounds/[id]/stats/page.tsx","./components/course-rounds.tsx","./app/my-rounds/course/[name]/page.tsx","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/switch/root/SwitchRoot.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/switch/thumb/SwitchThumb.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/switch/index.parts.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/switch/index.d.ts","./components/ui/switch.tsx","./components/new-round.tsx","./app/my-rounds/new/page.tsx","./components/rounds-stats-summary.tsx","./app/my-rounds/stats/page.tsx","./components/org-members.tsx","./app/organizations/[id]/members/page.tsx","./components/public-tournament.tsx","./app/t/[id]/page.tsx","./components/public-live.tsx","./app/t/[id]/live/page.tsx","./components/tournament-detail.tsx","./components/individual-tournament-detail.tsx","./components/tournament-router.tsx","./app/tournaments/[id]/page.tsx","./components/tournament-leaderboard.tsx","./app/tournaments/[id]/leaderboard/page.tsx","./components/tournament-program.tsx","./app/tournaments/[id]/program/page.tsx","./components/session-blind-draw.tsx","./app/tournaments/[id]/sessions/[sessionId]/page.tsx","./components/session-individual-leaderboard.tsx","./app/tournaments/[id]/sessions/[sessionId]/individual-leaderboard/page.tsx","./components/session-scorecard.tsx","./app/tournaments/[id]/sessions/[sessionId]/matches/[matchId]/page.tsx","./components/team-chat.tsx","./app/tournaments/[id]/teams/[teamId]/chat/page.tsx","./components/verify-form.tsx","./app/verify/page.tsx","./components/verify-email-form.tsx","./app/verify-email/page.tsx","./components/watch-round.tsx","./app/watch/[id]/page.tsx","./components/ui/card.tsx","./.next/types/cache-life.d.ts","./.next/types/validator.ts","./.next/dev/types/cache-life.d.ts","./.next/dev/types/routes.d.ts","./.next/dev/types/validator.ts"],"fileIdsList":[[91,145,162,163,487,488,489,490,734],[91,145,162,163,734,736],[91,145,162,163,230,531,547,578,581,662,666,670,672,674,676,686,688,689,692,693,695,702,704,706,708,710,714,716,718,720,722,724,726,728,730,732,734,736,737],[91,145,162,163,487,488,489,490,736],[91,145,162,163,230,531,534,547,578,581,662,666,668,670,672,674,676,686,688,689,692,693,695,702,704,706,708,710,714,716,718,720,722,724,726,728,730,732,734,736],[91,145,162,163,230,580,734,736],[91,145,162,163,230,532,661,734,736],[91,145,162,163,230,665,734,736],[91,145,162,163,230,529,532,545,546,734,736],[91,145,162,163,230,532,734,736],[91,145,162,163,230,667,734,736],[91,145,162,163,230,671,734,736],[91,145,162,163,230,669,734,736],[91,145,162,163,230,673,734,736],[91,145,162,163,230,687,734,736],[91,145,162,163,230,678,684,734,736],[81,91,145,162,163,230,685,734,736],[91,145,162,163,230,678,690,691,734,736],[91,145,162,163,230,521,734,736],[91,145,162,163,230,694,734,736],[81,91,145,162,163,230,701,734,736],[91,145,162,163,230,675,734,736],[91,145,162,163,230,703,734,736],[91,145,162,163,230,705,734,736],[91,145,162,163,230,505,521,577,734,736],[91,145,162,163,230,709,734,736],[91,145,162,163,230,532,707,734,736],[91,145,162,163,230,715,734,736],[91,145,162,163,230,713,734,736],[91,145,162,163,230,717,734,736],[91,145,162,163,230,721,734,736],[91,145,162,163,230,723,734,736],[91,145,162,163,230,719,734,736],[91,145,162,163,230,725,734,736],[81,91,145,162,163,230,579,729,734,736],[81,91,145,162,163,230,579,727,734,736],[91,145,162,163,230,731,734,736],[81,91,145,162,163,230,511,521,538,548,558,574,575,576,579,734,736],[81,91,145,162,163,230,511,521,548,579,664,734,736],[81,91,145,162,163,230,511,521,542,548,558,574,575,658,659,660,663,664,734,736],[81,91,145,162,163,230,511,542,548,683,734,736],[81,91,145,162,163,230,511,542,548,734,736],[81,91,145,162,163,230,511,521,542,548,574,579,734,736],[81,91,145,162,163,230,511,542,548,558,574,575,658,659,734,736],[81,91,145,162,163,230,539,548,734,736],[81,91,145,162,163,230,521,548,576,734,736],[81,91,145,162,163,230,511,521,548,665,734,736],[81,91,145,162,163,230,511,521,542,548,558,574,575,579,700,734,736],[81,91,145,162,163,230,511,521,542,548,579,734,736],[81,91,145,162,163,230,511,548,558,574,575,734,736],[81,91,145,162,163,230,511,521,548,558,579,664,734,736],[81,91,145,162,163,230,548,579,660,734,736],[81,91,145,162,163,230,511,542,548,579,734,736],[81,91,145,162,163,230,511,542,548,558,574,575,579,734,736],[81,91,145,162,163,230,511,521,537,542,548,558,574,575,678,683,684,734,736],[81,91,145,162,163,198,200,230,511,542,548,558,734,736],[81,91,145,162,163,230,521,677,734,736],[81,91,145,162,163,230,511,521,548,734,736],[81,91,145,162,163,230,511,542,548,558,734,736],[81,91,145,162,163,230,511,537,542,548,558,734,736],[81,91,145,162,163,230,539,734,736],[91,145,162,163,230,511,548,659,734,736],[81,91,145,162,163,230,511,542,548,558,574,575,658,700,734,736],[81,91,145,162,163,230,711,712,734,736],[91,145,162,163,230,542,548,658,734,736],[81,91,145,162,163,230,548,558,574,575,734,736],[91,145,162,163,230,542,557,680,682,734,736],[91,145,162,163,230,542,555,557,734,736],[81,91,145,162,163,230,542,734,736],[81,91,145,162,163,230,542,548,657,734,736],[81,91,145,162,163,230,542,573,734,736],[91,145,162,163,230,542,699,734,736],[81,91,145,162,163,230,521,548,558,574,575,576,734,736],[91,145,162,163,230,542,548,734,736],[91,145,162,163,230,734,736],[91,145,162,163,230,540,541,734,736],[91,145,162,163,532,533,534,734,736],[81,91,145,162,163,553,734,736],[91,145,162,163,554,734,736],[91,145,162,163,230,552,637,657,734,736],[81,91,145,162,163,634,734,736],[81,91,145,162,163,550,551,553,562,734,736],[81,91,145,162,163,553,562,734,736],[81,91,145,162,163,553,562,564,734,736],[91,145,162,163,562,563,565,566,567,568,569,570,734,736],[91,145,162,163,562,563,565,566,567,568,569,734,736],[81,91,145,162,163,553,561,734,736],[81,91,145,162,163,562,564,734,736],[81,91,145,162,163,622,734,736],[81,91,145,162,163,587,598,622,734,736],[81,91,145,162,163,553,606,734,736],[81,91,145,162,163,551,564,596,602,622,734,736],[81,91,145,162,163,587,622,734,736],[91,145,162,163,622,734,736],[91,145,162,163,550,622,734,736],[91,145,162,163,587,622,734,736],[91,145,162,163,551,603,622,734,736],[91,145,162,163,613,622,734,736],[81,91,145,162,163,553,587,613,622,734,736],[91,145,162,163,612,622,734,736],[91,145,162,163,552,596,602,603,734,736],[91,145,162,163,586,587,604,607,608,609,610,614,615,616,617,618,619,620,621,622,623,624,625,626,734,736],[91,145,162,163,613,734,736],[81,91,145,162,163,551,586,587,603,604,607,608,609,610,613,614,615,616,617,618,619,620,621,623,627,734,736],[91,145,162,163,602,611,734,736],[81,91,145,162,163,550,551,553,559,734,736],[91,145,162,163,560,734,736],[81,91,145,162,163,553,571,734,736],[91,145,162,163,572,734,736],[91,145,162,163,550,734,736],[81,91,145,162,163,560,562,734,736],[91,145,162,163,549,734,736],[81,91,145,162,163,552,734,736],[81,91,145,162,163,553,605,734,736],[81,91,145,162,163,734,736],[81,91,145,162,163,553,628,734,736],[81,91,145,162,163,553,564,734,736],[81,91,145,162,163,553,637,734,736],[91,145,162,163,629,630,637,638,639,640,641,642,643,644,645,646,647,648,649,651,652,653,655,656,734,736],[91,145,162,163,629,630,636,637,638,639,640,641,642,643,644,645,646,647,648,649,651,652,653,654,655,734,736],[81,91,145,162,163,553,564,598,628,734,736],[81,91,145,162,163,627,734,736],[81,91,145,162,163,230,550,551,602,631,632,633,635,636,734,736],[81,91,145,162,163,631,637,734,736],[91,145,162,163,631,734,736],[81,91,145,162,163,553,564,587,596,598,602,603,627,637,657,734,736],[91,145,162,163,230,637,650,734,736],[81,91,145,162,163,631,734,736],[81,91,145,162,163,553,636,734,736],[81,91,145,162,163,637,734,736],[91,145,162,163,679,734,736],[91,145,162,163,696,697,698,734,736],[91,145,162,163,696,697,734,736],[81,91,145,162,163,550,552,553,562,734,736],[81,91,145,162,163,553,696,734,736],[81,91,145,162,163,551,734,736],[91,145,162,163,553,681,734,736],[91,145,162,163,597,599,600,601,734,736],[81,91,145,162,163,586,734,736],[81,91,145,162,163,551,564,596,598,600,734,736],[91,145,162,163,553,564,599,603,627,734,736],[81,91,145,162,163,582,627,734,736],[91,145,162,163,592,734,736],[81,91,145,162,163,230,592,734,736],[91,145,162,163,588,734,736],[91,145,162,163,589,734,736],[91,145,162,163,589,590,592,593,594,595,734,736],[91,145,162,163,591,592,734,736],[91,145,162,163,582,734,736],[91,145,162,163,583,584,734,736],[81,91,145,162,163,585,734,736],[91,142,143,145,162,163,734,736],[91,144,145,162,163,734,736],[145,162,163,734,736],[91,145,150,162,163,180,734,736],[91,145,146,151,156,162,163,165,177,188,734,736],[91,145,146,147,156,162,163,165,734,736],[91,145,148,162,163,189,734,736],[91,145,149,150,157,162,163,166,734,736],[91,145,150,162,163,177,185,734,736],[91,145,151,153,156,162,163,165,734,736],[91,144,145,152,162,163,734,736],[91,145,153,154,162,163,734,736],[91,145,155,156,162,163,734,736],[91,144,145,156,162,163,734,736],[91,145,156,157,158,162,163,177,188,734,736],[91,145,156,157,158,162,163,172,177,180,734,736],[91,137,145,153,156,159,162,163,165,177,188,734,736],[91,145,156,157,159,160,162,163,165,177,185,188,734,736],[91,145,159,161,162,163,177,185,188,734,736],[91,145,156,162,163,734,736],[91,145,162,163,164,188,734,736],[91,145,153,156,162,163,165,177,734,736],[91,145,162,163,166,734,736],[91,145,162,163,167,734,736],[91,144,145,162,163,168,734,736],[91,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,734,736],[91,145,162,163,170,734,736],[91,145,162,163,171,734,736],[91,145,156,162,163,172,173,734,736],[91,145,162,163,172,174,189,191,734,736],[91,145,157,162,163,734,736],[91,145,156,162,163,177,178,180,734,736],[91,145,162,163,179,180,734,736],[91,145,162,163,177,178,734,736],[91,145,162,163,180,734,736],[91,145,162,163,181,734,736],[91,142,145,162,163,177,182,734,736],[91,145,156,162,163,183,184,734,736],[91,145,162,163,183,184,734,736],[91,145,150,162,163,165,177,185,734,736],[91,145,162,163,186,734,736],[88,89,90,91,92,93,94,95,96,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,734,736],[91,145,162,163,165,187,734,736],[91,145,159,162,163,171,188,734,736],[91,145,150,162,163,189,734,736],[91,145,162,163,177,190,734,736],[91,145,162,163,164,191,734,736],[91,145,162,163,192,734,736],[91,145,150,162,163,734,736],[91,137,145,162,163,734,736],[91,145,162,163,193,734,736],[91,137,145,156,158,162,163,168,177,180,188,190,191,193,734,736],[91,145,162,163,177,194,734,736],[81,85,91,145,162,163,196,197,198,200,482,527,734,736],[81,85,91,145,162,163,196,197,198,199,463,482,527,734,736],[81,85,91,145,162,163,196,197,199,200,482,527,734,736],[81,91,145,162,163,200,463,464,734,736],[81,91,145,162,163,200,463,734,736],[81,85,91,145,162,163,197,198,199,200,482,527,734,736],[81,85,91,145,162,163,196,198,199,200,482,527,734,736],[79,80,91,145,162,163,734,736],[91,145,162,163,540,556,734,736],[91,145,162,163,540,734,736],[91,145,162,163,485,734,736],[91,145,162,163,487,488,489,490,734,736],[91,145,162,163,433,496,497,734,736],[91,145,162,163,205,206,208,220,244,359,370,478,734,736],[91,145,162,163,208,239,240,241,243,478,734,736],[91,145,162,163,208,376,378,380,381,383,478,480,734,736],[91,145,162,163,208,242,279,478,734,736],[91,145,162,163,206,208,219,220,226,232,237,358,359,360,369,478,480,734,736],[91,145,162,163,478,734,736],[91,145,162,163,215,221,240,260,355,734,736],[91,145,162,163,208,734,736],[91,145,162,163,201,215,221,734,736],[91,145,162,163,387,734,736],[91,145,162,163,384,385,387,734,736],[91,145,162,163,384,386,478,734,736],[91,145,159,162,163,260,457,475,734,736],[91,145,159,162,163,331,334,350,355,475,734,736],[91,145,159,162,163,303,475,734,736],[91,145,162,163,363,734,736],[91,145,162,163,362,363,364,734,736],[91,145,162,163,362,734,736],[87,91,145,159,162,163,201,208,220,226,232,238,240,244,245,258,259,326,356,357,370,478,482,734,736],[91,145,162,163,205,208,242,279,376,377,382,478,530,734,736],[91,145,162,163,242,530,734,736],[91,145,162,163,205,259,428,478,530,734,736],[91,145,162,163,530,734,736],[91,145,162,163,208,242,243,530,734,736],[91,145,162,163,379,530,734,736],[91,145,162,163,245,358,361,368,734,736],[81,91,145,162,163,433,734,736],[91,145,162,163,171,215,230,734,736],[91,145,162,163,215,230,734,736],[81,91,145,162,163,300,734,736],[81,91,145,162,163,230,734,736],[81,91,145,162,163,221,230,433,734,736],[91,145,162,163,215,286,300,301,512,519,734,736],[91,145,162,163,285,513,514,515,516,518,734,736],[91,145,162,163,336,734,736],[91,145,162,163,336,337,734,736],[91,145,162,163,219,221,288,289,734,736],[91,145,162,163,221,295,296,734,736],[91,145,162,163,221,290,298,734,736],[91,145,162,163,295,734,736],[91,145,162,163,213,221,288,289,290,291,292,293,294,295,298,734,736],[91,145,162,163,221,288,295,296,297,299,734,736],[91,145,162,163,221,289,291,292,734,736],[91,145,162,163,289,291,294,296,734,736],[91,145,162,163,517,734,736],[91,145,162,163,221,734,736],[81,91,145,162,163,209,506,734,736],[81,91,145,162,163,188,734,736],[81,91,145,162,163,242,277,734,736],[81,91,145,162,163,242,370,734,736],[91,145,162,163,275,280,734,736],[81,91,145,162,163,276,484,734,736],[91,145,162,163,543,734,736],[81,85,91,145,159,162,163,196,197,198,199,200,482,526,734,736],[91,145,159,162,163,221,734,736],[91,145,159,162,163,220,225,306,323,365,366,370,425,427,478,479,734,736],[91,145,162,163,258,367,734,736],[91,145,162,163,482,734,736],[91,145,162,163,207,734,736],[81,91,145,162,163,212,215,430,446,448,734,736],[91,145,162,163,171,215,430,445,446,447,529,734,736],[91,145,162,163,439,440,441,442,443,444,734,736],[91,145,162,163,441,734,736],[91,145,162,163,445,734,736],[91,145,162,163,230,394,395,397,734,736],[81,91,145,162,163,221,388,389,390,391,396,734,736],[91,145,162,163,394,396,734,736],[91,145,162,163,392,734,736],[91,145,162,163,393,734,736],[81,91,145,162,163,230,276,484,734,736],[81,91,145,162,163,230,483,484,734,736],[81,91,145,162,163,230,484,734,736],[91,145,162,163,323,324,734,736],[91,145,162,163,324,734,736],[91,145,159,162,163,479,484,734,736],[91,145,162,163,353,734,736],[91,144,145,162,163,352,734,736],[91,145,162,163,215,221,227,229,331,344,348,350,427,430,467,468,475,479,734,736],[91,145,162,163,221,270,292,734,736],[91,145,162,163,331,342,345,350,734,736],[81,91,145,162,163,212,215,331,334,350,353,387,434,435,436,437,438,449,450,451,452,453,454,455,456,530,734,736],[91,145,162,163,212,215,240,331,338,339,340,343,344,734,736],[91,145,162,163,177,221,240,342,349,430,431,475,734,736],[91,145,162,163,346,734,736],[91,145,159,162,163,171,209,221,225,235,267,268,271,323,326,391,425,426,467,478,479,480,482,530,734,736],[91,145,162,163,212,213,215,734,736],[91,145,162,163,331,734,736],[91,144,145,162,163,240,267,268,325,326,327,328,329,330,479,734,736],[91,145,162,163,350,734,736],[91,144,145,162,163,214,215,225,229,265,331,338,339,340,341,342,345,346,347,348,349,468,734,736],[91,145,159,162,163,265,266,338,479,480,734,736],[91,145,162,163,240,268,323,326,331,427,479,734,736],[91,145,159,162,163,478,480,734,736],[91,145,159,162,163,177,475,479,480,734,736],[91,145,159,162,163,171,201,215,220,227,229,232,235,242,262,267,268,269,270,271,306,307,309,312,314,317,318,319,320,322,370,425,427,475,478,479,480,734,736],[91,145,159,162,163,177,734,736],[91,145,162,163,208,209,210,238,475,476,477,482,484,530,734,736],[91,145,162,163,205,206,478,734,736],[91,145,162,163,399,734,736],[91,145,159,162,163,177,188,217,383,387,388,389,390,391,397,398,530,734,736],[91,145,162,163,171,188,201,215,217,229,232,268,307,312,322,323,376,403,404,405,411,414,415,425,427,475,478,734,736],[91,145,162,163,232,238,245,258,268,326,478,734,736],[91,145,159,162,163,188,209,220,229,268,409,475,478,734,736],[91,145,162,163,429,734,736],[91,145,159,162,163,399,412,413,422,734,736],[91,145,162,163,475,478,734,736],[91,145,162,163,328,468,734,736],[91,145,162,163,229,267,370,484,734,736],[91,145,159,162,163,171,207,312,372,376,405,411,414,417,475,734,736],[91,145,159,162,163,245,258,376,418,734,736],[91,145,162,163,208,269,370,420,478,480,734,736],[91,145,159,162,163,188,391,478,734,736],[91,145,159,162,163,242,269,370,371,372,381,399,419,421,478,734,736],[87,91,145,159,162,163,267,424,482,484,734,736],[91,145,162,163,321,425,734,736],[91,145,159,162,163,171,215,218,220,221,227,229,235,244,245,258,268,271,307,309,319,322,323,370,403,404,405,406,408,410,425,427,475,484,734,736],[91,145,159,162,163,177,245,411,416,422,475,734,736],[91,145,162,163,248,249,250,251,252,253,254,255,256,257,734,736],[91,145,162,163,262,313,734,736],[91,145,162,163,315,734,736],[91,145,162,163,313,734,736],[91,145,162,163,315,316,734,736],[91,145,159,162,163,219,220,221,225,226,479,734,736],[91,145,159,162,163,171,207,209,227,231,267,270,271,305,425,475,480,482,484,734,736],[91,145,159,162,163,171,188,211,218,219,229,231,268,423,468,474,479,734,736],[91,145,162,163,338,734,736],[91,145,162,163,339,734,736],[91,145,162,163,221,232,467,734,736],[91,145,162,163,340,734,736],[91,145,162,163,214,734,736],[91,145,162,163,216,228,734,736],[91,145,159,162,163,216,220,227,734,736],[91,145,162,163,223,228,734,736],[91,145,162,163,224,734,736],[91,145,162,163,216,217,734,736],[91,145,162,163,216,272,734,736],[91,145,162,163,216,734,736],[91,145,162,163,218,262,311,734,736],[91,145,162,163,310,734,736],[91,145,162,163,215,217,218,734,736],[91,145,162,163,218,308,734,736],[91,145,162,163,215,217,734,736],[91,145,162,163,267,370,734,736],[91,145,162,163,467,734,736],[91,145,159,162,163,188,227,229,233,267,370,424,427,430,431,432,458,459,462,466,468,475,479,734,736],[91,145,162,163,281,284,286,287,300,301,734,736],[81,91,145,162,163,198,200,230,460,461,734,736],[81,91,145,162,163,198,200,230,460,461,465,734,736],[91,145,162,163,354,734,736],[91,145,162,163,240,261,266,267,331,332,333,334,335,337,350,351,353,356,424,427,478,480,734,736],[91,145,162,163,300,734,736],[91,145,159,162,163,305,475,734,736],[91,145,162,163,305,734,736],[91,145,159,162,163,227,273,302,304,306,424,475,482,484,734,736],[91,145,162,163,281,282,283,284,286,287,300,301,483,734,736],[87,91,145,159,162,163,171,188,216,217,229,235,267,268,271,370,422,423,425,475,478,479,482,734,736],[91,145,162,163,212,215,222,734,736],[91,145,162,163,266,268,400,403,734,736],[91,145,162,163,266,401,469,470,471,472,473,734,736],[91,145,159,162,163,262,478,734,736],[91,145,159,162,163,734,736],[91,145,162,163,265,350,734,736],[91,145,162,163,264,734,736],[91,145,162,163,266,319,734,736],[91,145,162,163,263,265,478,734,736],[91,145,159,162,163,211,266,400,401,402,475,478,479,734,736],[81,91,145,162,163,215,221,299,734,736],[81,91,145,162,163,213,734,736],[91,145,162,163,203,204,734,736],[81,91,145,162,163,209,734,736],[81,91,145,162,163,215,285,734,736],[81,87,91,145,162,163,267,271,482,484,734,736],[91,145,162,163,209,506,507,734,736],[81,91,145,162,163,280,734,736],[81,91,145,162,163,171,188,207,274,276,278,279,484,734,736],[91,145,162,163,215,242,479,734,736],[91,145,162,163,215,407,734,736],[81,91,145,157,159,162,163,171,205,207,280,378,482,483,734,736],[81,91,145,162,163,196,197,198,199,200,482,527,734,736],[81,82,83,84,85,91,145,162,163,734,736],[91,145,162,163,373,374,375,734,736],[91,145,162,163,373,734,736],[81,85,91,145,159,161,162,163,171,195,196,197,198,199,200,201,207,235,240,417,445,480,481,484,527,734,736],[91,145,162,163,492,734,736],[91,145,162,163,494,734,736],[91,145,162,163,498,734,736],[91,145,162,163,544,734,736],[91,145,162,163,500,734,736],[91,145,162,163,502,503,504,734,736],[91,145,162,163,508,734,736],[86,91,145,162,163,486,491,493,495,499,501,505,509,511,521,522,524,528,529,530,531,734,736],[91,145,162,163,510,734,736],[91,145,162,163,520,734,736],[91,145,162,163,276,734,736],[91,145,162,163,523,734,736],[91,144,145,162,163,266,400,401,403,469,470,472,473,525,527,734,736],[91,145,162,163,195,734,736],[91,145,162,163,177,195,734,736],[91,103,106,109,110,145,162,163,188,734,736],[91,106,145,162,163,177,188,734,736],[91,106,110,145,162,163,188,734,736],[91,145,162,163,177,734,736],[91,100,145,162,163,734,736],[91,104,145,162,163,734,736],[91,102,103,106,145,162,163,188,734,736],[91,145,162,163,165,185,734,736],[91,100,145,162,163,195,734,736],[91,102,106,145,162,163,165,188,734,736],[91,97,98,99,101,105,145,156,162,163,177,188,734,736],[91,106,114,122,145,162,163,734,736],[91,98,104,145,162,163,734,736],[91,106,131,132,145,162,163,734,736],[91,98,101,106,145,162,163,180,188,195,734,736],[91,106,145,162,163,734,736],[91,102,106,145,162,163,188,734,736],[91,97,145,162,163,734,736],[91,100,101,102,104,105,106,107,108,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,132,133,134,135,136,145,162,163,734,736],[91,106,124,127,145,153,162,163,734,736],[91,106,114,115,116,145,162,163,734,736],[91,104,106,115,117,145,162,163,734,736],[91,105,145,162,163,734,736],[91,98,100,106,145,162,163,734,736],[91,106,110,115,117,145,162,163,734,736],[91,110,145,162,163,734,736],[91,104,106,109,145,162,163,188,734,736],[91,98,102,106,114,145,162,163,734,736],[91,106,124,145,162,163,734,736],[91,117,145,162,163,734,736],[91,100,106,131,145,162,163,180,193,195,734,736]],"fileInfos":[{"version":"e41c290ef7dd7dab3493e6cbe5909e0148edf4a8dad0271be08edec368a0f7b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"8fd575e12870e9944c7e1d62e1f5a73fcf23dd8d3a321f2a2c74c20d022283fe","impliedFormat":1},{"version":"e12a46ce14b817d4c9e6b2b478956452330bf00c9801b79de46f7a1815b5bd40","impliedFormat":1},{"version":"4fd3f3422b2d2a3dfd5cdd0f387b3a8ec45f006c6ea896a4cb41264c2100bb2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"69e65d976bf166ce4a9e6f6c18f94d2424bf116e90837ace179610dbccad9b42","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"62bb211266ee48b2d0edf0d8d1b191f0c24fc379a82bd4c1692a082c540bc6b1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"936e80ad36a2ee83fc3caf008e7c4c5afe45b3cf3d5c24408f039c1d47bdc1df","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"fef8cfad2e2dc5f5b3d97a6f4f2e92848eb1b88e897bb7318cef0e2820bceaab","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"f1e2a172204962276504466a6393426d2ca9c54894b1ad0a6c9dad867a65f876","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"b5ce7a470bc3628408429040c4e3a53a27755022a32fd05e2cb694e7015386c7","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"bab26767638ab3557de12c900f0b91f710c7dc40ee9793d5a27d32c04f0bf646","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"87dc0f382502f5bbce5129bdc0aea21e19a3abbc19259e0b43ae038a9fc4e326","affectsGlobalScope":true,"impliedFormat":1},{"version":"b1cb28af0c891c8c96b2d6b7be76bd394fddcfdb4709a20ba05a7c1605eea0f9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2fef54945a13095fdb9b84f705f2b5994597640c46afeb2ce78352fab4cb3279","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac77cb3e8c6d3565793eb90a8373ee8033146315a3dbead3bde8db5eaf5e5ec6","affectsGlobalScope":true,"impliedFormat":1},{"version":"56e4ed5aab5f5920980066a9409bfaf53e6d21d3f8d020c17e4de584d29600ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ece9f17b3866cc077099c73f4983bddbcb1dc7ddb943227f1ec070f529dedd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a6282c8827e4b9a95f4bf4f5c205673ada31b982f50572d27103df8ceb8013c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1c9319a09485199c1f7b0498f2988d6d2249793ef67edda49d1e584746be9032","affectsGlobalScope":true,"impliedFormat":1},{"version":"e3a2a0cee0f03ffdde24d89660eba2685bfbdeae955a6c67e8c4c9fd28928eeb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811c71eee4aa0ac5f7adf713323a5c41b0cf6c4e17367a34fbce379e12bbf0a4","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"d4b1d2c51d058fc21ec2629fff7a76249dec2e36e12960ea056e3ef89174080f","affectsGlobalScope":true,"impliedFormat":1},{"version":"61d6a2092f48af66dbfb220e31eea8b10bc02b6932d6e529005fd2d7b3281290","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"7e29f41b158de217f94cb9676bf9cbd0cd9b5a46e1985141ed36e075c52bf6ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"dc0a7f107690ee5cd8afc8dbf05c4df78085471ce16bdd9881642ec738bc81fe","impliedFormat":1},{"version":"acd8fd5090ac73902278889c38336ff3f48af6ba03aa665eb34a75e7ba1dccc4","impliedFormat":1},{"version":"d6258883868fb2680d2ca96bc8b1352cab69874581493e6d52680c5ffecdb6cc","impliedFormat":1},{"version":"1b61d259de5350f8b1e5db06290d31eaebebc6baafd5f79d314b5af9256d7153","impliedFormat":1},{"version":"f258e3960f324a956fc76a3d3d9e964fff2244ff5859dcc6ce5951e5413ca826","impliedFormat":1},{"version":"643f7232d07bf75e15bd8f658f664d6183a0efaca5eb84b48201c7671a266979","impliedFormat":1},{"version":"21da358700a3893281ce0c517a7a30cbd46be020d9f0c3f2834d0a8ad1f5fc75","impliedFormat":1},{"version":"cebfeedf63623d54f7423d58cae1ec204c0d8d97a66d5fb17579e26a902085e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"378281aa35786c27d5811af7e6bcaa492eebd0c7013d48137c35bbc69a2b9751","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"1b2dd1cbeb0cc6ae20795958ba5950395ebb2849b7c8326853dd15530c77ab0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"387a023d363f755eb63450a66c28b14cdd7bc30a104565e2dbf0a8988bb4a56c","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","impliedFormat":1},{"version":"487b694c3de27ddf4ad107d4007ad304d29effccf9800c8ae23c2093638d906a","impliedFormat":1},{"version":"3a80bc85f38526ca3b08007ee80712e7bb0601df178b23fbf0bf87036fce40ce","impliedFormat":1},{"version":"ccf4552357ce3c159ef75f0f0114e80401702228f1898bdc9402214c9499e8c0","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","impliedFormat":1},{"version":"2931540c47ee0ff8a62860e61782eb17b155615db61e36986e54645ec67f67c2","impliedFormat":1},{"version":"ccab02f3920fc75c01174c47fcf67882a11daf16baf9e81701d0a94636e94556","impliedFormat":1},{"version":"f6faf5f74e4c4cc309a6c6a6c4da02dbb840be5d3e92905a23dcd7b2b0bd1986","impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","impliedFormat":1},{"version":"33e981bf6376e939f99bd7f89abec757c64897d33c005036b9a10d9587d80187","impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","impliedFormat":1},{"version":"b41767d372275c154c7ea6c9d5449d9a741b8ce080f640155cc88ba1763e35b3","impliedFormat":1},{"version":"3bacf516d686d08682751a3bd2519ea3b8041a164bfb4f1d35728993e70a2426","impliedFormat":1},{"version":"7fb266686238369442bd1719bc0d7edd0199da4fb8540354e1ff7f16669b4323","impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","impliedFormat":1},{"version":"5b03a034c72146b61573aab280f295b015b9168470f2df05f6080a2122f9b4df","impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","impliedFormat":1},{"version":"54c3e2371e3d016469ad959697fd257e5621e16296fa67082c2575d0bf8eced0","impliedFormat":1},{"version":"beb8233b2c220cfa0feea31fbe9218d89fa02faa81ef744be8dce5acb89bb1fd","impliedFormat":1},{"version":"c183b931b68ad184bc8e8372bf663f3d33304772fb482f29fb91b3c391031f3e","impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","impliedFormat":1},{"version":"48cc3ec153b50985fb95153258a710782b25975b10dd4ac8a4f3920632d10790","impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","impliedFormat":1},{"version":"e1528ca65ac90f6fa0e4a247eb656b4263c470bb22d9033e466463e13395e599","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"866078923a56d026e39243b4392e282c1c63159723996fa89243140e1388a98d","impliedFormat":1},{"version":"830171b27c5fdf9bcbe4cf7d428fcf3ae2c67780fb7fbdccdf70d1623d938bc4","affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d97fb21da858fb18b8ae72c314e9743fd52f73ebe2764e12af1db32fc03f853f","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ea15fd99b2e34cb25fe8346c955000bb70c8b423ae4377a972ef46bfb37f595","impliedFormat":1},{"version":"7cf69dd5502c41644c9e5106210b5da7144800670cbe861f66726fa209e231c4","impliedFormat":1},{"version":"72c1f5e0a28e473026074817561d1bc9647909cf253c8d56c41d1df8d95b85f7","impliedFormat":1},{"version":"f9b4137a0d285bd77dba2e6e895530112264310ae47e07bf311feae428fb8b61","affectsGlobalScope":true,"impliedFormat":1},{"version":"8b21e13ed07d0df176ae31d6b7f01f7b17d66dbeb489c0d31d00de2ca14883da","impliedFormat":1},{"version":"51aecd2df90a3cffea1eb4696b33b2d78594ea2aa2138e6b9471ec4841c6c2ee","impliedFormat":1},{"version":"9d8f9e63e29a3396285620908e7f14d874d066caea747dc4b2c378f0599166b4","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"f929f0b6b3421a2d34344b0f421f45aeb2c84ad365ebf29d04312023b3accc58","impliedFormat":1},{"version":"db9ada976f9e52e13f7ae8b9a320f4b67b87685938c5879187d8864b2fbe97f3","impliedFormat":1},{"version":"9f39e70a354d0fba29ac3cdf6eca00b7f9e96f64b2b2780c432e8ea27f133743","impliedFormat":1},{"version":"0dace96cc0f7bc6d0ee2044921bdf19fe42d16284dbcc8ae200800d1c9579335","impliedFormat":1},{"version":"a2e2bbde231b65c53c764c12313897ffdfb6c49183dd31823ee2405f2f7b5378","impliedFormat":1},{"version":"ad1cc0ed328f3f708771272021be61ab146b32ecf2b78f3224959ff1e2cd2a5c","impliedFormat":1},{"version":"c64e1888baaa3253ca4405b455e4bf44f76357868a1bd0a52998ade9a092ad78","affectsGlobalScope":true,"impliedFormat":1},{"version":"d8cf132379078d0974a59df26069689a2d33c7dc826b5be56231841cb2f32e58","impliedFormat":1},{"version":"fbf413fc617837453c878a9174a1f1b383616857a3f8366bc41cf30df4aea7d5","impliedFormat":1},{"version":"148c73ec11318850f571172ceae3e55ce479d850fe18ec8eae0abd99d9f6c319","impliedFormat":1},{"version":"230bdc111d7578276e4a3bb9d075d85c78c6b68f428c3a9935e2eaa10f4ae1f5","impliedFormat":1},{"version":"e8aabbee5e7b9101b03bb4222607d57f38859b8115a8050a4eb91b4ee43a3a73","impliedFormat":1},{"version":"bbf42f98a5819f4f06e18c8b669a994afe9a17fe520ae3454a195e6eabf7700d","impliedFormat":1},{"version":"c0bb1b65757c72bbf8ddf7eaa532223bacf58041ff16c883e76f45506596e925","impliedFormat":1},{"version":"c8b85f7aed29f8f52b813f800611406b0bfe5cf3224d20a4bdda7c7f73ce368e","affectsGlobalScope":true,"impliedFormat":1},{"version":"145dcf25fd4967c610c53d93d7bc4dce8fbb1b6dd7935362472d4ae49363c7ba","impliedFormat":1},{"version":"ff65b8a8bd380c6d129becc35de02f7c29ad7ce03300331ca91311fb4044d1a9","impliedFormat":1},{"version":"04bf1aa481d1adfb16d93d76e44ce71c51c8ef68039d849926551199489637f6","impliedFormat":1},{"version":"9043daec15206650fa119bad6b8d70136021ea7d52673a71f79a87a42ee38d44","affectsGlobalScope":true,"impliedFormat":1},{"version":"0b055dae40c0e27154f109c4ff771ae748db161c503a1687e3d4b9c91ba20de3","affectsGlobalScope":true,"impliedFormat":1},{"version":"a58a15da4c5ba3df60c910a043281256fa52d36a0fcdef9b9100c646282e88dd","impliedFormat":1},{"version":"b36beffbf8acdc3ebc58c8bb4b75574b31a2169869c70fc03f82895b93950a12","impliedFormat":1},{"version":"de263f0089aefbfd73c89562fb7254a7468b1f33b61839aafc3f035d60766cb4","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"8c81fd4a110490c43d7c578e8c6f69b3af01717189196899a6a44f93daa57a3a","impliedFormat":1},{"version":"5fb39858b2459864b139950a09adae4f38dad87c25bf572ce414f10e4bd7baab","impliedFormat":1},{"version":"65faec1b4bd63564aeec33eab9cacfaefd84ce2400f03903a71a1841fbce195f","impliedFormat":1},{"version":"b33b74b97952d9bf4fbd2951dcfbb5136656ddb310ce1c84518aaa77dbca9992","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"6b306cd4282bbb54d4a6bb23cfb7a271160983dfc38c67b5a132504cfcc34896","affectsGlobalScope":true,"impliedFormat":1},{"version":"c119835edf36415081dfd9ed15fc0cd37aaa28d232be029ad073f15f3d88c323","impliedFormat":1},{"version":"450172a56b944c2d83f45cc11c9a388ea967cd301a21202aa0a23c34c7506a18","impliedFormat":1},{"version":"9705cd157ffbb91c5cab48bdd2de5a437a372e63f870f8a8472e72ff634d47c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"ae86f30d5d10e4f75ce8dcb6e1bd3a12ecec3d071a21e8f462c5c85c678efb41","impliedFormat":1},{"version":"72f8936aebf0c4a1adab767b97d34ba7d3a308afcf76de4417b9c16fb92ed548","impliedFormat":1},{"version":"e03460fe72b259f6d25ad029f085e4bedc3f90477da4401d8fbc1efa9793230e","impliedFormat":1},{"version":"4286a3a6619514fca656089aee160bb6f2e77f4dd53dc5a96b26a0b4fc778055","impliedFormat":1},{"version":"69e0a41d620fb678a899c65e073413b452f4db321b858fe422ad93fd686cd49a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3585d6891e9ea18e07d0755a6d90d71331558ba5dc5561933553209f886db106","affectsGlobalScope":true,"impliedFormat":1},{"version":"86be71cbb0593468644932a6eb96d527cfa600cecfc0b698af5f52e51804451d","impliedFormat":1},{"version":"84dd6b0fd2505135692935599d6606f50a421389e8d4535194bcded307ee5cf2","impliedFormat":1},{"version":"0d5b085f36e6dc55bc6332ecb9c733be3a534958c238fb8d8d18d4a2b6f2a15a","impliedFormat":1},{"version":"db19ea066fdc5f97df3f769e582ae3000380ab7942e266654bdb1a4650d19eaf","affectsGlobalScope":true,"impliedFormat":1},{"version":"2a034894bf28c220a331c7a0229d33564803abe2ac1b9a5feee91b6b9b6e88ea","impliedFormat":1},{"version":"63228283eda38338f98e629d8033bffe727d2247ba2f3a1cbd1273a6827a1ec4","impliedFormat":1},{"version":"2beff543f6e9a9701df88daeee3cdd70a34b4a1c11cb4c734472195a5cb2af54","impliedFormat":1},{"version":"2e07abf27aa06353d46f4448c0bbac73431f6065eef7113128a5cd804d0c384d","impliedFormat":1},{"version":"be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","impliedFormat":1},{"version":"42bc0e1a903408137c3df2b06dfd7e402cdab5bbfa5fcfb871b22ebfdb30bd0b","impliedFormat":1},{"version":"9894dafe342b976d251aac58e616ac6df8db91fb9d98934ff9dd103e9e82578f","impliedFormat":1},{"version":"413df52d4ea14472c2fa5bee62f7a40abd1eb49be0b9722ee01ee4e52e63beb2","impliedFormat":1},{"version":"db6d2d9daad8a6d83f281af12ce4355a20b9a3e71b82b9f57cddcca0a8964a96","impliedFormat":1},{"version":"446a50749b24d14deac6f8843e057a6355dd6437d1fac4f9e5ce4a5071f34bff","impliedFormat":1},{"version":"182e9fcbe08ac7c012e0a6e2b5798b4352470be29a64fdc114d23c2bab7d5106","impliedFormat":1},{"version":"2f4e6b4d39426a1b85ecf4bdeb9dddbf4d9b3397d95d8555d46f925c9519ec7d","impliedFormat":1},{"version":"78a2869ad0cbf3f9045dda08c0d4562b7e1b2bfe07b19e0db072f5c3c56e9584","impliedFormat":1},{"version":"89d5d28d4f57e000b836ac273079be1b75710e28ce14750d081fb420d37e2ca5","impliedFormat":1},{"version":"fd4e24ccff3966390600d7f5d6aa1fed5a512e92ada735ea5fbc933d313ad3d3","impliedFormat":1},{"version":"b7cddfe1aa6b86b5fad3c9ccb30d05b3ccb165aebbf112f48d2d8a5f69dd98b1","impliedFormat":1},{"version":"a86f82d646a739041d6702101afa82dcb935c416dd93cbca7fd754fd0282ce1f","impliedFormat":1},{"version":"ad0d1d75d129b1c80f911be438d6b61bfa8703930a8ff2be2f0e1f8a91841c64","impliedFormat":1},{"version":"bd2c7ada3dee03653d3f601011d30072194bc3970cd93208f9588fbdc0c69347","impliedFormat":1},{"version":"e480da45d32313e7174b265674da504f075f59ef326852f0c5a5d863b438ae85","impliedFormat":1},{"version":"ad54850f61fcf5d014e11be80d2f46fea9265cfa7e77456da876f7833ef81769","impliedFormat":1},{"version":"6f7c9e8bd2b5b6a080b07080065f94900bd3c7e5ebbd3047bc33fcce2fab1dd8","impliedFormat":1},{"version":"3e7efde639c6a6c3edb9847b3f61e308bf7a69685b92f665048c45132f51c218","impliedFormat":1},{"version":"df45ca1176e6ac211eae7ddf51336dc075c5314bc5c253651bae639defd5eec5","impliedFormat":1},{"version":"8a0e762ceb20c7e72504feef83d709468a70af4abccb304f32d6b9bac1129b2c","impliedFormat":1},{"version":"da5950ee2a90721df6f3fba45f5d05308f7e4c35835392215dd2cd404505e2de","impliedFormat":1},{"version":"ce75b1aebb33d510ff28af960a9221410a3eaf7f18fc5f21f9404075fba77256","impliedFormat":1},{"version":"f42d5fed19610d485c646a0c430e768115567d078c7fc855c57b0c578b3d6cd3","impliedFormat":1},{"version":"ee8df1cb8d0faaca4013a1b442e99130769ce06f438d18d510fed95890067563","impliedFormat":1},{"version":"d5630f2ad9b4541e5ce891648121022f9412ecdca1820baa1f0104f70fd7eff7","impliedFormat":1},{"version":"4d15375ab13497104bc8fe56fdef2b5fd6853f29255737d23a33fa306ff7fd69","impliedFormat":1},{"version":"2cd3fc1d0d6a1e85baffd2d4f50f5efb192b5446eef567e97c94765402f0aad4","impliedFormat":1},{"version":"e4cbf2f1e89ecccaddd2c045e600ae41b732295953fb06247c7dcbc2d281ed30","impliedFormat":1},{"version":"6dcedaef57dff0d79a05ab0ab602cde74db803d1e765468bf91263786a383e1b","impliedFormat":1},{"version":"8c1697d90c394a6fd955b98eae01238eff628e129b987a68aea10f898a48e7da","impliedFormat":1},{"version":"7580e62139cb2b44a0270c8d01abcbfcba2819a02514a527342447fa69b34ef1","impliedFormat":1},{"version":"42c169fb8c2d42f4f668c624a9a11e719d5d07dacbebb63cbcf7ef365b0a75b3","impliedFormat":1},{"version":"f374cb24e93e7798c4d9e83ff872fa52d2cdb36306392b840a6ddf46cb925cb6","impliedFormat":1},{"version":"d10d63718e1646c2279e3b33831f82c60e31f622b2b7020f1196409ca4c09242","impliedFormat":1},{"version":"106c6025f1d99fd468fd8bf6e5bda724e11e5905a4076c5d29790b6c3745e50c","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"148679c6d0f449210a96e7d2e562d589e56fcde87f843a92808b3ff103f1a774","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"02436d7e9ead85e09a2f8e27d5f47d9464bced31738dec138ca735390815c9f0","impliedFormat":1},{"version":"f8d5ff8eafd37499f2b6a98659dd9b45a321de186b8db6b6142faed0fea3de77","impliedFormat":1},{"version":"c86fe861cf1b4c46a0fb7d74dffe596cf679a2e5e8b1456881313170f092e3fa","impliedFormat":1},{"version":"a22dd55aa4d39906252000ab8e8a1b83b195eef7f4274eb51e457c1f11cf6580","impliedFormat":1},{"version":"540cc83ab772a2c6bc509fe1354f314825b5dba3669efdfbe4693ecd3048e34f","impliedFormat":1},{"version":"121b0696021ab885c570bbeb331be8ad82c6efe2f3b93a6e63874901bebc13e3","impliedFormat":1},{"version":"612d9da66bb046a9c1e2e8d026245ded881fc4b9f98cbfae714415d57ee0ae0b","impliedFormat":1},{"version":"32c2ad9494dad5d11b0564a619fee18f388db6c1e9e2cd3c360b3122549691eb","impliedFormat":1},{"version":"6c301d40aec56a74ec7bd7324e31a728dadf9bfba3e96def02938d3d973534ec","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"aa14cee20aa0db79f8df101fc027d929aec10feb5b8a8da3b9af3895d05b7ba2","impliedFormat":1},{"version":"493c700ac3bd317177b2eb913805c87fe60d4e8af4fb39c41f04ba81fae7e170","impliedFormat":1},{"version":"aeb554d876c6b8c818da2e118d8b11e1e559adbe6bf606cc9a611c1b6c09f670","impliedFormat":1},{"version":"acf5a2ac47b59ca07afa9abbd2b31d001bf7448b041927befae2ea5b1951d9f9","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"d71291eff1e19d8762a908ba947e891af44749f3a2cbc5bd2ec4b72f72ea795f","impliedFormat":1},{"version":"c0480e03db4b816dff2682b347c95f2177699525c54e7e6f6aa8ded890b76be7","impliedFormat":1},{"version":"25a5f6fd3a2243c859eddc99ab5fba11d970af2fe7a5df9c32b7668f76f97b01","impliedFormat":1},{"version":"8d207e1f9d2c30d6f77dfa693f3827c3fbf0d89240297e10bdfe1041d433df68","impliedFormat":1},{"version":"b620391fe8060cf9bedc176a4d01366e6574d7a71e0ac0ab344a4e76576fcbb8","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"2652448ac55a2010a1f71dd141f828b682298d39728f9871e1cdf8696ef443fd","impliedFormat":1},{"version":"d682336018141807fb602709e2d95a192828fcb8d5ba06dda3833a8ea98f69e3","impliedFormat":1},{"version":"6124e973eab8c52cabf3c07575204efc1784aca6b0a30c79eb85fe240a857efa","impliedFormat":1},{"version":"0d891735a21edc75df51f3eb995e18149e119d1ce22fd40db2b260c5960b914e","impliedFormat":1},{"version":"3b414b99a73171e1c4b7b7714e26b87d6c5cb03d200352da5342ab4088a54c85","impliedFormat":1},{"version":"4fbd3116e00ed3a6410499924b6403cc9367fdca303e34838129b328058ede40","impliedFormat":1},{"version":"9c82171d836c47486074e4ca8e059735bf97b205e70b196535b5efd40cbe1bc5","impliedFormat":1},{"version":"8c70ddc0c22d85e56011d49fddfaae3405eb53d47b59327b9dd589e82df672e7","impliedFormat":1},{"version":"2f9c89cbb29d362290531b48880a4024f258c6033aaeb7e59fbc62db26819650","impliedFormat":1},{"version":"a365c4d3bed3be4e4e20793c999c51f5cd7e6792322f14650949d827fbcd170f","impliedFormat":1},{"version":"c5426dbfc1cf90532f66965a7aa8c1136a78d4d0f96d8180ecbfc11d7722f1a5","impliedFormat":1},{"version":"65a15fc47900787c0bd18b603afb98d33ede930bed1798fc984d5ebb78b26cf9","impliedFormat":1},{"version":"9d202701f6e0744adb6314d03d2eb8fc994798fc83d91b691b75b07626a69801","impliedFormat":1},{"version":"de9d2df7663e64e3a91bf495f315a7577e23ba088f2949d5ce9ec96f44fba37d","impliedFormat":1},{"version":"c7af78a2ea7cb1cd009cfb5bdb48cd0b03dad3b54f6da7aab615c2e9e9d570c5","impliedFormat":1},{"version":"1ee45496b5f8bdee6f7abc233355898e5bf9bd51255db65f5ff7ede617ca0027","impliedFormat":1},{"version":"273782b8454e78f6a8b30d2cfbf6860499c930595095fcc1689637115f0eddda","affectsGlobalScope":true,"impliedFormat":1},{"version":"3fbdd025f9d4d820414417eeb4107ffa0078d454a033b506e22d3a23bc3d9c41","affectsGlobalScope":true,"impliedFormat":1},{"version":"dba114fb6a32b355a9cfc26ca2276834d72fe0e94cd2c3494005547025015369","impliedFormat":1},{"version":"a8f8e6ab2fa07b45251f403548b78eaf2022f3c2254df3dc186cb2671fe4996d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa6c12a7c0f6b84d512f200690bfc74819e99efae69e4c95c4cd30f6884c526e","impliedFormat":1},{"version":"f1c32f9ce9c497da4dc215c3bc84b722ea02497d35f9134db3bb40a8d918b92b","impliedFormat":1},{"version":"b73c319af2cc3ef8f6421308a250f328836531ea3761823b4cabbd133047aefa","affectsGlobalScope":true,"impliedFormat":1},{"version":"e433b0337b8106909e7953015e8fa3f2d30797cea27141d1c5b135365bb975a6","impliedFormat":1},{"version":"9f9bb6755a8ce32d656ffa4763a8144aa4f274d6b69b59d7c32811031467216e","impliedFormat":1},{"version":"5c32bdfbd2d65e8fffbb9fbda04d7165e9181b08dad61154961852366deb7540","impliedFormat":1},{"version":"ddff7fc6edbdc5163a09e22bf8df7bef75f75369ebd7ecea95ba55c4386e2441","impliedFormat":1},{"version":"0c05e9842ec4f8b7bfebfd3ca61604bb8c914ba8da9b5337c4f25da427a005f2","impliedFormat":1},{"version":"faed7a5153215dbd6ebe76dfdcc0af0cfe760f7362bed43284be544308b114cf","impliedFormat":1},{"version":"7029e566b8df176f703fb59fd437a38670c7a0e02c58b2d66dfb5b2e2b2defdb","impliedFormat":1},{"version":"7f2aa4d4989a82530aaac3f72b3dceca90e9c25bee0b1a327e8a08a1262435ad","impliedFormat":1},{"version":"d96b39301d0ded3f1a27b47759676a33a02f6f5049bfcbde81e533fd10f50dcb","impliedFormat":1},{"version":"e9f147ecca73d9346a4c073432843c159ccbe50bdcb678a78f6da10eae2cecf4","impliedFormat":1},{"version":"de061f7d72bd65c06fc1419f841dfdcb29a8e22fe6fa527d1e6eb20b897d4de0","impliedFormat":1},{"version":"663beafc2446079574570cba86e9b15f986f908ddb1b01274509970126fee945","impliedFormat":1},{"version":"a3102887d5058bf4cb5b37fa6964c09e9527c42053b3b5c642b89878620748de","impliedFormat":1},{"version":"0aaaa1727edd29673d85c9b26d7ca4d54e5407a48586903c51b48b7f7d196f61","impliedFormat":1},{"version":"d35bca0b261bff02635758c48e8ab99c61c420d0dfabbcf467e847171d876b7d","impliedFormat":1},{"version":"3bc12c40d90c342ff88a3d876996c555ed5cbee5fe8c3308a240b321f401ee46","impliedFormat":1},{"version":"ba130768aae855a5477e9e148e5c879548e6e7ccbcc56fd1934c8a18ea5b7569","impliedFormat":1},{"version":"2e4f37ffe8862b14d8e24ae8763daaa8340c0df0b859d9a9733def0eee7562d9","impliedFormat":1},{"version":"d38530db0601215d6d767f280e3a3c54b2a83b709e8d9001acb6f61c67e965fc","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"b499af2054a037a162b3b72cd886f48bbf32a3502c865c6e29fac7d2ab3ce0b5","impliedFormat":1},{"version":"b83cb14474fa60c5f3ec660146b97d122f0735627f80d82dd03e8caa39b4388c","impliedFormat":1},{"version":"48773ca557b0319c2ee62ae249cf52a81709e8be139920d6479a66274de7c4ed","impliedFormat":1},{"version":"7274fbffbd7c9589d8d0ffba68157237afd5cecff1e99881ea3399127e60572f","impliedFormat":1},{"version":"b73cbf0a72c8800cf8f96a9acfe94f3ad32ca71342a8908b8ae484d61113f647","impliedFormat":1},{"version":"bae6dd176832f6423966647382c0d7ba9e63f8c167522f09a982f086cd4e8b23","impliedFormat":1},{"version":"20865ac316b8893c1a0cc383ccfc1801443fbcc2a7255be166cf90d03fac88c9","impliedFormat":1},{"version":"c9958eb32126a3843deedda8c22fb97024aa5d6dd588b90af2d7f2bfac540f23","impliedFormat":1},{"version":"461d0ad8ae5f2ff981778af912ba71b37a8426a33301daa00f21c6ccb27f8156","impliedFormat":1},{"version":"e927c2c13c4eaf0a7f17e6022eee8519eb29ef42c4c13a31e81a611ab8c95577","impliedFormat":1},{"version":"fcafff163ca5e66d3b87126e756e1b6dfa8c526aa9cd2a2b0a9da837d81bbd72","impliedFormat":1},{"version":"70246ad95ad8a22bdfe806cb5d383a26c0c6e58e7207ab9c431f1cb175aca657","impliedFormat":1},{"version":"f00f3aa5d64ff46e600648b55a79dcd1333458f7a10da2ed594d9f0a44b76d0b","impliedFormat":1},{"version":"772d8d5eb158b6c92412c03228bd9902ccb1457d7a705b8129814a5d1a6308fc","impliedFormat":1},{"version":"802e797bcab5663b2c9f63f51bdf67eff7c41bc64c0fd65e6da3e7941359e2f7","impliedFormat":1},{"version":"b01bd582a6e41457bc56e6f0f9de4cb17f33f5f3843a7cf8210ac9c18472fb0f","impliedFormat":1},{"version":"8b4327413e5af38cd8cb97c59f48c3c866015d5d642f28518e3a891c469f240e","impliedFormat":1},{"version":"4cceef18d7f088e797a463e90b7a9dad10c6bc667724b7686e3e740ae00122be","impliedFormat":1},{"version":"7ee86fbb3754388e004de0ef9e6505485ddfb3be7640783d6d015711c03d302d","impliedFormat":1},{"version":"cc1954b539604b1e562319119ac7e888172208b32ca873f9a357a92c826bd046","impliedFormat":1},{"version":"a67b87d0281c97dfc1197ef28dfe397fc2c865ccd41f7e32b53f647184cc7307","impliedFormat":1},{"version":"771ffb773f1ddd562492a6b9aaca648192ac3f056f0e1d997678ff97dbb6bf9b","impliedFormat":1},{"version":"43e96a3d5d1411ab40ba2f61d6a3192e58177bcf3b133a80ad2a16591611726d","impliedFormat":1},{"version":"232f70c0cf2b432f3a6e56a8dc3417103eb162292a9fd376d51a3a9ea5fbbf6f","impliedFormat":1},{"version":"bb8f2dbc03533abca2066ce4655c119bff353dd4514375beb93c08590c03e023","impliedFormat":1},{"version":"706dd95827e7ebaabda91d5db2b755233e0952d98570e9c032b0f066a15c1177","affectsGlobalScope":true,"impliedFormat":1},{"version":"0b103e9abfe82d14c0ad06a55d9f91d6747154ef7cacc73cf27ecad2bfb3afcf","impliedFormat":1},{"version":"cd9304972e6d616197fb44fce00540a904f38b54306a1951b5dbeaf3c01ab5bd","impliedFormat":1},{"version":"77438e2c397a3db78407621cfc57241a305b310ddea2c185f1d555248297f587","impliedFormat":1},{"version":"120599fd965257b1f4d0ff794bc696162832d9d8467224f4665f713a3119078b","impliedFormat":1},{"version":"43ba4f2fa8c698f5c304d21a3ef596741e8e85a810b7c1f9b692653791d8d97a","impliedFormat":1},{"version":"5433f33b0a20300cca35d2f229a7fc20b0e8477c44be2affeb21cb464af60c76","impliedFormat":1},{"version":"db036c56f79186da50af66511d37d9fe77fa6793381927292d17f81f787bb195","impliedFormat":1},{"version":"a6805fcafed712aea7759f8bc731014f9d22738c1d6ef9d43b8091d1d48346d5","impliedFormat":1},{"version":"c49469a5349b3cc1965710b5b0f98ed6c028686aa8450bcb3796728873eb923e","impliedFormat":1},{"version":"4a889f2c763edb4d55cb624257272ac10d04a1cad2ed2948b10ed4a7fda2a428","impliedFormat":1},{"version":"7bb79aa2fead87d9d56294ef71e056487e848d7b550c9a367523ee5416c44cfa","impliedFormat":1},{"version":"d88ea80a6447d7391f52352ec97e56b52ebec934a4a4af6e2464cfd8b39c3ba8","impliedFormat":1},{"version":"142617b3cdf902b69c6464c9fbd942b60ab3e733ca18c032b19e0f7e2adbefe8","impliedFormat":1},{"version":"0b603555f1881f87256ffd6344d3e3ed6d466c2e701eabf381f28be8c2125892","impliedFormat":1},{"version":"897e4f7662488e3ecc79e743bdd3b78f13bdb69a97851afa5b440c4211e32ea9","impliedFormat":1},{"version":"e2e1c6d3b2d93add5200bd7bc1a8cccb4e446836b2111ece45db8683a2c765de","impliedFormat":1},{"version":"251b03d5cd243854ce870d9a9a39f491faf69898c5d6b5eee28cc7649c57417b","impliedFormat":1},{"version":"27ff4196654e6373c9af16b6165120e2dd2169f9ad6abb5c935af5abd8c7938c","impliedFormat":1},{"version":"2c4de79f406d137390608e8c0a44fba2ff8e00bacfcae7c9d1781fef10e9440d","impliedFormat":1},{"version":"07ba23a10465791be5d22deaf5ef7de7658774ddff53721e5ea17fedea1bc721","impliedFormat":1},{"version":"dca8c645c5afeb03b1ecedbf16323f33e7d0afaa6256c8e047e6e38087a97f53","impliedFormat":1},{"version":"775f181bd4a533d6f8b5e55ec1d9f1624559720ae8a70e9432258da26b38d27c","impliedFormat":1},{"version":"796273b2edc72e78a04e86d7c58ae94d370ab93a0ddf40b1aa85a37a1c29ecd7","impliedFormat":1},{"version":"5df15a69187d737d6d8d066e189ae4f97e41f4d53712a46b2710ff9f8563ec9f","impliedFormat":1},{"version":"7715134a0cf07dd41a9da2895d708625a3a303a0385e355ecaaf0b8bfaef2550","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"622694a8522b46f6310c2a9b5d2530dde1e2854cb5829354e6d1ff8f371cf469","impliedFormat":1},{"version":"cd8ce8d68567f62dd580b3c3c37777ac3f5b81944c7417f5ea83030eab533385","impliedFormat":1},{"version":"e5c939d896565dcac0f6fbdbada11284e7728ef26a069561c09aa5aa4a788393","impliedFormat":1},{"version":"9e2739b32f741859263fdba0244c194ca8e96da49b430377930b8f721d77c000","impliedFormat":1},{"version":"a9e6c0ff3f8186fccd05752cf75fc94e147c02645087ac6de5cc16403323d870","impliedFormat":1},{"version":"49af4b52f0d4d2304c5f2c6fe5fab3e153e0acc38830d0202821b877c097dd02","impliedFormat":1},{"version":"49c346823ba6d4b12278c12c977fb3a31c06b9ca719015978cb145eb86da1c61","impliedFormat":1},{"version":"bfac6e50eaa7e73bb66b7e052c38fdc8ccfc8dbde2777648642af33cf349f7f1","impliedFormat":1},{"version":"92f7c1a4da7fbfd67a2228d1687d5c2e1faa0ba865a94d3550a3941d7527a45d","impliedFormat":1},{"version":"f53b120213a9289d9a26f5af90c4c686dd71d91487a0aa5451a38366c70dc64b","impliedFormat":1},{"version":"e68b8e5a1df7c1be2bc105141456ecba70215806e1c28bfbc5c12bfce4be6e68","impliedFormat":1},{"version":"511c8f02329808d47d00b859c532ae9115590048b17325a946c74dac48428650","impliedFormat":1},{"version":"57d67b72e06059adc5e9454de26bbfe567d412b962a501d263c75c2db430f40e","impliedFormat":1},{"version":"b5f9e66625783eefcbe3d2da074b2e7ba2066d61ce3fc6ef4f22805ad946cab4","impliedFormat":1},{"version":"e37115962d284b9f7a37c2bdd2add50f88365dde41f5e0ff591ffc48a8ec7575","impliedFormat":1},{"version":"6459054aabb306821a043e02b89d54da508e3a6966601a41e71c166e4ea1474f","impliedFormat":1},{"version":"bb37588926aba35c9283fe8d46ebf4e79ffe976343105f5c6d45f282793352b2","impliedFormat":1},{"version":"f89488602bec98a142072fae7ea5ba99431a569ff580c64b7be39896474799d8","impliedFormat":1},{"version":"bbbc47961f39a57df103cf4ca3bb8f8732b4b6678a18225a0aa76d59c466956c","impliedFormat":1},{"version":"2e6114a7dd6feeef85b2c80120fdbfb59a5529c0dcc5bfa8447b6996c97a69f5","impliedFormat":1},{"version":"2ffb043dc5163458e473b7010859f86e01dc4edffcae0a93d885d028b426a546","impliedFormat":1},{"version":"c8f004e6036aa1c764ad4ec543cf89a5c1893a9535c80ef3f2b653e370de45e6","impliedFormat":1},{"version":"dd80b1e600d00f5c6a6ba23f455b84a7db121219e68f89f10552c54ba46e4dc9","impliedFormat":1},{"version":"b064c36f35de7387d71c599bfcf28875849a1dbc733e82bd26cae3d1cd060521","impliedFormat":1},{"version":"05c7280d72f3ed26f346cbe7cbbbb002fb7f15739197cbbee6ab3fd1a6cb9347","impliedFormat":1},{"version":"8de9fe97fa9e00ec00666fa77ab6e91b35d25af8ca75dabcb01e14ad3299b150","impliedFormat":1},{"version":"04b7b2e0832dfd3c31e81df3975e8d8fda28e7ff999b0aa2932608a8f6661d5c","impliedFormat":1},{"version":"ca2d34c6ed5cbd3070b8b6f32f42ae54adcc6499c1e4b99f0a5798b3f27cc653","impliedFormat":1},{"version":"9ec68995e66dd6b9dac834bf5ae85fde802714ea2e82151a5d1d53ef01b463ef","impliedFormat":1},{"version":"5c4d626b4902f2ef8a1cc146d761d276cef988016dc674e3b98fbad70e64bc9f","impliedFormat":1},{"version":"fdfaa0aad899524962e2955287b5b991ffe3be50f64e02eb60c933ca44644a94","impliedFormat":1},{"version":"53c972a0f9bc3a4ec70fff7314123ea8cfcf75b3703046f767d2dc1eea87b2fb","impliedFormat":1},{"version":"f974e4a06953682a2c15d5bd5114c0284d5abf8bc0fe4da25cb9159427b70072","impliedFormat":1},{"version":"50256e9c31318487f3752b7ac12ff365c8949953e04568009c8705db802776fb","impliedFormat":1},{"version":"7d73b24e7bf31dfb8a931ca6c4245f6bb0814dfae17e4b60c9e194a631fe5f7b","impliedFormat":1},{"version":"d130c5f73768de51402351d5dc7d1b36eaec980ca697846e53156e4ea9911476","impliedFormat":1},{"version":"413586add0cfe7369b64979d4ec2ed56c3f771c0667fbde1bf1f10063ede0b08","impliedFormat":1},{"version":"06472528e998d152375ad3bd8ebcb69ff4694fd8d2effaf60a9d9f25a37a097a","impliedFormat":1},{"version":"7303b45138d2511035056a5901a1490ebdcbf055cbb1276f8629c5121cbe733e","impliedFormat":1},{"version":"27f874cd5327507eeff699a74567f60c1215b94509f4308633a7b01922471ed2","impliedFormat":1},{"version":"a401617604fa1f6ce437b81689563dfdc377069e4c58465dbd8d16069aede0a5","impliedFormat":1},{"version":"2c6cf04bc525caf6546e859e8ef10bfb9573837ec0bc5ec7b53a7b1b8ca72781","impliedFormat":1},{"version":"8695dec09ad439b0ceef3776ea68a232e381135b516878f0901ed2ea114fd0fe","impliedFormat":1},{"version":"304b44b1e97dd4c94697c3313df89a578dca4930a104454c99863f1784a54357","impliedFormat":1},{"version":"0a437ae178f999b46b6153d79095b60c42c996bc0458c04955f1c996dc68b971","impliedFormat":1},{"version":"74b2a5e5197bd0f2e0077a1ea7c07455bbea67b87b0869d9786d55104006784f","impliedFormat":1},{"version":"4a7baeb6325920044f66c0f8e5e6f1f52e06e6d87588d837bdf44feb6f35c664","impliedFormat":1},{"version":"87cc05fe13108f02e12da7e3efd8e360fef78d96a0c9e11408ea1b1b9fb3e03d","impliedFormat":1},{"version":"1abbf67c218d23c2ce76887caac2df6c7dab3d97ba2b65348432b876f510002a","impliedFormat":1},{"version":"1a82deef4c1d39f6882f28d275cad4c01f907b9b39be9cbc472fcf2cf051e05b","impliedFormat":1},{"version":"4b20fcf10a5413680e39f5666464859fc56b1003e7dfe2405ced82371ebd49b6","impliedFormat":1},{"version":"c06ef3b2569b1c1ad99fcd7fe5fba8d466e2619da5375dfa940a94e0feea899b","impliedFormat":1},{"version":"f7d628893c9fa52ba3ab01bcb5e79191636c4331ee5667ecc6373cbccff8ae12","impliedFormat":1},{"version":"1d879125d1ec570bf04bc1f362fdbe0cb538315c7ac4bcfcdf0c1e9670846aa6","impliedFormat":1},{"version":"dad97c99382889e9c7d1a9d8275500ff71235130fae9f8916fdbf3641d56e592","impliedFormat":1},{"version":"a6dba407fc287f1e25454e75028c91bbc00675f2d1c4e8b3edcc36c08611a486","impliedFormat":1},{"version":"d663134457d8d669ae0df34eabd57028bddc04fc444c4bc04bc5215afc91e1f4","impliedFormat":1},{"version":"e91f7b1344577a02f051b9b471f33044fef8334a76dc9e1de003d17595a5219b","impliedFormat":1},{"version":"c0723195c85e19656d6b5b9fdb81d3f3403c1ae4679e722c6ea058c516b38d12","impliedFormat":1},{"version":"b55eb9f72166093b5460d34b34f5d8699c968de3bc3fc696e40f2c93f2ebf650","impliedFormat":1},{"version":"71d9eb4c4e99456b78ae182fb20a5dfc20eb1667f091dbb9335b3c017dd1c783","impliedFormat":1},{"version":"cfa846a7b7847a1d973605fbb8c91f47f3a0f0643c18ac05c47077ebc72e71c7","impliedFormat":1},{"version":"1594da19968752a22b2ac48c2d0e60575700e745c577a8a4a676b841238ad5bb","impliedFormat":1},{"version":"e0cee12109e0a10a4c3d6769fcc7644b7c1ea7f52365bea51728f5af29f8a137","impliedFormat":1},{"version":"7d4254b4c6c67a29d5e7f65e67d72540480ac2cfb041ca484847f5ae70480b62","impliedFormat":1},{"version":"3536968defef8a75514f547ead5e2e9c1e984820290ec9b00c5fdfb6ef786535","impliedFormat":1},{"version":"d83773870080c30a230e322ce13a9c6f3398e8dacea4ea8a83e26370f3bac23e","impliedFormat":1},{"version":"dcfeaf98d66314fec29a9076c4290e45d0b196a65827becc19138e9c7b855f37","impliedFormat":1},{"version":"6849fe9210fe4946d5f085bfed36758f33dc6ae15a751338d178dd4daa017c46","impliedFormat":1},{"version":"888cda0fa66d7f74e985a3f7b1af1f64b8ff03eb3d5e80d051c3cbdeb7f32ab7","impliedFormat":1},{"version":"60681e13f3545be5e9477acb752b741eae6eaf4cc01658a25ec05bff8b82a2ef","impliedFormat":1},{"version":"ffae4e1e06aa848a1e4bcef162cd1c48e5909b26223515981310af9c036bdfc7","impliedFormat":1},{"version":"a57b1802794433adec9ff3fed12aa79d671faed86c49b09e02e1ac41b4f1d33a","impliedFormat":1},{"version":"34e16eb7c31768a11a08aebcfb3d70d7b8f0b016197e98d8419e566ceae6d6c8","impliedFormat":1},{"version":"f94ec1f7e4b709d26960306c9082a7a1b728a6e13089346aa48ba57c74cbf47e","impliedFormat":1},{"version":"9a11cb4033405e96c247cd5aa29790212aaffdd127869e8a5219103f0b389fd5","impliedFormat":1},{"version":"01479d9d5a5dda16d529b91811375187f61a06e74be294a35ecce77e0b9e8d6c","impliedFormat":1},{"version":"aff5213585cb72e94054dfe17250ff315f3569b3919d1ef1ad235f37c4ee894e","impliedFormat":1},{"version":"fb2ea35e1be6388d722d7725e2b49c697d34d9c890c3b96758faaeb86d35cef8","impliedFormat":1},{"version":"ce0df82a9ae6f914ba08409d4d883983cc08e6d59eb2df02d8e4d68309e7848b","impliedFormat":1},{"version":"1a4dc28334a926d90ba6a2d811ba0ff6c22775fcc13679521f034c124269fd40","impliedFormat":1},{"version":"f05315ff85714f0b87cc0b54bcd3dde2716e5a6b99aedcc19cad02bf2403e08c","impliedFormat":1},{"version":"5fad3b31fc17a5bc58095118a8b160f5260964787c52e7eb51e3d4fcf5d4a6f0","impliedFormat":1},{"version":"72105519d0390262cf0abe84cf41c926ade0ff475d35eb21307b2f94de985778","impliedFormat":1},{"version":"456006a6975b26c0a1785feddae165f6d307e2d601ffde27e21fc4a790e448a4","impliedFormat":1},{"version":"c857e0aae3f5f444abd791ec81206020fbcc1223e187316677e026d1c1d6fe08","impliedFormat":1},{"version":"ccf6dd45b708fb74ba9ed0f2478d4eb9195c9dfef0ff83a6092fa3cf2ff53b4f","impliedFormat":1},{"version":"1fe0d18b111e1145a7e7601855bccd4ca20f24e3b9a5aba6bb1fa9d1a7059170","impliedFormat":1},{"version":"5632c3c26d420c063eebe64c45b1248b9492a67bf44f1d0c57e9dc8f6cf449bb","impliedFormat":1},{"version":"0df5aa619ab12993a39ea6dae062ee46eadbb4d738916460e636ada52bced75b","impliedFormat":1},{"version":"8fca3039857709484e5893c05c1f9126ab7451fa6c29e19bb8c2411a2e937345","impliedFormat":1},{"version":"35069c2c417bd7443ae7c7cafd1de02f665bf015479fec998985ffbbf500628c","impliedFormat":1},{"version":"10ab7be91f87ebe8916b62cf28af2e45b5601fc7b0e311adf838f912c6b31dd8","impliedFormat":1},{"version":"bc636fbc08e0979ceb7eb0731a33000283d77a33b62e1f71ee65be50394e40ba","impliedFormat":1},{"version":"7e0b7f91c5ab6e33f511efc640d36e6f933510b11be24f98836a20a2dc914c2d","impliedFormat":1},{"version":"045b752f44bf9bbdcaffd882424ab0e15cb8d11fa94e1448942e338c8ef19fba","impliedFormat":1},{"version":"2894c56cad581928bb37607810af011764a2f511f575d28c9f4af0f2ef02d1ab","impliedFormat":1},{"version":"0a72186f94215d020cb386f7dca81d7495ab6c17066eb07d0f44a5bf33c1b21a","impliedFormat":1},{"version":"75bbd3be047d539988a0ff0b56384ef7a6a25f3b676ad96bee547d44c31622a7","impliedFormat":1},{"version":"42960001a776b089ade681ab5cfddc936e0afb0615133ec1841f3dee89d3e1bf","impliedFormat":1},{"version":"0aedb02516baf3e66b2c1db9fef50666d6ed257edac0f866ea32f1aa05aa474f","impliedFormat":1},{"version":"da47712b394d944328245482603bc6f416d3949b67c9392279caab595076b510","affectsGlobalScope":true,"impliedFormat":1},{"version":"37d0071d8f0a06dc55c2c5e0ec3391affd4fd107c53410bf358196ec0bf3923f","impliedFormat":1},{"version":"b213dad76ca37fd552274c9499056e1c0d9c1bd38a55bb7f68b22ba6b84c3ad7","impliedFormat":1},{"version":"56ccb49443bfb72e5952f7012f0de1a8679f9f75fc93a5c1ac0bafb28725fc5f","impliedFormat":1},{"version":"20fa37b636fdcc1746ea0738f733d0aed17890d1cd7cb1b2f37010222c23f13e","impliedFormat":1},{"version":"d90b9f1520366d713a73bd30c5a9eb0040d0fb6076aff370796bc776fd705943","impliedFormat":1},{"version":"bc03c3c352f689e38c0ddd50c39b1e65d59273991bfc8858a9e3c0ebb79c023b","impliedFormat":1},{"version":"19df3488557c2fc9b4d8f0bac0fd20fb59aa19dec67c81f93813951a81a867f8","affectsGlobalScope":true,"impliedFormat":1},{"version":"b25350193e103ae90423c5418ddb0ad1168dc9c393c9295ef34980b990030617","affectsGlobalScope":true,"impliedFormat":1},{"version":"bef86adb77316505c6b471da1d9b8c9e428867c2566270e8894d4d773a1c4dc2","impliedFormat":1},{"version":"5a49adaef698b7ad7e6127949fa1b0bbd3d46b7cbd11c54e392a4dcdd51f5190","impliedFormat":1},{"version":"6ee598cdfdd0fa52039dca135b3dfff7b49035dc13292143e0a93843e3861967","impliedFormat":1},{"version":"27be6622e2922a1b412eb057faa854831b95db9db5035c3f6d4b677b902ab3b7","impliedFormat":1},{"version":"5c634644d45a1b6bc7b05e71e05e52ec04f3d73d9ac85d5927f647a5f965181a","impliedFormat":1},{"version":"2489bf04d77dc025ba67f49f1a56eb24b9db477d5ff88123d887e163ed1776aa","impliedFormat":1},{"version":"63a7595a5015e65262557f883463f934904959da563b4f788306f699411e9bac","impliedFormat":1},{"version":"4ba137d6553965703b6b55fd2000b4e07ba365f8caeb0359162ad7247f9707a6","impliedFormat":1},{"version":"0b77b819b5417775fccb20c678293cf614c054a5b1a65421a5b933a9124ba998","impliedFormat":1},{"version":"eb5acb58487367e502d994b57e2c58255d8241f481ea8efa8e79af23af3f41c2","impliedFormat":1},{"version":"9252d498a77517aab5d8d4b5eb9d71e4b225bbc7123df9713e08181de63180f6","impliedFormat":1},{"version":"b1f1d57fde8247599731b24a733395c880a6561ec0c882efaaf20d7df968c5af","impliedFormat":1},{"version":"6715dc4eb59c8ea9abe2b78c235ed331dc710a06fe56798868dbc4d40cd1b707","impliedFormat":1},{"version":"35e6379c3f7cb27b111ad4c1aa69538fd8e788ab737b8ff7596a1b40e96f4f90","impliedFormat":1},{"version":"1fffe726740f9787f15b532e1dc870af3cd964dbe29e191e76121aa3dd8693f2","impliedFormat":1},{"version":"5a3ea721d03a361ccbdd7390ccd75f6e84cbca3a3f01f4b331ecc9af31890c49","impliedFormat":1},{"version":"e7dfaee4af38d45b1cab8a1ee0b3bc1f85ddcf64545ed391d675d78ae6526274","affectsGlobalScope":true,"impliedFormat":1},{"version":"e8daa443eaf9a27fd382cc1f8ebe30330c0f4d89511cfb469166874806751d35","impliedFormat":1},{"version":"af48e58339188d5737b608d41411a9c054685413d8ae88b8c1d0d9bfabdf6e7e","impliedFormat":1},{"version":"616775f16134fa9d01fc677ad3f76e68c051a056c22ab552c64cc281a9686790","impliedFormat":1},{"version":"65c24a8baa2cca1de069a0ba9fba82a173690f52d7e2d0f1f7542d59d5eb4db0","impliedFormat":1},{"version":"f9fe6af238339a0e5f7563acee3178f51db37f32a2e7c09f85273098cee7ec49","impliedFormat":1},{"version":"1de8c302fd35220d8f29dea378a4ae45199dc8ff83ca9923aca1400f2b28848a","impliedFormat":1},{"version":"77e71242e71ebf8528c5802993697878f0533db8f2299b4d36aa015bae08a79c","impliedFormat":1},{"version":"98a787be42bd92f8c2a37d7df5f13e5992da0d967fab794adbb7ee18370f9849","impliedFormat":1},{"version":"332248ee37cca52903572e66c11bef755ccc6e235835e63d3c3e60ddda3e9b93","impliedFormat":1},{"version":"94e8cc88ae2ef3d920bb3bdc369f48436db123aa2dc07f683309ad8c9968a1e1","impliedFormat":1},{"version":"4545c1a1ceca170d5d83452dd7c4994644c35cf676a671412601689d9a62da35","impliedFormat":1},{"version":"320f4091e33548b554d2214ce5fc31c96631b513dffa806e2e3a60766c8c49d9","impliedFormat":1},{"version":"a2d648d333cf67b9aeac5d81a1a379d563a8ffa91ddd61c6179f68de724260ff","impliedFormat":1},{"version":"d90d5f524de38889d1e1dbc2aeef00060d779f8688c02766ddb9ca195e4a713d","impliedFormat":1},{"version":"07ed3ddab975995eea41b22f3010506fb9f5fb301d04820b07d7a1aee5477d7c","impliedFormat":1},{"version":"969d8b0965849f4bae7cab0ba90bd1e1220e95999c2c6f01117fa7500901c017","impliedFormat":1},{"version":"6ec840ee5e2bc103f557fe38b1d585ee250540468713d7634ee066de372bf332","impliedFormat":1},{"version":"b0309e1eda99a9e76f87c18992d9c3689b0938266242835dd4611f2b69efe456","impliedFormat":1},{"version":"47699512e6d8bebf7be488182427189f999affe3addc1c87c882d36b7f2d0b0e","impliedFormat":1},{"version":"6ceb10ca57943be87ff9debe978f4ab73593c0c85ee802c051a93fc96aaf7a20","impliedFormat":1},{"version":"1de3ffe0cc28a9fe2ac761ece075826836b5a02f340b412510a59ba1d41a505a","impliedFormat":1},{"version":"e46d6cc08d243d8d0d83986f609d830991f00450fb234f5b2f861648c42dc0d8","impliedFormat":1},{"version":"1c0a98de1323051010ce5b958ad47bc1c007f7921973123c999300e2b7b0ecc0","impliedFormat":1},{"version":"ff863d17c6c659440f7c5c536e4db7762d8c2565547b2608f36b798a743606ca","impliedFormat":1},{"version":"5412ad0043cd60d1f1406fc12cb4fb987e9a734decbdd4db6f6acf71791e36fe","impliedFormat":1},{"version":"ad036a85efcd9e5b4f7dd5c1a7362c8478f9a3b6c3554654ca24a29aa850a9c5","impliedFormat":1},{"version":"fedebeae32c5cdd1a85b4e0504a01996e4a8adf3dfa72876920d3dd6e42978e7","impliedFormat":1},{"version":"e297c0a524edee7677939122f90027bfbe5f2698939d9a85728e5044b39c7124","impliedFormat":1},{"version":"cdf21eee8007e339b1b9945abf4a7b44930b1d695cc528459e68a3adc39a622e","impliedFormat":1},{"version":"bc9ee0192f056b3d5527bcd78dc3f9e527a9ba2bdc0a2c296fbc9027147df4b2","impliedFormat":1},{"version":"b62381cae176db34f003cc6172ee8f3e0122014889d66391aa73698105cf4934","impliedFormat":1},{"version":"1d9c0a9a6df4e8f29dc84c25c5aa0bb1da5456ebede7a03e03df08bb8b27bae6","impliedFormat":1},{"version":"84380af21da938a567c65ef95aefb5354f676368ee1a1cbb4cae81604a4c7d17","impliedFormat":1},{"version":"1af3e1f2a5d1332e136f8b0b95c0e6c0a02aaabd5092b36b64f3042a03debf28","impliedFormat":1},{"version":"30d8da250766efa99490fc02801047c2c6d72dd0da1bba6581c7e80d1d8842a4","impliedFormat":1},{"version":"03566202f5553bd2d9de22dfab0c61aa163cabb64f0223c08431fb3fc8f70280","impliedFormat":1},{"version":"41eb514d9ce0a6e87957f08a4b7af70d93f87637f37dee706e2d92a6601c25a9","impliedFormat":1},{"version":"e7765aa8bcb74a38b3230d212b4547686eb9796621ffb4367a104451c3f9614f","impliedFormat":1},{"version":"1de80059b8078ea5749941c9f863aa970b4735bdbb003be4925c853a8b6b4450","impliedFormat":1},{"version":"1d079c37fa53e3c21ed3fa214a27507bda9991f2a41458705b19ed8c2b61173d","impliedFormat":1},{"version":"5bf5c7a44e779790d1eb54c234b668b15e34affa95e78eada73e5757f61ed76a","impliedFormat":1},{"version":"5835a6e0d7cd2738e56b671af0e561e7c1b4fb77751383672f4b009f4e161d70","impliedFormat":1},{"version":"4b7f74b772140395e7af67c4841be1ab867c11b3b82a51b1aeb692822b76c872","impliedFormat":1},{"version":"7bd01f0f28cd3aeb2046274d85208e245965f6f2948edf4f7b2057bcf9f22ccc","impliedFormat":99},{"version":"d2f2cf2b8cc92bea913cda4a076e0f790b23a21e84f989d12f0116a7fe3906e0","impliedFormat":99},{"version":"6de125ea94866c736c6d58d68eb15272cf7d1020a5b459fea1c660027eca9a90","affectsGlobalScope":true,"impliedFormat":1},{"version":"f5b20bc288ee49989c95b20847fc93b96bf61cc0845598897a6a53a967dd7d07","affectsGlobalScope":true,"impliedFormat":1},{"version":"064ac1c2ac4b2867c2ceaa74bbdce0cb6a4c16e7c31a6497097159c18f74aa7c","impliedFormat":1},{"version":"3dc14e1ab45e497e5d5e4295271d54ff689aeae00b4277979fdd10fa563540ae","impliedFormat":1},{"version":"d3b315763d91265d6b0e7e7fa93cfdb8a80ce7cdd2d9f55ba0f37a22db00bdb8","impliedFormat":1},{"version":"b789bf89eb19c777ed1e956dbad0925ca795701552d22e68fd130a032008b9f9","impliedFormat":1},{"version":"1d76866f6aca5bb7b9eb003266628c298607c1242691d029de1b19b39f9c9942","affectsGlobalScope":true},"7b550dda9686c16f36a17bf9051d5dbf31e98555b30d114ac49fc49a1e712651",{"version":"a78793bb6b4b642f2821eb60c9ddfa0d3dd186b28568b5f0f4c268ba2b250bd2","signature":"f5803d39603639ee91c7f21fbd6d792f12358eca4fcf74146d6c2f1343b0c135"},{"version":"eccc6b88a6c5c3815e21afbafc1991ee4066019645d20ed3d05aafc9e54d0f58","signature":"5b3beb9e110c494b12be107c3cece59af336b53d20e481c9bdd7eb554799bde6"},{"version":"8c5eb64fd57d3eedf82b7e474cd7d178e5c0b2eb02d3db9c72e428119b7c99f0","signature":"487e769753040e86224af6f95fdbf933a94c34f1b395d6e506c48417b2ba7ff2"},{"version":"6c2e6283e9451736465aa62fc5e01cabbb61513446957e0507956f7248162bed","signature":"b006d19a31dd8e0870f79a2b7eb1a4f25f72306232d4c8b5d71fe9b14565b36d"},{"version":"c57b441e0c0a9cbdfa7d850dae1f8a387d6f81cbffbc3cd0465d530084c2417d","impliedFormat":99},{"version":"51954e948be6a5b728fcfaf561f12331b4f54f068934c77adfc8f70eea17d285","impliedFormat":1},{"version":"e46429536f43f5910f5b2b0c50bb2769b510a4558bb4b5d20a3b0a9091dbf7c3","signature":"400b40fe5d5f4140993b0ac871686d2b7611ab791e8810b2e14f2d89701fc49e"},{"version":"fe93c474ab38ac02e30e3af073412b4f92b740152cf3a751fdaee8cbea982341","impliedFormat":1},{"version":"3255b97f3f24af29c79cc1aa88004efb13b6285ebdde0a567bf32e19bb65250d","impliedFormat":1},{"version":"1e00b8bf9e3766c958218cd6144ffe08418286f89ff44ba5a2cc830c03dd22c7","impliedFormat":1},{"version":"c8bb01579c9ef5995c8390a8cc7d63ff4da8626643859c77ddadf8d9e5696678","signature":"ea0a7def22250c96963ab5de115c944676e0841797a7edc114d470925252c2a0"},{"version":"e78f1ff29c9175e7e451629df69f06b52e729cfa0d64fb6b0a6555142275b078","signature":"4ef8b0b2f91fee53ff8b0ee7f69fd93b8a294bd7da4c44e40057bf1e14aa0763"},{"version":"166c048927c78e0ca166de2df67186fd3bd3fa9c28092d332370122a54a4b66f","impliedFormat":1},{"version":"44dafc547130d69f8f3ff19973b00a4443a133ceb8cb24c03e0ca9b01626aee6","impliedFormat":99},{"version":"c24ab9ac84d65b417a807ada25456697bb2adf1189fa80cb240625dfb3e61c42","impliedFormat":99},{"version":"d6d57aa3e60250a8526871b3e14abc5a12dc5bb7891512628ffa5c40de369d62","impliedFormat":99},{"version":"0d121799b2cbac398e84a4708fcd82f980f33bf3ac7721a3cf9db9f28d9eb2b2","impliedFormat":99},{"version":"8aed22221e416e7811335f9518690ea0db873de1d0286602e644e1700cd166a4","impliedFormat":99},{"version":"9c3663fa36fb976609d8fc43372ae38dc2e066dab014a955f40ae6430733fb71","impliedFormat":99},{"version":"e38a172f8912eebc79671e07b81687a304a9d366a47933fde9f97ad79f8ac08a","impliedFormat":99},{"version":"2fbe402f0ee5aa8ab55367f88030f79d46211c0a0f342becaa9f648bf8534e9d","impliedFormat":1},{"version":"b94258ef37e67474ac5522e9c519489a55dcb3d4a8f645e335fc68ea2215fe88","impliedFormat":1},{"version":"4e679215c9c8480ea8b876cf74d77c1225da544adaf6acba43634a434275a69d","signature":"677d5d88630239f2364c5a7de8936ab5763cec65206614c814f0122d61d9fd33"},{"version":"539bede51b7dfa42c5168884f8f2075ee38d50fb900da46436f2855e46e84af4","impliedFormat":99},{"version":"4244c6a12dc14768ddc3ee2737ccf1c6c0d07353785d5979df7d250eda461a61","impliedFormat":99},{"version":"cce820aba9ba9d1984461c67d0d543d8eba7ea25c6a1be7a47c31cc18907a631","impliedFormat":99},{"version":"3e7e929f14bc384ed6da8ef0c7dc3aae5ea191494c9554e4327186ef40db2573","impliedFormat":99},{"version":"9d28b54569f4cd1c0c21d95d0e70f6fdbfd792121604b95d7c2d067ee0f0fd11","impliedFormat":99},{"version":"4d857105510df8011cfb5b3769dec55624a1df92e85d399cd03bc82bb89d090c","impliedFormat":99},{"version":"b92dbe495c1dee9f3cd7c1466fdf02476f19ae18a7ecc829e3ed8a380b2d597f","impliedFormat":99},{"version":"bd3e3a447a34cf3dd875ed550ea349589b2dfcd6b3a14abebdc946366bd812a4","impliedFormat":99},{"version":"6b100282ebfd5bfa4d8c72ad5dd3e9d325aa32d0a53623d6171fc2cc2899fbc4","impliedFormat":99},{"version":"31e0d88a23043e61cff5cb8e9bab319b5ca680f2b663e8582d4800a19da8600c","impliedFormat":99},{"version":"5cf87865e3a8b12bb8ddfb311846ff2eae24983faa365f53d9224fb182cd0269","impliedFormat":99},{"version":"98ef9c3f5f15c18abcd6fa9f12e93e1bbd608225501151603cce97642dbc961d","impliedFormat":99},{"version":"c80a56a10f1a8e01c4b7f08df6e9260ae076c910402dae8173fa6c7dcacc51fe","impliedFormat":99},{"version":"becca0fa1bbcc55ad69918f65f771b76904ac98b2563cb34f26fbe240c3d4db3","impliedFormat":99},{"version":"32b344c3765dc7c383516f3326c108ca84c33df44ece8ac789fce47d47ba8810","impliedFormat":99},{"version":"f7d6ecff9a4d631feeaf401c02bb87e26ddb38131c55d15a43b9290747390847","signature":"f774a7e8875649db68f5952801f001f3c316560ede472e5663af7a8c9c18f938"},{"version":"7f19b8476658d25ff197c84030e58cd7395059d876a54630e025951e474ebdae","signature":"cbb6e618cfae37c1feb246b78260428cf2bcba79a0c9bf1ac60d511a4158692d"},{"version":"3a33d94e3e35f61f5c30828f795c8db1d1fbfd2eb9c7d578a8d9276c6d94cc7b","signature":"d4e4eb785403cd7ffa1eb9afe44d7601b52a99cb205972508f2ef4ff5739bd7d"},{"version":"ba1c5c19412fd7f60a9411ec61344c4ec425d77b5a3a78d8f7ce51d1cb4b3855","signature":"5cec2d5111000faf9f06f8fa85b11b747c2fb4db35ada617f019f80de10790b5"},{"version":"b0309aa43d7073c58b43d47f80311b81aad17b395940f43faff11387ff8b2038","signature":"cde8c57cf796fc88faa8ed92f02bb868c64100eb22c6bcf935487bd8eeb1ed08"},{"version":"2ba6dd2fc6a213bef7e1dba18791da6e83992ff17f6bbd50f2c8f7f4b6fa2d82","signature":"132db3de92d2a718bd41fe3f47e75fb6916b1af96d1289347316ee1abc358b06"},{"version":"4f09605424e0a2ce414f48096837fbfaf06f13956829438a5e1743c347bf5cde","signature":"f03c9fc060940404460998108d975d541cbef8644269862a531d5b41edb0ac40"},{"version":"9800c238da31c600373883bb7a1a3ddb431684992dfd2b3112fbc1983403c427","signature":"c1c0fdbb129948e18a8b11c893490ee3ce0055631ad8de1eee247343d8359ce6"},{"version":"2b4276dde46aa2faf0dd86119999c76b81e6488cd6b0d0fcf9fb985769cd11c0","impliedFormat":99},{"version":"38d4cff03e87dc58bfd50ffe5a3fb25e6e6d4136a1282883285baf71d35967c5","impliedFormat":99},{"version":"5ecea63968444d55f7c3cf677cbec9525db9229953b34f06be0386a24b0fffd2","impliedFormat":99},{"version":"6ea9c8bf2ae4d47a0dbc2a1f9ac1e36c639b2ac9225c4d271c2f63a2faf24831","impliedFormat":99},{"version":"a3d603c46b55d51493799241b8a456169d36301cc926ff72c75f5480e7eb25bf","impliedFormat":99},{"version":"773c18e2bcc18598df8f8b2be930eb26b22608edf368e42e9ca3484828ec4122","impliedFormat":99},{"version":"9eb8e1320fc0ecbfba15c0f3452dfc1957543dfbd466aaf8b67ddb0f2ad0f217","impliedFormat":1},{"version":"4faca872dbd194a17b3ee267bd8ddc3daf3d16df96f4e43a02c7d9a862022c4f","impliedFormat":99},{"version":"f89aeba83b1744a0d697c1b7fb8a06d8dc4cf7c0d469d2419772121f499efa8e","impliedFormat":99},{"version":"144a4e5780b800c0553949169f50be285eccbdb0298afd83ef2ae03fef77e2d2","impliedFormat":99},{"version":"66aeb47bf8638d6767f7b4ff684c2d794391c981590073025e98f98e1afed499","impliedFormat":99},{"version":"26748898fec8579096c776866e8e6f07754845b3d08f5ae98c3a59baa9e85c2e","impliedFormat":99},{"version":"6d805abd62920edbd9ed4b20be26d040d01529f3ce53fdab9ca4d0fa9b589f02","impliedFormat":99},{"version":"edbeff52e73b0ff82898142aaf8ffb336910a8ccdfc31b79960ea4ad4c9f407b","impliedFormat":99},{"version":"b64a8c7a27133db3f181199d21c0c582e86f38ba57a031a126b77229526b4916","impliedFormat":99},{"version":"1b1c48c4d7cbe6f40616594c2a3f6f95bb1dcefd200a7e4167e47b67725b631a","impliedFormat":99},{"version":"97b02501eb45f487174d5a0ff89b6a95690d50e9eae242e2162118edd5f2705c","impliedFormat":99},{"version":"8eca47167dadd486582ecd4e41f7fba6ae66cc4a4c5202f1f7acf34129a0dadf","impliedFormat":99},{"version":"390dc7901776ab8f54c64100156ae561f4601a6913e86e7d1658715d7c414763","impliedFormat":99},{"version":"6841c30168fa7fe25409d83ae5e210c6340cd412e543e98625d277764d136994","impliedFormat":99},{"version":"1dc13534a1f9f442a683b92ae3200c611f19c2e8039c0b381e09076c85e90049","impliedFormat":99},{"version":"f68896096cba0f06ffbf39a67c2280f6f2e5b90c75db56a3f9ae5f7f3bb54460","impliedFormat":99},{"version":"ceed5b112e868b1c223f4f24e03d302e8043f6bd8f6cf8ede8e75f0471ede6d3","impliedFormat":99},{"version":"26cfaec143443411bc7d5363f274f885ced430b8f4bee25a81f7827248848d7b","impliedFormat":99},{"version":"f9a591e5fe0be6728cc84e70325aacafffcf203b051ddef37d65651b43c05056","impliedFormat":99},{"version":"293c0a3e323608f4e20667acc74d7bdba727db954b840ee1db03f2fd5807c761","impliedFormat":99},{"version":"de8b4c367880fe92a0a740b706f08a46d1cf9e3981d55c2701e82423e81ef0ef","impliedFormat":99},{"version":"8f1b151c1f2ab5f797666d1d53199ad5cec66ab6f4c46f4c02d51ac6d47998d3","impliedFormat":99},{"version":"3a3fd6f5ca85ceeb293f2a010125f9455404958122b6dd0ba0b34f7dab74feb5","impliedFormat":99},{"version":"5bd7f6f573ac89ec20aaf326e79394da8a89fbff8a297aa864de9137ca045678","impliedFormat":99},{"version":"01498e4cffc8badc7ba4bacb201e88d89e7ffa46d0a710f1d0510a11104b4e15","impliedFormat":99},{"version":"5916b6f272fe245dd8032c708487405eb9f5b8a83ded6630ec1ab02df36ed63d","impliedFormat":99},{"version":"9e003336714371c98108af3dc53342829039327f8b1db97d826ca81805746b7f","impliedFormat":99},{"version":"d8954254110123f6f5c9fda6280674f6b853d47df7fc41c2090e741dc8b6b627","impliedFormat":99},{"version":"1e257873c8332ca2066f2f9409269352839d6ef6cd5624ba6b777199e78f70af","impliedFormat":99},{"version":"409e0eb04698050bf40f5da901990a58f7119591fad21dc5f4d94ef20be7f457","impliedFormat":99},{"version":"0fa0e6941b1d3095f625c4bda62c0a37247ab8749cfdbc8b44f4b4518d5482f5","impliedFormat":99},{"version":"56c7641a2de5b6695f415a89826b724734aa8cdcc72a112c8e80c3d140b86f4b","impliedFormat":99},{"version":"e96bd939a55117abe6ccbc02839f2f4d9ce3893368fea528ca91c59ebddf496f","impliedFormat":99},{"version":"81f6bf27eedb1ed92466abfcee33795a6b2304691ae01f42e60f8c76894fade7","impliedFormat":99},{"version":"228127c94406a1583eb6e1314a611d9ee26418d4590b4466ed0468d5be0b7e41","impliedFormat":99},{"version":"26a0c2d883e1ed55ba00810d957dedcde5d16d637e33063686e2bc3f58a5c64a","impliedFormat":99},{"version":"68099697ac4e919f6f1832389f32eb67d3d94fae744967f33e0cbc049a222a3a","impliedFormat":99},{"version":"bfb900f7de2066a4be644c269285fda8ccca40b065476a27b082173014d00467","impliedFormat":99},{"version":"ca2f4468c5bcfc7a37aace0a1f20f92b226b097bebb834f29ecbce8ee399c6cf","impliedFormat":99},{"version":"b787dc6f2d3fa1d219038e6b360245b6c7d15c00a64ee31b24c56e823a0e71ea","impliedFormat":99},{"version":"ce757254610e6b3a37357a5892c46a86525d0d7f0a5e4a907f6707a3928b8a63","impliedFormat":99},{"version":"31a63b6b49338cc6ddb1a318beda72d3d8fd523e50f8dd5d5ff3accd39a490d7","impliedFormat":99},{"version":"8fc7615fb8fa095406f5a58b1b817b9a685c516058877a3d2c06d4545fdf5dbe","impliedFormat":99},{"version":"eeab2749ab639ddcd1b01f3c3acebe9616e0265021bf6aff910070015b03b92b","impliedFormat":99},{"version":"d5da26af31358a4883edb6112879018b14c7c1fbcc457aa36961b03ee17bedea","impliedFormat":99},{"version":"4e93fb2d2c59bbc1f1a5211b36c447efe4d0af568d682ef1e5eb5f84ca6ccc2e","impliedFormat":99},{"version":"b6cc07bc56c7418ab500421d2629c26acdb6ec2e9af85c5e2c5d7e8c9f92566d","impliedFormat":99},{"version":"3d13fe973e92e708ad3dbbf1b2385bb799f8e70c8da71a1ac72fcb5521c8a5e9","impliedFormat":99},{"version":"16f3f66b5182e57c554d0e374e29fdc0a899c1321b3f94fa997d19abf9faf931","impliedFormat":99},{"version":"df459c67b9eef71318851331b23926961f0476b4b9d1674addcb627eabe583b2","impliedFormat":99},{"version":"b93915733f1802a8d9093116a5e660385afda0f72a5bab9c9bd356549a61f1cd","impliedFormat":99},{"version":"1dfb1491b28cf3734f3c07745a56d7758a12d546f81a70fbfe8ee9faeb528c54","impliedFormat":99},{"version":"42b00a511331c7f79039ae3be83fd5941b212ce193654c3ac5d82157227b69ad","impliedFormat":99},{"version":"1a5914065c7c6e2b6e98437c4ba6059bfeda3441f0f55cb07189df1e78698922","impliedFormat":99},{"version":"66d3f42196f32c639d8240bbd520852abc024d673c713bc5ab26b4bdde750a74","impliedFormat":99},{"version":"18ce9175445b3edec11bbca1c682a8f3919e74fb3504abd883ac462ea8089a0d","impliedFormat":99},{"version":"aec96f94a27fc61d5f4ead202628e811199f887d48bb7614b7e1706b629cd1ba","impliedFormat":99},{"version":"2ab01a0368f65b3b891e25416ae785dca54808f70d8f6204b99589ed4f7f1d2f","impliedFormat":99},{"version":"efd87ca2190e2d0f905e30343443c8d2f0d32cb2b1c9e05b2cb97b0061abec08","impliedFormat":99},{"version":"3323edd649d2c85d3528912cdf6fb770258d59d75241575fcf25c7850837de79","impliedFormat":99},{"version":"e0c8deca5262879f11ee84fe6664dcd197a60f546930363bf2d78d37c3138c69","impliedFormat":99},{"version":"3b8ba0456ce7763676505d103eb336a6b97659d6fc8a7d7d186558ea0b8c7ea5","impliedFormat":99},{"version":"7bae4a3f50a844fcbdc504d717f5f13dd7178ca99f131b230305db6b55e1dbc3","impliedFormat":99},{"version":"f3a4668046e42e9f5ebfdc8b79238eb27c399443dd1dcfcd4c4c3652692ed031","impliedFormat":99},{"version":"07160ad59d49c6f2999f9703542918df3eb1ef93857bc560b4baa7c6775d22ce","impliedFormat":99},{"version":"cbb7705b157d0be735c4564681d749529d26ac77296757fdf4d861a76e279d4d","impliedFormat":99},{"version":"c23b9684ab48ea6956f0a510c3cf724277fa457efa7ff85d5907f4e4290ba0aa","impliedFormat":99},{"version":"3859635347ecc10d4ff5dd68bde66420f5a8539b7313dd62020aa025208d2222","impliedFormat":99},{"version":"f49e8feb6d7473579a5ceb5872598bbc1be3723da653993472720629c72fa0ba","impliedFormat":99},{"version":"14c727434cfe6a078b51a88d005033ad01a76bec36292d7b47369d82e48e1cb0","impliedFormat":99},{"version":"1c1ae4ec02de9778286f84e0d15a1b74cc610c13c5b6a13c1ada2e6770eeb4e1","signature":"6d6449b80881e70de3f27d314c1e8a6353071f30442df504dc00e429b4f2252f"},{"version":"0e354464ab326224591b02875550f37ae289785c1558a494f858e3d2b289851c","signature":"8123bb1692f9138dedb3997a3c3ca15cafd7bacbafc0f67f50c62d4c8774fccb"},{"version":"366cdf6ad7ecd5713a24f28af7a40765cb9d2538540d232b7521f0ea5ce0bb13","signature":"6a9d493e0c5865db9fc7519b48fb79ba3eaf00535a697ff7ea1f0dabccf4d15d"},{"version":"de708061021467057ac23fc62822e8d0840a216e24dcc423d4658a95033fdb8d","signature":"0283eafdc2b9ec618b38542bb438d3e514d72ca37203be108f45f0f960019a0e"},{"version":"59ce31d87135ea5719f4451f6a288ac851746e6200088bcc1d3a7b1c0094c442","signature":"797b1b35838a594d3ba015115250d87d02d7e3ec59ac0ca62d4d32e0dd79bb50"},{"version":"417e42f4fe8f9ce5963dee9e82641653dd15266a75153b4624e299ca60a04c07","signature":"3db332987db7707a742b841f11d8a5590960b598d15bc3f678600d28afa94a91"},{"version":"dfa56c0062ac0ab7ed8916d742280b29f945b1ce10554b32b238fa0e84b94e98","signature":"245cbd0512d5ad85db0cbf054c74dad653b62f2da5300ceff0e87ff99dce5caf"},{"version":"d816a6c5cda27aa2ad5faf35260b390ee5045f33e275b1b97e8dcc97cd0a974d","signature":"cc20a19087b1df060516722de9e6c4aa87a5c239c5ad1ecd83b61cd17c9bd16f"},{"version":"8dedf145bef222eb6a09d63a4a6d8f340cd315cbf61ede98ed459bb5b5ffcaeb","signature":"7a4fe07bffc8b6ec1785aec2b011f454fc93828d90b9269ccdda995af85bf073"},{"version":"3c4236141de15959ae49ddd25544197e49e3a566b2d7d5f650e8be4e68b3d8d5","signature":"827c3c8128c93043529320108a33afe0cc3459ba09c04192f4600b80c73607b2"},{"version":"d08cf172a38f58995eda96e030606dce61e63fb16c50a0e20bf0148b1dd34cbd","signature":"485253ab3565278dbb431417d5ff09907309ee9e5c88550c115d882e872593c7"},{"version":"ceb8eb94f3be6f723de8230d338a50a8696f145270f68c514a804fab5343c404","signature":"c7429d15c4b492628c3b307bb6e3cfe8eb2ee741469b1ca524f90b84a143fe8f"},{"version":"8ad83abba071cb20fe7166a8adae3bd77a79be8711426fd96f5be354d868efdb","signature":"ac02d8be5e6d2e71292fe738d73adebc09aaa60603a8e51f7230faec34c5378a"},{"version":"87ed35cd068b7438bdc0f845b4ee101e98d3903b3722ef6f358df292e73d7d73","signature":"e4dbf6dbee6e1a22a1b988a18d5cb517c17e9ada9d691f2b4eb9e689452a17ac"},{"version":"05c806043f883a81bee91b72428e9551f7cff48fd3d03ce2ffbf22af8f0e10cc","signature":"5e1e3ed0f08feb1037e9f84157aa96a5177bba21b5b4d818d026532d0ff007b0"},{"version":"c1f1a1ff58583a523e6eb11fd5801a5bb0b3b531417bb0ce53db02a59a5545e9","signature":"d092958065bbc70478d8049066581c325f46a724087ef328cf2d47fbdc282b9a"},{"version":"0872ece3daf3df853bb9a0ca45cdae713dbf616445fff7c792bad8484d5f97ea","signature":"a0948a68b9c152fab283c5f443f8ec9535c005d8bbe560b2b8ff87f54279b68a"},{"version":"0b7fc25d6c77adeedb6cb69fe0eea69155fe7684212c7f3b2b440e90bac48a5b","signature":"7fc4d84099fd385c7645d9d41f854d415baad44aad0b6e07d6d89a89ef7bfc49"},{"version":"ef1b801787af0d1c73a87574b74f49c79c2c89c85360e0faedf3b345d527ad5f","signature":"a501d75788b8433deb30e2113ccbad77c77fb3ce7426757194549f621804f562"},{"version":"1408cfd0bd1cd2dd7d3a1f36a64552b2cd985acffeadbbb7a1fba337554620e5","signature":"e45edace128227a34a1958d4c889a4cf3f24ad784771495cd0004e7bbb07bfc5"},{"version":"b01f4830e8a111ec641ad7342febd9d3e77ad1e113110a3eaaaa4f162700710d","signature":"7be4e60a740deaefbfe7896964791aff6e6b2e3e12b93adbfbb29f8f7628dc34"},{"version":"c840e12da509f2c771dbc356a23750227a43d1864640c7e6404b8f9ec86b3871","impliedFormat":99},{"version":"6ae92eaaaef30fae975de604d3af31d5b00eca7f02d89fab589152df926685fd","impliedFormat":99},{"version":"e3b3780b68c70ff42894b68ee8f49186ef3335fc86a9106dd07e11580b460cc1","impliedFormat":99},{"version":"459fda119c27213d3defde220c051414020029abd52fb6fb9aea0beedc512c6b","impliedFormat":99},{"version":"968b0403af74a785c9408ca69a677235e49d0c80b2c27fb9858ad421a8778c7d","signature":"816603a3a3ce186bf710d1be189162cda5af637d36d8f318022aa2e448e3565a"},{"version":"bd8b85f093ee11def0903d12d44e65e46b1702bab79359a93be502cf6a0fd7ef","signature":"eaf8e3e068e23fbc82dbc1aa21ddfbe54baff2a86d75d50902329a5472efe0de"},{"version":"7bed892cb97ab2b5c3b42bb32b6b2f4bf0d6e1716fb6f5b8fde5da945e39944c","signature":"53774aa9ee1a946a98d3b2659d6aa4d81b0468b05525256a44acbfb4105a8412"},"0294f36355f84a2f9a078ff3dfc0fb90feecd3912f9fc7a213635e08d01348c5",{"version":"767a2e055fe6e021328f51448538e15f93cd53f89e5ba8c83c3a1e94b6630c22","signature":"a4ef05d8a5a879ad9030332befb57989305a3c6e1ed2dca110f645794beed040"},{"version":"1762c51f9a975e4451b8941109881e59f775acd37e13bbab014a48e8731d75a1","signature":"ef1e776b998a0c02087c6501bd07c78832baeac620b5021c245df4836dfa2f17"},{"version":"880acfd4ebc7da22bd68d4bee7f5ffd9f89d48b494110186b50ed856f0076ca7","signature":"cf8b1979ffd2f536106875f0b58c79a8b401278739c3d70dac5d4e201cb3c32c"},{"version":"c4df065d6e678a9d213416fcc5a2db8cc3870916ce86121775d873e23229224a","signature":"335690b6dd1a9cc476cd01cf766c5ae1234e90a318eb383a96d2e147358250a3"},{"version":"b839eb3a08e4549339974eedb277393502294a80e3718ee06bc8fd5cdd20ae37","signature":"324717f7bc7631d3a880b8e42f32c7f6225a2b8295c2793568c99de08e1fef1b"},{"version":"2f69175a10d85d5c041a48ad3699ac5a6055eaefd43f9440d8a57778a0ffec92","signature":"c2c4afc8c03490924367c6e6a877dddec82fb37ef9f2554c5754c71c7188e3e9"},{"version":"423483c43cd900e3315654a720cba2abdff48ade505900609d40ce4c5c323833","signature":"e6a25231cd80539f77cdaa44ccf5be365ffd178c050299a383c1a7a167a80a37"},{"version":"0edae8aaff2ba9ad6291e8b5313de1b7b7f4e3c1ffadc256d9f45953b3edc649","signature":"8754fe347bd81594a37b5a19a333e4a8063a20f443fab1af850f7edf12bb5537"},{"version":"f07845a4f9772a545efa7c1086e83a81732c72026d3f365f0bb52bfae5b66752","signature":"b3b0332e154b1a2ccb655349b7e2471c65f61a5b523c06973d88cb50ce785d19"},{"version":"082062d5e676f7049e4a684dc6726c631fe153cb457e494cc240bbe3be5650a4","impliedFormat":99},{"version":"ab2e38bc6b61891361655b5d2e45c4cbc95c9db4920453fd5329cac7fd4e1385","impliedFormat":99},{"version":"9bb8a03e0015254999c1e96830242fa3764856fd14958d3b097149f4491b1fa6","impliedFormat":99},{"version":"21655de6cf9d8df920b2dd8aaa5d6b4f88630c5c6f4df66947f4a2c4e59f7c79","impliedFormat":99},{"version":"c98e4b259d28463c34c65cdbfba447d5ccd4fed0a138b7acf8c796309e61e2d6","signature":"d9ed1c6c07bd03524f35e2b7cf385c3278909b3ed2daafb4b74d460d8b6420ce"},{"version":"e865b3bcde0a53c3527efd064be36793bc6a3c8103bb7d9c3fc3d57336bbca68","signature":"633e87523348a4bf2e0999dfa6d93004738ecbd7186f7f2ae5f350e5d7781d2b"},{"version":"fe71593dddfc086fd941f484c1094f952f54acdf7ec2d983acfaa61a816dec86","signature":"137673d794fa6adcae07fe30e38455116513f5894811194401033fe14bb56e38"},{"version":"c6c3dbe3b2cf62152b1c7493424edf66460d53d25e7d49c98f6a7d2c80334528","signature":"6a2879106635e78675da8924862839ed3ab8806d3e08316152b00a697a4141aa"},{"version":"3e7064de07e84a24d10943b69a0278752f23f1999b396153feb0a63f355beae3","signature":"d6e9a3e9eddda48d8308210ee9ecc68f9425bcc378fb1f8bec08ebb7e0f1b6d3"},{"version":"ce4eec21950930b52a7b7d76154bf16d87cbd7b057257d0857fbcb0bc690bd61","signature":"343542f6bbe309692eda70274e04acfbfe25aa0bfd9d2c9a711c5281bdb49cba"},{"version":"8ffdf73d62d015140d4c4edfe58757d2f21474d50da2f92d48e33c522b79dbc7","signature":"917fd3d6e4f13868f4a867924f3fbdd816fbad049d179cc81b17a45fd591cc86"},{"version":"4661743ca06bc9577174fc8ecd963ccaf0ec605c4aa89eaf6a622467397cf147","signature":"5879818ce307b9ed0a24051ba091c110081cbe6a2ba56008132dbdf4181ab8be"},{"version":"6be9cc6d63419c8acb7c6b32fdd7fff8285842e69dd35588019d93f95a52571b","signature":"6cc449dc9995a239ca20bb0e067ba9e5008acbd036727e8cd97bc0001bdbb2b7"},{"version":"7945ca8edb5a34539de61ed93c4877f1e90fa603e5b208740bde9cc00693067a","signature":"19d54bf73a5a0216df1cf3bd0ff80167cf2bda8b0aa052d2cc7ff7caaccc4efe"},{"version":"28cb8c81a73ab8485fcf4bebcaa7264cd678224a222b85a491be882798d8f550","signature":"1a3c71cf6a6fbdd7591116f48d95ef7dedf3cba8345149fe195729409cd34fe2"},{"version":"0c259776b22163c556ef08874cf8133e76b1700eee5477ac2608dd7bf8083e45","signature":"fec7336827914a1816050bca31f2ee284f4d7deaf7211898cdd094a0deb2b513"},{"version":"c8dea650241e838a1c6d0904e3f90109f1f934678fe64c7bba7c38b1af5dd4dd","signature":"82fd574ed84b5a906cdc2676c26414e76adc47c70dc6b848e0f4343438a34590"},{"version":"b531db01208aaffa47aff4926c165af00a6c8c13fa231187deb75f69eeb16aca","signature":"17fe8278e6a30c0377d12ab4098d280e74b21daa2c7f8dff2bb60d3795555bbe"},{"version":"b68acd2160533c5f326af2de608d45c4cc3cebced25340efa69a4cfec6658f38","signature":"7c199173b55f57172fe53234f0fc95cc87e6565a13ce83e7b47918742bd47dfc"},{"version":"307d88c82e8c9436b09ac27af700c3acc03a87bc6745cfbb51069a75e2b6d2c0","signature":"001c2ad8eef03b07f14e41bee9a415db23dccadc02a78919faee940e545787d2"},{"version":"14ddfd8524dece18a84e2745bd609fb9dffd6339abe7c85af10f5cbb8cb67bfe","signature":"ee0527b1ff34f0ceffb2348f0095b1c1cddf4faa46085ea2a110cebaee50d347"},{"version":"c33939a108059c5e0acb880ef4ecb37bfb63226078c58485b5d25585d04fc7b0","signature":"b843fbec02d0cb54db1702adcfd063ad08120abeda2498a1787b64590684359c"},{"version":"9a9e04e07fe35d1874951bdc8de7359f75e671b9397557f7384ad6b2a315cf58","signature":"18a4751b41acc2856ed6f45b34fbe725cf9c7e4d90eba0ad2feb4e2d0990ec5e"},{"version":"5170e400dc24e75c4b25865bbe0b20886ed49f59f59e7503b5caca5270cabde2","signature":"8aef51ae2a7868e062803dec9fbb656da273542c7c9f4621d5464b9605dfe009"},{"version":"a78eb0d122e55304d670c8a5e01b0ac0f37dbce5a53869669be076470a7f4d78","signature":"a7a3a87855b2d2cc8ae413e677b4899ffc58a5e4a20f055c07a404da99541520"},{"version":"e1024bac63fac77ff2dcf562cf00a82a2ee2f22f02d78869c293991716cf192c","signature":"0da53c45d881830a663b858e68023d38da5ac262bfdc606024742e9b45b74032"},{"version":"3787076bd7bc40a1c348d115b0fcefb37f77e0b92e941f2efa44d464396ca328","signature":"986ef67c1983fdc1546cd063b625ccee6cde32da7705ab8e1acebfa0fb39df5c"},{"version":"5a2588c9ed56c03c0e357d58330d2bb6d89e390f4be303a09ee854c0bcb36a92","signature":"5283c1424f61516c54136798c3e7e256528a1a75581132e32e616d76add1160d"},{"version":"19e8160ad82839d8ece0f64fa0354373220e0b0192fb63601bd2bee81f2fd782","signature":"1592edebde83872e3c0cdd72f5eadddab86cf295a6d19f09620b84442fd7854e"},{"version":"50873ca05ca34d25a3324222510a86a9599b3a311d9fff1406d8de0be6a9eaa8","signature":"e5f9d5f6f37816cef8cfd651f6a81ff29222429b81fe76184722549e7904facf"},{"version":"586f0de1456ff1f22947e2aec82642f7ec102d0ad0b6f13edeb79dbaa8b9d11b","signature":"8c6eba7333e72b090341a04f45aab07e000ee74e78276b2c041119bf6dd60587"},{"version":"4e150e90bb4f5066740c6bf43a8e5061e998b884d37c7170caaae8dcd808b8ab","signature":"2445a341f73ffabebf7b9d8886a913be095356c2ba0673e3cc2e7fa68b500c36"},{"version":"6dc5f5d9c28c0aa23452a32416ce337ac09ce633ad893b988a1ce256fd7e72be","signature":"828afa634f40e99fc918fbfbacbc0148c5502118e44433a914acadf42cbcc802"},{"version":"052c188cf49779dda6dd016340f5b1158da01b9cc983108dc4037ec5c0575afa","signature":"2840fdae09764d8ef1660442dd476e00484643fff12b1039a7e44c6df5df5b53"},{"version":"8dcf898abdf1bfe773b873d5e1b329b8f4d061fdba45f2ad842a3fdb4db5ab07","signature":"b410611afb0627ebadfbb1f24feaba95b75f2d6eb44e9b4a78cdbca1b5f64aaf"},{"version":"977a787ed6eda7e66ef0c3b925af8b08c9a4d8780e23cc8359c6a427dc84862a","signature":"5177c419b162946d62af34a97439621b087540542b9a706a057c72b7106ea818"},{"version":"bc4194f8ea1fdb9f7d518df1015108da6bd5a38aba9d64b6c134ce83c76fb51c","signature":"4c09a264144e8e6e0b181b42eb9c86fbe44114927c0141188c9105549fcc7969"},{"version":"55e5845f6f31bcb05e539336a48ced65fce64e02af796e6da40f4aee7bc78e91","signature":"0b8fd7aa2222d31c2de139a76ef2aa14dc86717c45b9ef30e2c733be1fa2707e"},"d1986184a09a52db8228cb2bb2a61a8c05c9354e5b93cec8e2628d8579c892d7","d9fa276add16d87de0375e4ee85d1b8427515e8e3f08f1adf87c4e0cbc89f09e","d1986184a09a52db8228cb2bb2a61a8c05c9354e5b93cec8e2628d8579c892d7",{"version":"d6da2f7267888d32e5a56f45a1123201a1cb03142b7c86062e4b1c5039ace251","affectsGlobalScope":true},"3efb6dfbe3ab1ffdc29659624be4f5de2ba5d3ea7d643eb45a5da376f261700c"],"root":[[534,539],542,546,547,558,[574,581],[658,678],[683,695],[700,738]],"options":{"allowJs":true,"esModuleInterop":true,"jsx":4,"module":99,"skipLibCheck":true,"strict":true,"target":2},"referencedMap":[[736,1],[737,2],[738,3],[734,4],[534,2],[735,5],[581,6],[662,7],[666,8],[547,9],[536,10],[668,11],[672,12],[670,13],[674,14],[688,15],[689,16],[686,17],[692,18],[693,19],[695,20],[702,21],[676,22],[704,23],[706,24],[578,25],[710,26],[708,27],[716,28],[714,29],[718,30],[722,31],[724,32],[720,33],[726,34],[730,35],[728,36],[732,37],[580,38],[694,39],[665,40],[687,41],[671,42],[669,43],[712,44],[663,45],[577,46],[667,47],[701,48],[673,49],[705,50],[675,51],[661,52],[709,53],[707,54],[664,42],[685,55],[677,56],[684,41],[678,57],[690,42],[691,42],[703,58],[719,59],[721,42],[723,60],[546,61],[725,59],[660,62],[711,44],[715,42],[717,63],[713,64],[659,65],[576,66],[683,67],[558,68],[733,69],[658,70],[574,71],[575,69],[700,72],[729,58],[727,73],[731,41],[579,74],[537,75],[538,75],[539,75],[542,76],[535,77],[554,78],[555,79],[634,80],[635,81],[567,82],[566,83],[565,84],[571,85],[570,86],[569,83],[563,83],[562,87],[568,88],[623,89],[604,90],[607,91],[603,92],[621,93],[587,94],[624,95],[608,94],[609,96],[625,94],[619,97],[610,94],[614,98],[615,94],[616,99],[613,100],[617,93],[626,101],[618,89],[627,102],[620,103],[622,104],[612,105],[560,106],[561,107],[572,108],[573,109],[551,110],[559,111],[605,2],[549,2],[550,112],[611,2],[553,113],[606,114],[564,115],[629,116],[630,117],[639,117],[638,118],[641,78],[640,78],[657,119],[656,120],[642,78],[643,78],[644,121],[645,122],[646,116],[647,118],[649,117],[648,78],[637,123],[632,124],[636,125],[631,126],[651,127],[650,128],[655,78],[652,129],[653,78],[633,130],[680,131],[679,78],[654,78],[699,132],[698,133],[696,134],[697,135],[552,136],[682,137],[681,114],[602,138],[597,139],[601,140],[599,2],[600,141],[628,142],[591,115],[594,143],[592,2],[595,144],[589,145],[590,146],[596,147],[593,148],[598,115],[583,149],[585,150],[586,151],[582,2],[584,2],[378,2],[142,152],[143,152],[144,153],[91,154],[145,155],[146,156],[147,157],[89,2],[148,158],[149,159],[150,160],[151,161],[152,162],[153,163],[154,163],[155,164],[156,165],[157,166],[158,167],[92,2],[90,2],[159,168],[160,169],[161,170],[162,171],[163,2],[164,172],[165,173],[166,174],[167,175],[168,176],[169,177],[170,178],[171,179],[172,180],[173,180],[174,181],[175,2],[176,182],[177,183],[179,184],[178,185],[180,186],[181,187],[182,188],[183,189],[184,190],[185,191],[186,192],[88,2],[195,193],[187,194],[188,195],[189,196],[190,197],[191,198],[192,199],[93,2],[94,200],[95,2],[96,2],[138,201],[139,202],[140,2],[141,186],[193,203],[194,204],[199,205],[463,115],[200,206],[198,207],[465,208],[464,209],[196,210],[461,2],[197,211],[79,2],[81,212],[460,115],[230,115],[557,213],[556,214],[540,2],[80,2],[548,115],[486,215],[491,216],[498,217],[481,218],[234,2],[242,219],[382,220],[385,221],[357,2],[370,222],[377,223],[259,2],[359,2],[240,2],[356,224],[402,225],[241,2],[232,226],[384,227],[386,228],[387,229],[458,230],[351,231],[304,232],[364,233],[365,234],[363,235],[362,2],[358,236],[383,237],[243,238],[428,2],[429,239],[270,240],[244,241],[271,240],[307,240],[210,240],[380,242],[379,2],[369,243],[476,2],[219,2],[497,244],[436,245],[437,246],[433,247],[515,2],[334,2],[438,248],[434,249],[520,250],[519,251],[514,2],[285,2],[337,252],[336,2],[513,253],[435,115],[290,254],[297,255],[299,256],[289,2],[294,257],[296,258],[298,259],[293,260],[291,2],[295,261],[516,2],[512,2],[518,262],[517,2],[288,263],[507,264],[510,265],[278,266],[277,267],[276,268],[523,115],[275,269],[264,2],[525,2],[544,270],[543,2],[526,115],[527,271],[202,2],[366,272],[367,273],[368,274],[206,2],[371,2],[226,275],[201,2],[450,115],[208,276],[449,277],[448,278],[439,2],[440,2],[447,2],[442,2],[445,279],[441,2],[443,280],[446,281],[444,280],[239,2],[236,2],[237,240],[391,2],[396,282],[397,283],[395,284],[393,285],[394,286],[389,2],[456,248],[231,248],[485,287],[492,288],[496,289],[325,290],[324,2],[319,2],[472,291],[480,292],[352,293],[353,294],[431,295],[341,2],[454,296],[329,115],[346,297],[457,298],[342,2],[345,299],[343,2],[455,300],[452,301],[451,2],[453,2],[349,2],[427,302],[214,303],[327,304],[331,305],[347,306],[350,307],[339,308],[332,309],[479,310],[405,311],[323,312],[211,313],[478,314],[207,315],[398,316],[390,2],[399,317],[416,318],[388,2],[415,319],[87,2],[410,320],[235,2],[430,321],[406,2],[220,2],[222,2],[361,2],[414,322],[238,2],[262,323],[348,324],[268,325],[328,2],[413,2],[392,2],[418,326],[419,327],[360,2],[421,328],[423,329],[422,330],[372,2],[412,313],[425,331],[322,332],[411,333],[417,334],[247,2],[251,2],[250,2],[249,2],[254,2],[248,2],[257,2],[256,2],[253,2],[252,2],[255,2],[258,335],[246,2],[314,336],[313,2],[318,337],[315,338],[317,339],[320,337],[316,338],[227,340],[306,341],[475,342],[473,2],[502,343],[504,344],[468,345],[503,346],[215,347],[212,347],[245,2],[229,348],[228,349],[224,350],[225,351],[233,352],[261,352],[272,352],[308,353],[273,353],[217,354],[216,2],[312,355],[311,356],[310,357],[309,358],[218,359],[459,360],[260,361],[467,362],[432,363],[462,364],[466,365],[355,366],[354,367],[335,368],[321,369],[303,370],[305,371],[302,372],[424,373],[326,2],[490,2],[223,374],[426,375],[474,376],[333,2],[263,377],[340,378],[338,379],[265,380],[400,381],[469,2],[266,382],[401,382],[488,2],[487,2],[489,2],[471,2],[470,2],[403,383],[330,2],[300,384],[221,385],[279,2],[205,386],[267,2],[494,115],[204,2],[506,387],[287,115],[500,248],[286,388],[483,389],[284,387],[209,2],[508,390],[282,115],[283,115],[274,2],[203,2],[281,391],[280,392],[269,393],[344,179],[404,179],[420,2],[408,394],[407,2],[292,263],[213,2],[301,115],[477,275],[484,395],[82,115],[85,396],[86,397],[83,115],[84,2],[381,200],[376,398],[375,2],[374,399],[373,2],[482,400],[493,401],[495,402],[499,403],[545,404],[501,405],[505,406],[533,407],[509,407],[532,408],[511,409],[521,410],[522,411],[524,412],[528,413],[531,275],[530,2],[529,414],[588,2],[409,415],[541,2],[77,2],[78,2],[13,2],[14,2],[16,2],[15,2],[2,2],[17,2],[18,2],[19,2],[20,2],[21,2],[22,2],[23,2],[24,2],[3,2],[25,2],[26,2],[4,2],[27,2],[31,2],[28,2],[29,2],[30,2],[32,2],[33,2],[34,2],[5,2],[35,2],[36,2],[37,2],[38,2],[6,2],[42,2],[39,2],[40,2],[41,2],[43,2],[7,2],[44,2],[49,2],[50,2],[45,2],[46,2],[47,2],[48,2],[8,2],[54,2],[51,2],[52,2],[53,2],[55,2],[9,2],[56,2],[57,2],[58,2],[60,2],[59,2],[61,2],[62,2],[10,2],[63,2],[64,2],[65,2],[11,2],[66,2],[67,2],[68,2],[69,2],[70,2],[1,2],[71,2],[72,2],[12,2],[75,2],[74,2],[73,2],[76,2],[114,416],[126,417],[112,418],[127,419],[136,420],[103,421],[104,422],[102,423],[135,414],[130,424],[134,425],[106,426],[123,427],[105,428],[133,429],[100,430],[101,424],[107,431],[108,2],[113,432],[111,431],[98,433],[137,434],[128,435],[117,436],[116,431],[118,437],[121,438],[115,439],[119,440],[131,414],[109,441],[110,442],[122,443],[99,419],[125,444],[124,431],[120,445],[129,2],[97,2],[132,446]],"affectedFilesPendingEmit":[738,735,581,662,666,547,536,668,672,670,674,688,689,686,692,693,695,702,676,704,706,578,710,708,716,714,718,722,724,720,726,730,728,732,580,694,665,687,671,669,712,663,577,667,701,673,705,675,661,709,707,664,685,677,684,678,690,691,703,719,721,723,546,725,660,711,715,717,713,659,576,683,558,733,658,574,575,700,729,727,731,579,537,538,539,542],"version":"5.7.3"} \ No newline at end of file +{"fileNames":["./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es5.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2023.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.esnext.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.dom.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2022.regexp.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2023.array.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2023.collection.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2023.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.collection.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.object.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.promise.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.regexp.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.es2024.string.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.esnext.array.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.esnext.collection.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.esnext.intl.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.esnext.decorators.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.esnext.iterator.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/global.d.ts","./node_modules/.pnpm/csstype@3.2.3/node_modules/csstype/index.d.ts","./node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/css.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/macro.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/style.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/global.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/styled-jsx/types/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/get-page-files.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/globals.typedarray.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/buffer.buffer.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/globals.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/crypto.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/events.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/utility.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/header.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/readable.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/fetch.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/formdata.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/connector.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/client-stats.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/client.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/errors.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/dispatcher.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/global-dispatcher.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/global-origin.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/pool-stats.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/pool.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/handlers.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/balanced-pool.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/h2c-client.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/agent.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-interceptor.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-call-history.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-agent.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-client.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-pool.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/snapshot-agent.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-errors.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/proxy-agent.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/retry-handler.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/retry-agent.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/api.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/cache-interceptor.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/interceptors.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/util.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/cookies.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/patch.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/websocket.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/eventsource.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/content-type.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/cache.d.ts","./node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/index.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/navigator.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/storage.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/streams.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/assert.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/assert/strict.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/async_hooks.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/buffer.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/child_process.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/cluster.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/console.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/constants.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/crypto.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/dgram.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/dns.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/dns/promises.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/domain.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/events.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/fs.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/fs/promises.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/http.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/http2.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/https.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/inspector.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/inspector.generated.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/module.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/net.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/os.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/path.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/perf_hooks.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/process.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/punycode.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/querystring.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/readline.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/readline/promises.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/repl.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/sea.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/sqlite.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream/promises.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream/consumers.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream/web.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/string_decoder.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/test.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/timers.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/timers/promises.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/tls.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/trace_events.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/tty.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/url.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/util.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/v8.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/vm.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/wasi.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/worker_threads.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/zlib.d.ts","./node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/ts5.7/index.d.ts","./node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/canary.d.ts","./node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/experimental.d.ts","./node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/index.d.ts","./node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/canary.d.ts","./node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/experimental.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/fallback.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/webpack/webpack.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/modern-browserslist-target.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/entry-constants.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/constants.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/bundler.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/config.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/load-custom-routes.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/image-config.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/subresource-integrity-plugin.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/body-streams.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/search-params.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/segment-cache/vary-params-decoding.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/vary-params.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/params.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-kind.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/route-definition.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matches/route-match.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/app-router-headers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/cache-control.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/app-router-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/cache-handlers/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/use-cache/use-cache-wrapper.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/resume-data-cache/cache-store.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/resume-data-cache/resume-data-cache.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/constants.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/render-result.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/response-cache/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/response-cache/index.d.ts","./node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/jsx-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/userspace/pages/pages-dev-overlay-setup.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/static-paths/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/app-page-route-definition.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/adapter/setup-node-env.external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/instrumentation/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/setup-exception-listeners.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/worker.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/experimental/ppr.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/page-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/app/app-segment-config.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/pages/pages-segment-config.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/analysis/get-page-static-info.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/loaders/get-module-build-info.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/middleware-plugin.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/require-hook.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-polyfill-crypto.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-baseline.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/error-inspect.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/console-file.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/console-exit.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/console-dim.external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/unhandled-rejection.external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/random.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/date.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/web-crypto.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/node-crypto.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment-extensions/fast-set-immediate.external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/node-environment.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/page-extensions-type.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/module.compiled.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/app-route-route-definition.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/i18n-provider.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/next-url.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@edge-runtime/cookies/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/cookies.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/request.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/deep-readonly.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/incremental-cache/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/middleware-route-matcher.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/flight-manifest-plugin.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/next-font-manifest-plugin.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/locale-route-definition.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/pages-route-definition.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/mitt.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/with-router.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/router.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/route-loader.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/page-loader.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/bloom-filter.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/router.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router-context.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/loadable-context.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/loadable.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/image-config-context.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/readonly-url-search-params.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/hooks-client-context.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/head-manager-context.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/flight-data-helpers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/cache-key.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/router-reducer/fetch-server-response.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/segment-cache/segment-value-encoding.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/scheduler.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/cache-map.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/vary-path.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/cache.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/router-reducer/ppr-navigations.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/segment-cache/navigation.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/router-reducer/router-reducer-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/app-router-context.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/server-inserted-html.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/vendored/contexts/entrypoints.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/module.compiled.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/templates/pages.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/module.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/render.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-definitions/pages-api-route-definition.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matches/pages-api-route-match.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matchers/route-matcher.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matcher-providers/route-matcher-provider.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-matcher-managers/route-matcher-manager.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/normalizer.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/locale-route-normalizer.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/pathname-normalizer.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/suffix.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/rsc.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/next-data.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/builtin-request-context.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/normalizers/request/segment-prefix-rsc.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/pages/builtin/_error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/load-default-error-components.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/base-server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/after.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/after-context.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/use-cache/cache-life.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-async-storage-instance.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/lazy-result.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/create-error-handler.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/action-revalidation-kind.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-async-storage.external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/async-storage/work-store.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/http.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/hooks-server-context.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-route/shared-modules.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/redirect-status-code.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/redirect-error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/adapters/request-cookies.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/async-storage/draft-mode-provider.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/adapters/headers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/cache-signal.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/boundary-tracking.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/instant-validation-error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/parse-relative-url.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/instant-samples.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/dynamic-rendering.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-unit-async-storage-instance.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/implicit-tags.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/staged-rendering.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/work-unit-async-storage.external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/templates/app-route.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/action-async-storage-instance.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/action-async-storage.external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-route/module.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-route/module.compiled.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/app/app-segments.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/get-supported-browsers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/utils.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/rendering-mode.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/build-prefetch-segment-data-route.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/cpu-profile.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/result.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/helpers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/turborepo-access-trace/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/export/routes/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/export/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/export/worker.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/worker.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/coalesced-function.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/trace.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/shared.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/trace/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/load-jsconfig.d.ts","./node_modules/.pnpm/@next+env@16.2.6/node_modules/@next/env/dist/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/telemetry-plugin/use-cache-tracker-utils.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/plugins/telemetry-plugin/telemetry-plugin.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/telemetry/storage.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/build-context.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack-config.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/swc/generated-native.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/define-env.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/swc/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/swc/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/parse-version-info.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/shared/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/dev-indicator-server-state.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/cache-indicator.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/parse-stack.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/server/shared.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/shared/stack-frame.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/utils/get-error-by-type.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/container/runtime-error/render-error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/dev-overlay/shared.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/debug-channel.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/hot-reloader-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/fetch-event.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/response.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/segment-config/middleware/middleware-config.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/parse-url.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/base-http/node.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/async-callback-set.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/route-regex.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/route-matcher.d.ts","./node_modules/.pnpm/sharp@0.34.5/node_modules/sharp/lib/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/image-optimizer.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/next-server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/lru-cache.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/dev-bundler-service.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/static-paths-worker.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/dev/next-dev-server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/next.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/render-server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/router/utils/path-match.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/filesystem.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/router-utils/router-server-context.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/route-module.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/load-components.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/adapter.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/loaders/metadata/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/webpack/loaders/next-app-loader/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/lib/app-dir-module.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/app-render.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/vendored/contexts/entrypoints.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/error-boundary.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/layout-router.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/render-from-template-context.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/client-page.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/client-segment.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/http-access-fallback/error-boundary.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/alternative-urls-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/extra-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/metadata-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/manifest-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/opengraph-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/twitter-types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/metadata-interface.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/resolvers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/types/icons.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/resolve-metadata.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/metadata/metadata.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/lib/framework/boundary-components.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/rsc/preloads.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/rsc/postpone.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/rsc/taint.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/collect-segment-data.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/instant-validation/instant-validation.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/next-devtools/userspace/app/segment-explorer-node.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/app-render/entry-base.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/templates/app-page.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/helpers/prerender-manifest-matcher.d.ts","./node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/jsx-dev-runtime.d.ts","./node_modules/.pnpm/@types+react@19.2.14/node_modules/@types/react/compiler-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/vendored/rsc/entrypoints.d.ts","./node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/client.d.ts","./node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/static.d.ts","./node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.14/node_modules/@types/react-dom/server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/vendored/ssr/entrypoints.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/route-modules/app-page/module.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/fallback-params.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/image-response.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/user-agent.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/url-pattern.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/after/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/connection.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/exports/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request-meta.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/cli/next-test.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/size-limit.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/config-shared.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/base-http/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/api-utils/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/build/adapter/build-complete.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/html-context.shared-runtime.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/utils.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/pages/_app.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/app.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/unstable-cache.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/revalidate.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/web/spec-extension/unstable-no-store.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/use-cache/cache-tag.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/cache.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/pages/_document.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/document.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/dynamic.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dynamic.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/pages/_error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/catch-error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/api/error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/head.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/head.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/cookies.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/headers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/server/request/draft-mode.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/headers.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/get-img-props.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/image-component.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/shared/lib/image-external.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/image.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/link.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/link.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unrecognized-action-error.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/redirect.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/not-found.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/forbidden.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unauthorized.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unstable-rethrow.server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/unstable-rethrow.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/navigation.react-server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/components/navigation.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/navigation.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/router.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/client/script.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/script.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@edge-runtime/primitives/url.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@vercel/og/satori/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@vercel/og/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/server.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/types/global.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/types/compiled.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/image-types/global.d.ts","./.next/types/routes.d.ts","./next-env.d.ts","./app/manifest.ts","./lib/offline-queue.ts","./lib/push-subscribe.ts","./lib/pwa-install.ts","./node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/clsx.d.mts","./node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/types.d.ts","./lib/utils.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@next/font/dist/types.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/dist/compiled/@next/font/dist/google/index.d.ts","./node_modules/.pnpm/next@16.2.6_@babel+core@7.29.7_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/next/font/google/index.d.ts","./components/sw-register.tsx","./app/layout.tsx","./node_modules/.pnpm/lucide-react@1.17.0_react@19.2.4/node_modules/lucide-react/dist/lucide-react.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/reason-parts.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/reasons.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/createBaseUIEventDetails.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/types/index.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/types.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/button/Button.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/button/index.d.ts","./node_modules/.pnpm/class-variance-authority@0.7.1/node_modules/class-variance-authority/dist/types.d.ts","./node_modules/.pnpm/class-variance-authority@0.7.1/node_modules/class-variance-authority/dist/index.d.ts","./components/ui/button.tsx","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/form-context/FormContext.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/form/Form.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/form/index.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/root/FieldRoot.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/label/FieldLabel.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/useTransitionStatus.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/error/FieldError.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/description/FieldDescription.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/control/FieldControl.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/validity/FieldValidity.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/item/FieldItem.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/index.parts.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/field/index.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/input/Input.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/input/index.d.ts","./components/ui/input.tsx","./components/ui/label.tsx","./components/two-factor-flow.tsx","./components/login-form.tsx","./app/page.tsx","./components/wordmark.tsx","./components/account-settings.tsx","./app/account/page.tsx","./node_modules/.pnpm/@floating-ui+utils@0.2.11/node_modules/@floating-ui/utils/dist/floating-ui.utils.d.mts","./node_modules/.pnpm/@floating-ui+core@1.7.5/node_modules/@floating-ui/core/dist/floating-ui.core.d.mts","./node_modules/.pnpm/@floating-ui+utils@0.2.11/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.d.mts","./node_modules/.pnpm/@floating-ui+dom@1.7.6/node_modules/@floating-ui/dom/dist/floating-ui.dom.d.mts","./node_modules/.pnpm/@floating-ui+react-dom@2.1.8_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.d.mts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/components/FloatingTreeStore.d.ts","./node_modules/.pnpm/reselect@5.2.0/node_modules/reselect/dist/reselect.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/store/createSelector.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/store/createSelectorMemoized.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/fastHooks.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/store/Store.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/store/useStore.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/store/ReactStore.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/store/StoreInspector.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/store/index.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/utils/popups/inlineRect.d.ts","./node_modules/.pnpm/@base-ui+utils@0.2.9_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/useEnhancedClickHandler.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/utils/popups/popupTriggerMap.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/utils/popups/store.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/utils/popups/popupStoreUtils.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/utils/popups/index.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/components/FloatingRootStore.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/components/FloatingFocusManager.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/getStateAttributesProps.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/useRenderElement.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/components/FloatingPortal.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useClientPoint.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useDismiss.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useFocus.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/internals/shadowDom.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/utils/element.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useHoverShared.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useHover.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useHoverFloatingInteraction.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useHoverReferenceInteraction.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useListNavigation.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useTypeahead.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useFloatingRootContext.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/safePolygon.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/components/FloatingTree.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/types.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/components/FloatingDelayGroup.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useClick.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useFloating.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/hooks/useSyncedFloatingRootContext.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/floating-ui-react/index.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/utils/useAnchorPositioning.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/arrow/MenuArrow.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/backdrop/MenuBackdrop.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/store/MenuStore.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/root/MenuRootContext.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menubar/MenubarContext.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/context-menu/root/ContextMenuRoot.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/context-menu/root/ContextMenuRootContext.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/store/MenuHandle.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/root/MenuRoot.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/checkbox-item/MenuCheckboxItem.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/checkbox-item-indicator/MenuCheckboxItemIndicator.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/group/MenuGroup.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/group-label/MenuGroupLabel.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/item/MenuItem.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/link-item/MenuLinkItem.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/popup/MenuPopup.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/portal/MenuPortal.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/positioner/MenuPositioner.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/radio-group/MenuRadioGroup.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/radio-item/MenuRadioItem.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/radio-item-indicator/MenuRadioItemIndicator.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/submenu-root/MenuSubmenuRootContext.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/submenu-root/MenuSubmenuRoot.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/trigger/MenuTrigger.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/viewport/MenuViewport.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/separator/Separator.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/submenu-trigger/MenuSubmenuTrigger.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/index.parts.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/menu/index.d.ts","./components/ui/dropdown-menu.tsx","./components/tournament-status-badge.tsx","./components/tournament-card.tsx","./components/public-club.tsx","./app/clubs/[slug]/page.tsx","./components/install-prompt.tsx","./components/round-card.tsx","./components/dashboard.tsx","./app/dashboard/page.tsx","./components/more-menu.tsx","./app/more/page.tsx","./components/friends.tsx","./app/my-friends/page.tsx","./components/friend-profile.tsx","./app/my-friends/[id]/page.tsx","./components/notifications.tsx","./app/my-notifications/page.tsx","./components/own-rounds.tsx","./app/my-rounds/page.tsx","./components/round-header.tsx","./components/round-page-shell.tsx","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/merge-props/mergeProps.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/merge-props/index.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/use-render/useRender.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/use-render/index.d.ts","./components/ui/badge.tsx","./components/round-leaderboard.tsx","./components/round-detail.tsx","./app/my-rounds/[id]/page.tsx","./components/flight-group-leaderboard.tsx","./app/my-rounds/[id]/flights/page.tsx","./app/my-rounds/[id]/leaderboard/page.tsx","./components/round-scorecard.tsx","./components/round-stats.tsx","./app/my-rounds/[id]/scorecard/page.tsx","./app/my-rounds/[id]/stats/page.tsx","./components/course-rounds.tsx","./app/my-rounds/course/[name]/page.tsx","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/switch/root/SwitchRoot.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/switch/thumb/SwitchThumb.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/switch/index.parts.d.ts","./node_modules/.pnpm/@base-ui+react@1.5.0_@date-fns+tz@1.4.1_@types+react@19.2.14_date-fns@4.1.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/switch/index.d.ts","./components/ui/switch.tsx","./components/new-round.tsx","./app/my-rounds/new/page.tsx","./components/rounds-stats-summary.tsx","./app/my-rounds/stats/page.tsx","./components/org-members.tsx","./app/organizations/[id]/members/page.tsx","./components/public-tournament.tsx","./app/t/[id]/page.tsx","./components/public-live.tsx","./app/t/[id]/live/page.tsx","./components/tournament-detail.tsx","./components/individual-tournament-detail.tsx","./components/tournament-router.tsx","./app/tournaments/[id]/page.tsx","./components/tournament-leaderboard.tsx","./app/tournaments/[id]/leaderboard/page.tsx","./components/tournament-program.tsx","./app/tournaments/[id]/program/page.tsx","./components/session-blind-draw.tsx","./app/tournaments/[id]/sessions/[sessionId]/page.tsx","./components/session-individual-leaderboard.tsx","./app/tournaments/[id]/sessions/[sessionId]/individual-leaderboard/page.tsx","./components/session-scorecard.tsx","./app/tournaments/[id]/sessions/[sessionId]/matches/[matchId]/page.tsx","./components/team-chat.tsx","./app/tournaments/[id]/teams/[teamId]/chat/page.tsx","./components/verify-form.tsx","./app/verify/page.tsx","./components/verify-email-form.tsx","./app/verify-email/page.tsx","./components/watch-round.tsx","./app/watch/[id]/page.tsx","./components/ui/card.tsx","./.next/types/cache-life.d.ts","./.next/types/validator.ts"],"fileIdsList":[[91,145,162,163,487,488,489,490],[91,145,162,163],[91,145,162,163,230,531,534,547,578,581,662,666,668,670,672,674,676,686,688,689,692,693,695,702,704,706,708,710,714,716,718,720,722,724,726,728,730,732],[91,145,162,163,230,580],[91,145,162,163,230,532,661],[91,145,162,163,230,665],[91,145,162,163,230,529,532,545,546],[91,145,162,163,230,532],[91,145,162,163,230,667],[91,145,162,163,230,671],[91,145,162,163,230,669],[91,145,162,163,230,673],[91,145,162,163,230,687],[91,145,162,163,230,678,684],[81,91,145,162,163,230,685],[91,145,162,163,230,678,690,691],[91,145,162,163,230,521],[91,145,162,163,230,694],[81,91,145,162,163,230,701],[91,145,162,163,230,675],[91,145,162,163,230,703],[91,145,162,163,230,705],[91,145,162,163,230,505,521,577],[91,145,162,163,230,709],[91,145,162,163,230,532,707],[91,145,162,163,230,715],[91,145,162,163,230,713],[91,145,162,163,230,717],[91,145,162,163,230,721],[91,145,162,163,230,723],[91,145,162,163,230,719],[91,145,162,163,230,725],[81,91,145,162,163,230,579,729],[81,91,145,162,163,230,579,727],[91,145,162,163,230,731],[81,91,145,162,163,230,511,521,538,548,558,574,575,576,579],[81,91,145,162,163,230,511,521,548,579,664],[81,91,145,162,163,230,511,521,542,548,558,574,575,658,659,660,663,664],[81,91,145,162,163,230,511,542,548,683],[81,91,145,162,163,230,511,542,548],[81,91,145,162,163,230,511,521,542,548,574,579],[81,91,145,162,163,230,511,542,548,558,574,575,658,659],[81,91,145,162,163,230,539,548],[81,91,145,162,163,230,521,548,576],[81,91,145,162,163,230,511,521,548,665],[81,91,145,162,163,230,511,521,542,548,558,574,575,579,700],[81,91,145,162,163,230,511,521,542,548,579],[81,91,145,162,163,230,511,548,558,574,575],[81,91,145,162,163,230,511,521,548,558,579,664],[81,91,145,162,163,230,548,579,660],[81,91,145,162,163,230,511,542,548,579],[81,91,145,162,163,230,511,542,548,558,574,575,579],[81,91,145,162,163,230,511,521,537,542,548,558,574,575,678,683,684],[81,91,145,162,163,198,200,230,511,542,548,558],[81,91,145,162,163,230,521,677],[81,91,145,162,163,230,511,521,548],[81,91,145,162,163,230,511,542,548,558],[81,91,145,162,163,230,511,537,542,548,558],[81,91,145,162,163,230,539],[91,145,162,163,230,511,548,659],[81,91,145,162,163,230,511,542,548,558,574,575,658,700],[81,91,145,162,163,230,711,712],[91,145,162,163,230,542,548,658],[81,91,145,162,163,230,548,558,574,575],[91,145,162,163,230,542,557,680,682],[91,145,162,163,230,542,555,557],[81,91,145,162,163,230,542],[81,91,145,162,163,230,542,548,657],[81,91,145,162,163,230,542,573],[91,145,162,163,230,542,699],[81,91,145,162,163,230,521,548,558,574,575,576],[91,145,162,163,230,542,548],[91,145,162,163,230],[91,145,162,163,230,540,541],[91,145,162,163,532,533,534],[81,91,145,162,163,553],[91,145,162,163,554],[91,145,162,163,230,552,637,657],[81,91,145,162,163,634],[81,91,145,162,163,550,551,553,562],[81,91,145,162,163,553,562],[81,91,145,162,163,553,562,564],[91,145,162,163,562,563,565,566,567,568,569,570],[91,145,162,163,562,563,565,566,567,568,569],[81,91,145,162,163,553,561],[81,91,145,162,163,562,564],[81,91,145,162,163,622],[81,91,145,162,163,587,598,622],[81,91,145,162,163,553,606],[81,91,145,162,163,551,564,596,602,622],[81,91,145,162,163,587,622],[91,145,162,163,622],[91,145,162,163,550,622],[91,145,162,163,587,622],[91,145,162,163,551,603,622],[91,145,162,163,613,622],[81,91,145,162,163,553,587,613,622],[91,145,162,163,612,622],[91,145,162,163,552,596,602,603],[91,145,162,163,586,587,604,607,608,609,610,614,615,616,617,618,619,620,621,622,623,624,625,626],[91,145,162,163,613],[81,91,145,162,163,551,586,587,603,604,607,608,609,610,613,614,615,616,617,618,619,620,621,623,627],[91,145,162,163,602,611],[81,91,145,162,163,550,551,553,559],[91,145,162,163,560],[81,91,145,162,163,553,571],[91,145,162,163,572],[91,145,162,163,550],[81,91,145,162,163,560,562],[91,145,162,163,549],[81,91,145,162,163,552],[81,91,145,162,163,553,605],[81,91,145,162,163],[81,91,145,162,163,553,628],[81,91,145,162,163,553,564],[81,91,145,162,163,553,637],[91,145,162,163,629,630,637,638,639,640,641,642,643,644,645,646,647,648,649,651,652,653,655,656],[91,145,162,163,629,630,636,637,638,639,640,641,642,643,644,645,646,647,648,649,651,652,653,654,655],[81,91,145,162,163,553,564,598,628],[81,91,145,162,163,627],[81,91,145,162,163,230,550,551,602,631,632,633,635,636],[81,91,145,162,163,631,637],[91,145,162,163,631],[81,91,145,162,163,553,564,587,596,598,602,603,627,637,657],[91,145,162,163,230,637,650],[81,91,145,162,163,631],[81,91,145,162,163,553,636],[81,91,145,162,163,637],[91,145,162,163,679],[91,145,162,163,696,697,698],[91,145,162,163,696,697],[81,91,145,162,163,550,552,553,562],[81,91,145,162,163,553,696],[81,91,145,162,163,551],[91,145,162,163,553,681],[91,145,162,163,597,599,600,601],[81,91,145,162,163,586],[81,91,145,162,163,551,564,596,598,600],[91,145,162,163,553,564,599,603,627],[81,91,145,162,163,582,627],[91,145,162,163,592],[81,91,145,162,163,230,592],[91,145,162,163,588],[91,145,162,163,589],[91,145,162,163,589,590,592,593,594,595],[91,145,162,163,591,592],[91,145,162,163,582],[91,145,162,163,583,584],[81,91,145,162,163,585],[91,142,143,145,162,163],[91,144,145,162,163],[145,162,163],[91,145,150,162,163,180],[91,145,146,151,156,162,163,165,177,188],[91,145,146,147,156,162,163,165],[91,145,148,162,163,189],[91,145,149,150,157,162,163,166],[91,145,150,162,163,177,185],[91,145,151,153,156,162,163,165],[91,144,145,152,162,163],[91,145,153,154,162,163],[91,145,155,156,162,163],[91,144,145,156,162,163],[91,145,156,157,158,162,163,177,188],[91,145,156,157,158,162,163,172,177,180],[91,137,145,153,156,159,162,163,165,177,188],[91,145,156,157,159,160,162,163,165,177,185,188],[91,145,159,161,162,163,177,185,188],[91,145,156,162,163],[91,145,162,163,164,188],[91,145,153,156,162,163,165,177],[91,145,162,163,166],[91,145,162,163,167],[91,144,145,162,163,168],[91,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194],[91,145,162,163,170],[91,145,162,163,171],[91,145,156,162,163,172,173],[91,145,162,163,172,174,189,191],[91,145,157,162,163],[91,145,156,162,163,177,178,180],[91,145,162,163,179,180],[91,145,162,163,177,178],[91,145,162,163,180],[91,145,162,163,181],[91,142,145,162,163,177,182],[91,145,156,162,163,183,184],[91,145,162,163,183,184],[91,145,150,162,163,165,177,185],[91,145,162,163,186],[88,89,90,91,92,93,94,95,96,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194],[91,145,162,163,165,187],[91,145,159,162,163,171,188],[91,145,150,162,163,189],[91,145,162,163,177,190],[91,145,162,163,164,191],[91,145,162,163,192],[91,145,150,162,163],[91,137,145,162,163],[91,145,162,163,193],[91,137,145,156,158,162,163,168,177,180,188,190,191,193],[91,145,162,163,177,194],[81,85,91,145,162,163,196,197,198,200,482,527],[81,85,91,145,162,163,196,197,198,199,463,482,527],[81,85,91,145,162,163,196,197,199,200,482,527],[81,91,145,162,163,200,463,464],[81,91,145,162,163,200,463],[81,85,91,145,162,163,197,198,199,200,482,527],[81,85,91,145,162,163,196,198,199,200,482,527],[79,80,91,145,162,163],[91,145,162,163,540,556],[91,145,162,163,540],[91,145,162,163,485],[91,145,162,163,433,496,497],[91,145,162,163,205,206,208,220,244,359,370,478],[91,145,162,163,208,239,240,241,243,478],[91,145,162,163,208,376,378,380,381,383,478,480],[91,145,162,163,208,242,279,478],[91,145,162,163,206,208,219,220,226,232,237,358,359,360,369,478,480],[91,145,162,163,478],[91,145,162,163,215,221,240,260,355],[91,145,162,163,208],[91,145,162,163,201,215,221],[91,145,162,163,387],[91,145,162,163,384,385,387],[91,145,162,163,384,386,478],[91,145,159,162,163,260,457,475],[91,145,159,162,163,331,334,350,355,475],[91,145,159,162,163,303,475],[91,145,162,163,363],[91,145,162,163,362,363,364],[91,145,162,163,362],[87,91,145,159,162,163,201,208,220,226,232,238,240,244,245,258,259,326,356,357,370,478,482],[91,145,162,163,205,208,242,279,376,377,382,478,530],[91,145,162,163,242,530],[91,145,162,163,205,259,428,478,530],[91,145,162,163,530],[91,145,162,163,208,242,243,530],[91,145,162,163,379,530],[91,145,162,163,245,358,361,368],[81,91,145,162,163,433],[91,145,162,163,171,215,230],[91,145,162,163,215,230],[81,91,145,162,163,300],[81,91,145,162,163,230],[81,91,145,162,163,221,230,433],[91,145,162,163,215,286,300,301,512,519],[91,145,162,163,285,513,514,515,516,518],[91,145,162,163,336],[91,145,162,163,336,337],[91,145,162,163,219,221,288,289],[91,145,162,163,221,295,296],[91,145,162,163,221,290,298],[91,145,162,163,295],[91,145,162,163,213,221,288,289,290,291,292,293,294,295,298],[91,145,162,163,221,288,295,296,297,299],[91,145,162,163,221,289,291,292],[91,145,162,163,289,291,294,296],[91,145,162,163,517],[91,145,162,163,221],[81,91,145,162,163,209,506],[81,91,145,162,163,188],[81,91,145,162,163,242,277],[81,91,145,162,163,242,370],[91,145,162,163,275,280],[81,91,145,162,163,276,484],[91,145,162,163,543],[81,85,91,145,159,162,163,196,197,198,199,200,482,526],[91,145,159,162,163,221],[91,145,159,162,163,220,225,306,323,365,366,370,425,427,478,479],[91,145,162,163,258,367],[91,145,162,163,482],[91,145,162,163,207],[81,91,145,162,163,212,215,430,446,448],[91,145,162,163,171,215,430,445,446,447,529],[91,145,162,163,439,440,441,442,443,444],[91,145,162,163,441],[91,145,162,163,445],[91,145,162,163,230,394,395,397],[81,91,145,162,163,221,388,389,390,391,396],[91,145,162,163,394,396],[91,145,162,163,392],[91,145,162,163,393],[81,91,145,162,163,230,276,484],[81,91,145,162,163,230,483,484],[81,91,145,162,163,230,484],[91,145,162,163,323,324],[91,145,162,163,324],[91,145,159,162,163,479,484],[91,145,162,163,353],[91,144,145,162,163,352],[91,145,162,163,215,221,227,229,331,344,348,350,427,430,467,468,475,479],[91,145,162,163,221,270,292],[91,145,162,163,331,342,345,350],[81,91,145,162,163,212,215,331,334,350,353,387,434,435,436,437,438,449,450,451,452,453,454,455,456,530],[91,145,162,163,212,215,240,331,338,339,340,343,344],[91,145,162,163,177,221,240,342,349,430,431,475],[91,145,162,163,346],[91,145,159,162,163,171,209,221,225,235,267,268,271,323,326,391,425,426,467,478,479,480,482,530],[91,145,162,163,212,213,215],[91,145,162,163,331],[91,144,145,162,163,240,267,268,325,326,327,328,329,330,479],[91,145,162,163,350],[91,144,145,162,163,214,215,225,229,265,331,338,339,340,341,342,345,346,347,348,349,468],[91,145,159,162,163,265,266,338,479,480],[91,145,162,163,240,268,323,326,331,427,479],[91,145,159,162,163,478,480],[91,145,159,162,163,177,475,479,480],[91,145,159,162,163,171,201,215,220,227,229,232,235,242,262,267,268,269,270,271,306,307,309,312,314,317,318,319,320,322,370,425,427,475,478,479,480],[91,145,159,162,163,177],[91,145,162,163,208,209,210,238,475,476,477,482,484,530],[91,145,162,163,205,206,478],[91,145,162,163,399],[91,145,159,162,163,177,188,217,383,387,388,389,390,391,397,398,530],[91,145,162,163,171,188,201,215,217,229,232,268,307,312,322,323,376,403,404,405,411,414,415,425,427,475,478],[91,145,162,163,232,238,245,258,268,326,478],[91,145,159,162,163,188,209,220,229,268,409,475,478],[91,145,162,163,429],[91,145,159,162,163,399,412,413,422],[91,145,162,163,475,478],[91,145,162,163,328,468],[91,145,162,163,229,267,370,484],[91,145,159,162,163,171,207,312,372,376,405,411,414,417,475],[91,145,159,162,163,245,258,376,418],[91,145,162,163,208,269,370,420,478,480],[91,145,159,162,163,188,391,478],[91,145,159,162,163,242,269,370,371,372,381,399,419,421,478],[87,91,145,159,162,163,267,424,482,484],[91,145,162,163,321,425],[91,145,159,162,163,171,215,218,220,221,227,229,235,244,245,258,268,271,307,309,319,322,323,370,403,404,405,406,408,410,425,427,475,484],[91,145,159,162,163,177,245,411,416,422,475],[91,145,162,163,248,249,250,251,252,253,254,255,256,257],[91,145,162,163,262,313],[91,145,162,163,315],[91,145,162,163,313],[91,145,162,163,315,316],[91,145,159,162,163,219,220,221,225,226,479],[91,145,159,162,163,171,207,209,227,231,267,270,271,305,425,475,480,482,484],[91,145,159,162,163,171,188,211,218,219,229,231,268,423,468,474,479],[91,145,162,163,338],[91,145,162,163,339],[91,145,162,163,221,232,467],[91,145,162,163,340],[91,145,162,163,214],[91,145,162,163,216,228],[91,145,159,162,163,216,220,227],[91,145,162,163,223,228],[91,145,162,163,224],[91,145,162,163,216,217],[91,145,162,163,216,272],[91,145,162,163,216],[91,145,162,163,218,262,311],[91,145,162,163,310],[91,145,162,163,215,217,218],[91,145,162,163,218,308],[91,145,162,163,215,217],[91,145,162,163,267,370],[91,145,162,163,467],[91,145,159,162,163,188,227,229,233,267,370,424,427,430,431,432,458,459,462,466,468,475,479],[91,145,162,163,281,284,286,287,300,301],[81,91,145,162,163,198,200,230,460,461],[81,91,145,162,163,198,200,230,460,461,465],[91,145,162,163,354],[91,145,162,163,240,261,266,267,331,332,333,334,335,337,350,351,353,356,424,427,478,480],[91,145,162,163,300],[91,145,159,162,163,305,475],[91,145,162,163,305],[91,145,159,162,163,227,273,302,304,306,424,475,482,484],[91,145,162,163,281,282,283,284,286,287,300,301,483],[87,91,145,159,162,163,171,188,216,217,229,235,267,268,271,370,422,423,425,475,478,479,482],[91,145,162,163,212,215,222],[91,145,162,163,266,268,400,403],[91,145,162,163,266,401,469,470,471,472,473],[91,145,159,162,163,262,478],[91,145,159,162,163],[91,145,162,163,265,350],[91,145,162,163,264],[91,145,162,163,266,319],[91,145,162,163,263,265,478],[91,145,159,162,163,211,266,400,401,402,475,478,479],[81,91,145,162,163,215,221,299],[81,91,145,162,163,213],[91,145,162,163,203,204],[81,91,145,162,163,209],[81,91,145,162,163,215,285],[81,87,91,145,162,163,267,271,482,484],[91,145,162,163,209,506,507],[81,91,145,162,163,280],[81,91,145,162,163,171,188,207,274,276,278,279,484],[91,145,162,163,215,242,479],[91,145,162,163,215,407],[81,91,145,157,159,162,163,171,205,207,280,378,482,483],[81,91,145,162,163,196,197,198,199,200,482,527],[81,82,83,84,85,91,145,162,163],[91,145,162,163,373,374,375],[91,145,162,163,373],[81,85,91,145,159,161,162,163,171,195,196,197,198,199,200,201,207,235,240,417,445,480,481,484,527],[91,145,162,163,492],[91,145,162,163,494],[91,145,162,163,498],[91,145,162,163,544],[91,145,162,163,500],[91,145,162,163,502,503,504],[91,145,162,163,508],[86,91,145,162,163,486,491,493,495,499,501,505,509,511,521,522,524,528,529,530,531],[91,145,162,163,510],[91,145,162,163,520],[91,145,162,163,276],[91,145,162,163,523],[91,144,145,162,163,266,400,401,403,469,470,472,473,525,527],[91,145,162,163,195],[91,145,162,163,177,195],[91,103,106,109,110,145,162,163,188],[91,106,145,162,163,177,188],[91,106,110,145,162,163,188],[91,145,162,163,177],[91,100,145,162,163],[91,104,145,162,163],[91,102,103,106,145,162,163,188],[91,145,162,163,165,185],[91,100,145,162,163,195],[91,102,106,145,162,163,165,188],[91,97,98,99,101,105,145,156,162,163,177,188],[91,106,114,122,145,162,163],[91,98,104,145,162,163],[91,106,131,132,145,162,163],[91,98,101,106,145,162,163,180,188,195],[91,106,145,162,163],[91,102,106,145,162,163,188],[91,97,145,162,163],[91,100,101,102,104,105,106,107,108,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,132,133,134,135,136,145,162,163],[91,106,124,127,145,153,162,163],[91,106,114,115,116,145,162,163],[91,104,106,115,117,145,162,163],[91,105,145,162,163],[91,98,100,106,145,162,163],[91,106,110,115,117,145,162,163],[91,110,145,162,163],[91,104,106,109,145,162,163,188],[91,98,102,106,114,145,162,163],[91,106,124,145,162,163],[91,117,145,162,163],[91,100,106,131,145,162,163,180,193,195]],"fileInfos":[{"version":"e41c290ef7dd7dab3493e6cbe5909e0148edf4a8dad0271be08edec368a0f7b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"8fd575e12870e9944c7e1d62e1f5a73fcf23dd8d3a321f2a2c74c20d022283fe","impliedFormat":1},{"version":"e12a46ce14b817d4c9e6b2b478956452330bf00c9801b79de46f7a1815b5bd40","impliedFormat":1},{"version":"4fd3f3422b2d2a3dfd5cdd0f387b3a8ec45f006c6ea896a4cb41264c2100bb2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"69e65d976bf166ce4a9e6f6c18f94d2424bf116e90837ace179610dbccad9b42","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"62bb211266ee48b2d0edf0d8d1b191f0c24fc379a82bd4c1692a082c540bc6b1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"936e80ad36a2ee83fc3caf008e7c4c5afe45b3cf3d5c24408f039c1d47bdc1df","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"fef8cfad2e2dc5f5b3d97a6f4f2e92848eb1b88e897bb7318cef0e2820bceaab","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"f1e2a172204962276504466a6393426d2ca9c54894b1ad0a6c9dad867a65f876","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"b5ce7a470bc3628408429040c4e3a53a27755022a32fd05e2cb694e7015386c7","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"bab26767638ab3557de12c900f0b91f710c7dc40ee9793d5a27d32c04f0bf646","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"87dc0f382502f5bbce5129bdc0aea21e19a3abbc19259e0b43ae038a9fc4e326","affectsGlobalScope":true,"impliedFormat":1},{"version":"b1cb28af0c891c8c96b2d6b7be76bd394fddcfdb4709a20ba05a7c1605eea0f9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2fef54945a13095fdb9b84f705f2b5994597640c46afeb2ce78352fab4cb3279","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac77cb3e8c6d3565793eb90a8373ee8033146315a3dbead3bde8db5eaf5e5ec6","affectsGlobalScope":true,"impliedFormat":1},{"version":"56e4ed5aab5f5920980066a9409bfaf53e6d21d3f8d020c17e4de584d29600ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ece9f17b3866cc077099c73f4983bddbcb1dc7ddb943227f1ec070f529dedd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a6282c8827e4b9a95f4bf4f5c205673ada31b982f50572d27103df8ceb8013c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1c9319a09485199c1f7b0498f2988d6d2249793ef67edda49d1e584746be9032","affectsGlobalScope":true,"impliedFormat":1},{"version":"e3a2a0cee0f03ffdde24d89660eba2685bfbdeae955a6c67e8c4c9fd28928eeb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811c71eee4aa0ac5f7adf713323a5c41b0cf6c4e17367a34fbce379e12bbf0a4","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"d4b1d2c51d058fc21ec2629fff7a76249dec2e36e12960ea056e3ef89174080f","affectsGlobalScope":true,"impliedFormat":1},{"version":"61d6a2092f48af66dbfb220e31eea8b10bc02b6932d6e529005fd2d7b3281290","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"7e29f41b158de217f94cb9676bf9cbd0cd9b5a46e1985141ed36e075c52bf6ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"dc0a7f107690ee5cd8afc8dbf05c4df78085471ce16bdd9881642ec738bc81fe","impliedFormat":1},{"version":"acd8fd5090ac73902278889c38336ff3f48af6ba03aa665eb34a75e7ba1dccc4","impliedFormat":1},{"version":"d6258883868fb2680d2ca96bc8b1352cab69874581493e6d52680c5ffecdb6cc","impliedFormat":1},{"version":"1b61d259de5350f8b1e5db06290d31eaebebc6baafd5f79d314b5af9256d7153","impliedFormat":1},{"version":"f258e3960f324a956fc76a3d3d9e964fff2244ff5859dcc6ce5951e5413ca826","impliedFormat":1},{"version":"643f7232d07bf75e15bd8f658f664d6183a0efaca5eb84b48201c7671a266979","impliedFormat":1},{"version":"21da358700a3893281ce0c517a7a30cbd46be020d9f0c3f2834d0a8ad1f5fc75","impliedFormat":1},{"version":"cebfeedf63623d54f7423d58cae1ec204c0d8d97a66d5fb17579e26a902085e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"378281aa35786c27d5811af7e6bcaa492eebd0c7013d48137c35bbc69a2b9751","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"1b2dd1cbeb0cc6ae20795958ba5950395ebb2849b7c8326853dd15530c77ab0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"387a023d363f755eb63450a66c28b14cdd7bc30a104565e2dbf0a8988bb4a56c","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","impliedFormat":1},{"version":"487b694c3de27ddf4ad107d4007ad304d29effccf9800c8ae23c2093638d906a","impliedFormat":1},{"version":"3a80bc85f38526ca3b08007ee80712e7bb0601df178b23fbf0bf87036fce40ce","impliedFormat":1},{"version":"ccf4552357ce3c159ef75f0f0114e80401702228f1898bdc9402214c9499e8c0","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","impliedFormat":1},{"version":"2931540c47ee0ff8a62860e61782eb17b155615db61e36986e54645ec67f67c2","impliedFormat":1},{"version":"ccab02f3920fc75c01174c47fcf67882a11daf16baf9e81701d0a94636e94556","impliedFormat":1},{"version":"f6faf5f74e4c4cc309a6c6a6c4da02dbb840be5d3e92905a23dcd7b2b0bd1986","impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","impliedFormat":1},{"version":"33e981bf6376e939f99bd7f89abec757c64897d33c005036b9a10d9587d80187","impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","impliedFormat":1},{"version":"b41767d372275c154c7ea6c9d5449d9a741b8ce080f640155cc88ba1763e35b3","impliedFormat":1},{"version":"3bacf516d686d08682751a3bd2519ea3b8041a164bfb4f1d35728993e70a2426","impliedFormat":1},{"version":"7fb266686238369442bd1719bc0d7edd0199da4fb8540354e1ff7f16669b4323","impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","impliedFormat":1},{"version":"5b03a034c72146b61573aab280f295b015b9168470f2df05f6080a2122f9b4df","impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","impliedFormat":1},{"version":"54c3e2371e3d016469ad959697fd257e5621e16296fa67082c2575d0bf8eced0","impliedFormat":1},{"version":"beb8233b2c220cfa0feea31fbe9218d89fa02faa81ef744be8dce5acb89bb1fd","impliedFormat":1},{"version":"c183b931b68ad184bc8e8372bf663f3d33304772fb482f29fb91b3c391031f3e","impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","impliedFormat":1},{"version":"48cc3ec153b50985fb95153258a710782b25975b10dd4ac8a4f3920632d10790","impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","impliedFormat":1},{"version":"e1528ca65ac90f6fa0e4a247eb656b4263c470bb22d9033e466463e13395e599","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"866078923a56d026e39243b4392e282c1c63159723996fa89243140e1388a98d","impliedFormat":1},{"version":"830171b27c5fdf9bcbe4cf7d428fcf3ae2c67780fb7fbdccdf70d1623d938bc4","affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d97fb21da858fb18b8ae72c314e9743fd52f73ebe2764e12af1db32fc03f853f","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ea15fd99b2e34cb25fe8346c955000bb70c8b423ae4377a972ef46bfb37f595","impliedFormat":1},{"version":"7cf69dd5502c41644c9e5106210b5da7144800670cbe861f66726fa209e231c4","impliedFormat":1},{"version":"72c1f5e0a28e473026074817561d1bc9647909cf253c8d56c41d1df8d95b85f7","impliedFormat":1},{"version":"f9b4137a0d285bd77dba2e6e895530112264310ae47e07bf311feae428fb8b61","affectsGlobalScope":true,"impliedFormat":1},{"version":"8b21e13ed07d0df176ae31d6b7f01f7b17d66dbeb489c0d31d00de2ca14883da","impliedFormat":1},{"version":"51aecd2df90a3cffea1eb4696b33b2d78594ea2aa2138e6b9471ec4841c6c2ee","impliedFormat":1},{"version":"9d8f9e63e29a3396285620908e7f14d874d066caea747dc4b2c378f0599166b4","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"f929f0b6b3421a2d34344b0f421f45aeb2c84ad365ebf29d04312023b3accc58","impliedFormat":1},{"version":"db9ada976f9e52e13f7ae8b9a320f4b67b87685938c5879187d8864b2fbe97f3","impliedFormat":1},{"version":"9f39e70a354d0fba29ac3cdf6eca00b7f9e96f64b2b2780c432e8ea27f133743","impliedFormat":1},{"version":"0dace96cc0f7bc6d0ee2044921bdf19fe42d16284dbcc8ae200800d1c9579335","impliedFormat":1},{"version":"a2e2bbde231b65c53c764c12313897ffdfb6c49183dd31823ee2405f2f7b5378","impliedFormat":1},{"version":"ad1cc0ed328f3f708771272021be61ab146b32ecf2b78f3224959ff1e2cd2a5c","impliedFormat":1},{"version":"c64e1888baaa3253ca4405b455e4bf44f76357868a1bd0a52998ade9a092ad78","affectsGlobalScope":true,"impliedFormat":1},{"version":"d8cf132379078d0974a59df26069689a2d33c7dc826b5be56231841cb2f32e58","impliedFormat":1},{"version":"fbf413fc617837453c878a9174a1f1b383616857a3f8366bc41cf30df4aea7d5","impliedFormat":1},{"version":"148c73ec11318850f571172ceae3e55ce479d850fe18ec8eae0abd99d9f6c319","impliedFormat":1},{"version":"230bdc111d7578276e4a3bb9d075d85c78c6b68f428c3a9935e2eaa10f4ae1f5","impliedFormat":1},{"version":"e8aabbee5e7b9101b03bb4222607d57f38859b8115a8050a4eb91b4ee43a3a73","impliedFormat":1},{"version":"bbf42f98a5819f4f06e18c8b669a994afe9a17fe520ae3454a195e6eabf7700d","impliedFormat":1},{"version":"c0bb1b65757c72bbf8ddf7eaa532223bacf58041ff16c883e76f45506596e925","impliedFormat":1},{"version":"c8b85f7aed29f8f52b813f800611406b0bfe5cf3224d20a4bdda7c7f73ce368e","affectsGlobalScope":true,"impliedFormat":1},{"version":"145dcf25fd4967c610c53d93d7bc4dce8fbb1b6dd7935362472d4ae49363c7ba","impliedFormat":1},{"version":"ff65b8a8bd380c6d129becc35de02f7c29ad7ce03300331ca91311fb4044d1a9","impliedFormat":1},{"version":"04bf1aa481d1adfb16d93d76e44ce71c51c8ef68039d849926551199489637f6","impliedFormat":1},{"version":"9043daec15206650fa119bad6b8d70136021ea7d52673a71f79a87a42ee38d44","affectsGlobalScope":true,"impliedFormat":1},{"version":"0b055dae40c0e27154f109c4ff771ae748db161c503a1687e3d4b9c91ba20de3","affectsGlobalScope":true,"impliedFormat":1},{"version":"a58a15da4c5ba3df60c910a043281256fa52d36a0fcdef9b9100c646282e88dd","impliedFormat":1},{"version":"b36beffbf8acdc3ebc58c8bb4b75574b31a2169869c70fc03f82895b93950a12","impliedFormat":1},{"version":"de263f0089aefbfd73c89562fb7254a7468b1f33b61839aafc3f035d60766cb4","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"8c81fd4a110490c43d7c578e8c6f69b3af01717189196899a6a44f93daa57a3a","impliedFormat":1},{"version":"5fb39858b2459864b139950a09adae4f38dad87c25bf572ce414f10e4bd7baab","impliedFormat":1},{"version":"65faec1b4bd63564aeec33eab9cacfaefd84ce2400f03903a71a1841fbce195f","impliedFormat":1},{"version":"b33b74b97952d9bf4fbd2951dcfbb5136656ddb310ce1c84518aaa77dbca9992","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"6b306cd4282bbb54d4a6bb23cfb7a271160983dfc38c67b5a132504cfcc34896","affectsGlobalScope":true,"impliedFormat":1},{"version":"c119835edf36415081dfd9ed15fc0cd37aaa28d232be029ad073f15f3d88c323","impliedFormat":1},{"version":"450172a56b944c2d83f45cc11c9a388ea967cd301a21202aa0a23c34c7506a18","impliedFormat":1},{"version":"9705cd157ffbb91c5cab48bdd2de5a437a372e63f870f8a8472e72ff634d47c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"ae86f30d5d10e4f75ce8dcb6e1bd3a12ecec3d071a21e8f462c5c85c678efb41","impliedFormat":1},{"version":"72f8936aebf0c4a1adab767b97d34ba7d3a308afcf76de4417b9c16fb92ed548","impliedFormat":1},{"version":"e03460fe72b259f6d25ad029f085e4bedc3f90477da4401d8fbc1efa9793230e","impliedFormat":1},{"version":"4286a3a6619514fca656089aee160bb6f2e77f4dd53dc5a96b26a0b4fc778055","impliedFormat":1},{"version":"69e0a41d620fb678a899c65e073413b452f4db321b858fe422ad93fd686cd49a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3585d6891e9ea18e07d0755a6d90d71331558ba5dc5561933553209f886db106","affectsGlobalScope":true,"impliedFormat":1},{"version":"86be71cbb0593468644932a6eb96d527cfa600cecfc0b698af5f52e51804451d","impliedFormat":1},{"version":"84dd6b0fd2505135692935599d6606f50a421389e8d4535194bcded307ee5cf2","impliedFormat":1},{"version":"0d5b085f36e6dc55bc6332ecb9c733be3a534958c238fb8d8d18d4a2b6f2a15a","impliedFormat":1},{"version":"db19ea066fdc5f97df3f769e582ae3000380ab7942e266654bdb1a4650d19eaf","affectsGlobalScope":true,"impliedFormat":1},{"version":"2a034894bf28c220a331c7a0229d33564803abe2ac1b9a5feee91b6b9b6e88ea","impliedFormat":1},{"version":"63228283eda38338f98e629d8033bffe727d2247ba2f3a1cbd1273a6827a1ec4","impliedFormat":1},{"version":"2beff543f6e9a9701df88daeee3cdd70a34b4a1c11cb4c734472195a5cb2af54","impliedFormat":1},{"version":"2e07abf27aa06353d46f4448c0bbac73431f6065eef7113128a5cd804d0c384d","impliedFormat":1},{"version":"be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","impliedFormat":1},{"version":"42bc0e1a903408137c3df2b06dfd7e402cdab5bbfa5fcfb871b22ebfdb30bd0b","impliedFormat":1},{"version":"9894dafe342b976d251aac58e616ac6df8db91fb9d98934ff9dd103e9e82578f","impliedFormat":1},{"version":"413df52d4ea14472c2fa5bee62f7a40abd1eb49be0b9722ee01ee4e52e63beb2","impliedFormat":1},{"version":"db6d2d9daad8a6d83f281af12ce4355a20b9a3e71b82b9f57cddcca0a8964a96","impliedFormat":1},{"version":"446a50749b24d14deac6f8843e057a6355dd6437d1fac4f9e5ce4a5071f34bff","impliedFormat":1},{"version":"182e9fcbe08ac7c012e0a6e2b5798b4352470be29a64fdc114d23c2bab7d5106","impliedFormat":1},{"version":"2f4e6b4d39426a1b85ecf4bdeb9dddbf4d9b3397d95d8555d46f925c9519ec7d","impliedFormat":1},{"version":"78a2869ad0cbf3f9045dda08c0d4562b7e1b2bfe07b19e0db072f5c3c56e9584","impliedFormat":1},{"version":"89d5d28d4f57e000b836ac273079be1b75710e28ce14750d081fb420d37e2ca5","impliedFormat":1},{"version":"fd4e24ccff3966390600d7f5d6aa1fed5a512e92ada735ea5fbc933d313ad3d3","impliedFormat":1},{"version":"b7cddfe1aa6b86b5fad3c9ccb30d05b3ccb165aebbf112f48d2d8a5f69dd98b1","impliedFormat":1},{"version":"a86f82d646a739041d6702101afa82dcb935c416dd93cbca7fd754fd0282ce1f","impliedFormat":1},{"version":"ad0d1d75d129b1c80f911be438d6b61bfa8703930a8ff2be2f0e1f8a91841c64","impliedFormat":1},{"version":"bd2c7ada3dee03653d3f601011d30072194bc3970cd93208f9588fbdc0c69347","impliedFormat":1},{"version":"e480da45d32313e7174b265674da504f075f59ef326852f0c5a5d863b438ae85","impliedFormat":1},{"version":"ad54850f61fcf5d014e11be80d2f46fea9265cfa7e77456da876f7833ef81769","impliedFormat":1},{"version":"6f7c9e8bd2b5b6a080b07080065f94900bd3c7e5ebbd3047bc33fcce2fab1dd8","impliedFormat":1},{"version":"3e7efde639c6a6c3edb9847b3f61e308bf7a69685b92f665048c45132f51c218","impliedFormat":1},{"version":"df45ca1176e6ac211eae7ddf51336dc075c5314bc5c253651bae639defd5eec5","impliedFormat":1},{"version":"8a0e762ceb20c7e72504feef83d709468a70af4abccb304f32d6b9bac1129b2c","impliedFormat":1},{"version":"da5950ee2a90721df6f3fba45f5d05308f7e4c35835392215dd2cd404505e2de","impliedFormat":1},{"version":"ce75b1aebb33d510ff28af960a9221410a3eaf7f18fc5f21f9404075fba77256","impliedFormat":1},{"version":"f42d5fed19610d485c646a0c430e768115567d078c7fc855c57b0c578b3d6cd3","impliedFormat":1},{"version":"ee8df1cb8d0faaca4013a1b442e99130769ce06f438d18d510fed95890067563","impliedFormat":1},{"version":"d5630f2ad9b4541e5ce891648121022f9412ecdca1820baa1f0104f70fd7eff7","impliedFormat":1},{"version":"4d15375ab13497104bc8fe56fdef2b5fd6853f29255737d23a33fa306ff7fd69","impliedFormat":1},{"version":"2cd3fc1d0d6a1e85baffd2d4f50f5efb192b5446eef567e97c94765402f0aad4","impliedFormat":1},{"version":"e4cbf2f1e89ecccaddd2c045e600ae41b732295953fb06247c7dcbc2d281ed30","impliedFormat":1},{"version":"6dcedaef57dff0d79a05ab0ab602cde74db803d1e765468bf91263786a383e1b","impliedFormat":1},{"version":"8c1697d90c394a6fd955b98eae01238eff628e129b987a68aea10f898a48e7da","impliedFormat":1},{"version":"7580e62139cb2b44a0270c8d01abcbfcba2819a02514a527342447fa69b34ef1","impliedFormat":1},{"version":"42c169fb8c2d42f4f668c624a9a11e719d5d07dacbebb63cbcf7ef365b0a75b3","impliedFormat":1},{"version":"f374cb24e93e7798c4d9e83ff872fa52d2cdb36306392b840a6ddf46cb925cb6","impliedFormat":1},{"version":"d10d63718e1646c2279e3b33831f82c60e31f622b2b7020f1196409ca4c09242","impliedFormat":1},{"version":"106c6025f1d99fd468fd8bf6e5bda724e11e5905a4076c5d29790b6c3745e50c","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"148679c6d0f449210a96e7d2e562d589e56fcde87f843a92808b3ff103f1a774","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"02436d7e9ead85e09a2f8e27d5f47d9464bced31738dec138ca735390815c9f0","impliedFormat":1},{"version":"f8d5ff8eafd37499f2b6a98659dd9b45a321de186b8db6b6142faed0fea3de77","impliedFormat":1},{"version":"c86fe861cf1b4c46a0fb7d74dffe596cf679a2e5e8b1456881313170f092e3fa","impliedFormat":1},{"version":"a22dd55aa4d39906252000ab8e8a1b83b195eef7f4274eb51e457c1f11cf6580","impliedFormat":1},{"version":"540cc83ab772a2c6bc509fe1354f314825b5dba3669efdfbe4693ecd3048e34f","impliedFormat":1},{"version":"121b0696021ab885c570bbeb331be8ad82c6efe2f3b93a6e63874901bebc13e3","impliedFormat":1},{"version":"612d9da66bb046a9c1e2e8d026245ded881fc4b9f98cbfae714415d57ee0ae0b","impliedFormat":1},{"version":"32c2ad9494dad5d11b0564a619fee18f388db6c1e9e2cd3c360b3122549691eb","impliedFormat":1},{"version":"6c301d40aec56a74ec7bd7324e31a728dadf9bfba3e96def02938d3d973534ec","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"aa14cee20aa0db79f8df101fc027d929aec10feb5b8a8da3b9af3895d05b7ba2","impliedFormat":1},{"version":"493c700ac3bd317177b2eb913805c87fe60d4e8af4fb39c41f04ba81fae7e170","impliedFormat":1},{"version":"aeb554d876c6b8c818da2e118d8b11e1e559adbe6bf606cc9a611c1b6c09f670","impliedFormat":1},{"version":"acf5a2ac47b59ca07afa9abbd2b31d001bf7448b041927befae2ea5b1951d9f9","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"d71291eff1e19d8762a908ba947e891af44749f3a2cbc5bd2ec4b72f72ea795f","impliedFormat":1},{"version":"c0480e03db4b816dff2682b347c95f2177699525c54e7e6f6aa8ded890b76be7","impliedFormat":1},{"version":"25a5f6fd3a2243c859eddc99ab5fba11d970af2fe7a5df9c32b7668f76f97b01","impliedFormat":1},{"version":"8d207e1f9d2c30d6f77dfa693f3827c3fbf0d89240297e10bdfe1041d433df68","impliedFormat":1},{"version":"b620391fe8060cf9bedc176a4d01366e6574d7a71e0ac0ab344a4e76576fcbb8","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"2652448ac55a2010a1f71dd141f828b682298d39728f9871e1cdf8696ef443fd","impliedFormat":1},{"version":"d682336018141807fb602709e2d95a192828fcb8d5ba06dda3833a8ea98f69e3","impliedFormat":1},{"version":"6124e973eab8c52cabf3c07575204efc1784aca6b0a30c79eb85fe240a857efa","impliedFormat":1},{"version":"0d891735a21edc75df51f3eb995e18149e119d1ce22fd40db2b260c5960b914e","impliedFormat":1},{"version":"3b414b99a73171e1c4b7b7714e26b87d6c5cb03d200352da5342ab4088a54c85","impliedFormat":1},{"version":"4fbd3116e00ed3a6410499924b6403cc9367fdca303e34838129b328058ede40","impliedFormat":1},{"version":"9c82171d836c47486074e4ca8e059735bf97b205e70b196535b5efd40cbe1bc5","impliedFormat":1},{"version":"8c70ddc0c22d85e56011d49fddfaae3405eb53d47b59327b9dd589e82df672e7","impliedFormat":1},{"version":"2f9c89cbb29d362290531b48880a4024f258c6033aaeb7e59fbc62db26819650","impliedFormat":1},{"version":"a365c4d3bed3be4e4e20793c999c51f5cd7e6792322f14650949d827fbcd170f","impliedFormat":1},{"version":"c5426dbfc1cf90532f66965a7aa8c1136a78d4d0f96d8180ecbfc11d7722f1a5","impliedFormat":1},{"version":"65a15fc47900787c0bd18b603afb98d33ede930bed1798fc984d5ebb78b26cf9","impliedFormat":1},{"version":"9d202701f6e0744adb6314d03d2eb8fc994798fc83d91b691b75b07626a69801","impliedFormat":1},{"version":"de9d2df7663e64e3a91bf495f315a7577e23ba088f2949d5ce9ec96f44fba37d","impliedFormat":1},{"version":"c7af78a2ea7cb1cd009cfb5bdb48cd0b03dad3b54f6da7aab615c2e9e9d570c5","impliedFormat":1},{"version":"1ee45496b5f8bdee6f7abc233355898e5bf9bd51255db65f5ff7ede617ca0027","impliedFormat":1},{"version":"273782b8454e78f6a8b30d2cfbf6860499c930595095fcc1689637115f0eddda","affectsGlobalScope":true,"impliedFormat":1},{"version":"3fbdd025f9d4d820414417eeb4107ffa0078d454a033b506e22d3a23bc3d9c41","affectsGlobalScope":true,"impliedFormat":1},{"version":"dba114fb6a32b355a9cfc26ca2276834d72fe0e94cd2c3494005547025015369","impliedFormat":1},{"version":"a8f8e6ab2fa07b45251f403548b78eaf2022f3c2254df3dc186cb2671fe4996d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa6c12a7c0f6b84d512f200690bfc74819e99efae69e4c95c4cd30f6884c526e","impliedFormat":1},{"version":"f1c32f9ce9c497da4dc215c3bc84b722ea02497d35f9134db3bb40a8d918b92b","impliedFormat":1},{"version":"b73c319af2cc3ef8f6421308a250f328836531ea3761823b4cabbd133047aefa","affectsGlobalScope":true,"impliedFormat":1},{"version":"e433b0337b8106909e7953015e8fa3f2d30797cea27141d1c5b135365bb975a6","impliedFormat":1},{"version":"9f9bb6755a8ce32d656ffa4763a8144aa4f274d6b69b59d7c32811031467216e","impliedFormat":1},{"version":"5c32bdfbd2d65e8fffbb9fbda04d7165e9181b08dad61154961852366deb7540","impliedFormat":1},{"version":"ddff7fc6edbdc5163a09e22bf8df7bef75f75369ebd7ecea95ba55c4386e2441","impliedFormat":1},{"version":"0c05e9842ec4f8b7bfebfd3ca61604bb8c914ba8da9b5337c4f25da427a005f2","impliedFormat":1},{"version":"faed7a5153215dbd6ebe76dfdcc0af0cfe760f7362bed43284be544308b114cf","impliedFormat":1},{"version":"7029e566b8df176f703fb59fd437a38670c7a0e02c58b2d66dfb5b2e2b2defdb","impliedFormat":1},{"version":"7f2aa4d4989a82530aaac3f72b3dceca90e9c25bee0b1a327e8a08a1262435ad","impliedFormat":1},{"version":"d96b39301d0ded3f1a27b47759676a33a02f6f5049bfcbde81e533fd10f50dcb","impliedFormat":1},{"version":"e9f147ecca73d9346a4c073432843c159ccbe50bdcb678a78f6da10eae2cecf4","impliedFormat":1},{"version":"de061f7d72bd65c06fc1419f841dfdcb29a8e22fe6fa527d1e6eb20b897d4de0","impliedFormat":1},{"version":"663beafc2446079574570cba86e9b15f986f908ddb1b01274509970126fee945","impliedFormat":1},{"version":"a3102887d5058bf4cb5b37fa6964c09e9527c42053b3b5c642b89878620748de","impliedFormat":1},{"version":"0aaaa1727edd29673d85c9b26d7ca4d54e5407a48586903c51b48b7f7d196f61","impliedFormat":1},{"version":"d35bca0b261bff02635758c48e8ab99c61c420d0dfabbcf467e847171d876b7d","impliedFormat":1},{"version":"3bc12c40d90c342ff88a3d876996c555ed5cbee5fe8c3308a240b321f401ee46","impliedFormat":1},{"version":"ba130768aae855a5477e9e148e5c879548e6e7ccbcc56fd1934c8a18ea5b7569","impliedFormat":1},{"version":"2e4f37ffe8862b14d8e24ae8763daaa8340c0df0b859d9a9733def0eee7562d9","impliedFormat":1},{"version":"d38530db0601215d6d767f280e3a3c54b2a83b709e8d9001acb6f61c67e965fc","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"b499af2054a037a162b3b72cd886f48bbf32a3502c865c6e29fac7d2ab3ce0b5","impliedFormat":1},{"version":"b83cb14474fa60c5f3ec660146b97d122f0735627f80d82dd03e8caa39b4388c","impliedFormat":1},{"version":"48773ca557b0319c2ee62ae249cf52a81709e8be139920d6479a66274de7c4ed","impliedFormat":1},{"version":"7274fbffbd7c9589d8d0ffba68157237afd5cecff1e99881ea3399127e60572f","impliedFormat":1},{"version":"b73cbf0a72c8800cf8f96a9acfe94f3ad32ca71342a8908b8ae484d61113f647","impliedFormat":1},{"version":"bae6dd176832f6423966647382c0d7ba9e63f8c167522f09a982f086cd4e8b23","impliedFormat":1},{"version":"20865ac316b8893c1a0cc383ccfc1801443fbcc2a7255be166cf90d03fac88c9","impliedFormat":1},{"version":"c9958eb32126a3843deedda8c22fb97024aa5d6dd588b90af2d7f2bfac540f23","impliedFormat":1},{"version":"461d0ad8ae5f2ff981778af912ba71b37a8426a33301daa00f21c6ccb27f8156","impliedFormat":1},{"version":"e927c2c13c4eaf0a7f17e6022eee8519eb29ef42c4c13a31e81a611ab8c95577","impliedFormat":1},{"version":"fcafff163ca5e66d3b87126e756e1b6dfa8c526aa9cd2a2b0a9da837d81bbd72","impliedFormat":1},{"version":"70246ad95ad8a22bdfe806cb5d383a26c0c6e58e7207ab9c431f1cb175aca657","impliedFormat":1},{"version":"f00f3aa5d64ff46e600648b55a79dcd1333458f7a10da2ed594d9f0a44b76d0b","impliedFormat":1},{"version":"772d8d5eb158b6c92412c03228bd9902ccb1457d7a705b8129814a5d1a6308fc","impliedFormat":1},{"version":"802e797bcab5663b2c9f63f51bdf67eff7c41bc64c0fd65e6da3e7941359e2f7","impliedFormat":1},{"version":"b01bd582a6e41457bc56e6f0f9de4cb17f33f5f3843a7cf8210ac9c18472fb0f","impliedFormat":1},{"version":"8b4327413e5af38cd8cb97c59f48c3c866015d5d642f28518e3a891c469f240e","impliedFormat":1},{"version":"4cceef18d7f088e797a463e90b7a9dad10c6bc667724b7686e3e740ae00122be","impliedFormat":1},{"version":"7ee86fbb3754388e004de0ef9e6505485ddfb3be7640783d6d015711c03d302d","impliedFormat":1},{"version":"cc1954b539604b1e562319119ac7e888172208b32ca873f9a357a92c826bd046","impliedFormat":1},{"version":"a67b87d0281c97dfc1197ef28dfe397fc2c865ccd41f7e32b53f647184cc7307","impliedFormat":1},{"version":"771ffb773f1ddd562492a6b9aaca648192ac3f056f0e1d997678ff97dbb6bf9b","impliedFormat":1},{"version":"43e96a3d5d1411ab40ba2f61d6a3192e58177bcf3b133a80ad2a16591611726d","impliedFormat":1},{"version":"232f70c0cf2b432f3a6e56a8dc3417103eb162292a9fd376d51a3a9ea5fbbf6f","impliedFormat":1},{"version":"bb8f2dbc03533abca2066ce4655c119bff353dd4514375beb93c08590c03e023","impliedFormat":1},{"version":"706dd95827e7ebaabda91d5db2b755233e0952d98570e9c032b0f066a15c1177","affectsGlobalScope":true,"impliedFormat":1},{"version":"0b103e9abfe82d14c0ad06a55d9f91d6747154ef7cacc73cf27ecad2bfb3afcf","impliedFormat":1},{"version":"cd9304972e6d616197fb44fce00540a904f38b54306a1951b5dbeaf3c01ab5bd","impliedFormat":1},{"version":"77438e2c397a3db78407621cfc57241a305b310ddea2c185f1d555248297f587","impliedFormat":1},{"version":"120599fd965257b1f4d0ff794bc696162832d9d8467224f4665f713a3119078b","impliedFormat":1},{"version":"43ba4f2fa8c698f5c304d21a3ef596741e8e85a810b7c1f9b692653791d8d97a","impliedFormat":1},{"version":"5433f33b0a20300cca35d2f229a7fc20b0e8477c44be2affeb21cb464af60c76","impliedFormat":1},{"version":"db036c56f79186da50af66511d37d9fe77fa6793381927292d17f81f787bb195","impliedFormat":1},{"version":"a6805fcafed712aea7759f8bc731014f9d22738c1d6ef9d43b8091d1d48346d5","impliedFormat":1},{"version":"c49469a5349b3cc1965710b5b0f98ed6c028686aa8450bcb3796728873eb923e","impliedFormat":1},{"version":"4a889f2c763edb4d55cb624257272ac10d04a1cad2ed2948b10ed4a7fda2a428","impliedFormat":1},{"version":"7bb79aa2fead87d9d56294ef71e056487e848d7b550c9a367523ee5416c44cfa","impliedFormat":1},{"version":"d88ea80a6447d7391f52352ec97e56b52ebec934a4a4af6e2464cfd8b39c3ba8","impliedFormat":1},{"version":"142617b3cdf902b69c6464c9fbd942b60ab3e733ca18c032b19e0f7e2adbefe8","impliedFormat":1},{"version":"0b603555f1881f87256ffd6344d3e3ed6d466c2e701eabf381f28be8c2125892","impliedFormat":1},{"version":"897e4f7662488e3ecc79e743bdd3b78f13bdb69a97851afa5b440c4211e32ea9","impliedFormat":1},{"version":"e2e1c6d3b2d93add5200bd7bc1a8cccb4e446836b2111ece45db8683a2c765de","impliedFormat":1},{"version":"251b03d5cd243854ce870d9a9a39f491faf69898c5d6b5eee28cc7649c57417b","impliedFormat":1},{"version":"27ff4196654e6373c9af16b6165120e2dd2169f9ad6abb5c935af5abd8c7938c","impliedFormat":1},{"version":"2c4de79f406d137390608e8c0a44fba2ff8e00bacfcae7c9d1781fef10e9440d","impliedFormat":1},{"version":"07ba23a10465791be5d22deaf5ef7de7658774ddff53721e5ea17fedea1bc721","impliedFormat":1},{"version":"dca8c645c5afeb03b1ecedbf16323f33e7d0afaa6256c8e047e6e38087a97f53","impliedFormat":1},{"version":"775f181bd4a533d6f8b5e55ec1d9f1624559720ae8a70e9432258da26b38d27c","impliedFormat":1},{"version":"796273b2edc72e78a04e86d7c58ae94d370ab93a0ddf40b1aa85a37a1c29ecd7","impliedFormat":1},{"version":"5df15a69187d737d6d8d066e189ae4f97e41f4d53712a46b2710ff9f8563ec9f","impliedFormat":1},{"version":"7715134a0cf07dd41a9da2895d708625a3a303a0385e355ecaaf0b8bfaef2550","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"622694a8522b46f6310c2a9b5d2530dde1e2854cb5829354e6d1ff8f371cf469","impliedFormat":1},{"version":"cd8ce8d68567f62dd580b3c3c37777ac3f5b81944c7417f5ea83030eab533385","impliedFormat":1},{"version":"e5c939d896565dcac0f6fbdbada11284e7728ef26a069561c09aa5aa4a788393","impliedFormat":1},{"version":"9e2739b32f741859263fdba0244c194ca8e96da49b430377930b8f721d77c000","impliedFormat":1},{"version":"a9e6c0ff3f8186fccd05752cf75fc94e147c02645087ac6de5cc16403323d870","impliedFormat":1},{"version":"49af4b52f0d4d2304c5f2c6fe5fab3e153e0acc38830d0202821b877c097dd02","impliedFormat":1},{"version":"49c346823ba6d4b12278c12c977fb3a31c06b9ca719015978cb145eb86da1c61","impliedFormat":1},{"version":"bfac6e50eaa7e73bb66b7e052c38fdc8ccfc8dbde2777648642af33cf349f7f1","impliedFormat":1},{"version":"92f7c1a4da7fbfd67a2228d1687d5c2e1faa0ba865a94d3550a3941d7527a45d","impliedFormat":1},{"version":"f53b120213a9289d9a26f5af90c4c686dd71d91487a0aa5451a38366c70dc64b","impliedFormat":1},{"version":"e68b8e5a1df7c1be2bc105141456ecba70215806e1c28bfbc5c12bfce4be6e68","impliedFormat":1},{"version":"511c8f02329808d47d00b859c532ae9115590048b17325a946c74dac48428650","impliedFormat":1},{"version":"57d67b72e06059adc5e9454de26bbfe567d412b962a501d263c75c2db430f40e","impliedFormat":1},{"version":"b5f9e66625783eefcbe3d2da074b2e7ba2066d61ce3fc6ef4f22805ad946cab4","impliedFormat":1},{"version":"e37115962d284b9f7a37c2bdd2add50f88365dde41f5e0ff591ffc48a8ec7575","impliedFormat":1},{"version":"6459054aabb306821a043e02b89d54da508e3a6966601a41e71c166e4ea1474f","impliedFormat":1},{"version":"bb37588926aba35c9283fe8d46ebf4e79ffe976343105f5c6d45f282793352b2","impliedFormat":1},{"version":"f89488602bec98a142072fae7ea5ba99431a569ff580c64b7be39896474799d8","impliedFormat":1},{"version":"bbbc47961f39a57df103cf4ca3bb8f8732b4b6678a18225a0aa76d59c466956c","impliedFormat":1},{"version":"2e6114a7dd6feeef85b2c80120fdbfb59a5529c0dcc5bfa8447b6996c97a69f5","impliedFormat":1},{"version":"2ffb043dc5163458e473b7010859f86e01dc4edffcae0a93d885d028b426a546","impliedFormat":1},{"version":"c8f004e6036aa1c764ad4ec543cf89a5c1893a9535c80ef3f2b653e370de45e6","impliedFormat":1},{"version":"dd80b1e600d00f5c6a6ba23f455b84a7db121219e68f89f10552c54ba46e4dc9","impliedFormat":1},{"version":"b064c36f35de7387d71c599bfcf28875849a1dbc733e82bd26cae3d1cd060521","impliedFormat":1},{"version":"05c7280d72f3ed26f346cbe7cbbbb002fb7f15739197cbbee6ab3fd1a6cb9347","impliedFormat":1},{"version":"8de9fe97fa9e00ec00666fa77ab6e91b35d25af8ca75dabcb01e14ad3299b150","impliedFormat":1},{"version":"04b7b2e0832dfd3c31e81df3975e8d8fda28e7ff999b0aa2932608a8f6661d5c","impliedFormat":1},{"version":"ca2d34c6ed5cbd3070b8b6f32f42ae54adcc6499c1e4b99f0a5798b3f27cc653","impliedFormat":1},{"version":"9ec68995e66dd6b9dac834bf5ae85fde802714ea2e82151a5d1d53ef01b463ef","impliedFormat":1},{"version":"5c4d626b4902f2ef8a1cc146d761d276cef988016dc674e3b98fbad70e64bc9f","impliedFormat":1},{"version":"fdfaa0aad899524962e2955287b5b991ffe3be50f64e02eb60c933ca44644a94","impliedFormat":1},{"version":"53c972a0f9bc3a4ec70fff7314123ea8cfcf75b3703046f767d2dc1eea87b2fb","impliedFormat":1},{"version":"f974e4a06953682a2c15d5bd5114c0284d5abf8bc0fe4da25cb9159427b70072","impliedFormat":1},{"version":"50256e9c31318487f3752b7ac12ff365c8949953e04568009c8705db802776fb","impliedFormat":1},{"version":"7d73b24e7bf31dfb8a931ca6c4245f6bb0814dfae17e4b60c9e194a631fe5f7b","impliedFormat":1},{"version":"d130c5f73768de51402351d5dc7d1b36eaec980ca697846e53156e4ea9911476","impliedFormat":1},{"version":"413586add0cfe7369b64979d4ec2ed56c3f771c0667fbde1bf1f10063ede0b08","impliedFormat":1},{"version":"06472528e998d152375ad3bd8ebcb69ff4694fd8d2effaf60a9d9f25a37a097a","impliedFormat":1},{"version":"7303b45138d2511035056a5901a1490ebdcbf055cbb1276f8629c5121cbe733e","impliedFormat":1},{"version":"27f874cd5327507eeff699a74567f60c1215b94509f4308633a7b01922471ed2","impliedFormat":1},{"version":"a401617604fa1f6ce437b81689563dfdc377069e4c58465dbd8d16069aede0a5","impliedFormat":1},{"version":"2c6cf04bc525caf6546e859e8ef10bfb9573837ec0bc5ec7b53a7b1b8ca72781","impliedFormat":1},{"version":"8695dec09ad439b0ceef3776ea68a232e381135b516878f0901ed2ea114fd0fe","impliedFormat":1},{"version":"304b44b1e97dd4c94697c3313df89a578dca4930a104454c99863f1784a54357","impliedFormat":1},{"version":"0a437ae178f999b46b6153d79095b60c42c996bc0458c04955f1c996dc68b971","impliedFormat":1},{"version":"74b2a5e5197bd0f2e0077a1ea7c07455bbea67b87b0869d9786d55104006784f","impliedFormat":1},{"version":"4a7baeb6325920044f66c0f8e5e6f1f52e06e6d87588d837bdf44feb6f35c664","impliedFormat":1},{"version":"87cc05fe13108f02e12da7e3efd8e360fef78d96a0c9e11408ea1b1b9fb3e03d","impliedFormat":1},{"version":"1abbf67c218d23c2ce76887caac2df6c7dab3d97ba2b65348432b876f510002a","impliedFormat":1},{"version":"1a82deef4c1d39f6882f28d275cad4c01f907b9b39be9cbc472fcf2cf051e05b","impliedFormat":1},{"version":"4b20fcf10a5413680e39f5666464859fc56b1003e7dfe2405ced82371ebd49b6","impliedFormat":1},{"version":"c06ef3b2569b1c1ad99fcd7fe5fba8d466e2619da5375dfa940a94e0feea899b","impliedFormat":1},{"version":"f7d628893c9fa52ba3ab01bcb5e79191636c4331ee5667ecc6373cbccff8ae12","impliedFormat":1},{"version":"1d879125d1ec570bf04bc1f362fdbe0cb538315c7ac4bcfcdf0c1e9670846aa6","impliedFormat":1},{"version":"dad97c99382889e9c7d1a9d8275500ff71235130fae9f8916fdbf3641d56e592","impliedFormat":1},{"version":"a6dba407fc287f1e25454e75028c91bbc00675f2d1c4e8b3edcc36c08611a486","impliedFormat":1},{"version":"d663134457d8d669ae0df34eabd57028bddc04fc444c4bc04bc5215afc91e1f4","impliedFormat":1},{"version":"e91f7b1344577a02f051b9b471f33044fef8334a76dc9e1de003d17595a5219b","impliedFormat":1},{"version":"c0723195c85e19656d6b5b9fdb81d3f3403c1ae4679e722c6ea058c516b38d12","impliedFormat":1},{"version":"b55eb9f72166093b5460d34b34f5d8699c968de3bc3fc696e40f2c93f2ebf650","impliedFormat":1},{"version":"71d9eb4c4e99456b78ae182fb20a5dfc20eb1667f091dbb9335b3c017dd1c783","impliedFormat":1},{"version":"cfa846a7b7847a1d973605fbb8c91f47f3a0f0643c18ac05c47077ebc72e71c7","impliedFormat":1},{"version":"1594da19968752a22b2ac48c2d0e60575700e745c577a8a4a676b841238ad5bb","impliedFormat":1},{"version":"e0cee12109e0a10a4c3d6769fcc7644b7c1ea7f52365bea51728f5af29f8a137","impliedFormat":1},{"version":"7d4254b4c6c67a29d5e7f65e67d72540480ac2cfb041ca484847f5ae70480b62","impliedFormat":1},{"version":"3536968defef8a75514f547ead5e2e9c1e984820290ec9b00c5fdfb6ef786535","impliedFormat":1},{"version":"d83773870080c30a230e322ce13a9c6f3398e8dacea4ea8a83e26370f3bac23e","impliedFormat":1},{"version":"dcfeaf98d66314fec29a9076c4290e45d0b196a65827becc19138e9c7b855f37","impliedFormat":1},{"version":"6849fe9210fe4946d5f085bfed36758f33dc6ae15a751338d178dd4daa017c46","impliedFormat":1},{"version":"888cda0fa66d7f74e985a3f7b1af1f64b8ff03eb3d5e80d051c3cbdeb7f32ab7","impliedFormat":1},{"version":"60681e13f3545be5e9477acb752b741eae6eaf4cc01658a25ec05bff8b82a2ef","impliedFormat":1},{"version":"ffae4e1e06aa848a1e4bcef162cd1c48e5909b26223515981310af9c036bdfc7","impliedFormat":1},{"version":"a57b1802794433adec9ff3fed12aa79d671faed86c49b09e02e1ac41b4f1d33a","impliedFormat":1},{"version":"34e16eb7c31768a11a08aebcfb3d70d7b8f0b016197e98d8419e566ceae6d6c8","impliedFormat":1},{"version":"f94ec1f7e4b709d26960306c9082a7a1b728a6e13089346aa48ba57c74cbf47e","impliedFormat":1},{"version":"9a11cb4033405e96c247cd5aa29790212aaffdd127869e8a5219103f0b389fd5","impliedFormat":1},{"version":"01479d9d5a5dda16d529b91811375187f61a06e74be294a35ecce77e0b9e8d6c","impliedFormat":1},{"version":"aff5213585cb72e94054dfe17250ff315f3569b3919d1ef1ad235f37c4ee894e","impliedFormat":1},{"version":"fb2ea35e1be6388d722d7725e2b49c697d34d9c890c3b96758faaeb86d35cef8","impliedFormat":1},{"version":"ce0df82a9ae6f914ba08409d4d883983cc08e6d59eb2df02d8e4d68309e7848b","impliedFormat":1},{"version":"1a4dc28334a926d90ba6a2d811ba0ff6c22775fcc13679521f034c124269fd40","impliedFormat":1},{"version":"f05315ff85714f0b87cc0b54bcd3dde2716e5a6b99aedcc19cad02bf2403e08c","impliedFormat":1},{"version":"5fad3b31fc17a5bc58095118a8b160f5260964787c52e7eb51e3d4fcf5d4a6f0","impliedFormat":1},{"version":"72105519d0390262cf0abe84cf41c926ade0ff475d35eb21307b2f94de985778","impliedFormat":1},{"version":"456006a6975b26c0a1785feddae165f6d307e2d601ffde27e21fc4a790e448a4","impliedFormat":1},{"version":"c857e0aae3f5f444abd791ec81206020fbcc1223e187316677e026d1c1d6fe08","impliedFormat":1},{"version":"ccf6dd45b708fb74ba9ed0f2478d4eb9195c9dfef0ff83a6092fa3cf2ff53b4f","impliedFormat":1},{"version":"1fe0d18b111e1145a7e7601855bccd4ca20f24e3b9a5aba6bb1fa9d1a7059170","impliedFormat":1},{"version":"5632c3c26d420c063eebe64c45b1248b9492a67bf44f1d0c57e9dc8f6cf449bb","impliedFormat":1},{"version":"0df5aa619ab12993a39ea6dae062ee46eadbb4d738916460e636ada52bced75b","impliedFormat":1},{"version":"8fca3039857709484e5893c05c1f9126ab7451fa6c29e19bb8c2411a2e937345","impliedFormat":1},{"version":"35069c2c417bd7443ae7c7cafd1de02f665bf015479fec998985ffbbf500628c","impliedFormat":1},{"version":"10ab7be91f87ebe8916b62cf28af2e45b5601fc7b0e311adf838f912c6b31dd8","impliedFormat":1},{"version":"bc636fbc08e0979ceb7eb0731a33000283d77a33b62e1f71ee65be50394e40ba","impliedFormat":1},{"version":"7e0b7f91c5ab6e33f511efc640d36e6f933510b11be24f98836a20a2dc914c2d","impliedFormat":1},{"version":"045b752f44bf9bbdcaffd882424ab0e15cb8d11fa94e1448942e338c8ef19fba","impliedFormat":1},{"version":"2894c56cad581928bb37607810af011764a2f511f575d28c9f4af0f2ef02d1ab","impliedFormat":1},{"version":"0a72186f94215d020cb386f7dca81d7495ab6c17066eb07d0f44a5bf33c1b21a","impliedFormat":1},{"version":"75bbd3be047d539988a0ff0b56384ef7a6a25f3b676ad96bee547d44c31622a7","impliedFormat":1},{"version":"42960001a776b089ade681ab5cfddc936e0afb0615133ec1841f3dee89d3e1bf","impliedFormat":1},{"version":"0aedb02516baf3e66b2c1db9fef50666d6ed257edac0f866ea32f1aa05aa474f","impliedFormat":1},{"version":"da47712b394d944328245482603bc6f416d3949b67c9392279caab595076b510","affectsGlobalScope":true,"impliedFormat":1},{"version":"37d0071d8f0a06dc55c2c5e0ec3391affd4fd107c53410bf358196ec0bf3923f","impliedFormat":1},{"version":"b213dad76ca37fd552274c9499056e1c0d9c1bd38a55bb7f68b22ba6b84c3ad7","impliedFormat":1},{"version":"56ccb49443bfb72e5952f7012f0de1a8679f9f75fc93a5c1ac0bafb28725fc5f","impliedFormat":1},{"version":"20fa37b636fdcc1746ea0738f733d0aed17890d1cd7cb1b2f37010222c23f13e","impliedFormat":1},{"version":"d90b9f1520366d713a73bd30c5a9eb0040d0fb6076aff370796bc776fd705943","impliedFormat":1},{"version":"bc03c3c352f689e38c0ddd50c39b1e65d59273991bfc8858a9e3c0ebb79c023b","impliedFormat":1},{"version":"19df3488557c2fc9b4d8f0bac0fd20fb59aa19dec67c81f93813951a81a867f8","affectsGlobalScope":true,"impliedFormat":1},{"version":"b25350193e103ae90423c5418ddb0ad1168dc9c393c9295ef34980b990030617","affectsGlobalScope":true,"impliedFormat":1},{"version":"bef86adb77316505c6b471da1d9b8c9e428867c2566270e8894d4d773a1c4dc2","impliedFormat":1},{"version":"5a49adaef698b7ad7e6127949fa1b0bbd3d46b7cbd11c54e392a4dcdd51f5190","impliedFormat":1},{"version":"6ee598cdfdd0fa52039dca135b3dfff7b49035dc13292143e0a93843e3861967","impliedFormat":1},{"version":"27be6622e2922a1b412eb057faa854831b95db9db5035c3f6d4b677b902ab3b7","impliedFormat":1},{"version":"5c634644d45a1b6bc7b05e71e05e52ec04f3d73d9ac85d5927f647a5f965181a","impliedFormat":1},{"version":"2489bf04d77dc025ba67f49f1a56eb24b9db477d5ff88123d887e163ed1776aa","impliedFormat":1},{"version":"63a7595a5015e65262557f883463f934904959da563b4f788306f699411e9bac","impliedFormat":1},{"version":"4ba137d6553965703b6b55fd2000b4e07ba365f8caeb0359162ad7247f9707a6","impliedFormat":1},{"version":"0b77b819b5417775fccb20c678293cf614c054a5b1a65421a5b933a9124ba998","impliedFormat":1},{"version":"eb5acb58487367e502d994b57e2c58255d8241f481ea8efa8e79af23af3f41c2","impliedFormat":1},{"version":"9252d498a77517aab5d8d4b5eb9d71e4b225bbc7123df9713e08181de63180f6","impliedFormat":1},{"version":"b1f1d57fde8247599731b24a733395c880a6561ec0c882efaaf20d7df968c5af","impliedFormat":1},{"version":"6715dc4eb59c8ea9abe2b78c235ed331dc710a06fe56798868dbc4d40cd1b707","impliedFormat":1},{"version":"35e6379c3f7cb27b111ad4c1aa69538fd8e788ab737b8ff7596a1b40e96f4f90","impliedFormat":1},{"version":"1fffe726740f9787f15b532e1dc870af3cd964dbe29e191e76121aa3dd8693f2","impliedFormat":1},{"version":"5a3ea721d03a361ccbdd7390ccd75f6e84cbca3a3f01f4b331ecc9af31890c49","impliedFormat":1},{"version":"e7dfaee4af38d45b1cab8a1ee0b3bc1f85ddcf64545ed391d675d78ae6526274","affectsGlobalScope":true,"impliedFormat":1},{"version":"e8daa443eaf9a27fd382cc1f8ebe30330c0f4d89511cfb469166874806751d35","impliedFormat":1},{"version":"af48e58339188d5737b608d41411a9c054685413d8ae88b8c1d0d9bfabdf6e7e","impliedFormat":1},{"version":"616775f16134fa9d01fc677ad3f76e68c051a056c22ab552c64cc281a9686790","impliedFormat":1},{"version":"65c24a8baa2cca1de069a0ba9fba82a173690f52d7e2d0f1f7542d59d5eb4db0","impliedFormat":1},{"version":"f9fe6af238339a0e5f7563acee3178f51db37f32a2e7c09f85273098cee7ec49","impliedFormat":1},{"version":"1de8c302fd35220d8f29dea378a4ae45199dc8ff83ca9923aca1400f2b28848a","impliedFormat":1},{"version":"77e71242e71ebf8528c5802993697878f0533db8f2299b4d36aa015bae08a79c","impliedFormat":1},{"version":"98a787be42bd92f8c2a37d7df5f13e5992da0d967fab794adbb7ee18370f9849","impliedFormat":1},{"version":"332248ee37cca52903572e66c11bef755ccc6e235835e63d3c3e60ddda3e9b93","impliedFormat":1},{"version":"94e8cc88ae2ef3d920bb3bdc369f48436db123aa2dc07f683309ad8c9968a1e1","impliedFormat":1},{"version":"4545c1a1ceca170d5d83452dd7c4994644c35cf676a671412601689d9a62da35","impliedFormat":1},{"version":"320f4091e33548b554d2214ce5fc31c96631b513dffa806e2e3a60766c8c49d9","impliedFormat":1},{"version":"a2d648d333cf67b9aeac5d81a1a379d563a8ffa91ddd61c6179f68de724260ff","impliedFormat":1},{"version":"d90d5f524de38889d1e1dbc2aeef00060d779f8688c02766ddb9ca195e4a713d","impliedFormat":1},{"version":"07ed3ddab975995eea41b22f3010506fb9f5fb301d04820b07d7a1aee5477d7c","impliedFormat":1},{"version":"969d8b0965849f4bae7cab0ba90bd1e1220e95999c2c6f01117fa7500901c017","impliedFormat":1},{"version":"6ec840ee5e2bc103f557fe38b1d585ee250540468713d7634ee066de372bf332","impliedFormat":1},{"version":"b0309e1eda99a9e76f87c18992d9c3689b0938266242835dd4611f2b69efe456","impliedFormat":1},{"version":"47699512e6d8bebf7be488182427189f999affe3addc1c87c882d36b7f2d0b0e","impliedFormat":1},{"version":"6ceb10ca57943be87ff9debe978f4ab73593c0c85ee802c051a93fc96aaf7a20","impliedFormat":1},{"version":"1de3ffe0cc28a9fe2ac761ece075826836b5a02f340b412510a59ba1d41a505a","impliedFormat":1},{"version":"e46d6cc08d243d8d0d83986f609d830991f00450fb234f5b2f861648c42dc0d8","impliedFormat":1},{"version":"1c0a98de1323051010ce5b958ad47bc1c007f7921973123c999300e2b7b0ecc0","impliedFormat":1},{"version":"ff863d17c6c659440f7c5c536e4db7762d8c2565547b2608f36b798a743606ca","impliedFormat":1},{"version":"5412ad0043cd60d1f1406fc12cb4fb987e9a734decbdd4db6f6acf71791e36fe","impliedFormat":1},{"version":"ad036a85efcd9e5b4f7dd5c1a7362c8478f9a3b6c3554654ca24a29aa850a9c5","impliedFormat":1},{"version":"fedebeae32c5cdd1a85b4e0504a01996e4a8adf3dfa72876920d3dd6e42978e7","impliedFormat":1},{"version":"e297c0a524edee7677939122f90027bfbe5f2698939d9a85728e5044b39c7124","impliedFormat":1},{"version":"cdf21eee8007e339b1b9945abf4a7b44930b1d695cc528459e68a3adc39a622e","impliedFormat":1},{"version":"bc9ee0192f056b3d5527bcd78dc3f9e527a9ba2bdc0a2c296fbc9027147df4b2","impliedFormat":1},{"version":"b62381cae176db34f003cc6172ee8f3e0122014889d66391aa73698105cf4934","impliedFormat":1},{"version":"1d9c0a9a6df4e8f29dc84c25c5aa0bb1da5456ebede7a03e03df08bb8b27bae6","impliedFormat":1},{"version":"84380af21da938a567c65ef95aefb5354f676368ee1a1cbb4cae81604a4c7d17","impliedFormat":1},{"version":"1af3e1f2a5d1332e136f8b0b95c0e6c0a02aaabd5092b36b64f3042a03debf28","impliedFormat":1},{"version":"30d8da250766efa99490fc02801047c2c6d72dd0da1bba6581c7e80d1d8842a4","impliedFormat":1},{"version":"03566202f5553bd2d9de22dfab0c61aa163cabb64f0223c08431fb3fc8f70280","impliedFormat":1},{"version":"41eb514d9ce0a6e87957f08a4b7af70d93f87637f37dee706e2d92a6601c25a9","impliedFormat":1},{"version":"e7765aa8bcb74a38b3230d212b4547686eb9796621ffb4367a104451c3f9614f","impliedFormat":1},{"version":"1de80059b8078ea5749941c9f863aa970b4735bdbb003be4925c853a8b6b4450","impliedFormat":1},{"version":"1d079c37fa53e3c21ed3fa214a27507bda9991f2a41458705b19ed8c2b61173d","impliedFormat":1},{"version":"5bf5c7a44e779790d1eb54c234b668b15e34affa95e78eada73e5757f61ed76a","impliedFormat":1},{"version":"5835a6e0d7cd2738e56b671af0e561e7c1b4fb77751383672f4b009f4e161d70","impliedFormat":1},{"version":"4b7f74b772140395e7af67c4841be1ab867c11b3b82a51b1aeb692822b76c872","impliedFormat":1},{"version":"7bd01f0f28cd3aeb2046274d85208e245965f6f2948edf4f7b2057bcf9f22ccc","impliedFormat":99},{"version":"d2f2cf2b8cc92bea913cda4a076e0f790b23a21e84f989d12f0116a7fe3906e0","impliedFormat":99},{"version":"6de125ea94866c736c6d58d68eb15272cf7d1020a5b459fea1c660027eca9a90","affectsGlobalScope":true,"impliedFormat":1},{"version":"f5b20bc288ee49989c95b20847fc93b96bf61cc0845598897a6a53a967dd7d07","affectsGlobalScope":true,"impliedFormat":1},{"version":"064ac1c2ac4b2867c2ceaa74bbdce0cb6a4c16e7c31a6497097159c18f74aa7c","impliedFormat":1},{"version":"3dc14e1ab45e497e5d5e4295271d54ff689aeae00b4277979fdd10fa563540ae","impliedFormat":1},{"version":"d3b315763d91265d6b0e7e7fa93cfdb8a80ce7cdd2d9f55ba0f37a22db00bdb8","impliedFormat":1},{"version":"b789bf89eb19c777ed1e956dbad0925ca795701552d22e68fd130a032008b9f9","impliedFormat":1},{"version":"1d76866f6aca5bb7b9eb003266628c298607c1242691d029de1b19b39f9c9942","affectsGlobalScope":true},"7b550dda9686c16f36a17bf9051d5dbf31e98555b30d114ac49fc49a1e712651",{"version":"a78793bb6b4b642f2821eb60c9ddfa0d3dd186b28568b5f0f4c268ba2b250bd2","signature":"f5803d39603639ee91c7f21fbd6d792f12358eca4fcf74146d6c2f1343b0c135"},{"version":"eccc6b88a6c5c3815e21afbafc1991ee4066019645d20ed3d05aafc9e54d0f58","signature":"5b3beb9e110c494b12be107c3cece59af336b53d20e481c9bdd7eb554799bde6"},{"version":"8c5eb64fd57d3eedf82b7e474cd7d178e5c0b2eb02d3db9c72e428119b7c99f0","signature":"487e769753040e86224af6f95fdbf933a94c34f1b395d6e506c48417b2ba7ff2"},{"version":"6c2e6283e9451736465aa62fc5e01cabbb61513446957e0507956f7248162bed","signature":"b006d19a31dd8e0870f79a2b7eb1a4f25f72306232d4c8b5d71fe9b14565b36d"},{"version":"c57b441e0c0a9cbdfa7d850dae1f8a387d6f81cbffbc3cd0465d530084c2417d","impliedFormat":99},{"version":"51954e948be6a5b728fcfaf561f12331b4f54f068934c77adfc8f70eea17d285","impliedFormat":1},{"version":"e46429536f43f5910f5b2b0c50bb2769b510a4558bb4b5d20a3b0a9091dbf7c3","signature":"400b40fe5d5f4140993b0ac871686d2b7611ab791e8810b2e14f2d89701fc49e"},{"version":"fe93c474ab38ac02e30e3af073412b4f92b740152cf3a751fdaee8cbea982341","impliedFormat":1},{"version":"3255b97f3f24af29c79cc1aa88004efb13b6285ebdde0a567bf32e19bb65250d","impliedFormat":1},{"version":"1e00b8bf9e3766c958218cd6144ffe08418286f89ff44ba5a2cc830c03dd22c7","impliedFormat":1},{"version":"c8bb01579c9ef5995c8390a8cc7d63ff4da8626643859c77ddadf8d9e5696678","signature":"ea0a7def22250c96963ab5de115c944676e0841797a7edc114d470925252c2a0"},{"version":"e78f1ff29c9175e7e451629df69f06b52e729cfa0d64fb6b0a6555142275b078","signature":"4ef8b0b2f91fee53ff8b0ee7f69fd93b8a294bd7da4c44e40057bf1e14aa0763"},{"version":"166c048927c78e0ca166de2df67186fd3bd3fa9c28092d332370122a54a4b66f","impliedFormat":1},{"version":"44dafc547130d69f8f3ff19973b00a4443a133ceb8cb24c03e0ca9b01626aee6","impliedFormat":99},{"version":"c24ab9ac84d65b417a807ada25456697bb2adf1189fa80cb240625dfb3e61c42","impliedFormat":99},{"version":"d6d57aa3e60250a8526871b3e14abc5a12dc5bb7891512628ffa5c40de369d62","impliedFormat":99},{"version":"0d121799b2cbac398e84a4708fcd82f980f33bf3ac7721a3cf9db9f28d9eb2b2","impliedFormat":99},{"version":"8aed22221e416e7811335f9518690ea0db873de1d0286602e644e1700cd166a4","impliedFormat":99},{"version":"9c3663fa36fb976609d8fc43372ae38dc2e066dab014a955f40ae6430733fb71","impliedFormat":99},{"version":"e38a172f8912eebc79671e07b81687a304a9d366a47933fde9f97ad79f8ac08a","impliedFormat":99},{"version":"2fbe402f0ee5aa8ab55367f88030f79d46211c0a0f342becaa9f648bf8534e9d","impliedFormat":1},{"version":"b94258ef37e67474ac5522e9c519489a55dcb3d4a8f645e335fc68ea2215fe88","impliedFormat":1},{"version":"4e679215c9c8480ea8b876cf74d77c1225da544adaf6acba43634a434275a69d","signature":"677d5d88630239f2364c5a7de8936ab5763cec65206614c814f0122d61d9fd33"},{"version":"539bede51b7dfa42c5168884f8f2075ee38d50fb900da46436f2855e46e84af4","impliedFormat":99},{"version":"4244c6a12dc14768ddc3ee2737ccf1c6c0d07353785d5979df7d250eda461a61","impliedFormat":99},{"version":"cce820aba9ba9d1984461c67d0d543d8eba7ea25c6a1be7a47c31cc18907a631","impliedFormat":99},{"version":"3e7e929f14bc384ed6da8ef0c7dc3aae5ea191494c9554e4327186ef40db2573","impliedFormat":99},{"version":"9d28b54569f4cd1c0c21d95d0e70f6fdbfd792121604b95d7c2d067ee0f0fd11","impliedFormat":99},{"version":"4d857105510df8011cfb5b3769dec55624a1df92e85d399cd03bc82bb89d090c","impliedFormat":99},{"version":"b92dbe495c1dee9f3cd7c1466fdf02476f19ae18a7ecc829e3ed8a380b2d597f","impliedFormat":99},{"version":"bd3e3a447a34cf3dd875ed550ea349589b2dfcd6b3a14abebdc946366bd812a4","impliedFormat":99},{"version":"6b100282ebfd5bfa4d8c72ad5dd3e9d325aa32d0a53623d6171fc2cc2899fbc4","impliedFormat":99},{"version":"31e0d88a23043e61cff5cb8e9bab319b5ca680f2b663e8582d4800a19da8600c","impliedFormat":99},{"version":"5cf87865e3a8b12bb8ddfb311846ff2eae24983faa365f53d9224fb182cd0269","impliedFormat":99},{"version":"98ef9c3f5f15c18abcd6fa9f12e93e1bbd608225501151603cce97642dbc961d","impliedFormat":99},{"version":"c80a56a10f1a8e01c4b7f08df6e9260ae076c910402dae8173fa6c7dcacc51fe","impliedFormat":99},{"version":"becca0fa1bbcc55ad69918f65f771b76904ac98b2563cb34f26fbe240c3d4db3","impliedFormat":99},{"version":"32b344c3765dc7c383516f3326c108ca84c33df44ece8ac789fce47d47ba8810","impliedFormat":99},{"version":"f7d6ecff9a4d631feeaf401c02bb87e26ddb38131c55d15a43b9290747390847","signature":"f774a7e8875649db68f5952801f001f3c316560ede472e5663af7a8c9c18f938"},{"version":"7f19b8476658d25ff197c84030e58cd7395059d876a54630e025951e474ebdae","signature":"cbb6e618cfae37c1feb246b78260428cf2bcba79a0c9bf1ac60d511a4158692d"},{"version":"3a33d94e3e35f61f5c30828f795c8db1d1fbfd2eb9c7d578a8d9276c6d94cc7b","signature":"d4e4eb785403cd7ffa1eb9afe44d7601b52a99cb205972508f2ef4ff5739bd7d"},{"version":"ba1c5c19412fd7f60a9411ec61344c4ec425d77b5a3a78d8f7ce51d1cb4b3855","signature":"5cec2d5111000faf9f06f8fa85b11b747c2fb4db35ada617f019f80de10790b5"},{"version":"b0309aa43d7073c58b43d47f80311b81aad17b395940f43faff11387ff8b2038","signature":"cde8c57cf796fc88faa8ed92f02bb868c64100eb22c6bcf935487bd8eeb1ed08"},{"version":"2ba6dd2fc6a213bef7e1dba18791da6e83992ff17f6bbd50f2c8f7f4b6fa2d82","signature":"132db3de92d2a718bd41fe3f47e75fb6916b1af96d1289347316ee1abc358b06"},{"version":"4f09605424e0a2ce414f48096837fbfaf06f13956829438a5e1743c347bf5cde","signature":"f03c9fc060940404460998108d975d541cbef8644269862a531d5b41edb0ac40"},{"version":"9800c238da31c600373883bb7a1a3ddb431684992dfd2b3112fbc1983403c427","signature":"c1c0fdbb129948e18a8b11c893490ee3ce0055631ad8de1eee247343d8359ce6"},{"version":"2b4276dde46aa2faf0dd86119999c76b81e6488cd6b0d0fcf9fb985769cd11c0","impliedFormat":99},{"version":"38d4cff03e87dc58bfd50ffe5a3fb25e6e6d4136a1282883285baf71d35967c5","impliedFormat":99},{"version":"5ecea63968444d55f7c3cf677cbec9525db9229953b34f06be0386a24b0fffd2","impliedFormat":99},{"version":"6ea9c8bf2ae4d47a0dbc2a1f9ac1e36c639b2ac9225c4d271c2f63a2faf24831","impliedFormat":99},{"version":"a3d603c46b55d51493799241b8a456169d36301cc926ff72c75f5480e7eb25bf","impliedFormat":99},{"version":"773c18e2bcc18598df8f8b2be930eb26b22608edf368e42e9ca3484828ec4122","impliedFormat":99},{"version":"9eb8e1320fc0ecbfba15c0f3452dfc1957543dfbd466aaf8b67ddb0f2ad0f217","impliedFormat":1},{"version":"4faca872dbd194a17b3ee267bd8ddc3daf3d16df96f4e43a02c7d9a862022c4f","impliedFormat":99},{"version":"f89aeba83b1744a0d697c1b7fb8a06d8dc4cf7c0d469d2419772121f499efa8e","impliedFormat":99},{"version":"144a4e5780b800c0553949169f50be285eccbdb0298afd83ef2ae03fef77e2d2","impliedFormat":99},{"version":"66aeb47bf8638d6767f7b4ff684c2d794391c981590073025e98f98e1afed499","impliedFormat":99},{"version":"26748898fec8579096c776866e8e6f07754845b3d08f5ae98c3a59baa9e85c2e","impliedFormat":99},{"version":"6d805abd62920edbd9ed4b20be26d040d01529f3ce53fdab9ca4d0fa9b589f02","impliedFormat":99},{"version":"edbeff52e73b0ff82898142aaf8ffb336910a8ccdfc31b79960ea4ad4c9f407b","impliedFormat":99},{"version":"b64a8c7a27133db3f181199d21c0c582e86f38ba57a031a126b77229526b4916","impliedFormat":99},{"version":"1b1c48c4d7cbe6f40616594c2a3f6f95bb1dcefd200a7e4167e47b67725b631a","impliedFormat":99},{"version":"97b02501eb45f487174d5a0ff89b6a95690d50e9eae242e2162118edd5f2705c","impliedFormat":99},{"version":"8eca47167dadd486582ecd4e41f7fba6ae66cc4a4c5202f1f7acf34129a0dadf","impliedFormat":99},{"version":"390dc7901776ab8f54c64100156ae561f4601a6913e86e7d1658715d7c414763","impliedFormat":99},{"version":"6841c30168fa7fe25409d83ae5e210c6340cd412e543e98625d277764d136994","impliedFormat":99},{"version":"1dc13534a1f9f442a683b92ae3200c611f19c2e8039c0b381e09076c85e90049","impliedFormat":99},{"version":"f68896096cba0f06ffbf39a67c2280f6f2e5b90c75db56a3f9ae5f7f3bb54460","impliedFormat":99},{"version":"ceed5b112e868b1c223f4f24e03d302e8043f6bd8f6cf8ede8e75f0471ede6d3","impliedFormat":99},{"version":"26cfaec143443411bc7d5363f274f885ced430b8f4bee25a81f7827248848d7b","impliedFormat":99},{"version":"f9a591e5fe0be6728cc84e70325aacafffcf203b051ddef37d65651b43c05056","impliedFormat":99},{"version":"293c0a3e323608f4e20667acc74d7bdba727db954b840ee1db03f2fd5807c761","impliedFormat":99},{"version":"de8b4c367880fe92a0a740b706f08a46d1cf9e3981d55c2701e82423e81ef0ef","impliedFormat":99},{"version":"8f1b151c1f2ab5f797666d1d53199ad5cec66ab6f4c46f4c02d51ac6d47998d3","impliedFormat":99},{"version":"3a3fd6f5ca85ceeb293f2a010125f9455404958122b6dd0ba0b34f7dab74feb5","impliedFormat":99},{"version":"5bd7f6f573ac89ec20aaf326e79394da8a89fbff8a297aa864de9137ca045678","impliedFormat":99},{"version":"01498e4cffc8badc7ba4bacb201e88d89e7ffa46d0a710f1d0510a11104b4e15","impliedFormat":99},{"version":"5916b6f272fe245dd8032c708487405eb9f5b8a83ded6630ec1ab02df36ed63d","impliedFormat":99},{"version":"9e003336714371c98108af3dc53342829039327f8b1db97d826ca81805746b7f","impliedFormat":99},{"version":"d8954254110123f6f5c9fda6280674f6b853d47df7fc41c2090e741dc8b6b627","impliedFormat":99},{"version":"1e257873c8332ca2066f2f9409269352839d6ef6cd5624ba6b777199e78f70af","impliedFormat":99},{"version":"409e0eb04698050bf40f5da901990a58f7119591fad21dc5f4d94ef20be7f457","impliedFormat":99},{"version":"0fa0e6941b1d3095f625c4bda62c0a37247ab8749cfdbc8b44f4b4518d5482f5","impliedFormat":99},{"version":"56c7641a2de5b6695f415a89826b724734aa8cdcc72a112c8e80c3d140b86f4b","impliedFormat":99},{"version":"e96bd939a55117abe6ccbc02839f2f4d9ce3893368fea528ca91c59ebddf496f","impliedFormat":99},{"version":"81f6bf27eedb1ed92466abfcee33795a6b2304691ae01f42e60f8c76894fade7","impliedFormat":99},{"version":"228127c94406a1583eb6e1314a611d9ee26418d4590b4466ed0468d5be0b7e41","impliedFormat":99},{"version":"26a0c2d883e1ed55ba00810d957dedcde5d16d637e33063686e2bc3f58a5c64a","impliedFormat":99},{"version":"68099697ac4e919f6f1832389f32eb67d3d94fae744967f33e0cbc049a222a3a","impliedFormat":99},{"version":"bfb900f7de2066a4be644c269285fda8ccca40b065476a27b082173014d00467","impliedFormat":99},{"version":"ca2f4468c5bcfc7a37aace0a1f20f92b226b097bebb834f29ecbce8ee399c6cf","impliedFormat":99},{"version":"b787dc6f2d3fa1d219038e6b360245b6c7d15c00a64ee31b24c56e823a0e71ea","impliedFormat":99},{"version":"ce757254610e6b3a37357a5892c46a86525d0d7f0a5e4a907f6707a3928b8a63","impliedFormat":99},{"version":"31a63b6b49338cc6ddb1a318beda72d3d8fd523e50f8dd5d5ff3accd39a490d7","impliedFormat":99},{"version":"8fc7615fb8fa095406f5a58b1b817b9a685c516058877a3d2c06d4545fdf5dbe","impliedFormat":99},{"version":"eeab2749ab639ddcd1b01f3c3acebe9616e0265021bf6aff910070015b03b92b","impliedFormat":99},{"version":"d5da26af31358a4883edb6112879018b14c7c1fbcc457aa36961b03ee17bedea","impliedFormat":99},{"version":"4e93fb2d2c59bbc1f1a5211b36c447efe4d0af568d682ef1e5eb5f84ca6ccc2e","impliedFormat":99},{"version":"b6cc07bc56c7418ab500421d2629c26acdb6ec2e9af85c5e2c5d7e8c9f92566d","impliedFormat":99},{"version":"3d13fe973e92e708ad3dbbf1b2385bb799f8e70c8da71a1ac72fcb5521c8a5e9","impliedFormat":99},{"version":"16f3f66b5182e57c554d0e374e29fdc0a899c1321b3f94fa997d19abf9faf931","impliedFormat":99},{"version":"df459c67b9eef71318851331b23926961f0476b4b9d1674addcb627eabe583b2","impliedFormat":99},{"version":"b93915733f1802a8d9093116a5e660385afda0f72a5bab9c9bd356549a61f1cd","impliedFormat":99},{"version":"1dfb1491b28cf3734f3c07745a56d7758a12d546f81a70fbfe8ee9faeb528c54","impliedFormat":99},{"version":"42b00a511331c7f79039ae3be83fd5941b212ce193654c3ac5d82157227b69ad","impliedFormat":99},{"version":"1a5914065c7c6e2b6e98437c4ba6059bfeda3441f0f55cb07189df1e78698922","impliedFormat":99},{"version":"66d3f42196f32c639d8240bbd520852abc024d673c713bc5ab26b4bdde750a74","impliedFormat":99},{"version":"18ce9175445b3edec11bbca1c682a8f3919e74fb3504abd883ac462ea8089a0d","impliedFormat":99},{"version":"aec96f94a27fc61d5f4ead202628e811199f887d48bb7614b7e1706b629cd1ba","impliedFormat":99},{"version":"2ab01a0368f65b3b891e25416ae785dca54808f70d8f6204b99589ed4f7f1d2f","impliedFormat":99},{"version":"efd87ca2190e2d0f905e30343443c8d2f0d32cb2b1c9e05b2cb97b0061abec08","impliedFormat":99},{"version":"3323edd649d2c85d3528912cdf6fb770258d59d75241575fcf25c7850837de79","impliedFormat":99},{"version":"e0c8deca5262879f11ee84fe6664dcd197a60f546930363bf2d78d37c3138c69","impliedFormat":99},{"version":"3b8ba0456ce7763676505d103eb336a6b97659d6fc8a7d7d186558ea0b8c7ea5","impliedFormat":99},{"version":"7bae4a3f50a844fcbdc504d717f5f13dd7178ca99f131b230305db6b55e1dbc3","impliedFormat":99},{"version":"f3a4668046e42e9f5ebfdc8b79238eb27c399443dd1dcfcd4c4c3652692ed031","impliedFormat":99},{"version":"07160ad59d49c6f2999f9703542918df3eb1ef93857bc560b4baa7c6775d22ce","impliedFormat":99},{"version":"cbb7705b157d0be735c4564681d749529d26ac77296757fdf4d861a76e279d4d","impliedFormat":99},{"version":"c23b9684ab48ea6956f0a510c3cf724277fa457efa7ff85d5907f4e4290ba0aa","impliedFormat":99},{"version":"3859635347ecc10d4ff5dd68bde66420f5a8539b7313dd62020aa025208d2222","impliedFormat":99},{"version":"f49e8feb6d7473579a5ceb5872598bbc1be3723da653993472720629c72fa0ba","impliedFormat":99},{"version":"14c727434cfe6a078b51a88d005033ad01a76bec36292d7b47369d82e48e1cb0","impliedFormat":99},{"version":"1c1ae4ec02de9778286f84e0d15a1b74cc610c13c5b6a13c1ada2e6770eeb4e1","signature":"6d6449b80881e70de3f27d314c1e8a6353071f30442df504dc00e429b4f2252f"},{"version":"0e354464ab326224591b02875550f37ae289785c1558a494f858e3d2b289851c","signature":"8123bb1692f9138dedb3997a3c3ca15cafd7bacbafc0f67f50c62d4c8774fccb"},{"version":"366cdf6ad7ecd5713a24f28af7a40765cb9d2538540d232b7521f0ea5ce0bb13","signature":"6a9d493e0c5865db9fc7519b48fb79ba3eaf00535a697ff7ea1f0dabccf4d15d"},{"version":"de708061021467057ac23fc62822e8d0840a216e24dcc423d4658a95033fdb8d","signature":"0283eafdc2b9ec618b38542bb438d3e514d72ca37203be108f45f0f960019a0e"},{"version":"59ce31d87135ea5719f4451f6a288ac851746e6200088bcc1d3a7b1c0094c442","signature":"797b1b35838a594d3ba015115250d87d02d7e3ec59ac0ca62d4d32e0dd79bb50"},{"version":"417e42f4fe8f9ce5963dee9e82641653dd15266a75153b4624e299ca60a04c07","signature":"3db332987db7707a742b841f11d8a5590960b598d15bc3f678600d28afa94a91"},{"version":"dfa56c0062ac0ab7ed8916d742280b29f945b1ce10554b32b238fa0e84b94e98","signature":"245cbd0512d5ad85db0cbf054c74dad653b62f2da5300ceff0e87ff99dce5caf"},{"version":"c94b208239748236f918cd33929ff9d52a9941b4a8251f54a318f72fb872cb64","signature":"cc20a19087b1df060516722de9e6c4aa87a5c239c5ad1ecd83b61cd17c9bd16f"},{"version":"8dedf145bef222eb6a09d63a4a6d8f340cd315cbf61ede98ed459bb5b5ffcaeb","signature":"7a4fe07bffc8b6ec1785aec2b011f454fc93828d90b9269ccdda995af85bf073"},{"version":"8972f785d4fa7ea7d8b1c84bc5fe8cf151d30ab4ff288756df96e0e082f7594a","signature":"827c3c8128c93043529320108a33afe0cc3459ba09c04192f4600b80c73607b2"},{"version":"d08cf172a38f58995eda96e030606dce61e63fb16c50a0e20bf0148b1dd34cbd","signature":"485253ab3565278dbb431417d5ff09907309ee9e5c88550c115d882e872593c7"},{"version":"ceb8eb94f3be6f723de8230d338a50a8696f145270f68c514a804fab5343c404","signature":"c7429d15c4b492628c3b307bb6e3cfe8eb2ee741469b1ca524f90b84a143fe8f"},{"version":"8ad83abba071cb20fe7166a8adae3bd77a79be8711426fd96f5be354d868efdb","signature":"ac02d8be5e6d2e71292fe738d73adebc09aaa60603a8e51f7230faec34c5378a"},{"version":"87ed35cd068b7438bdc0f845b4ee101e98d3903b3722ef6f358df292e73d7d73","signature":"e4dbf6dbee6e1a22a1b988a18d5cb517c17e9ada9d691f2b4eb9e689452a17ac"},{"version":"05c806043f883a81bee91b72428e9551f7cff48fd3d03ce2ffbf22af8f0e10cc","signature":"5e1e3ed0f08feb1037e9f84157aa96a5177bba21b5b4d818d026532d0ff007b0"},{"version":"c1f1a1ff58583a523e6eb11fd5801a5bb0b3b531417bb0ce53db02a59a5545e9","signature":"d092958065bbc70478d8049066581c325f46a724087ef328cf2d47fbdc282b9a"},{"version":"0872ece3daf3df853bb9a0ca45cdae713dbf616445fff7c792bad8484d5f97ea","signature":"a0948a68b9c152fab283c5f443f8ec9535c005d8bbe560b2b8ff87f54279b68a"},{"version":"0b7fc25d6c77adeedb6cb69fe0eea69155fe7684212c7f3b2b440e90bac48a5b","signature":"7fc4d84099fd385c7645d9d41f854d415baad44aad0b6e07d6d89a89ef7bfc49"},{"version":"ef1b801787af0d1c73a87574b74f49c79c2c89c85360e0faedf3b345d527ad5f","signature":"a501d75788b8433deb30e2113ccbad77c77fb3ce7426757194549f621804f562"},{"version":"1408cfd0bd1cd2dd7d3a1f36a64552b2cd985acffeadbbb7a1fba337554620e5","signature":"e45edace128227a34a1958d4c889a4cf3f24ad784771495cd0004e7bbb07bfc5"},{"version":"b01f4830e8a111ec641ad7342febd9d3e77ad1e113110a3eaaaa4f162700710d","signature":"7be4e60a740deaefbfe7896964791aff6e6b2e3e12b93adbfbb29f8f7628dc34"},{"version":"c840e12da509f2c771dbc356a23750227a43d1864640c7e6404b8f9ec86b3871","impliedFormat":99},{"version":"6ae92eaaaef30fae975de604d3af31d5b00eca7f02d89fab589152df926685fd","impliedFormat":99},{"version":"e3b3780b68c70ff42894b68ee8f49186ef3335fc86a9106dd07e11580b460cc1","impliedFormat":99},{"version":"459fda119c27213d3defde220c051414020029abd52fb6fb9aea0beedc512c6b","impliedFormat":99},{"version":"968b0403af74a785c9408ca69a677235e49d0c80b2c27fb9858ad421a8778c7d","signature":"816603a3a3ce186bf710d1be189162cda5af637d36d8f318022aa2e448e3565a"},{"version":"bd8b85f093ee11def0903d12d44e65e46b1702bab79359a93be502cf6a0fd7ef","signature":"eaf8e3e068e23fbc82dbc1aa21ddfbe54baff2a86d75d50902329a5472efe0de"},{"version":"c6c1ee507fc1c39b22272636502888ad2bc03c9d4a05d073d7b8be8b84d1ef81","signature":"53774aa9ee1a946a98d3b2659d6aa4d81b0468b05525256a44acbfb4105a8412"},{"version":"0294f36355f84a2f9a078ff3dfc0fb90feecd3912f9fc7a213635e08d01348c5","signature":"a858f290e6f252e0e404b7fc2a8636fb9c205f6e6c353d1b3188faf9af5ab234"},{"version":"767a2e055fe6e021328f51448538e15f93cd53f89e5ba8c83c3a1e94b6630c22","signature":"a4ef05d8a5a879ad9030332befb57989305a3c6e1ed2dca110f645794beed040"},{"version":"1762c51f9a975e4451b8941109881e59f775acd37e13bbab014a48e8731d75a1","signature":"ef1e776b998a0c02087c6501bd07c78832baeac620b5021c245df4836dfa2f17"},{"version":"880acfd4ebc7da22bd68d4bee7f5ffd9f89d48b494110186b50ed856f0076ca7","signature":"cf8b1979ffd2f536106875f0b58c79a8b401278739c3d70dac5d4e201cb3c32c"},{"version":"c4df065d6e678a9d213416fcc5a2db8cc3870916ce86121775d873e23229224a","signature":"335690b6dd1a9cc476cd01cf766c5ae1234e90a318eb383a96d2e147358250a3"},{"version":"b839eb3a08e4549339974eedb277393502294a80e3718ee06bc8fd5cdd20ae37","signature":"324717f7bc7631d3a880b8e42f32c7f6225a2b8295c2793568c99de08e1fef1b"},{"version":"2f69175a10d85d5c041a48ad3699ac5a6055eaefd43f9440d8a57778a0ffec92","signature":"c2c4afc8c03490924367c6e6a877dddec82fb37ef9f2554c5754c71c7188e3e9"},{"version":"423483c43cd900e3315654a720cba2abdff48ade505900609d40ce4c5c323833","signature":"e6a25231cd80539f77cdaa44ccf5be365ffd178c050299a383c1a7a167a80a37"},{"version":"0edae8aaff2ba9ad6291e8b5313de1b7b7f4e3c1ffadc256d9f45953b3edc649","signature":"8754fe347bd81594a37b5a19a333e4a8063a20f443fab1af850f7edf12bb5537"},{"version":"f07845a4f9772a545efa7c1086e83a81732c72026d3f365f0bb52bfae5b66752","signature":"b3b0332e154b1a2ccb655349b7e2471c65f61a5b523c06973d88cb50ce785d19"},{"version":"082062d5e676f7049e4a684dc6726c631fe153cb457e494cc240bbe3be5650a4","impliedFormat":99},{"version":"ab2e38bc6b61891361655b5d2e45c4cbc95c9db4920453fd5329cac7fd4e1385","impliedFormat":99},{"version":"9bb8a03e0015254999c1e96830242fa3764856fd14958d3b097149f4491b1fa6","impliedFormat":99},{"version":"21655de6cf9d8df920b2dd8aaa5d6b4f88630c5c6f4df66947f4a2c4e59f7c79","impliedFormat":99},{"version":"c98e4b259d28463c34c65cdbfba447d5ccd4fed0a138b7acf8c796309e61e2d6","signature":"d9ed1c6c07bd03524f35e2b7cf385c3278909b3ed2daafb4b74d460d8b6420ce"},{"version":"fc5d8550d25b323f0664e8a34f4ef08ea383888f09728fccf0998cccdb3d2ff3","signature":"633e87523348a4bf2e0999dfa6d93004738ecbd7186f7f2ae5f350e5d7781d2b"},{"version":"fe71593dddfc086fd941f484c1094f952f54acdf7ec2d983acfaa61a816dec86","signature":"137673d794fa6adcae07fe30e38455116513f5894811194401033fe14bb56e38"},{"version":"c6c3dbe3b2cf62152b1c7493424edf66460d53d25e7d49c98f6a7d2c80334528","signature":"6a2879106635e78675da8924862839ed3ab8806d3e08316152b00a697a4141aa"},{"version":"3e7064de07e84a24d10943b69a0278752f23f1999b396153feb0a63f355beae3","signature":"d6e9a3e9eddda48d8308210ee9ecc68f9425bcc378fb1f8bec08ebb7e0f1b6d3"},{"version":"ce4eec21950930b52a7b7d76154bf16d87cbd7b057257d0857fbcb0bc690bd61","signature":"343542f6bbe309692eda70274e04acfbfe25aa0bfd9d2c9a711c5281bdb49cba"},{"version":"8ffdf73d62d015140d4c4edfe58757d2f21474d50da2f92d48e33c522b79dbc7","signature":"917fd3d6e4f13868f4a867924f3fbdd816fbad049d179cc81b17a45fd591cc86"},{"version":"4661743ca06bc9577174fc8ecd963ccaf0ec605c4aa89eaf6a622467397cf147","signature":"5879818ce307b9ed0a24051ba091c110081cbe6a2ba56008132dbdf4181ab8be"},{"version":"6be9cc6d63419c8acb7c6b32fdd7fff8285842e69dd35588019d93f95a52571b","signature":"6cc449dc9995a239ca20bb0e067ba9e5008acbd036727e8cd97bc0001bdbb2b7"},{"version":"7945ca8edb5a34539de61ed93c4877f1e90fa603e5b208740bde9cc00693067a","signature":"19d54bf73a5a0216df1cf3bd0ff80167cf2bda8b0aa052d2cc7ff7caaccc4efe"},{"version":"28cb8c81a73ab8485fcf4bebcaa7264cd678224a222b85a491be882798d8f550","signature":"1a3c71cf6a6fbdd7591116f48d95ef7dedf3cba8345149fe195729409cd34fe2"},{"version":"0c259776b22163c556ef08874cf8133e76b1700eee5477ac2608dd7bf8083e45","signature":"fec7336827914a1816050bca31f2ee284f4d7deaf7211898cdd094a0deb2b513"},{"version":"c8dea650241e838a1c6d0904e3f90109f1f934678fe64c7bba7c38b1af5dd4dd","signature":"82fd574ed84b5a906cdc2676c26414e76adc47c70dc6b848e0f4343438a34590"},{"version":"b531db01208aaffa47aff4926c165af00a6c8c13fa231187deb75f69eeb16aca","signature":"17fe8278e6a30c0377d12ab4098d280e74b21daa2c7f8dff2bb60d3795555bbe"},{"version":"b68acd2160533c5f326af2de608d45c4cc3cebced25340efa69a4cfec6658f38","signature":"7c199173b55f57172fe53234f0fc95cc87e6565a13ce83e7b47918742bd47dfc"},{"version":"307d88c82e8c9436b09ac27af700c3acc03a87bc6745cfbb51069a75e2b6d2c0","signature":"001c2ad8eef03b07f14e41bee9a415db23dccadc02a78919faee940e545787d2"},{"version":"14ddfd8524dece18a84e2745bd609fb9dffd6339abe7c85af10f5cbb8cb67bfe","signature":"ee0527b1ff34f0ceffb2348f0095b1c1cddf4faa46085ea2a110cebaee50d347"},{"version":"5a1348cbf5183880d6eaeb31d669d5853280b2e41d58e42bb2f6cd3d1b3e5b71","signature":"b843fbec02d0cb54db1702adcfd063ad08120abeda2498a1787b64590684359c"},{"version":"9a9e04e07fe35d1874951bdc8de7359f75e671b9397557f7384ad6b2a315cf58","signature":"18a4751b41acc2856ed6f45b34fbe725cf9c7e4d90eba0ad2feb4e2d0990ec5e"},{"version":"5170e400dc24e75c4b25865bbe0b20886ed49f59f59e7503b5caca5270cabde2","signature":"8aef51ae2a7868e062803dec9fbb656da273542c7c9f4621d5464b9605dfe009"},{"version":"a78eb0d122e55304d670c8a5e01b0ac0f37dbce5a53869669be076470a7f4d78","signature":"a7a3a87855b2d2cc8ae413e677b4899ffc58a5e4a20f055c07a404da99541520"},{"version":"e1024bac63fac77ff2dcf562cf00a82a2ee2f22f02d78869c293991716cf192c","signature":"0da53c45d881830a663b858e68023d38da5ac262bfdc606024742e9b45b74032"},{"version":"3787076bd7bc40a1c348d115b0fcefb37f77e0b92e941f2efa44d464396ca328","signature":"986ef67c1983fdc1546cd063b625ccee6cde32da7705ab8e1acebfa0fb39df5c"},{"version":"5a2588c9ed56c03c0e357d58330d2bb6d89e390f4be303a09ee854c0bcb36a92","signature":"5283c1424f61516c54136798c3e7e256528a1a75581132e32e616d76add1160d"},{"version":"19e8160ad82839d8ece0f64fa0354373220e0b0192fb63601bd2bee81f2fd782","signature":"1592edebde83872e3c0cdd72f5eadddab86cf295a6d19f09620b84442fd7854e"},{"version":"50873ca05ca34d25a3324222510a86a9599b3a311d9fff1406d8de0be6a9eaa8","signature":"e5f9d5f6f37816cef8cfd651f6a81ff29222429b81fe76184722549e7904facf"},{"version":"586f0de1456ff1f22947e2aec82642f7ec102d0ad0b6f13edeb79dbaa8b9d11b","signature":"8c6eba7333e72b090341a04f45aab07e000ee74e78276b2c041119bf6dd60587"},{"version":"4e150e90bb4f5066740c6bf43a8e5061e998b884d37c7170caaae8dcd808b8ab","signature":"2445a341f73ffabebf7b9d8886a913be095356c2ba0673e3cc2e7fa68b500c36"},{"version":"6dc5f5d9c28c0aa23452a32416ce337ac09ce633ad893b988a1ce256fd7e72be","signature":"828afa634f40e99fc918fbfbacbc0148c5502118e44433a914acadf42cbcc802"},{"version":"052c188cf49779dda6dd016340f5b1158da01b9cc983108dc4037ec5c0575afa","signature":"2840fdae09764d8ef1660442dd476e00484643fff12b1039a7e44c6df5df5b53"},{"version":"8dcf898abdf1bfe773b873d5e1b329b8f4d061fdba45f2ad842a3fdb4db5ab07","signature":"b410611afb0627ebadfbb1f24feaba95b75f2d6eb44e9b4a78cdbca1b5f64aaf"},{"version":"977a787ed6eda7e66ef0c3b925af8b08c9a4d8780e23cc8359c6a427dc84862a","signature":"5177c419b162946d62af34a97439621b087540542b9a706a057c72b7106ea818"},{"version":"bc4194f8ea1fdb9f7d518df1015108da6bd5a38aba9d64b6c134ce83c76fb51c","signature":"4c09a264144e8e6e0b181b42eb9c86fbe44114927c0141188c9105549fcc7969"},{"version":"55e5845f6f31bcb05e539336a48ced65fce64e02af796e6da40f4aee7bc78e91","signature":"0b8fd7aa2222d31c2de139a76ef2aa14dc86717c45b9ef30e2c733be1fa2707e"},"d1986184a09a52db8228cb2bb2a61a8c05c9354e5b93cec8e2628d8579c892d7",{"version":"d9fa276add16d87de0375e4ee85d1b8427515e8e3f08f1adf87c4e0cbc89f09e","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"}],"root":[[534,539],542,546,547,558,[574,581],[658,678],[683,695],[700,735]],"options":{"allowJs":true,"esModuleInterop":true,"jsx":4,"module":99,"skipLibCheck":true,"strict":true,"target":2},"referencedMap":[[734,1],[534,2],[735,3],[581,4],[662,5],[666,6],[547,7],[536,8],[668,9],[672,10],[670,11],[674,12],[688,13],[689,14],[686,15],[692,16],[693,17],[695,18],[702,19],[676,20],[704,21],[706,22],[578,23],[710,24],[708,25],[716,26],[714,27],[718,28],[722,29],[724,30],[720,31],[726,32],[730,33],[728,34],[732,35],[580,36],[694,37],[665,38],[687,39],[671,40],[669,41],[712,42],[663,43],[577,44],[667,45],[701,46],[673,47],[705,48],[675,49],[661,50],[709,51],[707,52],[664,40],[685,53],[677,54],[684,39],[678,55],[690,40],[691,40],[703,56],[719,57],[721,40],[723,58],[546,59],[725,57],[660,60],[711,42],[715,40],[717,61],[713,62],[659,63],[576,64],[683,65],[558,66],[733,67],[658,68],[574,69],[575,67],[700,70],[729,56],[727,71],[731,39],[579,72],[537,73],[538,73],[539,73],[542,74],[535,75],[554,76],[555,77],[634,78],[635,79],[567,80],[566,81],[565,82],[571,83],[570,84],[569,81],[563,81],[562,85],[568,86],[623,87],[604,88],[607,89],[603,90],[621,91],[587,92],[624,93],[608,92],[609,94],[625,92],[619,95],[610,92],[614,96],[615,92],[616,97],[613,98],[617,91],[626,99],[618,87],[627,100],[620,101],[622,102],[612,103],[560,104],[561,105],[572,106],[573,107],[551,108],[559,109],[605,2],[549,2],[550,110],[611,2],[553,111],[606,112],[564,113],[629,114],[630,115],[639,115],[638,116],[641,76],[640,76],[657,117],[656,118],[642,76],[643,76],[644,119],[645,120],[646,114],[647,116],[649,115],[648,76],[637,121],[632,122],[636,123],[631,124],[651,125],[650,126],[655,76],[652,127],[653,76],[633,128],[680,129],[679,76],[654,76],[699,130],[698,131],[696,132],[697,133],[552,134],[682,135],[681,112],[602,136],[597,137],[601,138],[599,2],[600,139],[628,140],[591,113],[594,141],[592,2],[595,142],[589,143],[590,144],[596,145],[593,146],[598,113],[583,147],[585,148],[586,149],[582,2],[584,2],[378,2],[142,150],[143,150],[144,151],[91,152],[145,153],[146,154],[147,155],[89,2],[148,156],[149,157],[150,158],[151,159],[152,160],[153,161],[154,161],[155,162],[156,163],[157,164],[158,165],[92,2],[90,2],[159,166],[160,167],[161,168],[162,169],[163,2],[164,170],[165,171],[166,172],[167,173],[168,174],[169,175],[170,176],[171,177],[172,178],[173,178],[174,179],[175,2],[176,180],[177,181],[179,182],[178,183],[180,184],[181,185],[182,186],[183,187],[184,188],[185,189],[186,190],[88,2],[195,191],[187,192],[188,193],[189,194],[190,195],[191,196],[192,197],[93,2],[94,198],[95,2],[96,2],[138,199],[139,200],[140,2],[141,184],[193,201],[194,202],[199,203],[463,113],[200,204],[198,205],[465,206],[464,207],[196,208],[461,2],[197,209],[79,2],[81,210],[460,113],[230,113],[557,211],[556,212],[540,2],[80,2],[548,113],[486,213],[491,1],[498,214],[481,215],[234,2],[242,216],[382,217],[385,218],[357,2],[370,219],[377,220],[259,2],[359,2],[240,2],[356,221],[402,222],[241,2],[232,223],[384,224],[386,225],[387,226],[458,227],[351,228],[304,229],[364,230],[365,231],[363,232],[362,2],[358,233],[383,234],[243,235],[428,2],[429,236],[270,237],[244,238],[271,237],[307,237],[210,237],[380,239],[379,2],[369,240],[476,2],[219,2],[497,241],[436,242],[437,243],[433,244],[515,2],[334,2],[438,245],[434,246],[520,247],[519,248],[514,2],[285,2],[337,249],[336,2],[513,250],[435,113],[290,251],[297,252],[299,253],[289,2],[294,254],[296,255],[298,256],[293,257],[291,2],[295,258],[516,2],[512,2],[518,259],[517,2],[288,260],[507,261],[510,262],[278,263],[277,264],[276,265],[523,113],[275,266],[264,2],[525,2],[544,267],[543,2],[526,113],[527,268],[202,2],[366,269],[367,270],[368,271],[206,2],[371,2],[226,272],[201,2],[450,113],[208,273],[449,274],[448,275],[439,2],[440,2],[447,2],[442,2],[445,276],[441,2],[443,277],[446,278],[444,277],[239,2],[236,2],[237,237],[391,2],[396,279],[397,280],[395,281],[393,282],[394,283],[389,2],[456,245],[231,245],[485,284],[492,285],[496,286],[325,287],[324,2],[319,2],[472,288],[480,289],[352,290],[353,291],[431,292],[341,2],[454,293],[329,113],[346,294],[457,295],[342,2],[345,296],[343,2],[455,297],[452,298],[451,2],[453,2],[349,2],[427,299],[214,300],[327,301],[331,302],[347,303],[350,304],[339,305],[332,306],[479,307],[405,308],[323,309],[211,310],[478,311],[207,312],[398,313],[390,2],[399,314],[416,315],[388,2],[415,316],[87,2],[410,317],[235,2],[430,318],[406,2],[220,2],[222,2],[361,2],[414,319],[238,2],[262,320],[348,321],[268,322],[328,2],[413,2],[392,2],[418,323],[419,324],[360,2],[421,325],[423,326],[422,327],[372,2],[412,310],[425,328],[322,329],[411,330],[417,331],[247,2],[251,2],[250,2],[249,2],[254,2],[248,2],[257,2],[256,2],[253,2],[252,2],[255,2],[258,332],[246,2],[314,333],[313,2],[318,334],[315,335],[317,336],[320,334],[316,335],[227,337],[306,338],[475,339],[473,2],[502,340],[504,341],[468,342],[503,343],[215,344],[212,344],[245,2],[229,345],[228,346],[224,347],[225,348],[233,349],[261,349],[272,349],[308,350],[273,350],[217,351],[216,2],[312,352],[311,353],[310,354],[309,355],[218,356],[459,357],[260,358],[467,359],[432,360],[462,361],[466,362],[355,363],[354,364],[335,365],[321,366],[303,367],[305,368],[302,369],[424,370],[326,2],[490,2],[223,371],[426,372],[474,373],[333,2],[263,374],[340,375],[338,376],[265,377],[400,378],[469,2],[266,379],[401,379],[488,2],[487,2],[489,2],[471,2],[470,2],[403,380],[330,2],[300,381],[221,382],[279,2],[205,383],[267,2],[494,113],[204,2],[506,384],[287,113],[500,245],[286,385],[483,386],[284,384],[209,2],[508,387],[282,113],[283,113],[274,2],[203,2],[281,388],[280,389],[269,390],[344,177],[404,177],[420,2],[408,391],[407,2],[292,260],[213,2],[301,113],[477,272],[484,392],[82,113],[85,393],[86,394],[83,113],[84,2],[381,198],[376,395],[375,2],[374,396],[373,2],[482,397],[493,398],[495,399],[499,400],[545,401],[501,402],[505,403],[533,404],[509,404],[532,405],[511,406],[521,407],[522,408],[524,409],[528,410],[531,272],[530,2],[529,411],[588,2],[409,412],[541,2],[77,2],[78,2],[13,2],[14,2],[16,2],[15,2],[2,2],[17,2],[18,2],[19,2],[20,2],[21,2],[22,2],[23,2],[24,2],[3,2],[25,2],[26,2],[4,2],[27,2],[31,2],[28,2],[29,2],[30,2],[32,2],[33,2],[34,2],[5,2],[35,2],[36,2],[37,2],[38,2],[6,2],[42,2],[39,2],[40,2],[41,2],[43,2],[7,2],[44,2],[49,2],[50,2],[45,2],[46,2],[47,2],[48,2],[8,2],[54,2],[51,2],[52,2],[53,2],[55,2],[9,2],[56,2],[57,2],[58,2],[60,2],[59,2],[61,2],[62,2],[10,2],[63,2],[64,2],[65,2],[11,2],[66,2],[67,2],[68,2],[69,2],[70,2],[1,2],[71,2],[72,2],[12,2],[75,2],[74,2],[73,2],[76,2],[114,413],[126,414],[112,415],[127,416],[136,417],[103,418],[104,419],[102,420],[135,411],[130,421],[134,422],[106,423],[123,424],[105,425],[133,426],[100,427],[101,421],[107,428],[108,2],[113,429],[111,428],[98,430],[137,431],[128,432],[117,433],[116,428],[118,434],[121,435],[115,436],[119,437],[131,411],[109,438],[110,439],[122,440],[99,416],[125,441],[124,428],[120,442],[129,2],[97,2],[132,443]],"affectedFilesPendingEmit":[735,581,662,666,547,536,668,672,670,674,688,689,686,692,693,695,702,676,704,706,578,710,708,716,714,718,722,724,720,726,730,728,732,580,694,665,687,671,669,712,663,577,667,701,673,705,675,661,709,707,664,685,677,684,678,690,691,703,719,721,723,546,725,660,711,715,717,713,659,576,683,558,733,658,574,575,700,729,727,731,579,537,538,539,542],"version":"5.7.3"} \ No newline at end of file