diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 419bb0a..a5c9d22 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -392,7 +392,9 @@ "Bash(rm -f /opt/teecup/v0-zip22-login-*.png /opt/teecup/v0-integrated-*.png *)", "Bash(curl -s -o /dev/null -w \"friends/on-course anonym: %{http_code}\\\\n\" https://teecup.teeoff.no/friends/on-course)", "Bash(docker rm -f teecup_frontend_scratch teecup_api_scratch teecup-minio-scratch >/dev/null 2>&1 *)", - "Bash(curl -s -o /dev/null -w \"more: %{http_code}\\\\n\" https://teecup.teeoff.no/more)" + "Bash(curl -s -o /dev/null -w \"more: %{http_code}\\\\n\" https://teecup.teeoff.no/more)", + "Bash(echo \"tsc exit: $?\")", + "Bash(docker rm -f teecup_frontend_scratch teecup_api_scratch teecup-minio-scratch > /dev/null 2>&1 *)" ], "additionalDirectories": [ "/opt/teeoff/deploy", diff --git a/ARCHITECTURE_DECISIONS.md b/ARCHITECTURE_DECISIONS.md index 61c9d45..ed4a95a 100644 --- a/ARCHITECTURE_DECISIONS.md +++ b/ARCHITECTURE_DECISIONS.md @@ -3611,6 +3611,207 @@ Beslutning F) er en egen, senere runde — samme lagdeling som ADR-038 --- +## ADR-040: «Det store grepet» — sammenhengende rundeoppsett + delt +Score/Scorekort/Leaderboard-navigasjon + +**Kontekst:** reist 2026-08-01/02, som en direkte oppfølging av flere +visuelle redesign-runder (login-skjerm og dashbord, begge inspirert av et +eksternt design-verktøy, "Stitch"). Brukeren pekte på to strukturelle +problemer som selve fargeredesignet ikke løser: (1) rundeoppsettet er i +dag spredt over ETT langt skjema (`new-round.tsx`) OG en helt annen skjerm +(`round-detail.tsx`, etter at runden allerede er opprettet, for sider/ +Money Ball-rekkefølge), (2) Score/Scorekort/Leaderboard for en frittstående +runde deler ingen fast navigasjon — hver side bygger sin egen fulle +side-chrome, ulikt Stitchs skisse. + +**Beslutning A — rundeoppsettet blir én sammenhengende veiviser, 5 steg:** +Bane & tid → Spilleform (inkl. HCP-innstillinger) → Spillere → Lag/ +rekkefølge (kun to-sidede formater/Money Ball) → Deling. Erstatter dagens +ett-langt-skjema-pluss-en-helt-annen-skjerm-mønster. + +**Beslutning B — HCP-prosent er ALLTID justerbar, Match-HCP er en egen +bryter:** brukeren presiserte eksplisitt at prosenten (ADR-014s +`strategy`) aldri skal være låst til formatets standardverdi, og at +to-sidede formater i tillegg skal kunne skru `use_matchplay_handicap` +av/på — differensial-fordeling mellom sidene (laveste settes til 0 +mottatte, resten mottar differansen fordelt fra laveste stroke index) i +stedet for ren brutto når det ikke er noen forskjell. **Funnet ved +verifisering, ikke antatt:** begge deler fantes allerede FERDIGBYGD i +motoren (`match_play_strokes()` i `handicap_engine.py`, ADR-014s "fire +brytere" i `app/handicap.py`) — kun brukt av org-scopede turneringer +(`session.allowance_override`). Frittstående runder (`round`-tabellen) +manglet bare selve kolonnen; ingen ny regnelogikk. + +**Beslutning C — "Spillere og runde" (inkl. flighter) blir værende på +rundeoppsettsiden**, flytter IKKE inn i den nye delte fane-raden — dette +er forvaltning, ikke visning. Score/Scorekort/Leaderboard trenger i +stedet en kompakt, alltid tilgjengelig vei tilbake til oppsettsiden +(sammen med "Fullfør runde"/"Slett runde") — plassering (toppmeny vs. +footer vs. noe annet) er bevisst overlatt til V0 å foreslå ut fra en +presist beskrevet kontekst, ikke forhåndsbestemt her. + +**Beslutning D — Scorekort og Statistikk slås sammen til ÉN side,** +statistikk RETT UNDER scorekortet — den delte fane-raden blir dermed tre +faner (Score / Scorekort / Leaderboard), ikke fire. `/my-rounds/[id]/ +stats` forsvinner som egen rute, innholdet flytter inn i `/my-rounds/ +[id]/scorecard`. + +**Beslutning E — reelt, tidligere udokumentert funn: spillere i +scorekortet var ALDRI gruppert lagvis.** Brukeren rapporterte det +konkret (en ekte fourball-runde der Erol+Kåre var ett lag, Ellen +Anette+Christer det andre, men vist interleaved i rekkefølgen de ble +lagt til). Bekreftet i koden på BEGGE steder spillere vises for et +to-sidet format: `ScorecardGrid` (`round-detail.tsx`) og +`MatchScorecardGrid` (`round-scorecard.tsx`) sorterte ingen av dem på +`round_side_id` — ren innsettingsrekkefølge. Fikset med en liten, +presis sortering (stabil sort på side, side A samlet FØR side B) i +begge, IKKE en ny funksjon. Om celle-nivå-fargelegging (hvem som vant +hullet) alene er nok til å skille lagene visuelt, eller om det trengs +en tydeligere lag-blokk-markering, er et åpent spørsmål overlatt til V0 +(samme "spør V0"-mønster som Beslutning C). + +**Bygget og verifisert denne runden (2026-08-02):** +- Ny migrasjon `051_round_allowance_override.sql` — `round.allowance_ + override jsonb`, rent additivt. +- `app/routers/rounds.py`: ny `_round_allowance_override()`-hjelpe- + funksjon (henter/parser friskt fra `round`-raden, samme mønster som + `matches.py`/`scoring.py` sin `session.allowance_override` — IKKE + cachet i selve config-objektet), koblet inn i `_recompute_side_ + handicaps`/`_relative_strokes_for_round`. `RoundCreate`/`RoundUpdate`/ + `RoundOut` fikk `allowance_override`. PATCH gjenberegner `playing_ + handicap` for allerede tildelte sider ved endring (samme mønster som + `tournaments.py` sin `_recompute_session_matches`). +- Scratch-verifisert presist: 100%→50%-prosent ga `playing_handicap` + 24/10 → 12/5 (nøyaktig som beregnet for hånd fra HCP 20,0/10,0 mot en + slope/rating-satt egendefinert bane), `use_matchplay_handicap:false` + ga rå 12/5 i stedet for differensial 7/0 (`match_play_strokes([12,5])` + → laveste=5 til 0, differanse 7). Slagspill upåvirket (regresjon + bekreftet). +- Lag-grupperingsfiksen (Beslutning E) browserverifisert mot en fersk + scratch-fourball-runde som gjenskapte brukerens eget rapporterte + scenario nøyaktig (fire spillere lagt til i interleaved rekkefølge + A→Rødt, B→Blått, C→Rødt, D→Blått) — bekreftet visuelt i BÅDE + `ScorecardGrid` (live) og `MatchScorecardGrid` (post-runde) at Rødt + lag nå vises samlet, deretter Blått. +- Rullet ut mot ekte systemer 2026-08-02, bruker bekreftet eksplisitt: + 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 — bekrefter ruten når FastAPI), + `teeoff.no` upåvirket. + +**Steg 3 (V0-prompt 1, rundeoppsett-veiviseren) ✅ BYGGET, GRUNDIG +VERIFISERT OG LIVE (2026-08-02), samme dag:** V0-prompten (Beslutning A/B) +skrevet og 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`s `buildAllowanceOverride`), og en helt ny +innsendingsorkestrering — runden (+ deltakere + sider/lineup) finnes +FØRST når HELE veiviseren er fullført (runde → sider → medspillere med +`round_side_id` satt direkte på opprettelse → eierens egen side- +tildeling via PATCH → Money Ball-rekkefølge via PATCH → naviger til +runden), ulikt den gamle "opprett runde først, legg til spillere +etterpå"-flyten. +**Reelle feil funnet og rettet under integrering:** V0s egen `Tee`-type +manglet kjønnsfelt (ville vist ufiltrerte utslag for både eier og +gjester) — lagt til `genders: ApiGender[]`, filtrert riktig per kjent +kjønn. To TS-feil (prop-spredning som overskrev `course`/`ownGender` +tilbake til `null`, og en `"x"`-kjønn-mismatch i tee-filtreringen). +**Bevisst, ærlig begrensning:** en spiller funnet via `/people/search` +har ukjent kjønn før de faktisk er lagt til runden (samme begrensning +som `PersonMatch`-modellen alltid har hatt) — utslagslisten for en slik +spiller er derfor UFILTRERT i veiviseren, med en forklarende tekst; +server-validering gir en klar feil ved et faktisk uforenlig valg. +**Verifisert grundig i isolert scratch:** full produksjonsbuild (alle +ruter listet), og to komplette nettleser-gjennomkjøringer mot en fersk +scratch-backend — én Fourball-runde (egen bane, gjest, to sider +opprettet og tildelt interleaved, 90 % HCP-prosent) og én Slagspill- +runde (rene standardvalg). Bekreftet direkte mot databasen at +`allowance_override` ble bygget nøyaktig riktig i begge tilfeller +(`{"strategy":{"type":"per_player","percentage":0.9},...}` for +Fourball — riktig `per_player`, ikke `combined`, siden fourball ikke er +en side-enhet — og `null` for Slagspill, ingen unødvendig payload), +sidene/tildelingene stemte, og HCP ble beregnet korrekt fra ekte +profildata (kryssjekket mot hånd-regnet Course Handicap for begge +testspillerne). +**Rullet ut live 2026-08-02**, 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`/ +`/my-rounds/new` → 200, `teeoff.no` upåvirket. + +**Steg 4-5 (V0-prompt 2, den delte Score/Scorekort/Leaderboard-fane-raden ++ integrering) ✅ BYGGET, GRUNDIG VERIFISERT OG LIVE (2026-08-02), samme +dag:** V0-prompten (Beslutning C/D) skrevet — bevisst SMALERE i omfang +enn prompt 1: kun den delte header/fane-raden, ikke en redesign av selve +innholdet på Scorekort-siden (den sammenslåingen er ren sammenstabling av +to allerede ferdige komponenter, løst direkte i kode uten V0). Kjørt av +bruker (zip 26) — leverte `components/round-header.tsx` (kontekstblokk +med tilbakeknapp/bane/tee/dato/tid/Matchspill-merke, tre-fanet +segmentert kontroll, og en "Administrer"-knapp som åpner en bunnsheet- +dialog med lenke til "Spillere og runde" + "Fullfør runde" + to-stegs +"Slett runde") — tatt inn UENDRET (ren presentasjon, ingen logikk å +bytte ut). +**Ekte datalag bygget rundt den:** ny `components/round-page-shell.tsx` +— henter kun de feltene headeren trenger via `/rounds/{id}`, bygger +`tabHrefs` til de tre ekte rutene, og implementerer `onFinishRound`/ +`onDeleteRound` som egne, selvstendige handlinger (samme +`POST .../complete`/`DELETE ...`-endepunkter som `round-detail.tsx` sin +opprinnelige logikk, men uavhengig av den siden komponenten også må +fungere fra Scorekort- og Leaderboard-siden). +**Wiret inn i alle tre destinasjonene:** Score (`round-detail.tsx`sin +gamle egne header fjernet, erstattet med `RoundPageShell` — den +INTERNE "Score"/"Spillere og runde"-fanevekslingen beholdt UENDRET som +et bevisst lavrisiko-valg, nås nå i tillegg via headerens +"Administrer"-dialog med en ny `?tab=manage`-URL-parameter lest ved +mount), Scorekort (`round-scorecard.tsx` + `round-stats.tsx` slått +sammen til ÉN side via en ny `embedded`-prop på begge, statistikk RETT +UNDER scorekortet som spec-et i Beslutning D — gammel `/stats`-rute er +nå en ren redirect dit), Leaderboard. +**Reelle feil funnet og rettet under integrering:** to `
`- +landemerker på samme side etter sammenslåingen av Scorekort+Statistikk +(ugyldig HTML/tilgjengelighet) — rettet med en `ContentTag`-switch +(`div` når embedded, `main` ellers) i `round-stats.tsx`. Fjernet nå +overflødige kryss-lenker mellom de to sammenslåtte sidene, kollapset +"Se scorekort"/"Se full rundestatistikk" til én "Se scorekort og +statistikk"-knapp i `round-detail.tsx` sitt fullført-banner. +**Verifisert grundig i isolert scratch:** full produksjonsbuild (alle +ruter listet), full nettleser-gjennomkjøring gjennom hele "Administrer"- +flyten — headeren konsistent på tvers av alle tre fanene, dialogens +lenke til "Spillere og runde" bekreftet å faktisk åpne riktig intern +fane via `?tab=manage`, ekte "Fullfør runde" (bekreftet `completed_at` +satt via et direkte API-kall etterpå) og ekte to-stegs "Slett runde" +(bekreftet navigerte korrekt til tom-tilstand på `/my-rounds` +etterpå). Traff en Turbopack-flakighet i selve scratch-dev-serveren +underveis (stuck spinner, ingen nettverkskall i det hele tatt) — løst +med en frisk containerstart, bekreftet IKKE en kodefeil (samme +kjente ustabilitetsklasse som tidligere dokumentert i dette +prosjektet, ikke noe nytt). +**Rullet ut live 2026-08-02**, 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-040 («det store grepet») er dermed HELT FERDIG, alle 5 steg, +backend + frontend, live:** +1. ~~Backend: `allowance_override`-migrasjon + kobling~~ ✅ +2. ~~Kode-fiks: lag-sortering~~ ✅ +3. ~~V0-prompt 1: den samlede rundeoppsett-veiviseren~~ ✅ +4. ~~V0-prompt 2: den delte Score/Scorekort/Leaderboard-fane-raden~~ ✅ +5. ~~Integrering + browserverifisering + utrulling~~ ✅ + +Bevisst utenfor omfang, egen fremtidig vurdering om ønskelig: den +INTERNE "Score"/"Spillere og runde"-fanevekslingen på selve Score-siden +ble bevisst beholdt (ikke fjernet/erstattet av `?tab=manage`-mekanismen +alene) som et lavrisiko-valg — kan forenkles videre senere om ønsket, +ikke noe brukeren har bedt om. + +--- + ## Åpne spørsmål (ikke besluttet ennå) Disse må avklares før eller under de relevante fasene: diff --git a/CLAUDE.md b/CLAUDE.md index e27990f..6f29782 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5952,6 +5952,211 @@ Ferdig og verifisert: 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 + ``+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()`. `
`-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 `
`-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 `` 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 @@ -6214,3 +6419,11 @@ Neste steg: 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 fe1dd46..1b609f3 100644 --- a/DESIGN_SYSTEM.md +++ b/DESIGN_SYSTEM.md @@ -50,18 +50,28 @@ ellers virker helt av seg selv så lenge token-klassene brukes. | Token | Lys modus | Rolle | |---|---|---| -| `background` / `foreground` | nesten hvit / mørk grønn-grå | sideflate / brødtekst | -| `card` / `card-foreground` | hvit / mørk grønn-grå | kort, paneler, seksjoner | +| `background` / `foreground` | nesten hvit / 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ønn-tint | sekundær vekt, sjeldent brukt alene | -| `muted` / `muted-foreground` | lys grå-grønn / dempet | bakgrunnstoner, sekundær tekst/bildetekst | -| `accent` / `accent-foreground` | lys grønn-tint | hover-tilstand på nøytrale flater | +| `secondary` / `secondary-foreground` | lys grå | sekundær vekt, sjeldent brukt alene | +| `muted` / `muted-foreground` | lys grå / dempet | bakgrunnstoner, sekundær tekst/bildetekst | +| `accent` / `accent-foreground` | lys grå | hover-tilstand på nøytrale flater | | `destructive` | rød-oransje | slett/fare-handlinger | -| `border` / `input` | lys grå-grønn | kant på kort, felt, delelinjer | +| `border` / `input` | lys grå | kant på kort, felt, delelinjer | | `ring` | samme som `primary` | fokus-ring (tastatur-tilgjengelighet) | | `brand-orange` / `brand-orange-foreground` | `oklch(0.6792 0.2128 36.53)` | sekundærfarge — "over par"/advarsel, IKKE feil (det er `destructive`) | | `chart-1` … `chart-6` | grønn→blå→gul→oransje→rød-skala | statistikk-/fordelingsvisualiseringer (ADR-033), ALDRI brukt for kategoriske UI-tilstander | +**2026-08-02:** alle nøytrale tokens (`background`/`muted`/`accent`/ +`border`/`secondary` m.fl.) hadde tidligere en svak grønn hue (130-145) +iblandet selv når de skulle være "nøytral grå" — dette ga et vedvarende, +utilsiktet grønt skjær på nesten hver bakgrunn, hover-tilstand og kant i +hele appen (brukeren opplevde appen som "stuck i det lysegrønne +utseendet"). Rettet til ekte nøytral grå (`chroma 0`) på tvers av lys/ +mørk/media-query-blokkene. `primary` er UENDRET og fortsatt den eneste +tokenen som skal lese som grønn — reservert til valgt/aktiv tilstand og +primærhandling, se semantikk-listen under. + **Farge-semantikk, konsekvent på tvers av hele appen:** - `primary` (grønn) = valgt/aktiv, positivt resultat (under par, ledende, bekreftet), primærhandling. diff --git a/FEATURE_BACKLOG.md b/FEATURE_BACKLOG.md index a9c4378..eb17f55 100644 --- a/FEATURE_BACKLOG.md +++ b/FEATURE_BACKLOG.md @@ -3825,6 +3825,63 @@ beregnet og vist, banneret erstattet av det eksisterende felles `docker compose up -d --build teecup_frontend`), bruker bekreftet eksplisitt. +## Notat 2026-08-01/02: «Det store grepet» — sammenhengende rundeoppsett + delt Score/Scorekort/Leaderboard-navigasjon — ✅ HELT FERDIG (alle 5 steg), se ADR-040 + +Full beslutningslogg i ARCHITECTURE_DECISIONS.md (ADR-040) — dette notatet +er kun arbeidssporet/gjenstående plan, ikke beslutningene selv. + +**Bakgrunn:** oppstod som en direkte oppfølging av login-/dashbord- +redesign-rundene (inspirert av et eksternt design-verktøy, "Stitch"). +Brukeren pekte på to strukturelle hull utover selve fargespråket: +rundeoppsettet er spredt over to helt separate skjermer, og Score/ +Scorekort/Leaderboard for en frittstående runde deler ingen fast +navigasjon. + +**Fem load-bærende beslutninger bekreftet av bruker** (se ADR-040 for full +tekst): (A) rundeoppsettet blir én 5-stegs veiviser, (B) HCP-prosent alltid +justerbar + egen Match-HCP-bryter (`use_matchplay_handicap`), (C) +"Spillere og runde" (inkl. flighter) blir værende på oppsettsiden, ikke i +den nye fane-raden — plassering av vei-tilbake/fullfør/slett overlatt til +V0, (D) Scorekort+Statistikk slås sammen til ÉN side (statistikk under +scorekortet) — fane-raden blir 3 faner, ikke 4, (E) et reelt, tidligere +udokumentert funn: spillere i scorekortet var ALDRI gruppert lagvis (ren +innsettingsrekkefølge) — fikset med stabil sortering på `round_side_id`. + +**Status per 2026-08-02:** +1. ✅ Backend: migrasjon `051_round_allowance_override.sql` + + `_round_allowance_override()`-kobling i `app/routers/rounds.py`. + Scratch- og produksjonsverifisert (se ADR-040 for tallene). +2. ✅ Kode-fiks: lag-sortering i `ScorecardGrid` (`round-detail.tsx`) og + `MatchScorecardGrid` (`round-scorecard.tsx`). Browserverifisert mot + brukerens eget rapporterte bug-scenario (interleaved lag-tilføyelse). +3. ✅ **V0-prompt 1 BYGGET, VERIFISERT OG LIVE (2026-08-02):** + `frontend/components/new-round.tsx` er nå den samlede 5-stegs + veiviseren (zip 25 fra V0, flettet med ekte logikk — bane-søk/ + -opprettelse, `/people/search`, `allowance_override`-konstruksjon, + full innsendingsorkestrering runde→sider→medspillere→tildelinger). + Full detalj (inkl. de to reelle feilene funnet under integrering) i + ARCHITECTURE_DECISIONS.md (ADR-040). +4. ✅ **V0-prompt 2 BYGGET, VERIFISERT OG LIVE (2026-08-02):** ny delt + header/fane-rad (`components/round-header.tsx`, tatt inn uendret fra + V0 zip 26, presentasjon-only) + ny `components/round-page-shell.tsx` + (ekte datalag/handlinger) — koblet inn på alle tre destinasjonene. + Scorekort+Statistikk slått sammen til ÉN side (ren kode-sammenstabling + av to eksisterende komponenter, ikke sendt til V0 — bevisst utenfor + prompt 2s omfang). "Spillere og runde"/Fullfør/Slett nås via en + "Administrer"-dialog i headeren. Full detalj (inkl. den ene reelle + feilen funnet — to `
`-landemerker på samme side) i + ARCHITECTURE_DECISIONS.md (ADR-040). +5. ✅ Integrering + browserverifisering + utrulling — ferdig samme dag. + +**Bevisst IKKE dekket av V0-prompt 2, fortsatt et åpent spørsmål:** +Beslutning E sitt spørsmål om lag-grupperingen (sortering fikset i +steg 2) trenger mer enn celle-fargelegging for å skille lagene tydelig +nok i scorekortet — prompt 2 ble bevisst smalere i omfang enn først +planlagt (kun selve header/fane-chrome-en), så dette spørsmålet er +IKKE stilt til V0 ennå. Egen, senere, liten vurdering om ønskelig. + +--- + ## Bevisst endret fra opprinnelige (Gemini-)råd - 🔀 **Banedata:** API mot teeoff (ADR-004), IKKE direkte delt database. Direkte diff --git a/frontend/app/globals.css b/frontend/app/globals.css index f8bbef5..85b157c 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -55,23 +55,28 @@ :root { color-scheme: light; - --background: oklch(0.985 0.005 130); - --foreground: oklch(0.24 0.02 145); + /* Nøytrale flater er EKTE nøytral grå (chroma 0) -- før 2026-08-02 hadde + 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); + --foreground: oklch(0.24 0 0); --card: oklch(1 0 0); - --card-foreground: oklch(0.24 0.02 145); + --card-foreground: oklch(0.24 0 0); --popover: oklch(1 0 0); - --popover-foreground: oklch(0.24 0.02 145); + --popover-foreground: oklch(0.24 0 0); --primary: oklch(0.7512 0.1613 130.33); - --primary-foreground: oklch(0.22 0.04 140); - --secondary: oklch(0.96 0.02 130); - --secondary-foreground: oklch(0.3 0.03 140); - --muted: oklch(0.96 0.01 130); - --muted-foreground: oklch(0.52 0.02 140); - --accent: oklch(0.94 0.05 130); - --accent-foreground: oklch(0.3 0.04 140); + --primary-foreground: oklch(0.22 0 0); + --secondary: oklch(0.96 0 0); + --secondary-foreground: oklch(0.3 0 0); + --muted: oklch(0.96 0 0); + --muted-foreground: oklch(0.52 0 0); + --accent: oklch(0.94 0 0); + --accent-foreground: oklch(0.3 0 0); --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.9 0.015 130); - --input: oklch(0.9 0.015 130); + --border: oklch(0.9 0 0); + --input: oklch(0.9 0 0); --ring: oklch(0.7512 0.1613 130.33); --brand-orange: oklch(0.6792 0.2128 36.53); --brand-orange-foreground: oklch(0.99 0 0); @@ -104,20 +109,20 @@ .dark { color-scheme: dark; - --background: oklch(0.19 0.02 150); - --foreground: oklch(0.96 0.01 130); - --card: oklch(0.24 0.02 150); - --card-foreground: oklch(0.96 0.01 130); - --popover: oklch(0.24 0.02 150); - --popover-foreground: oklch(0.96 0.01 130); + --background: oklch(0.19 0 0); + --foreground: oklch(0.96 0 0); + --card: oklch(0.24 0 0); + --card-foreground: oklch(0.96 0 0); + --popover: oklch(0.24 0 0); + --popover-foreground: oklch(0.96 0 0); --primary: oklch(0.7512 0.1613 130.33); - --primary-foreground: oklch(0.2 0.04 140); - --secondary: oklch(0.28 0.02 150); - --secondary-foreground: oklch(0.96 0.01 130); - --muted: oklch(0.28 0.02 150); - --muted-foreground: oklch(0.7 0.02 135); - --accent: oklch(0.32 0.04 140); - --accent-foreground: oklch(0.96 0.01 130); + --primary-foreground: oklch(0.2 0 0); + --secondary: oklch(0.28 0 0); + --secondary-foreground: oklch(0.96 0 0); + --muted: oklch(0.28 0 0); + --muted-foreground: oklch(0.7 0 0); + --accent: oklch(0.32 0 0); + --accent-foreground: oklch(0.96 0 0); --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 12%); --input: oklch(1 0 0 / 16%); @@ -148,20 +153,20 @@ @media (prefers-color-scheme: dark) { :root:not(.light) { color-scheme: dark; - --background: oklch(0.19 0.02 150); - --foreground: oklch(0.96 0.01 130); - --card: oklch(0.24 0.02 150); - --card-foreground: oklch(0.96 0.01 130); - --popover: oklch(0.24 0.02 150); - --popover-foreground: oklch(0.96 0.01 130); + --background: oklch(0.19 0 0); + --foreground: oklch(0.96 0 0); + --card: oklch(0.24 0 0); + --card-foreground: oklch(0.96 0 0); + --popover: oklch(0.24 0 0); + --popover-foreground: oklch(0.96 0 0); --primary: oklch(0.7512 0.1613 130.33); - --primary-foreground: oklch(0.2 0.04 140); - --secondary: oklch(0.28 0.02 150); - --secondary-foreground: oklch(0.96 0.01 130); - --muted: oklch(0.28 0.02 150); - --muted-foreground: oklch(0.7 0.02 135); - --accent: oklch(0.32 0.04 140); - --accent-foreground: oklch(0.96 0.01 130); + --primary-foreground: oklch(0.2 0 0); + --secondary: oklch(0.28 0 0); + --secondary-foreground: oklch(0.96 0 0); + --muted: oklch(0.28 0 0); + --muted-foreground: oklch(0.7 0 0); + --accent: oklch(0.32 0 0); + --accent-foreground: oklch(0.96 0 0); --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 12%); --input: oklch(1 0 0 / 16%); diff --git a/frontend/app/my-rounds/[id]/leaderboard/page.tsx b/frontend/app/my-rounds/[id]/leaderboard/page.tsx index 930b926..33b8a31 100644 --- a/frontend/app/my-rounds/[id]/leaderboard/page.tsx +++ b/frontend/app/my-rounds/[id]/leaderboard/page.tsx @@ -1,3 +1,4 @@ +import { RoundPageShell } from "@/components/round-page-shell" import { RoundLeaderboard } from "@/components/round-leaderboard" export default async function RoundLeaderboardPage({ @@ -6,5 +7,9 @@ export default async function RoundLeaderboardPage({ params: Promise<{ id: string }> }) { const { id } = await params - return + return ( + + + + ) } diff --git a/frontend/app/my-rounds/[id]/page.tsx b/frontend/app/my-rounds/[id]/page.tsx index bf9ef85..1bb9671 100644 --- a/frontend/app/my-rounds/[id]/page.tsx +++ b/frontend/app/my-rounds/[id]/page.tsx @@ -1,6 +1,20 @@ +import { Suspense } from "react" import { RoundDetail } from "@/components/round-detail" export default async function RoundDetailPage({ params }: { params: Promise<{ id: string }> }) { const { id } = await params - return + return ( + + + } + > + + + ) } diff --git a/frontend/app/my-rounds/[id]/scorecard/page.tsx b/frontend/app/my-rounds/[id]/scorecard/page.tsx index 5cb1fe4..5e50cff 100644 --- a/frontend/app/my-rounds/[id]/scorecard/page.tsx +++ b/frontend/app/my-rounds/[id]/scorecard/page.tsx @@ -1,10 +1,20 @@ +import { RoundPageShell } from "@/components/round-page-shell" import { RoundScorecard } from "@/components/round-scorecard" +import { RoundStats } from "@/components/round-stats" +// Scorekort + Statistikk slått sammen til ÉN side (ADR-040 Beslutning D, +// "det store grepet") -- statistikk RETT UNDER scorekortet, delt header/ +// fane-rad via RoundPageShell. export default async function RoundScorecardPage({ params, }: { params: Promise<{ id: string }> }) { const { id } = await params - return + return ( + + + + + ) } diff --git a/frontend/app/my-rounds/[id]/stats/page.tsx b/frontend/app/my-rounds/[id]/stats/page.tsx index b5573cd..39b595f 100644 --- a/frontend/app/my-rounds/[id]/stats/page.tsx +++ b/frontend/app/my-rounds/[id]/stats/page.tsx @@ -1,10 +1,14 @@ -import { RoundStats } from "@/components/round-stats" +import { redirect } from "next/navigation" +// Slått sammen med scorekortet (ADR-040 Beslutning D, "det store grepet", +// 2026-08-02) -- statistikk vises nå RETT UNDER scorekortet på ÉN side. +// Redirect bevarer eventuelle eksisterende lenker/bokmerker til denne +// stien. export default async function RoundStatsPage({ params, }: { params: Promise<{ id: string }> }) { const { id } = await params - return + redirect(`/my-rounds/${id}/scorecard`) } diff --git a/frontend/components/account-settings.tsx b/frontend/components/account-settings.tsx index 16529d5..ac2928d 100644 --- a/frontend/components/account-settings.tsx +++ b/frontend/components/account-settings.tsx @@ -286,8 +286,8 @@ export function AccountSettings() {
-
-
@@ -630,8 +630,8 @@ function ProfileSection({ me, onChanged }: { me: Me; onChanged: () => void }) { return (
-
-
@@ -1019,8 +1019,8 @@ function EmailSection({ email }: { email: string }) { return (
-
-
@@ -1138,8 +1138,8 @@ function SecondaryEmailSection({ return (
-
-
@@ -1271,8 +1271,8 @@ function PasswordSection({ hasPassword, onChanged }: { hasPassword: boolean; onC return (
-
-
@@ -1382,8 +1382,8 @@ function PushNotificationSection() { return (
-
-
@@ -1466,8 +1466,8 @@ function NotificationEmailPrefsSection() { return (
-
-
diff --git a/frontend/components/course-rounds.tsx b/frontend/components/course-rounds.tsx index 8b29038..c5f02f8 100644 --- a/frontend/components/course-rounds.tsx +++ b/frontend/components/course-rounds.tsx @@ -189,8 +189,8 @@ function LoadingState() { function EmptyState() { return (
-
-