Chip/Bunker/Straffeslag/Anywayslag som egen skjerm (ADR-078)

Splittet ScoringWizard sitt "details"-steg i "direction"/"holeDetails",
og bygget om HoleStatsSheet (org-turneringer) fra én lang skjerm til
tilsvarende steg-flyt -- samme opplevelse i begge scoringsflyter, uten å
røre HoleStatsSheet sin egen (bevisst annerledes) lagringsmodell.

Visuell polering via V0: de fire tellerne fikk et 2x2-rutenett av
flis-kort i stedet for en 1-kolonne-stabel. Stepper-komponenten er delt
mellom begge flyter, så endringen gjelder automatisk begge steder.

tsc + 45/45 vitest, scratch-verifisert i begge flyter (lys+mørk). Rullet ut.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Erol Haagenrud 2026-08-16 09:53:16 +02:00
parent 2ea4c26990
commit b292401fe4
5 changed files with 304 additions and 103 deletions

View file

@ -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) ## Utviklingsplan (rekkefølge)
1. ✅ Land tenant-modell → **Organisasjon** (ADR-001/002/003) 1. ✅ Land tenant-modell → **Organisasjon** (ADR-001/002/003)

View file

@ -11690,3 +11690,29 @@ Neste steg:
**Rullet ut 2026-08-16** -- ingen migrasjon, `docker compose build **Rullet ut 2026-08-16** -- ingen migrasjon, `docker compose build
teecup_frontend && up -d` etter eksplisitt bekreftelse fra bruker. teecup_frontend && up -d` etter eksplisitt bekreftelse fra bruker.
Containeren startet rent. 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å.

View file

