Tilbake-navigering til en tidligere spiller i scoringsveiviseren (ADR-077)

Kontekst-raden i ScoringWizard (alle spillerne, aktiv fremhevet) var
bevisst ikke-interaktiv ved bygging. Trygt å gjøre interaktiv nå: hvert
felt lagres allerede for seg med en gang (PATCH per felt), så bytte
spiller midt i et steg gir ingen datatap. Løser at man måtte fullføre
hele samlebånd-flyten for å rette opp en tidligere spillers feil.

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Erol Haagenrud 2026-08-16 08:19:46 +02:00
parent f740814833
commit c498bfca8f
3 changed files with 119 additions and 6 deletions

View file

@ -7605,6 +7605,62 @@ turneringer (ADR-075), offentlig visning (dette dokumentet).
---
## ADR-077: Tilbake-navigering til en tidligere spiller i scoringsveiviseren ("Steg 1" av tre forbedringer i score-registreringen) (2026-08-16)
Foranledning: bruker observerte at når man fører score for flere spillere
etter hverandre i samlebånd-flyten (`ScoringWizard`, frittstående runder)
og har trykket "Neste" videre forbi en spiller, er det umulig å rette opp
en feilregistrering hos den spilleren INNE i selve veiviser-flyten — man
må fullføre veiviseren helt til slutten, tilbake på Score-fanen lete opp
riktig spillers kort for det aktuelle hullet, og åpne det på nytt. Første
av tre planlagte forbedringer (se `~/.claude/plans/joyful-wishing-crab.md`
for hele planen) — kun denne er round-only: org-turneringer har ingen
tilsvarende kjede-flyt (`HoleGrid` velger ett hull+én spiller om gangen),
og ADR-065 (per-deltaker-fullføring) er eksplisitt bygget kun for
frittstående runder.
**Løsning**: kontekst-raden i `ScoringWizard` (alle spillerne, aktiv
fremhevet) var BEVISST ikke-interaktiv ved bygging (2026-07-26-kommentaren:
"bytte spiller midt i et steg ville latt uferdige felt henge løst") —
denne antakelsen viste seg foreldet: `updateWizardStat` har siden lagret
hvert felt for seg MED EN GANG (PATCH per felt, ikke batch-lagring), så
det er trygt å bytte spiller når som helst uten datatap. Raden er nå
interaktiv: hver spiller-chip er en knapp, trykk på en ANNEN spiller enn
den aktive (inkludert allerede "Ferdig"-markerte -- bekreftet at backend
ikke har noen hard sperre mot å redigere en fullført spillers data, kun
runde-nivå `readOnly` blokkerer) bytter veiviseren dit via en ny
`switchWizardPlayer()`-funksjon i `RoundDetail` (samme mål-oppløsning som
`openPlayerOrTeamSideEntry`, inkl. scramble-solo-lag-siden-avviket, men
uten å røre `currentHole`). Steget nullstilles automatisk til "Slag" for
den nye spilleren via den allerede eksisterende `useEffect(() =>
setStepIndex(0), [player.id])`. "Ferdig"-markerte spillere vises med en
tekst-etikett ("· Ferdig") i raden, ikke bare farge (tilgjengelighetsregel
-- aldri fargekoding alene).
Ingen ny fil, ingen V0 (gjør et eksisterende, bevisst
ikke-interaktivt element interaktivt -- ingen ny visuell flate).
**Verifisert:** `tsc --noEmit` rent + 45/45 vitest. Egen scratch-database
+ scratch `teecup_api` (port 18104, live-montert kode,
`TEECUP_DEV_LOG_MAGIC_LINKS=true`) + lokal `next dev` (port 13104): runde
med tre spillere. Registrerte Par for spiller 1, byttet midt i steget til
spiller 2 (Bogey) via kontekst-raden -- spiller 1 sin allerede lagrede
score forble urørt. Byttet tilbake til spiller 1 FRA MIDT I spiller 2 sin
flyt, rettet scoren til Birdie -- lagret korrekt, spiller 2 sin Bogey
urørt. Trykket "Avslutt for Erol Scratch" (ADR-065), åpnet spiller 2 sin
veiviser på nytt, bekreftet at spiller 1 nå vises som "Erol Scratch ·
Ferdig" i kontekst-raden OG fortsatt er trykkbar -- åpnet ham, viste
korrekt lagret Birdie (ikke nullstilt), ingen sperre. Lys+mørk bekreftet.
Scratch-stacken fullstendig revet ned (database, rolle, container,
MinIO-bøtte, `next dev`) -- 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)

View file

