From a11c638fdaadb4486e642a995ae205d97f37d66c Mon Sep 17 00:00:00 2001 From: Erol Haagenrud Date: Mon, 27 Jul 2026 10:32:51 +0200 Subject: [PATCH] Compliance-pass shipped and live. Summary: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Deg Deg" fixed — the name is now always the real display name; the "Deg" badge alone marks your own row. This also fixed the same problem in the wizard header/context chips/footer, which had no badge at all and were showing "Deg" instead of the roster-context full name the spec requires. Score button now follows the golfscore language — reused ScoreMark's exact primary/brand-orange fill-vs-tint rules instead of an unconditional green circle. 13 smaller gaps fixed — 2 missing tabular-nums, 11 tap targets brought up to the 44px floor. Spec's §1 (scorecard-as-grid) deliberately left out — that's a bigger structural call, not something to slip in under a "fix known bugs" pass. Typechecked build was clean, both containers redeployed and booted cleanly, /health//dashboard → 200, teeoff.no unaffected. Docs updated (CLAUDE.md status + autoritative kilder, FEATURE_BACKLOG.md pointer). Same honest caveat as the rest of this week's hand-coded work: no real browser interaction test — worth a manual look, especially the score button's shape change at the 56px size. --- CLAUDE.md | 81 +++++++++++++++++++++++ FEATURE_BACKLOG.md | 12 ++++ frontend/components/round-detail.tsx | 99 ++++++++++++++++++---------- 3 files changed, 159 insertions(+), 33 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0a445cd..36844d8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,6 +19,16 @@ Les dette først i hver økt. Det koder hva vi har bestemt og hvordan vi jobber. turneringsformatene som ennå ikke er designet (Københavner/High-low-high/ Robbins/Try all, se FEATURE_BACKLOG.md), siden disse dokumentene trolig dekker akkurat de reglene som trengs der. +- **Design-dokumentasjon, to filer med bevisst motsatt retning (2026-07-27):** + `DESIGN_SYSTEM.md` (innsjekket i git) er DESKRIPTIV — hva som faktisk ER i + koden i dag (farger/typografi/komponentmønstre/golfscore-språket), avledet + direkte fra `globals.css`/`components/ui/*`. `teecup-scorekort-og-entry- + spec.md` (lastet opp av brukeren til prosjektroten, IKKE innsjekket i git) + er PRESKRIPTIV — hva scorekort-/score-entry-skjermene BØR bli, basert på en + sammenligning mot fem etablerte golf-apper. Der de to avviker vinner + kildekoden (DESIGN_SYSTEM.md beskriver den), men spec-dokumentets §3 + "Compliance-pass" er en konkret sjekkliste over kjente avvik — les den FØR + videre visuelt arbeid på scorekort-/score-entry-skjermene. ## Sikkerhetsregler (ufravikelige) - Rør ALDRI `teeoff`-databasen eller den ekte `teecup_db` uten at brukeren @@ -3495,6 +3505,77 @@ Ferdig og verifisert: 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 `Deg` 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. Neste steg: 0a. **Spillerliste-redesign — LIVE, men ikke nettleser-testet diff --git a/FEATURE_BACKLOG.md b/FEATURE_BACKLOG.md index f37a8ca..89b71ec 100644 --- a/FEATURE_BACKLOG.md +++ b/FEATURE_BACKLOG.md @@ -1326,6 +1326,18 @@ ARCHITECTURE_DECISIONS.md (ADR-033-oppdatering 2026-07-26), full verifiserings-/utrullingsdetalj i CLAUDE.md sin statuslogg — ikke duplisert her. +**Oppfølging 2026-07-27 — `teecup-scorekort-og-entry-spec.md` (nytt, +brukeropplastet PRESKRIPTIVT motstykke til `DESIGN_SYSTEM.md`, basert på +en 5-app-sammenligning) evaluert, §3 "Compliance-pass" delvis BYGGET SAMME +DAG — ✅ LIVE:** "Deg Deg"-badge-duplikat fjernet (`playerLabel()` viser nå +alltid ekte navn), scoringslistens score-knapp gjort om til å følge +`§Golfscore-språket` (gjenbruker `ScoreMark`s fargespråk fra +`round-scorecard.tsx`), pluss et system­atisk `tabular-nums`/44px- +trykkgulv-avvik-audit fikset (13 steder). Spec-dokumentets §1 (scorekort +som fullt grid, egen skjerm) bevisst IKKE bygget — større strukturell +endring, egen fremtidig runde. Full detalj i CLAUDE.md sin statuslogg +(2026-07-27) — ikke duplisert her. + --- ## En tredje (informasjons-)farge til designet — 🧠 DRØFTET 2026-07-25, IKKE besluttet diff --git a/frontend/components/round-detail.tsx b/frontend/components/round-detail.tsx index 9b70567..dc54e1d 100644 --- a/frontend/components/round-detail.tsx +++ b/frontend/components/round-detail.tsx @@ -52,12 +52,10 @@ type PuttBucket = "<1m" | "<2m" | "<3m" | "<5m" | "<8m" | "8m+" type Player = { id: string - // Visningsnavn -- "Deg" for spilleren SOM SER PÅ (viewer-relativt, se - // playerLabel()), IKKE nødvendigvis den faktiske display_name. + // Alltid den faktiske display_name (2026-07-27: fjernet "Deg"-erstatning + // her -- se playerLabel() -- brøt "roster-kontekst → fullt navn" og ga en + // "Deg Deg"-duplikat der en egen Badge også viste "Deg" ved siden av). name: string - // Den faktiske, urørte display_name -- ALDRI "Deg". Brukt til å - // forhåndsutfylle "Rediger"-flytens navnefelt for en gjest (2026-07-26). - rawName: string userId: string | null guestEmail: string | null isOwner: boolean @@ -264,11 +262,17 @@ function statToPatchBody(s: HoleStat) { } } -// Viewer-relativt (ADR-036 fase 3-utvidelsen, 2026-07-26): "Deg" skal vises -// for DEN SOM SER PÅ, ikke alltid runde-eieren -- en lenket medspiller som -// åpner runden skal se seg selv som "Deg" og eieren under sitt eget navn. -function playerLabel(p: ApiParticipant, viewerId: string | null): string { - return viewerId !== null && p.user_id === viewerId ? "Deg" : p.display_name +// Alltid det faktiske visningsnavnet -- ALDRI "Deg" (2026-07-27, compliance- +// pass mot teecup-scorekort-og-entry-spec.md). "Deg" var tidligere en +// viewer-relativ erstatning for selve navnet, men det produserte en ekte +// "Deg Deg"-duplikat der navnet OGSÅ sto ved siden av en egen "Deg"-badge +// (spillerkort, scoringslisten) -- og brøt spesifikasjonens eksplisitte +// "roster-kontekst → fullt navn"-regel i skjermer uten noen badge i det +// hele tatt (veiviserens header/kontekst-rad, "Neste: {navn}", fullført- +// banneret). `isSelf` (se players-konstruksjonen under) er fortsatt +// viewer-relativ og brukes alene til å vise "Deg"-badgen der det trengs. +function playerLabel(p: ApiParticipant): string { + return p.display_name } // --- Component ------------------------------------------------------------- @@ -414,8 +418,7 @@ export function RoundDetail({ roundId }: { roundId: string }) { const players: Player[] = round?.participants.map((p) => ({ id: p.id, - name: playerLabel(p, viewerId), - rawName: p.display_name, + name: playerLabel(p), userId: p.user_id, guestEmail: p.guest_email, isOwner: p.is_owner, @@ -651,7 +654,10 @@ export function RoundDetail({ roundId }: { roundId: string }) { return (