@ -316,26 +316,42 @@ export function Stepper({
max?: number max?: number
}) { }) {
const atMax = max !== undefined && value >= max 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 ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col items-center gap-3 rounded-2xl border border-border bg-card p-4">
<span className="text-base font-semibold text-foreground">{label}</span> <span className="text-center text-sm font-bold leading-tight text-foreground">{label}</span>
<div className="flex items-center justify-between gap-2 rounded-2xl border border-border bg-card p-1.5"> {/* Tallet bærer selve informasjonen (aldri farge alene); nullverdi
dempes kun visuelt via muted-foreground, som fortsatt er AA
kort. */}
<span
className={cn(
"text-4xl font-extrabold leading-none tabular-nums",
atMin ? "text-muted-foreground" : "text-foreground",
)}
>
{value}
</span>
<div className="flex w-full items-center gap-2">
<button <button
type="button" type="button"
disabled={readOnly || value <= 0} disabled={readOnly || atMin}
onClick={() => onChange(Math.max(0, value - 1))} onClick={() => onChange(Math.max(0, value - 1))}
aria-label={`Færre ${label}`} aria-label={`Færre ${label}`}
className="flex size-11 shrink-0 items-center justify-center rounded-xl border border-border bg-background text-foreground transition-colors hover:bg-accent/50 disabled:opacity-40" className="flex h-11 flex-1 items-center justify-center rounded-xl border border-border bg-background text-foreground transition-colors hover:bg-accent/50 disabled:opacity-40"
> >
<Minus aria-hidden="true" className="size-5" /> <Minus aria-hidden="true" className="size-5" />
</button> </button>
<span className="min-w-8 text-center text-xl font-extrabold tabular-nums text-foreground">{value}</span>
<button <button
type="button" type="button"
disabled={readOnly || atMax} disabled={readOnly || atMax}
onClick={() => onChange(value + 1)} onClick={() => onChange(value + 1)}
aria-label={`Flere ${label}`} aria-label={`Flere ${label}`}
className="flex size-11 shrink-0 items-center justify-center rounded-xl border border-border bg-background text-foreground transition-colors hover:bg-accent/50 disabled:opacity-40" className="flex h-11 flex-1 items-center justify-center rounded-xl border border-border bg-background text-foreground transition-colors hover:bg-accent/50 disabled:opacity-40"
> >
<Plus aria-hidden="true" className="size-5" /> <Plus aria-hidden="true" className="size-5" />
</button> </button>

View file

@ -3236,6 +3236,23 @@ type HolePatch = {
expected_version?: number | null 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({ function HoleStatsSheet({
hole, hole,
playerName, playerName,
@ -3268,6 +3285,43 @@ function HoleStatsSheet({
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(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() { async function save() {
if (strokes === null) { if (strokes === null) {
setError("Registrer antall slag først.") 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 ( return (
<div className="fixed inset-0 z-50 flex flex-col bg-background"> <div className="fixed inset-0 z-50 flex flex-col bg-background">
<header className="flex min-h-14 shrink-0 items-center gap-2 border-b border-border px-3"> <header className="flex min-h-14 shrink-0 items-center gap-2 border-b border-border px-3">
@ -3318,52 +3387,61 @@ function HoleStatsSheet({
<main className="flex min-h-0 flex-1 flex-col overflow-y-auto p-5 sm:p-6"> <main className="flex min-h-0 flex-1 flex-col overflow-y-auto p-5 sm:p-6">
<div className="mx-auto flex w-full max-w-sm flex-col gap-6"> <div className="mx-auto flex w-full max-w-sm flex-col gap-6">
<NumberPicker {step === "strokes" && (
label="Slag" <>
value={strokes} <NumberPicker
directValues={[1, 2, 3, 4, 5, 6, 7, 8, 9]} label="Slag"
expandValues={[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]} value={strokes}
expandLabel="10+" directValues={[1, 2, 3, 4, 5, 6, 7, 8, 9]}
parValue={hole.par} expandValues={[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]}
showGolfTerms expandLabel="10+"
strokesReceived={strokesReceived} parValue={hole.par}
onChange={setStrokes} showGolfTerms
readOnly={false} strokesReceived={strokesReceived}
/> onChange={setStrokes}
readOnly={false}
/>
{/* Spillerens per-hull-historikk (ADR-072) -- skjuler seg selv når {/* Spillerens per-hull-historikk (ADR-072) -- skjuler seg
banen er custom/håndlaget eller spilleren ikke har historikk selv når banen er custom/håndlaget eller spilleren ikke
ennå (se HoleHistoryPanel/app/hole_history.py). */} har historikk ennå (se HoleHistoryPanel/app/hole_
<HoleHistoryPanel url={historyUrl} /> history.py). */}
<HoleHistoryPanel url={historyUrl} />
</>
)}
<NumberPicker {step === "putts" && (
label="Putter" <NumberPicker
value={putts} label="Putter"
directValues={[0, 1, 2, 3, 4, 5, 6]} value={putts}
expandValues={[7, 8, 9, 10]} directValues={[0, 1, 2, 3, 4, 5, 6]}
expandLabel="7+" expandValues={[7, 8, 9, 10]}
maxValue={strokes ?? undefined} expandLabel="7+"
accent="orange" maxValue={strokes ?? undefined}
onChange={setPutts} accent="orange"
readOnly={false} onChange={setPutts}
/> readOnly={false}
/>
)}
<ChoiceRow {step === "puttDistance" && (
label="Avstand første putt" <ChoiceRow
options={[ label="Avstand første putt"
{ value: "<1m", label: "0-1m" }, options={[
{ value: "<2m", label: "1-2m" }, { value: "<1m", label: "0-1m" },
{ value: "<3m", label: "2-3m" }, { value: "<2m", label: "1-2m" },
{ value: "<5m", label: "3-5m" }, { value: "<3m", label: "2-3m" },
{ value: "<8m", label: "5-8m" }, { value: "<5m", label: "3-5m" },
{ value: "8m+", label: "8m+" }, { value: "<8m", label: "5-8m" },
]} { value: "8m+", label: "8m+" },
value={puttBucket} ]}
onChange={setPuttBucket} value={puttBucket}
readOnly={false} onChange={setPuttBucket}
/> readOnly={false}
/>
)}
{statLevel === "full" && ( {step === "direction" && (
<> <>
<ClubPicker <ClubPicker
label="Kølle brukt ved utslaget" label="Kølle brukt ved utslaget"
@ -3393,30 +3471,36 @@ function HoleStatsSheet({
readOnly={false} readOnly={false}
/> />
</WizardSection> </WizardSection>
<WizardSection title="Detaljer per hull">
<div className="grid grid-cols-1 gap-3">
<Stepper label="Chip" value={chip} onChange={setChip} readOnly={false} max={strokes ?? undefined} />
<Stepper label="Bunker" value={bunker} onChange={setBunker} readOnly={false} max={strokes ?? undefined} />
<Stepper label="Straffeslag" value={penalty} onChange={setPenalty} readOnly={false} max={strokes ?? undefined} />
<Stepper label="Anywayslag" value={anywayStrokes} onChange={setAnywayStrokes} readOnly={false} max={strokes ?? undefined} />
</div>
</WizardSection>
</> </>
)} )}
{/* 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" && (
<div className="grid grid-cols-2 gap-3">
<Stepper label="Chip" value={chip} onChange={setChip} readOnly={false} max={strokes ?? undefined} />
<Stepper label="Bunker" value={bunker} onChange={setBunker} readOnly={false} max={strokes ?? undefined} />
<Stepper label="Straffeslag" value={penalty} onChange={setPenalty} readOnly={false} max={strokes ?? undefined} />
<Stepper label="Anywayslag" value={anywayStrokes} onChange={setAnywayStrokes} readOnly={false} max={strokes ?? undefined} />
</div>
)}
{error && <p className="text-sm font-medium text-destructive">{error}</p>} {error && <p className="text-sm font-medium text-destructive">{error}</p>}
</div> </div>
</main> </main>
<footer className="shrink-0 border-t border-border p-4"> <footer className="grid shrink-0 grid-cols-2 gap-3 border-t border-border p-4">
<Button type="button" variant="outline" onClick={back} className="h-14 rounded-2xl text-base font-bold">
{isFirstStep ? "Avbryt" : "Forrige"}
</Button>
<Button <Button
type="button" type="button"
disabled={saving || strokes === null} disabled={!canProceed || saving}
onClick={save} onClick={forward}
className="h-14 w-full rounded-2xl text-base font-bold disabled:opacity-40" className="h-14 rounded-2xl text-base font-bold disabled:opacity-40"
> >
{saving ? "Lagrer…" : "Lagre"} {isLastStep ? (saving ? "Lagrer…" : "Lagre") : "Neste"}
</Button> </Button>
</footer> </footer>
</div> </div>

View file

@ -1921,12 +1921,17 @@ export function RoundDetail({ roundId }: { roundId: string }) {
// nullstiller til steg 1) eller "Ferdig" (er spilleren den siste, lukker // nullstiller til steg 1) eller "Ferdig" (er spilleren den siste, lukker
// veiviseren og varsler forelderen om å gå videre til neste hull). // 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[] { function wizardStepsFor(statLevel: StatLevel): WizardStep[] {
if (statLevel === "strokes_only") return ["strokes"] if (statLevel === "strokes_only") return ["strokes"]
if (statLevel === "strokes_and_putts") return ["strokes", "putts", "puttDistance"] 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 -- // 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- // ALDRI et allerede satt (manuelt ELLER tidligere auto-satt) approach-
// valg, og gjelder aldri motsatt vei (GIR usann sier ingenting om // valg, og gjelder aldri motsatt vei (GIR usann sier ingenting om
// retning -- en scrambling-birdie gir identisk slag/putt-mønster som en // 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å // ekte GIR). Trigges når "direction"-steget nås (ADR-078: flyttet fra det
// være kjent (kommer tidligere i wizard-rekkefølgen). // tidligere "details"-steget, som ble splittet i to), siden slag+putt
// begge må være kjent (kommer tidligere i wizard-rekkefølgen).
useEffect(() => { useEffect(() => {
if (step !== "details") return if (step !== "direction") return
if (stat.approach !== null) return if (stat.approach !== null) return
if (stat.strokes === null || stat.putts === null) return if (stat.strokes === null || stat.putts === null) return
if (stat.strokes - stat.putts <= hole.par - 2) { 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" && (
<> <>
<ClubPicker <ClubPicker
label="Kølle brukt ved utslaget" label="Kølle brukt ved utslaget"
@ -2225,11 +2236,6 @@ function ScoringWizard({
variant="inline" variant="inline"
/> />
{/* Seksjon 1 -- retning (2026-08-11): retningskorsene utgjør nå
en tydelig egen gruppe med overskrift, adskilt fra tellerne
under, det er visuelt klart at skjermen består av to
ulike typer inndata (brukertilbakemelding: umulig å vite at
man måtte skrolle for å finne resten). */}
<WizardSection title="Retning"> <WizardSection title="Retning">
{hole.par !== 3 && ( {hole.par !== 3 && (
<DirectionCross <DirectionCross
@ -2251,37 +2257,39 @@ function ScoringWizard({
readOnly={readOnly} readOnly={readOnly}
/> />
</WizardSection> </WizardSection>
{/* 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
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 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. */}
<WizardSection title="Detaljer per hull">
<div className="grid grid-cols-1 gap-3">
<Stepper label="Chip" value={stat.chip} onChange={(v) => onUpdate({ chip: v })} readOnly={readOnly} max={stat.strokes ?? undefined} />
<Stepper label="Bunker" value={stat.bunker} onChange={(v) => onUpdate({ bunker: v })} readOnly={readOnly} max={stat.strokes ?? undefined} />
<Stepper label="Straffeslag" value={stat.penalty} onChange={(v) => onUpdate({ penalty: v })} readOnly={readOnly} max={stat.strokes ?? undefined} />
<Stepper
label="Anywayslag"
value={stat.anywayStrokes ?? 0}
onChange={(v) => onUpdate({ anywayStrokes: v })}
readOnly={readOnly}
max={stat.strokes ?? undefined}
/>
</div>
</WizardSection>
</> </>
)} )}
{/* 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 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, det holder alle skjermbredder. */}
{step === "holeDetails" && (
<div className="grid grid-cols-2 gap-3">
<Stepper label="Chip" value={stat.chip} onChange={(v) => onUpdate({ chip: v })} readOnly={readOnly} max={stat.strokes ?? undefined} />
<Stepper label="Bunker" value={stat.bunker} onChange={(v) => onUpdate({ bunker: v })} readOnly={readOnly} max={stat.strokes ?? undefined} />
<Stepper label="Straffeslag" value={stat.penalty} onChange={(v) => onUpdate({ penalty: v })} readOnly={readOnly} max={stat.strokes ?? undefined} />
<Stepper
label="Anywayslag"
value={stat.anywayStrokes ?? 0}
onChange={(v) => onUpdate({ anywayStrokes: v })}
readOnly={readOnly}
max={stat.strokes ?? undefined}
/>
</div>
)}
</div> </div>
</ScrollFade> </ScrollFade>
</main> </main>