diff --git a/ARCHITECTURE_DECISIONS.md b/ARCHITECTURE_DECISIONS.md
index a085915..6fa037e 100644
--- a/ARCHITECTURE_DECISIONS.md
+++ b/ARCHITECTURE_DECISIONS.md
@@ -7663,6 +7663,73 @@ Containeren startet rent.
---
+## ADR-078: Chip/Bunker/Straffeslag/Anywayslag som egen skjerm ("Steg 2" av tre forbedringer i score-registreringen) (2026-08-16)
+
+Foranledning: bruker observerte at Chip/Bunker/Straffeslag/Anywayslag ble
+vist klemt inn under retningsvalget for utslag/innspill i begge
+scoringsflytene -- ikke godt nok, burde være en egen skjerm. Bekreftet
+(AskUserQuestion) at dette skal gjelde BEGGE flyter -- frittstående runder
+OG org-turneringer -- "opplevelsen for brukeren skal være lik uansett".
+
+**Strukturell splitt, begge flyter:**
+- Frittstående runder (`ScoringWizard`, `round-detail.tsx`): `WizardStep`
+ utvidet fra `"strokes" | "putts" | "puttDistance" | "details"` til
+ `"strokes" | "putts" | "puttDistance" | "direction" | "holeDetails"` --
+ "details" splittet i to egne steg. GIR-auto-inferensen (2026-07-30)
+ flyttet fra å trigge på `step !== "details"` til `step !== "direction"`.
+- Org-turneringer (`HoleStatsSheet`, `individual-tournament-detail.tsx`):
+ bygget om fra ÉN lang scrollbar skjerm til en LITEN intern steg-flyt
+ (samme stegrekkefølge som over), med samme auto-hopp og
+ GIR-auto-inferens som runde-siden. **Bevisst avgrensning**: lagringen
+ er UENDRET -- fortsatt ÉN batched `save()`-kall ved siste steg (nå
+ trigget fra "Lagre"-knappen i stedet for en alltid-synlig knapp), IKKE
+ konvertert til runde-sidens PATCH-per-felt-modell. De to flytene får nå
+ samme STRUKTUR/interaksjon, ikke delt kode eller delt lagrings-
+ mekanikk -- unngår å risikere en fungerende, annerledes arkitektur som
+ ikke var en del av forespørselen.
+
+**Visuell polering (V0-designet)**: siden det gjenstående "Detaljer per
+hull"-steget nå har en hel skjerm for seg selv, ble den tidligere
+1-kolonne-stabelen (mye tomrom under fire små tellere) erstattet med et
+FAST 2x2-rutenett av teller-FLISER -- egne kort med etikett + stort
+sentrert tall + -/+ nederst. Interaksjonskontrakten er uendret (minus
+disabled på 0, pluss disabled på `max`, alle trykkflater >=44px), kun det
+visuelle uttrykket er nytt. `Stepper`-komponenten (delt av begge flyter
+via `hole-stat-inputs.tsx`) oppdatert i selve komponenten -- automatisk
+identisk i begge flyter uten duplisert arbeid. `grid-cols-2` (ALDRI
+`grid-cols-3`) fordi seksjonen er fast `max-w-sm` (384px): to fliser gir
+~184px hver, nok til flisens to 44px-knapper side om side.
+
+V0-prompt skrevet av Claude (se `feedback_frontend_via_v0`), kjørt av
+bruker, eksport (`tee-cup (12).zip`) integrert -- KUN den relevante
+`Stepper`-omdesignet + grid-endringen ble portert inn (eksporten var en
+full prosjekt-snapshot fra en delt V0-arbeidsflate, inkludert en eldre,
+FØR-splitt kopi av hele `round-detail.tsx` -- ingen fullstendig
+filerstatning, kun det faktisk endrede uttrykket for Stepper/rutenett
+overført til den ekte, allerede steg-splittede filen). Zip slettet fra
+`Temp-uploads/` etter integrering.
+
+**Verifisert:** `tsc --noEmit` rent + 45/45 vitest (begge ganger --
+strukturell splitt, og etter V0-integrering). Egen scratch-database +
+scratch `teecup_api` + lokal `next dev`, to runder (port 18105/13105 for
+strukturell verifisering, port 18106/13106 for V0-integrering): begge
+flyter testet ende-til-ende gjennom ALLE steg (strokes → putts →
+puttDistance → direction → holeDetails), GIR-auto-inferens bekreftet
+fortsatt virker (trigget riktig på "direction"-steget), lagring bekreftet
+i begge flyter (round-siden PATCH-per-felt, turnering-siden batched
+"Lagre"), gjenåpning av et allerede utfylt hull viste korrekt lagrede
+verdier. 2x2-rutenettet med teller-flisene bekreftet identisk i begge
+flyter, interaksjon (øke/redusere, disabled ved 0/max) bekreftet
+fungerende. Lys+mørk bekreftet i begge runder. Scratch-stackene
+fullstendig revet ned -- ekte `teecup_db`/`teecup_api`/`teecup_frontend`
+urørt.
+
+**Ingen migrasjon** -- ren frontend-endring.
+
+**Rullet ut**: ikke ennå -- venter på bekreftelse fra bruker.
+
+---
+
## Utviklingsplan (rekkefølge)
1. ✅ Land tenant-modell → **Organisasjon** (ADR-001/002/003)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9cff4fe..7792e22 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11690,3 +11690,29 @@ Neste steg:
**Rullet ut 2026-08-16** -- ingen migrasjon, `docker compose build
teecup_frontend && up -d` etter eksplisitt bekreftelse fra bruker.
Containeren startet rent.
+
+94. **Chip/Bunker/Straffeslag/Anywayslag som egen skjerm (ADR-078) —
+ 2026-08-16.** Andre av tre forbedringer i score-registreringen. Disse
+ fire tellerne var klemt inn under retningsvalget for utslag/innspill i
+ begge scoringsflytene (frittstående runder OG org-turneringer, etter
+ eksplisitt "opplevelsen skal være lik uansett").
+
+ `ScoringWizard`s "details"-steg splittet i "direction"/"holeDetails".
+ `HoleStatsSheet` (org-turneringer) bygget om fra ÉN lang skjerm til en
+ tilsvarende liten intern steg-flyt -- lagringen selv er BEVISST
+ uendret (fortsatt én batched "Lagre" der, ikke konvertert til runde-
+ sidens PATCH-per-felt). Visuell polering via V0 (Claude-skrevet
+ prompt): de fire tellerne fikk et 2x2-rutenett av flis-kort i stedet
+ for en 1-kolonne-stabel med mye tomrom -- `Stepper`-komponenten (delt
+ av begge flyter) oppdatert ett sted, automatisk identisk begge steder.
+
+ **Verifisert:** `tsc --noEmit` rent + 45/45 vitest. To scratch-runder
+ (strukturell splitt, så V0-integrering): begge flyter testet
+ ende-til-ende gjennom alle steg, GIR-auto-inferens fortsatt virker,
+ lagring bekreftet i begge flyter, gjenåpning viste korrekt lagrede
+ verdier, 2x2-rutenettet identisk og fungerende i begge flyter.
+ Lys+mørk bekreftet. Scratch-stackene fullstendig revet ned.
+
+ **Ingen migrasjon.**
+
+ **Rullet ut**: ikke ennå.
diff --git a/frontend/components/hole-stat-inputs.tsx b/frontend/components/hole-stat-inputs.tsx
index a0ebd2d..8ea7364 100644
--- a/frontend/components/hole-stat-inputs.tsx
+++ b/frontend/components/hole-stat-inputs.tsx
@@ -316,26 +316,42 @@ export function Stepper({
max?: number
}) {
const atMax = max !== undefined && value >= max
+ const atMin = value <= 0
+ // Teller-FLIS (ADR-078, 2026-08-16, V0-designet) -- selvstendig kort med
+ // etikett + stort tall + -/+ på bunnen, ment for et 2x2-rutenett (se
+ // holeDetails-steget i round-detail.tsx/individual-tournament-detail.tsx).
+ // Interaksjonskontrakten er uendret: -/tall/+, minus disabled på 0, pluss
+ // disabled på max, alle trykkflater >=44px. Kun app-tokens.
return (
-
- {label}
-
+
+ {label}
+ {/* Tallet bærer selve informasjonen (aldri farge alene); nullverdi
+ dempes kun visuelt via muted-foreground, som fortsatt er AA på
+ kort. */}
+
+ {value}
+
+
- {value}
diff --git a/frontend/components/individual-tournament-detail.tsx b/frontend/components/individual-tournament-detail.tsx
index d0b62b4..3514c8b 100644
--- a/frontend/components/individual-tournament-detail.tsx
+++ b/frontend/components/individual-tournament-detail.tsx
@@ -3236,6 +3236,23 @@ type HolePatch = {
expected_version?: number | null
}
+// ADR-078 (2026-08-16): HoleStatsSheet bygget om fra ÉN lang scrollbar
+// skjerm til en liten intern steg-flyt -- samme visuelle språk (fulskjerm,
+// header/footer) og samme stegrekkefølge som ScoringWizard (frittstående
+// runder), etter eksplisitt ønske om at "opplevelsen for brukeren skal
+// være lik uansett". IKKE en gjenbruk av ScoringWizard-koden -- lagringen
+// er bevisst UENDRET (ÉN batched save() ved siste steg, ikke PATCH per
+// felt slik runde-siden gjør) for å unngå å røre en fungerende, annerledes
+// arkitektur mekanikk som ikke var en del av forespørselen. Se
+// wizardStepsFor()/WizardStep i round-detail.tsx for det speilede
+// mønsteret på runde-siden.
+type SheetStep = "strokes" | "putts" | "puttDistance" | "direction" | "holeDetails"
+
+function sheetStepsFor(statLevel: "strokes_and_putts" | "full"): SheetStep[] {
+ if (statLevel === "full") return ["strokes", "putts", "puttDistance", "direction", "holeDetails"]
+ return ["strokes", "putts", "puttDistance"]
+}
+
function HoleStatsSheet({
hole,
playerName,
@@ -3268,6 +3285,43 @@ function HoleStatsSheet({
const [saving, setSaving] = useState(false)
const [error, setError] = useState(null)
+ const steps = sheetStepsFor(statLevel)
+ const [stepIndex, setStepIndex] = useState(0)
+ const step = steps[Math.min(stepIndex, steps.length - 1)]
+ const isFirstStep = stepIndex === 0
+ const isLastStep = stepIndex === steps.length - 1
+ const canProceed = step === "strokes" ? strokes !== null : true
+
+ // Auto-hopp, samme mønster som ScoringWizard (round-detail.tsx) --
+ // hopper videre av seg selv når steget sitt eget felt får en verdi.
+ const autoField = step === "strokes" ? "strokes" : step === "putts" ? "putts" : step === "puttDistance" ? "puttBucket" : null
+ const enteredWithValueRef = useRef(false)
+ const firedRef = useRef(false)
+ useEffect(() => {
+ firedRef.current = false
+ enteredWithValueRef.current =
+ autoField === "strokes" ? strokes !== null : autoField === "putts" ? putts !== null : autoField === "puttBucket" ? puttBucket !== null : false
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [step])
+ useEffect(() => {
+ if (!autoField || firedRef.current || enteredWithValueRef.current) return
+ const value = autoField === "strokes" ? strokes : autoField === "putts" ? putts : puttBucket
+ if (value !== null) {
+ firedRef.current = true
+ forward()
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [strokes, putts, puttBucket])
+
+ // GIR-auto-inferens, samme formel/begrunnelse som ScoringWizard.
+ useEffect(() => {
+ if (step !== "direction") return
+ if (approach !== null) return
+ if (strokes === null || putts === null) return
+ if (strokes - putts <= hole.par - 2) setApproach("hit")
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [step, strokes, putts, approach, hole.par])
+
async function save() {
if (strokes === null) {
setError("Registrer antall slag først.")
@@ -3296,6 +3350,21 @@ function HoleStatsSheet({
}
}
+ function back() {
+ if (isFirstStep) {
+ onClose()
+ return
+ }
+ setStepIndex((i) => i - 1)
+ }
+ function forward() {
+ if (!isLastStep) {
+ setStepIndex((i) => i + 1)
+ return
+ }
+ void save()
+ }
+
return (
@@ -3318,52 +3387,61 @@ function HoleStatsSheet({
-
+ {step === "strokes" && (
+ <>
+
- {/* Spillerens per-hull-historikk (ADR-072) -- skjuler seg selv når
- banen er custom/håndlaget eller spilleren ikke har historikk
- ennå (se HoleHistoryPanel/app/hole_history.py). */}
-
+ {/* Spillerens per-hull-historikk (ADR-072) -- skjuler seg
+ selv når banen er custom/håndlaget eller spilleren ikke
+ har historikk ennå (se HoleHistoryPanel/app/hole_
+ history.py). */}
+
+ >
+ )}
-
+ {step === "putts" && (
+
+ )}
-
+ {step === "puttDistance" && (
+
+ )}
- {statLevel === "full" && (
+ {step === "direction" && (
<>
-
-
-
-
-
-
-
-
-
>
)}
+ {/* Teller-flis-rutenett (V0-designet, ADR-078, 2026-08-16) --
+ samme 2x2-mønster som round-detail.tsx sitt speilede
+ holeDetails-steg, se kommentaren der for begrunnelsen. */}
+ {step === "holeDetails" && (
+
+
+
+
+
+
+ )}
+
{error &&
{error}
}
-
diff --git a/frontend/components/round-detail.tsx b/frontend/components/round-detail.tsx
index 13958e6..d0136d0 100644
--- a/frontend/components/round-detail.tsx
+++ b/frontend/components/round-detail.tsx
@@ -1921,12 +1921,17 @@ export function RoundDetail({ roundId }: { roundId: string }) {
// nullstiller til steg 1) eller "Ferdig" (er spilleren den siste, lukker
// veiviseren og varsler forelderen om å gå videre til neste hull).
-type WizardStep = "strokes" | "putts" | "puttDistance" | "details"
+type WizardStep = "strokes" | "putts" | "puttDistance" | "direction" | "holeDetails"
function wizardStepsFor(statLevel: StatLevel): WizardStep[] {
if (statLevel === "strokes_only") return ["strokes"]
if (statLevel === "strokes_and_putts") return ["strokes", "putts", "puttDistance"]
- return ["strokes", "putts", "puttDistance", "details"]
+ // ADR-078 (2026-08-16): "details" splittet i to egne skjermer -- retning
+ // (utslag/innspill, + kølle/slagmåling som hører til utslaget) og
+ // Chip/Bunker/Straffeslag/Anywayslag fikk hver sin egen skjerm i stedet
+ // for å dele én, etter tilbakemelding om at detaljene druknet under
+ // retningsvalget.
+ return ["strokes", "putts", "puttDistance", "direction", "holeDetails"]
}
// Hvilket HoleStat-felt (om noen) som "eier" auto-hopp for et gitt steg --
@@ -2032,10 +2037,11 @@ function ScoringWizard({
// ALDRI et allerede satt (manuelt ELLER tidligere auto-satt) approach-
// valg, og gjelder aldri motsatt vei (GIR usann sier ingenting om
// retning -- en scrambling-birdie gir identisk slag/putt-mønster som en
- // ekte GIR). Trigges når "details"-steget nås, siden slag+putt begge må
- // være kjent (kommer tidligere i wizard-rekkefølgen).
+ // ekte GIR). Trigges når "direction"-steget nås (ADR-078: flyttet fra det
+ // tidligere "details"-steget, som ble splittet i to), siden slag+putt
+ // begge må være kjent (kommer tidligere i wizard-rekkefølgen).
useEffect(() => {
- if (step !== "details") return
+ if (step !== "direction") return
if (stat.approach !== null) return
if (stat.strokes === null || stat.putts === null) return
if (stat.strokes - stat.putts <= hole.par - 2) {
@@ -2206,7 +2212,12 @@ function ScoringWizard({
/>
)}
- {step === "details" && (
+ {/* ADR-078 (2026-08-16): "Retning" er nå EGEN skjerm -- inkl.
+ kølle/slagmåling, som begge hører til utslaget. Splittet fra
+ det tidligere "details"-steget etter tilbakemelding om at
+ Chip/Bunker/Straffeslag/Anywayslag druknet under retnings-
+ valget. */}
+ {step === "direction" && (
<>
- {/* Seksjon 1 -- retning (2026-08-11): retningskorsene utgjør nå
- en tydelig egen gruppe med overskrift, adskilt fra tellerne
- under, så det er visuelt klart at skjermen består av to
- ulike typer inndata (brukertilbakemelding: umulig å vite at
- man måtte skrolle for å finne resten). */}
{hole.par !== 3 && (
-
- {/* Seksjon 2 -- detaljer per hull (2026-08-11): egen overskrift
- + divider fra "Retning" over. Alle FIRE tellerne (Chip/
- Bunker/Straffeslag/Anywayslag) er konseptuelt samme type
- data (antall småhendelser per hull, typisk 0-3) og bruker nå
- ETT konsistent +/--stepper-mønster -- Anywayslag var
- tidligere det eneste tall-rutenettet, som brøt mønsteret
- uten grunn. Alltid stablet, ALDRI et sm:grid-cols-3-sprang:
- denne wizard-seksjonen er fast begrenset til max-w-sm
- (384px, se containeren over) uansett hvor bred selve
- VIEWPORTET er -- et viewport-basert grid-cols-3-brudd ved
- 640px trigges derfor på skjermer godt over 640px bredde mens
- innholdskolonnen fortsatt kun er 384px, som får stepperne
- (hver ~190px minimum pga. faste 44px-knapper) til å
- overlappe hverandre i stedet for å faktisk ha plass. */}
-
-
-
>
)}
+
+ {/* ADR-078 (2026-08-16): Chip/Bunker/Straffeslag/Anywayslag har nå
+ EGEN skjerm, splittet fra "Retning" (se over). Alle FIRE
+ tellerne er konseptuelt samme type data (antall småhendelser
+ per hull, typisk 0-3) og bruker ETT konsistent
+ +/--teller-flis-mønster (Stepper, hole-stat-inputs.tsx).
+
+ Layout (V0-designet, 2026-08-16): et FAST 2x2-rutenett av
+ teller-fliser i stedet for en høy stabel som etterlot mye
+ tomrom under nå som feltene har fått en egen skjerm. Fire
+ likeverdige felt leses da som ett samlet sett og fyller
+ kolonnen bevisst. grid-cols-2 er trygt -- og ALDRI
+ grid-cols-3 -- fordi seksjonen er fast max-w-sm (384px, se
+ containeren over): to fliser gir ~184px hver, rikelig til
+ flisens to 44px-knapper side om side, mens tre ville presset
+ hver flis under ~120px og fått knappene til å overlappe.
+ Viewport-uavhengig, så det holder på alle skjermbredder. */}
+ {step === "holeDetails" && (
+