@ -11663,3 +11663,28 @@ Neste steg:
Med dette er alle tre punktene fra ADR-043 sin opprinnelige
"gjenstår"-liste fullført.
93. **Tilbake-navigering til en tidligere spiller i scoringsveiviseren
(ADR-077) — 2026-08-16.** Første av tre forbedringer i score-
registreringen brukeren ba om etter OOM. I samlebånd-flyten
(`ScoringWizard`, frittstående runder) var det umulig å rette opp en
feilregistrering hos en tidligere spiller uten å fullføre hele
veiviseren og lete opp riktig kort manuelt etterpå.
Kontekst-raden med alle spillerne (bevisst ikke-interaktiv ved bygging
i 2026-07-26-runden) er nå interaktiv -- trykk på en annen spiller
(inkl. "Ferdig"-markerte) bytter veiviseren dit umiddelbart. Trygt
fordi hvert felt allerede lagres for seg med en gang (PATCH per felt,
ikke batch) -- ingen datatap ved bytte. Round-only (org-turneringer
har ingen tilsvarende kjede-flyt).
**Verifisert:** `tsc --noEmit` rent + 45/45 vitest. Scratch-database +
scratch `teecup_api` (port 18104) + lokal `next dev` (port 13104): tre
spillere, byttet spiller midt i steg uten datatap, rettet en allerede
lagret score etter bytte, bekreftet "Ferdig"-markerte spillere fortsatt
er trykkbare og viser korrekt lagret data (ikke nullstilt). Lys+mørk
bekreftet. Scratch-stacken fullstendig revet ned.
**Ingen migrasjon.**
**Rullet ut**: ikke ennå.

View file

@ -1130,6 +1130,21 @@ export function RoundDetail({ roundId }: { roundId: string }) {
setWizardPlayerId(playerId)
}
// Tilbake til en tidligere spiller fra INNI veiviseren (ADR-077,
// 2026-08-15) -- samme mål-oppløsning som openPlayerOrTeamSideEntry over,
// men rører IKKE currentHole (vi er allerede på riktig hull, kalt fra
// kontekst-raden i selve veiviseren).
function switchWizardPlayer(playerId: string) {
const target = players.find((p) => p.id === playerId)
if (scrambleSoloTeamSideId && target?.roundSideId === scrambleSoloTeamSideId) {
setWizardPlayerId(null)
setWizardSideId(scrambleSoloTeamSideId)
return
}
setActivePlayerId(playerId)
setWizardPlayerId(playerId)
}
// Samlebånd-fremdrift (2026-07-26): kalles fra veiviserens siste steg for
// siste spiller på hullet -- bytter til NESTE spiller i rekkefølgen (samme
// veiviser, nullstilt til steg 1), eller -- er aktiv spiller den siste --
@ -1874,6 +1889,7 @@ export function RoundDetail({ roundId }: { roundId: string }) {
onUpdate={updateWizardStat}
onClose={() => setWizardPlayerId(null)}
onAdvance={advanceWizardPlayer}
onSwitchPlayer={switchWizardPlayer}
readOnly={readOnly}
ownBagClubs={ownBagClubs}
isPending={pendingParticipantHoles.has(`${wizardPlayer.id}:${activeHole}`)}
@ -1931,6 +1947,7 @@ function ScoringWizard({
onUpdate,
onClose,
onAdvance,
onSwitchPlayer,
readOnly,
ownBagClubs,
isPending,
@ -1945,6 +1962,11 @@ function ScoringWizard({
onUpdate: (patch: Partial<HoleStat>) => void
onClose: () => void
onAdvance: () => void
// Tilbake til en TIDLIGERE (eller "Ferdig"-markert) spiller midt i
// samlebånd-flyten (ADR-077) -- se kontekst-raden under. Trygt fordi
// hvert felt allerede lagres for seg med en gang (updateWizardStat),
// ingen fare for å miste uferdige felt ved bytte.
onSwitchPlayer: (playerId: string) => void
readOnly: boolean
ownBagClubs: string[]
// Offline scoreregistrering (ADR-028-utvidelse, 2026-07-28) -- sann når
@ -2059,21 +2081,31 @@ function ScoringWizard({
{/* Kontekst-rad: alle spillerne, aktiv fremhevet -- samme prinsipp som
konkurrentappen: man mister aldri oversikten over hvem som allerede
er ferdig hullet. Ren informasjon, ikke en snarvei -- bytte
spiller midt i et steg ville latt uferdige felt henge løst. */}
er ferdig hullet. Interaktiv (ADR-077, 2026-08-15) -- trykk
en ANNEN spiller (inkl. "Ferdig"-markerte) bytter veiviseren dit,
for å rette opp allerede registrerte data uten å måtte fullføre
hele samlebåndet først og lete opp kortet nytt etterpå. Trygt
siden hvert felt allerede lagres for seg med en gang (se
onSwitchPlayer). */}
<div className="-mx-1 flex shrink-0 gap-2 overflow-x-auto border-b border-border px-4 py-2.5">
{players.map((p) => (
<span
<button
key={p.id}
type="button"
onClick={() => onSwitchPlayer(p.id)}
aria-current={p.id === player.id ? "true" : undefined}
className={cn(
"flex shrink-0 items-center rounded-full border px-3 py-1.5 text-sm font-bold",
"flex min-h-11 shrink-0 items-center gap-1.5 rounded-full border px-3 py-1.5 text-sm font-bold transition-colors",
p.id === player.id
? "border-primary bg-primary text-primary-foreground"
: "border-border bg-card text-muted-foreground",
: "border-border bg-card text-muted-foreground hover:bg-accent/50 hover:text-foreground",
)}
>
{p.name}
</span>
{p.completed && p.id !== player.id && (
<span className="text-xs font-semibold">· Ferdig</span>
)}
</button>
))}
</div>