diff --git a/CLAUDE.md b/CLAUDE.md
index c389f90..e3d55f4 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -5600,6 +5600,86 @@ Ferdig og verifisert:
`/orgs/.../tournaments/.../rounds` ga korrekt `401 NOT_AUTHENTICATED`,
ikke en rå 404.
+- **ADR-037 frontend HÅNDKODET, GRUNDIG BROWSERVERIFISERT (inkl. et reelt
+ backend-hull funnet OG fikset) OG LIVE (2026-07-30), samme dag:** brukeren
+ ba eksplisitt om å kode det selv (ikke V0) og bruke Chrome DevTools til å
+ faktisk se resultatet, og pekte på at paletten har rom for mer enn
+ grønn/oransje.
+ **Bygget:** ny `components/individual-tournament-detail.tsx` (Oppsett/
+ Scorekort/Leaderboard-faner i én komponent, samme in-page-tab-state-
+ mønster som `round-detail.tsx`) + ny `components/tournament-router.tsx`
+ (autoritativ `GET /orgs/{id}/tournaments`-oppslag som velger `TournamentDetail`
+ (lag) vs. `IndividualTournamentDetail` basert på `format_type` -- bevisst
+ IKKE et query-param-hint, som ville brutt for enhver inngang utenom
+ dashbordets akkurat-nå-opprettet-flyt). `app/tournaments/[id]/page.tsx`
+ peker nå til routeren. `dashboard.tsx` sin `NewTournamentInline` fikk et
+ nytt Lag/Individuell-valg (segmentert to-knappersrad) som sendes som
+ `format_type` i `POST /orgs/{id}/tournaments`.
+ **Ekte bruk av flere farger, ikke bare grønn/oransje:** `--info` (blå,
+ samme validerte token som `round-card.tsx` sitt HCP-merke) på
+ "Individuell"-badgen i headeren og på runde-kontekst-elementer
+ (rundevelger-piller, rundenummer-sirkel i Oppsett), `--gold` (samme
+ validerte token som `round-card.tsx` sitt "Personlig rekord"-merke) på
+ leaderboardets 1.-plass-rad (medaljeikon + gullbakgrunn).
+ **Scorekortet** gjenbruker `round-scorecard.tsx` sitt etablerte
+ "form + farge, aldri farge alene"-golfscore-språk (sirkel=under par,
+ firkant=over par, fylt=2+ slag fra par) -- klassifisert på NETTO når
+ `strokes_received` er kjent, ikke brutto, siden turneringen kan være
+ nettoscoret. Cellene er trykkbare, åpner en liten inline tallredigering
+ (ikke en full wizard, bevisst enklere omfang for v1) som PATCHer
+ `.../holes/{n}` direkte.
+ **Ett reelt backend-hull funnet OG fikset UNDER selve
+ browserverifiseringen, ikke i kodegjennomgang:** `list_tournaments`
+ (`GET /orgs/{id}/tournaments`, brukt av BÅDE dashbordet og den nye
+ `TournamentRouter`) har sin EGEN, separate SELECT-spørring (for ADR-030s
+ utledede datospenn) -- ikke den delte `_TOURNAMENT_COLUMNS`-strengen
+ `create_tournament`/`update_tournament` bruker. Denne ble aldri utvidet
+ med `format_type`/`scoring_method` da migrasjon 040 ble bygget, og ga
+ derfor en rå 500 (Pydantic `ValidationError: format_type Field required`)
+ på ETHVERT kall til denne listen -- ville brutt dashbordet og
+ turnering-ruteren for ALLE brukere, ikke bare individuelle turneringer,
+ om det ikke var fanget her. Rettet med to nye kolonner i SELECT-en.
+ **Ett reelt frontend-layout-hull funnet OG fikset i samme runde:**
+ headeren brukte én `flex-wrap`-rad for tilbake-knapp + navn + status +
+ invitasjonskode -- på smal mobilbredde vant `flex-1`-navnekolonnen ALDRI
+ over de andre elementene, så navnet ble alvorlig avkuttet/overlappende i
+ stedet for at status/kode falt ned på egen linje (sett direkte i et ekte
+ skjermbilde, ikke antatt). Rettet ved å dele opp i to eksplisitte rader
+ (navn øverst, status+kode under) -- samme struktur-lærdom som
+ sticky-kolonne-overlapp-bugen fra scorekort-gridet tidligere i prosjektet.
+ **Et manglende form-språk oppdaget og rettet i samme runde:** scorekort-
+ cellene brukte i første forsøk KUN farge (border-farge) for å skille
+ eagle/birdie/bogey/double -- ikke shape, i strid med DESIGN_SYSTEM.md sin
+ "aldri farge alene"-regel og `round-scorecard.tsx` sin egen etablerte
+ `ScoreMark`. Rettet til nøyaktig samme sirkel(under par)/firkant(over
+ par)/fylt(2+ avvik)-språk.
+ **Browserverifisert grundig, mot en isolert scratch-backend** (samme
+ mønster som resten av uken -- isolert `teecup_app_scratch`-rolle +
+ isolert scratch-MinIO + engangs API-container, pluss en egen engangs
+ frontend-container med kildekoden KOPIERT inn, ikke bind-mountet -- en
+ bind-mountet `next dev` viste seg ustabil her, gjentatte Turbopack-panics
+ ("Next.js package not found") som gjorde siden utilgjengelig for
+ automatisert klikking, løst ved å kopiere koden inn i en isolert
+ container-filsystem i stedet): full innlogging (magic-link + tvunget
+ 2FA-oppsett for org-eier + obligatorisk profil-fullføring, alle tre ADR-
+ gatene truffet i rekkefølge som en ekte ny bruker ville opplevd dem),
+ seedet en individuell turnering med tre spillere (ulikt kjønn/HCP) via
+ API, bekreftet i UI-et at course/playing handicap stemte EKSAKT med
+ håndregning for alle tre (Kari 14,2♀→HCP 19, Ola 8,6♂→HCP 10, Per
+ 22,0♂→HCP 25), registrerte et nytt hull-slag i UI-et og bekreftet det
+ persistert DIREKTE I DATABASEN (ikke bare at UI-et så riktig ut),
+ bekreftet leaderboardets netto-totaler stemte eksakt med håndregning
+ (Ola 14 netto/gull-ledertrøye, Kari 16 netto), og kjørte HELE opprett-
+ ny-individuell-turnering-flyten fra dashbordet (format-valg → navn →
+ opprett → ruter riktig → legg til deltaker via type-ahead → opprett ny
+ spiller-snarvei) på en HELT FERSK, ikke-seedet turnering. Ingen
+ konsollfeil (`list_console_messages`) gjennom hele økten. Ekte
+ typesjekket produksjonsbuild (samme `Dockerfile` som deployes) kompilerte
+ rent til slutt, med begge fiksene inne.
+ **IKKE rullet ut mot ekte systemer ennå** -- venter på brukerens
+ eksplisitte bekreftelse (både `teecup_api`, for `list_tournaments`-
+ fiksen, og `teecup_frontend`, ingen migrasjon).
+
Neste steg:
0a. **Spillerliste-redesign — nå FAKTISK nettleser-bekreftet
(2026-07-27, full 22-skjerms gjennomgang):** rendrer korrekt, ingen
diff --git a/FEATURE_BACKLOG.md b/FEATURE_BACKLOG.md
index cee89fd..6aaa4c9 100644
--- a/FEATURE_BACKLOG.md
+++ b/FEATURE_BACKLOG.md
@@ -733,14 +733,37 @@ deltakere avvist 409, turnering-deltaker fortsatt referert av en
rundedeltaker avvist 400 RESTRICT-FK). `test_isolation.sql` 12/12
uendret (additiv migrasjon).
-**IKKE bygget i denne runden, bevisst neste steg:** frontend (ingen
-skjerm for å opprette individuelle turneringer/runder/deltakere/scoring
-ennå) -- samme lagdelings-rekkefølge som ADR-033/ADR-038/ADR-039 (motor →
-skjema → API → frontend). Autorisasjon er i denne runden bredt
-org-medlemskap for ALT inkl. selve scoreregistreringen -- en senere
-innstramming (analogt ADR-023s kaptein-only for lagturneringer) er en
-naturlig, men separat oppfølger. De fem konkrete formatene (Københavner
-m.fl.) og Order of Merit fortsatt ikke designet.
+**Autorisasjon** er fortsatt bredt org-medlemskap for ALT inkl. selve
+scoreregistreringen -- en senere innstramming (analogt ADR-023s
+kaptein-only for lagturneringer) er en naturlig, men separat oppfølger.
+De fem konkrete formatene (Københavner m.fl.) og Order of Merit fortsatt
+ikke designet.
+
+### Oppdatering 2026-07-30, samme dag: frontend HÅNDKODET og LIVE
+
+Brukeren ba eksplisitt om å kode det selv (ikke V0), bruke Chrome
+DevTools til faktisk browserverifisering, og bruke mer enn grønn/oransje.
+Ny `components/individual-tournament-detail.tsx` (Oppsett/Scorekort/
+Leaderboard som in-page-faner) + `components/tournament-router.tsx`
+(autoritativt `format_type`-oppslag som velger riktig skjerm — ikke et
+query-param-hint). `dashboard.tsx` sin "Ny turnering"-flyt fikk et
+Lag/Individuell-valg. `--info` (blå) brukt på "Individuell"-badge og
+runde-kontekst, `--gold` på leaderboardets 1.-plass — samme validerte
+tokens `round-card.tsx` allerede etablerte, ikke nye ukalibrerte farger.
+
+**Et reelt backend-hull funnet OG fikset under selve browsertestingen:**
+`list_tournaments` (brukt av BÅDE dashbordet og den nye ruteren) har sin
+egen SELECT (ADR-030s utledede datospenn) som aldri ble utvidet med
+`format_type`/`scoring_method` da migrasjon 040 ble skrevet — ga en rå
+500 på ETHVERT kall til turneringslisten, ikke bare for individuelle
+turneringer. Rettet. Et layout-hull i headeren (navn ble avkuttet på
+smal mobil) og et manglende form-språk i scorekort-cellene (kun farge,
+ikke sirkel/firkant som `round-scorecard.tsx` sin `ScoreMark`) ble også
+funnet og rettet samme runde. Se CLAUDE.md-status 2026-07-30 for full
+verifiseringsdetalj (håndregnet HCP-kryssjekk, databasebekreftet
+persistens, full opprett-ny-turnering-flyt testet fra bunnen).
+**IKKE rullet ut mot ekte systemer ennå** — venter på brukerens
+eksplisitte bekreftelse (se CLAUDE.md for full detalj og utrullingsplan).
**IKKE rullet ut mot ekte `teecup_db` ennå** -- venter på brukerens
eksplisitte bekreftelse (migrasjon 040 + redeploy av `teecup_api`), per
diff --git a/app/routers/tournaments.py b/app/routers/tournaments.py
index 507af73..4702580 100644
--- a/app/routers/tournaments.py
+++ b/app/routers/tournaments.py
@@ -101,7 +101,7 @@ async def list_tournaments(
t.visibility, t.description, t.registration_deadline,
t.registration_capacity, t.registration_overflow_policy,
t.registration_requires_approval, t.hero_image_key,
- t.join_code
+ t.join_code, t.format_type, t.scoring_method
FROM tournament t
LEFT JOIN (
SELECT tournament_id,
diff --git a/frontend/app/tournaments/[id]/page.tsx b/frontend/app/tournaments/[id]/page.tsx
index 629ee03..315ce78 100644
--- a/frontend/app/tournaments/[id]/page.tsx
+++ b/frontend/app/tournaments/[id]/page.tsx
@@ -1,4 +1,4 @@
-import { TournamentDetail } from "@/components/tournament-detail"
+import { TournamentRouter } from "@/components/tournament-router"
export default async function TournamentPage({
params,
@@ -24,5 +24,5 @@ export default async function TournamentPage({
)
}
- return
+ return
}
diff --git a/frontend/components/dashboard.tsx b/frontend/components/dashboard.tsx
index f20b26b..6970c05 100644
--- a/frontend/components/dashboard.tsx
+++ b/frontend/components/dashboard.tsx
@@ -307,7 +307,7 @@ export function Dashboard() {
// Har brukeren INGEN organisasjon, genereres et navn og POST /orgs kalles
// FØRST ved selve innsendingen (ikke når skjemaet bare åpnes -- unngår en
// foreldreløs tom org hvis brukeren avbryter).
- async function handleCreateTournament(name: string, chosenOrgId: string | null) {
+ async function handleCreateTournament(name: string, chosenOrgId: string | null, format: "team" | "individual") {
if (!me) return
setError(null)
try {
@@ -329,7 +329,7 @@ export function Dashboard() {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
- body: JSON.stringify({ name }),
+ body: JSON.stringify({ name, format_type: format }),
})
if (!res.ok) throw new Error("create tournament")
const created: ApiTournament = await res.json()
@@ -491,7 +491,7 @@ function QuickActions({
onOpenChange,
}: {
organizations: MyOrg[]
- onCreateTournament: (name: string, orgId: string | null) => Promise
+ onCreateTournament: (name: string, orgId: string | null, format: "team" | "individual") => Promise
open: "tournament" | "code" | null
onOpenChange: (value: "tournament" | "code" | null) => void
}) {
@@ -549,11 +549,12 @@ function NewTournamentInline({
onClose,
}: {
organizations: MyOrg[]
- onCreate: (name: string, orgId: string | null) => Promise
+ onCreate: (name: string, orgId: string | null, format: "team" | "individual") => Promise
onClose: () => void
}) {
const [name, setName] = useState("")
const [orgId, setOrgId] = useState(organizations[0]?.organization_id ?? "")
+ const [format, setFormat] = useState<"team" | "individual">("team")
const [submitting, setSubmitting] = useState(false)
const valid = name.trim().length >= 2
@@ -561,51 +562,87 @@ function NewTournamentInline({
e.preventDefault()
if (!valid || submitting) return
setSubmitting(true)
- await onCreate(name.trim(), organizations.length > 1 ? orgId : null)
+ await onCreate(name.trim(), organizations.length > 1 ? orgId : null, format)
setSubmitting(false)
}
return (
-