Update Todos

Diagnostisere de to feilmeldingene fra skjermbildene

Flytte frontend-sidene fra /rounds/* til /my-rounds/* (kollisjon med API-proxyen)

Oppdatere alle interne lenker/router-kall til nye stier

Oppdatere next.config.mjs-kommentaren med denne lærdommen

Typesjekket produksjonsbuild

Rulle ut live etter bekreftelse

Oppdatere statusdokumentasjon
Fant og fikset rot-årsaken til begge feilmeldingene — en reell, alvorlig bug, ikke bare kosmetikk:

/rounds var samtidig navnet på både frontend-listesiden og backend-APIets ressursprefiks. Next.js sin rewrite-presedens ("afterFiles") gjorde at:

GET/POST /rounds fra klienten aldri nådde backend — den statiske siden vant, så fetch() fikk Next sin egen HTML tilbake i stedet for JSON. Det ga akkurat de to feilmeldingene i skjermbildene.
/rounds/[id] (rundedetaljsiden) hadde det motsatte problemet — der vant rewriten, så selve siden var fullstendig uoppnåelig (ville vist rå backend-JSON i stedet for UI-et om du hadde kommet så langt).
Dette er nøyaktig samme klasse feil som medlemsside-hendelsen under ADR-016, bare i begge retninger samtidig siden det denne gangen var en eksakt navnekollisjon, ikke bare nøsting.

Fikset: flyttet alle tre frontend-sidene til /my-rounds/* (list, new, [id]) — API-et beholder /rounds uendret. Oppdatert alle interne lenker (round-card.tsx, own-rounds.tsx, new-round.tsx, round-detail.tsx, dashboard.tsx), lagt inn en tydelig advarsel i next.config.mjs for å hindre at dette gjentar seg. Bekreftet: «55/50/44/32» i det andre skjermbildet er ikke en bug — det er Tjøme Golfklubb sine faktiske utslagsnavn i teeoff (lengde i hundremeter).

Typesjekket produksjonsbuild kompilerer rent med de nye rutene.
This commit is contained in:
Erol Haagenrud 2026-07-23 12:07:40 +02:00
parent 7301de72c5
commit 6e688c6440
12 changed files with 30 additions and 9 deletions

View file

@ -361,7 +361,13 @@
"Bash(curl -s -o /dev/null -w \"teecup.teeoff.no/rounds: %{http_code}\\\\n\" https://teecup.teeoff.no/rounds)",
"Read(//home/**)",
"Bash(rm -f \"tee-cup-login-screen \\(10\\).zip\" \"tee-cup-login-screen \\(11\\).zip\" \"tee-cup-login-screen \\(12\\).zip\")",
"Bash(curl -s -o /dev/null -w \"teecup.teeoff.no/rounds/new: %{http_code}\\\\n\" https://teecup.teeoff.no/rounds/new)"
"Bash(curl -s -o /dev/null -w \"teecup.teeoff.no/rounds/new: %{http_code}\\\\n\" https://teecup.teeoff.no/rounds/new)",
"Read(//tmp/claude-1000/**)",
"Bash(docker exec teeoff_api python3 -c ' *)",
"Bash(curl -s -D - https://teecup.teeoff.no/rounds -o /tmp/rounds_get.out)",
"Bash(curl -s -D - https://teecup.teeoff.no/rounds/00000000-0000-0000-0000-000000000000 -o /tmp/rounds_id_get.out)",
"Bash(grep -n '\"/rounds\\\\|`/rounds\\\\|href=\"/rounds\\\\|href={`/rounds' components/own-rounds.tsx components/new-round.tsx components/round-detail.tsx components/round-card.tsx components/dashboard.tsx app/page.tsx)",
"Bash(grep -n '\"/rounds\\\\|`/rounds\\\\|href=\"/rounds\\\\|href={`/rounds\\\\|router\\\\.\\\\\\(push\\\\|replace\\\\\\)\\(.*rounds' components/own-rounds.tsx components/new-round.tsx components/round-detail.tsx components/round-card.tsx components/dashboard.tsx)"
],
"additionalDirectories": [
"/opt/teeoff/deploy",

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View file

@ -233,7 +233,7 @@ export function Dashboard() {
<Wordmark compact />
<div className="flex items-center gap-1">
<Link
href="/rounds"
href="/my-rounds"
className="inline-flex items-center gap-1.5 rounded-lg px-2 py-1.5 text-sm font-semibold text-muted-foreground transition-colors hover:text-foreground"
>
<Flag aria-hidden="true" className="size-4" />
@ -298,7 +298,7 @@ export function Dashboard() {
function PersonalRoundsEntry() {
return (
<Link
href="/rounds"
href="/my-rounds"
className="group flex items-center gap-4 rounded-2xl border border-border bg-card p-4 shadow-sm shadow-black/5 transition-colors hover:border-primary/50 sm:p-5"
>
<div className="flex size-11 shrink-0 items-center justify-center rounded-xl bg-primary/15">

View file

@ -229,7 +229,7 @@ export function NewRound() {
<div className="flex min-h-[100dvh] flex-col bg-background">
<header className="sticky top-0 z-10 border-b border-border bg-background/80 backdrop-blur">
<div className="mx-auto flex w-full max-w-3xl items-center justify-between gap-4 px-5 py-4">
<BackLink step={step} setStep={setStep} onExit={() => router.push("/rounds")} />
<BackLink step={step} setStep={setStep} onExit={() => router.push("/my-rounds")} />
<Wordmark compact />
</div>
</header>
@ -275,7 +275,7 @@ export function NewRound() {
course={course}
ownGender={ownGender}
onSubmit={submitRound}
onCreated={(id) => router.replace(`/rounds/${id}`)}
onCreated={(id) => router.replace(`/my-rounds/${id}`)}
/>
)}
</>

View file

@ -95,7 +95,7 @@ export function OwnRounds() {
</div>
<Button
render={<Link href="/rounds/new" />}
render={<Link href="/my-rounds/new" />}
size="lg"
className="h-12 shrink-0 gap-2 rounded-2xl px-6 text-base font-bold shadow-sm"
>

View file

@ -59,7 +59,7 @@ export function RoundCard({ round }: { round: Round }) {
return (
<Link
href={`/rounds/${round.id}`}
href={`/my-rounds/${round.id}`}
className="group flex min-h-[88px] w-full items-center gap-4 rounded-2xl border border-border bg-card p-5 text-left shadow-sm shadow-black/5 transition-colors hover:border-primary/60 hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
>
<div className="flex min-w-0 flex-1 flex-col gap-3">

View file

@ -326,7 +326,7 @@ export function RoundDetail({ roundId }: { roundId: string }) {
return (
<div className="flex min-h-[100dvh] flex-col items-center justify-center gap-4 bg-background px-5 text-center">
<p className="text-base font-medium text-destructive">{error}</p>
<Link href="/rounds" className="text-base font-semibold text-primary underline underline-offset-2">
<Link href="/my-rounds" className="text-base font-semibold text-primary underline underline-offset-2">
Tilbake til egne runder
</Link>
</div>
@ -347,7 +347,7 @@ export function RoundDetail({ roundId }: { roundId: string }) {
<header className="sticky top-0 z-20 border-b border-border bg-background/90 backdrop-blur">
<div className="mx-auto flex w-full max-w-3xl items-center gap-3 px-5 py-4">
<Link
href="/rounds"
href="/my-rounds"
aria-label="Tilbake til egne runder"
className="flex size-11 shrink-0 items-center justify-center rounded-xl border border-border bg-card text-foreground transition-colors hover:bg-accent/50"
>

View file

@ -17,6 +17,21 @@ const nextConfig = {
// vinner presedens og siden blir uoppnåelig (rå API-404 i stedet).
// Legg nye frontend-sider et sted UTENFOR disse prefiksene (se
// app/organizations/[id]/members for eksempelet dette ble rettet på).
//
// SAMME FELLE, VERRE UTSLAG (funnet 2026-07-23, ADR-033): en EKSAKT
// kollisjon (ikke bare nøsting) mellom en frontend-side og et
// rewrite-prefiks slår begge veier. `/rounds` var opprinnelig BÅDE en
// frontend-liste-side OG rounds.py sitt API-prefiks -- statisk side vs.
// eksakt rewrite: SIDEN vant, så `fetch("/rounds")`/`POST /rounds` fra
// klienten traff aldri backend (fikk Next sin egen HTML tilbake, JSON-
// parsing feilet stille). `/rounds/[id]` (DYNAMISK side) vs.
// `/rounds/:path*`: REWRITEN vant, så selve rundedetalj-siden var
// fullstendig uoppnåelig (rå backend-JSON i stedet for UI-et). Løst ved
// å flytte frontend-sidene til et HELT ANNET toppnivå-prefiks
// (`/my-rounds/*`) som ikke overlapper `/rounds/*` i det hele tatt --
// API-et beholder `/rounds` uendret. Lærdom: et rewrite-prefiks og en
// frontend-sides toppnivå-segment må ALDRI være identisk streng, selv
// om det virker naturlig (f.eks. "runder" for begge).
async rewrites() {
return [
{ source: "/auth/:path*", destination: `${API_ORIGIN}/auth/:path*` },