{error}

- + Tilbake til egne runder
@@ -726,7 +732,7 @@ export function RoundDetail({ roundId }: { roundId: string }) { onClick={() => setPageTab("score")} aria-pressed={pageTab === "score"} className={cn( - "min-h-10 rounded-full px-4 text-sm font-bold transition-colors", + "min-h-11 rounded-full px-4 text-sm font-bold transition-colors", pageTab === "score" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground", )} > @@ -737,7 +743,7 @@ export function RoundDetail({ roundId }: { roundId: string }) { onClick={() => setPageTab("manage")} aria-pressed={pageTab === "manage"} className={cn( - "min-h-10 rounded-full px-4 text-sm font-bold transition-colors", + "min-h-11 rounded-full px-4 text-sm font-bold transition-colors", pageTab === "manage" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground", )} > @@ -905,6 +911,11 @@ export function RoundDetail({ roundId }: { roundId: string }) { const playerApiHole = playerHoles?.find((h) => h.hole_number === activeHole) ?? null const hasScore = Boolean(playerApiHole?.played) && playerApiHole?.score !== null const running = computeRunningToPar(playerHoles) + // Golfscore-språket (2026-07-27, compliance-pass): sirkel + // under par / rundet firkant over par / nøytral sirkel på + // par -- ALDRI samme grønne fylte sirkel uansett resultat + // (bogey og dobbel bogey skal ikke se ut som birdie). + const diff = hasScore && hole ? (playerApiHole!.score as number) - hole.par : null return (
  • @@ -935,14 +946,12 @@ export function RoundDetail({ roundId }: { roundId: string }) { }} aria-label={ hasScore - ? `Rediger ${player.name} sin registrering på hull ${activeHole}, ${playerApiHole?.score} slag` + ? `Rediger ${player.name} sin registrering på hull ${activeHole}, ${playerApiHole?.score} slag${diff !== null && hole ? `, ${golfTermForScore(playerApiHole!.score as number, hole.par) ?? ""}` : ""}` : `Registrer score for ${player.name} på hull ${activeHole}` } className={cn( - "flex size-14 shrink-0 items-center justify-center rounded-full border-2 text-xl font-extrabold tabular-nums transition-colors disabled:opacity-60", - hasScore - ? "border-primary bg-primary text-primary-foreground" - : "border-dashed border-border text-muted-foreground hover:border-primary/60 hover:text-primary", + "flex size-14 shrink-0 items-center justify-center border-2 text-xl font-extrabold tabular-nums transition-colors disabled:opacity-60", + scoreMarkClasses(diff), )} > {hasScore ? playerApiHole?.score : "–"} @@ -1669,7 +1678,7 @@ function PlayerList({ Utslag: {player.teeName} {" · "} HCP:{" "} - + {player.hcp !== null ? player.hcp.toFixed(1).replace(".", ",") : "ikke satt"} {/* Tildelte slag (course handicap) -- KUN vist når HCP @@ -1678,7 +1687,7 @@ function PlayerList({ {player.courseHandicap !== null && ( <> {" · "} - Tildelte slag: {player.courseHandicap} + Tildelte slag: {player.courseHandicap} )} @@ -1862,7 +1871,7 @@ function AddParticipantForm({ @@ -1904,7 +1913,7 @@ function AddGuestForm({ @@ -2043,7 +2052,7 @@ function EditRoundPanel({
    Rediger runde -
    @@ -2185,7 +2194,7 @@ function EditParticipantPanel({ const [teeName, setTeeName] = useState(player.teeName) const [hcp, setHcp] = useState(player.hcp !== null ? String(player.hcp).replace(".", ",") : "") const [statLevel, setStatLevel] = useState(player.statLevel) - const [guestName, setGuestName] = useState(player.rawName) + const [guestName, setGuestName] = useState(player.name) const [gender, setGender] = useState(player.gender) const [guestEmail, setGuestEmail] = useState(player.guestEmail ?? "") const [saving, setSaving] = useState(false) @@ -2223,7 +2232,7 @@ function EditParticipantPanel({ if (statLevel !== player.statLevel) body.stat_level = statLevel if (isGuest) { const trimmedName = guestName.trim() - if (trimmedName && trimmedName !== player.rawName) body.guest_name = trimmedName + if (trimmedName && trimmedName !== player.name) body.guest_name = trimmedName if (gender !== player.gender) body.gender = apiGender const trimmedEmail = guestEmail.trim() const normalizedEmail = trimmedEmail === "" ? null : trimmedEmail @@ -2510,7 +2519,7 @@ function ChangeCourseForm({ {officialCourses === null ? ( @@ -2539,7 +2552,7 @@ function ChangeCourseForm({ @@ -2565,7 +2578,7 @@ function ChangeCourseForm({ @@ -2593,7 +2606,7 @@ function ChangeCourseForm({ onClick={() => setTeeName(t.name)} aria-pressed={teeName === t.name} className={cn( - "h-10 rounded-lg border px-3 text-sm font-semibold transition-colors", + "h-11 rounded-lg border px-3 text-sm font-semibold transition-colors", teeName === t.name ? "border-primary bg-primary text-primary-foreground" : "border-border bg-background text-foreground hover:bg-accent/50", @@ -2610,7 +2623,7 @@ function ChangeCourseForm({
    )} - @@ -2684,6 +2697,26 @@ function golfTermForScore(n: number, par: number): string | null { return null } +// Golfscore-språket (§Golfscore-språket i DESIGN_SYSTEM.md, håndhevet +// eksplisitt av teecup-scorekort-og-entry-spec.md §0.3/§3): form encoder +// retning (sirkel = under par, "firkant" = over par) slik at betydningen +// overlever selv uten farge; fylt vs. border+10%-tint encoder omfang (fylt = +// 2 slag eller mer fra par). Samme klassifisering som `ScoreMark` i +// round-scorecard.tsx -- egen kopi her siden denne knappen (size-14, +// trykkbar) trenger andre grunnklasser enn scorekortets små tabell-merker, +// men SAMME fargespråk (2026-07-27, compliance-pass). Bevisst `rounded-2xl` +// for "over par" i stedet for scorekortets `rounded-[4px]` -- fortsatt +// tydelig IKKE en sirkel, men matcher denne skjermens øvrige store +// trykkflate-avrunding (NumberPicker/hull-panel bruker samme radius). +function scoreMarkClasses(diff: number | null): string { + if (diff === null) return "rounded-full border-dashed border-border text-muted-foreground hover:border-primary/60 hover:text-primary" + if (diff <= -2) return "rounded-full border-primary bg-primary text-primary-foreground" + if (diff === -1) return "rounded-full border-primary bg-primary/10 text-primary" + if (diff === 0) return "rounded-full border-border bg-card text-foreground" + if (diff === 1) return "rounded-2xl border-brand-orange bg-brand-orange/10 text-brand-orange" + return "rounded-2xl border-brand-orange bg-brand-orange text-brand-orange-foreground" +} + function NumberPicker({ label, value,