diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 9c289a5..16cb80d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -297,7 +297,25 @@ "Bash(python3 -c \"import ast; ast.parse\\(open\\('app/routers/matches.py'\\).read\\(\\)\\)\")", "Bash(python3 -c \"import ast; ast.parse\\(open\\('app/routers/scoring.py'\\).read\\(\\)\\)\")", "Bash(mkdir -p /opt/teecup/frontend/app/t/\\\\[id\\\\]/live)", - "Bash(python3 -c \"import ast; ast.parse\\(open\\('app/routers/messaging.py'\\).read\\(\\)\\)\")" + "Bash(python3 -c \"import ast; ast.parse\\(open\\('app/routers/messaging.py'\\).read\\(\\)\\)\")", + "Bash(python3 -c \"import PIL; print\\('PIL ok', PIL.__version__\\)\")", + "Bash(python3 -c \"import cairosvg\")", + "Bash(node -e \"console.log\\(require\\('sharp'\\)\\)\")", + "Bash(timeout 60 npx --yes -p sharp -e ' *)", + "Bash(mkdir -p /tmp/claude-1000/-opt-teecup/a8bd2fc3-4b9c-4682-a2be-cf36e143de78/scratchpad/iconbuild)", + "Bash(timeout 90 npm install sharp --no-audit --no-fund)", + "Bash(rm -rf node_modules package-lock.json)", + "Bash(timeout 90 npm install sharp@0.33.5 --no-audit --no-fund)", + "Bash(node gen.js)", + "Bash(cat)", + "Bash(node gen-apple.js)", + "Bash(curl -s http://localhost:18734/manifest.webmanifest)", + "Bash(curl -s http://localhost:18734/sw.js)", + "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" http://localhost:18734/offline.html)", + "Bash(curl -s -o /dev/null -w \"192: %{http_code}\\\\n\" http://localhost:18734/icons/icon-192.png)", + "Bash(curl -s -o /dev/null -w \"512: %{http_code}\\\\n\" http://localhost:18734/icons/icon-512.png)", + "Bash(curl -s -o /dev/null -w \"maskable: %{http_code}\\\\n\" http://localhost:18734/icons/icon-maskable-512.png)", + "Bash(curl -s -o /dev/null -w \"apple: %{http_code}\\\\n\" http://localhost:18734/apple-icon.png)" ], "additionalDirectories": [ "/opt/teeoff/deploy", diff --git a/ARCHITECTURE_DECISIONS.md b/ARCHITECTURE_DECISIONS.md index ab0db4e..9df83f1 100644 --- a/ARCHITECTURE_DECISIONS.md +++ b/ARCHITECTURE_DECISIONS.md @@ -1200,6 +1200,106 @@ status for scratch-verifisering og utrulling. --- +## ADR-028: PWA — installasjon + offline scoreregistrering + +Reist 2026-07-19, brukeren valgte å ta fatt på PWA (stod som "neste steg" i +CLAUDE.md siden ADR-006 vedtok prinsippet helt i starten av prosjektet — aldri +bygget). To beslutninger avklart eksplisitt med bruker (AskUserQuestion) før +bygging. + +**Beslutning A — Full offline scoreregistrering i v1, ikke bare +installasjon.** Bruker valgte det mest ambisiøse alternativet: BÅDE +manifest/ikoner/service worker (installerbar app) OG at scorekort-skjermen +skal fungere uten nett — skriv til en lokal IndexedDB-kø, synk automatisk når +nettet er tilbake. Matcher ADR-006s opprinnelige formulering ordrett +("score må kunne registreres uten nett og synkes senere"). + +**Beslutning B — Omfang av selve offline-skrivingen: kun `hole-scores`/ +`hole-results`, ingenting annet.** De to eksisterende scoreregistrerings- +endepunktene (ADR-012) er de eneste som køes. Bevisst UTENFOR omfang, ikke +glemt: +- Walkover/konsesjon (ADR-024) — sjeldnere handling, kan kreve nett. +- Chat/feed-posting (ADR-025) — bilder gjør en offline-kø vesentlig mer + komplisert (blob-lagring i IndexedDB), og er ikke "score"-handlingen + ADR-006 faktisk siktet til. +- Alle andre skrive-endepunkter (oppsett, roster, økter osv.) — forutsetter + nett i v1, uendret. + +**Beslutning C — Køen lever i klientkoden (IndexedDB, `lib/offline- +queue.ts`), IKKE i service workeren, og bruker `window` sitt `online`- +event, IKKE Background Sync API.** To bevisste forenklinger: +1. Å gi umiddelbar, presis UI-tilbakemelding ("lagret lokalt, venter på + synk", pending-antall) er enklere og mer testbart fra selve + React-komponenten enn fra en service worker sin `fetch`-handler. +2. Background Sync API (som ville gitt synk selv om appen er lukket) støttes + IKKE av iOS Safari i det hele tatt — en stor andel av klubb-/ + vennegjeng-brukerne er trolig på iPhone, og et rent + Background-Sync-avhengig design ville derfor vært brutt for dem. Et + `window.addEventListener("online", ...)`-mønster (pluss en manuell + "Synkroniser nå"-knapp i UI-et som reserve) fungerer overalt, på + bekostning av at synk krever at appen faktisk er åpen når nettet kommer + tilbake — akseptabelt for v1. + +`submitStroke`/`submitHoleResult` (`components/session-scorecard.tsx`) +sjekker `navigator.onLine` FØRST (unngår en unødvendig ventetid på et +nettverkskall som uansett vil feile), og fanger ellers en EKTE nettverksfeil +i fetch-kallet separat fra et avvist HTTP-svar (`res.ok === false`, f.eks. +409 "matchen er avgjort") — kun den førstnevnte køordner, sistnevnte viser +fortsatt den vanlige feilteksten uendret. Et lokalt overlay +(`pendingStrokes`/`pendingResults`, ikke persistert i selve +`scorecard`-staten) viser køede verdier umiddelbart i UI-et, merket +"Lagret lokalt · venter på synk", inkludert i hull-navigasjonens +registrert-markering. + +**Konfliktmodell: samme tillitsnivå som resten av appen, ingen ny +mekanisme.** Server-siden er allerede en upsert per hull (`ON CONFLICT ... +DO UPDATE`) — siste innsending vinner, ingen audit-trail (kjent, tidligere +dokumentert åpent spørsmål, se «Scoring-autorisasjon» i FEATURE_BACKLOG.md). +Køen sender i rekkefølge (aldri parallelt) for å respektere lokal +innsendingsrekkefølge ved flere endringer av samme hull offline. Et +definitivt HTTP-avvist forsøk ved synk (typisk: matchen ble avgjort på en +ANNEN enhet mens denne var offline) fjernes fra køen og vises som en +feilmelding — blir ALDRI hengende for alltid. + +**Service worker-strategi: nettverk først, cache som fallback — bevisst +IKKE stale-while-revalidate.** `public/sw.js` cacher (a) side-navigasjon og +(b) GET-kall under `/orgs/*`. Begge prøver ekte nettverk FØRST og faller +kun tilbake til cache ved reell feil. En stale-while-revalidate-strategi ble +vurdert og avvist: scorekortet gjør et `refetchScorecard()`-kall RETT ETTER +hver innsending, og må da alltid få fersk data — en umiddelbart utdatert +cache ville vist feil matchstatus/hull-tall rett etter en vellykket +innsending. `/auth/*` og `/public/*` caches bevisst ikke — omfanget er +begrenset til akkurat det scoreregistrerings-flyten trenger. + +**Kjent, akseptert begrensning (ikke løst i v1):** Cache Storage er nøklet +på URL, ikke på innlogget bruker — deler flere kontoer samme enhet/ +nettleser, kan en offline-fallback teoretisk vise data cachet av en +TIDLIGERE innlogget bruker på samme enhet. Ingen cache-tømming ved +utlogging bygget. Samme tillitsnivå/trusselmodell som appen ellers opererer +med (tillitsbasert klubb-/vennegjeng-verktøy, jf. `team_authz.py` sin +begrunnelse i ADR-023/025). + +**Ikoner: enkelt, midlertidig sett generert programmatisk (grønt +golf-flagg), ikke endelig design.** Bruker valgte å generere nå fremfor å +vente på ekte design, MED eksplisitt beskjed om at disse skal erstattes +senere — notert i FEATURE_BACKLOG.md. Erstattet samtidig den gamle +`public/apple-icon.png` (v0.app sin generiske plassholder-logo, ikke +TeeCup-merkevare i det hele tatt) med samme nye ikon, av konsistens. + +**Ikke testet i ekte nettleser (viktig, ikke bare en formalitet):** +verifisert med typesjekket produksjonsbuild (samme `Dockerfile` som +deployes) og en kort container-boot med `curl` (manifest/service worker/ +ikoner/offline.html svarer riktig), men INGEN faktisk browser-basert +offline-test (DevTools "Offline"-modus, "Legg til på hjemskjerm") er +gjennomført denne runden — ingen nettleserverktøy tilgjengelig i denne +økten. Anbefales sterkt at brukeren selv tester scorekort-siden med Chrome +DevTools sin Offline-bryter før tillit legges til flyten i skarp bruk. + +**Status: ✅ BYGGET, IKKE ENNÅ RULLET UT.** Se CLAUDE.md-status for +byggerunden. Venter på brukerens utrullingsbekreftelse. + +--- + ## Åpne spørsmål (ikke besluttet ennå) Disse må avklares før eller under de relevante fasene: diff --git a/CLAUDE.md b/CLAUDE.md index cd0b891..4bf32f7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1670,18 +1670,95 @@ Ferdig og verifisert: (ukjent turnering-id, ingen ekte data berørt) ga korrekt 404 fra selve applikasjonslaget. +- **Fire brukerrapporterte UI-/UX-hull, DIAGNOSTISERT OG NOTERT, IKKE fikset + (2026-07-19):** brukeren rapporterte fire ting fra faktisk bruk av + `teecup.teeoff.no` rett før PWA-runden startet. Root cause funnet ved + kodegjennomgang for tre av fire (ikke gjettet). Full detalj i + FEATURE_BACKLOG.md sin nye seksjon "Rapporterte UI-/UX-hull (2026-07-19)". + Kort: + 1. Dashboard-turneringskortet viser "Ingen datoer satt" alltid — leser + `tournament.start_date`/`end_date` (eget felt, ADR-015), som INGEN + UI-skjema noensinne skriver til. Skal enten få et faktisk skjemafelt, + eller kortet bør heller utlede datoen fra øktenes `scheduled_at`. + 2. **Reell, bekreftet rewrite-bug:** `/orgs/{id}/members` gir en rå + FastAPI-404 (`{"detail":"Not Found"}`) i stedet for medlemssiden. + `next.config.mjs` sin `rewrites()` returnerer en plain array (implisitt + "afterFiles") — DYNAMISKE Next.js-sider sjekkes ETTER rewrites, så + `/orgs/:path*`-proxy-regelen (ADR-016) fanger kallet FØR + `app/orgs/[id]/members/page.tsx` noensinne nås. Dette er den FØRSTE + frontend-siden som er nestet direkte under et allerede proxyet prefiks + — ingen tidligere skjerm har truffet dette. Selve siden/komponenten er + riktig bygget, kun ruten dit er blokkert. + 3. Ingen UI-vei til å opprette en ANDRE organisasjon når man allerede har + én — `CreateOrganizationState` i `dashboard.tsx` vises kun ved null + org-er. Backend støtter det fullt ut allerede (ADR-021). + 4. Ingen sammendrag/indikator noe sted for "alle runder har fått dato" — + må sjekkes manuelt per øktkort på program-skjermen. + **Ingen av de fire fikset i denne runden** — kun dokumentert på brukerens + eksplisitte instruks, PWA-runden prioriteres først. + +- **PWA: installasjon + full offline scoreregistrering, BYGGET, IKKE ENNÅ + RULLET UT (2026-07-19, ADR-028):** bruker valgte det mest ambisiøse + omfanget (installerbar app OG offline scoreregistrering, ikke bare + installasjon), og valgte å generere enkle ikoner nå fremfor å vente på + ekte design (med eksplisitt beskjed om at de er midlertidige). + **Ikoner:** ingen `PIL`/`rsvg-convert`/`imagemagick` tilgjengelig i miljøet + — løst med et scratch npm-prosjekt (`sharp@0.33.5`, node18-kompatibel + versjon; nyeste `sharp` krever node ≥20 og feilet først) som genererte et + enkelt grønt golf-flagg-ikonsett (`public/icons/icon-192.png`, + `icon-512.png`, `icon-maskable-512.png`) + erstattet den gamle + `public/apple-icon.png` (var v0.app sin generiske plassholderlogo, ikke + TeeCup-merkevare i det hele tatt). + **Bygget:** `app/manifest.ts` (Next.js sin innebygde manifest-generator, + ikke en statisk `manifest.json`), `appleWebApp`-metadata i `layout.tsx` + (iOS leser ikke manifest.json for hjemskjerm-oppførsel), + `components/sw-register.tsx` (stille no-op uten SW-støtte), + håndskrevet `public/sw.js` (ingen next-pwa/workbox-avhengighet — nettverk + først/cache-fallback for navigasjon + `/orgs/*`-GET-er, BEVISST ikke + stale-while-revalidate, se ADR-028 for hvorfor), `public/offline.html`. + **Offline scoreregistrering:** ny `lib/offline-queue.ts` (IndexedDB-kø, + ren klientkode — ikke i SW-en), koblet inn i `session-scorecard.tsx` sin + `submitStroke`/`submitHoleResult`: sjekker `navigator.onLine` først, køer + kun ved en EKTE nettverksfeil (ikke ved et avvist HTTP-svar som "matchen + er avgjort" — det vises fortsatt som vanlig feiltekst). Lokalt overlay + (`pendingStrokes`/`pendingResults`) viser køede verdier umiddelbart, + merket "Lagret lokalt · venter på synk". Auto-synk ved `window`s + `online`-event PLUSS en manuell "Synkroniser nå"-knapp (bevisst IKKE + Background Sync API — iOS Safari støtter den ikke). Et definitivt avvist + synk-forsøk (f.eks. matchen ble avgjort på en annen enhet mens denne var + offline) fjernes fra køen og vises som feilmelding, henger aldri for + alltid. + **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` som + deployes) kjørt og bekreftet — alle 16 ruter listet inkl. + `/manifest.webmanifest`. Kort container-boot + `curl` bekreftet manifest/ + service worker/ikoner/offline.html alle svarer riktig (200, riktig + innhold). + **IKKE gjort denne runden, viktig å være ærlig om:** ingen faktisk + nettleser-basert offline-test (Chrome DevTools sin Offline-bryter, + faktisk "Legg til på hjemskjerm") — intet nettleserverktøy tilgjengelig i + denne økten. Kun kodegjennomgang + build-verifisering. **Brukeren bør selv + teste scorekort-siden med DevTools Offline-modus før tillit i skarp + bruk.** + **Ikke rullet ut ennå** — venter på eksplisitt utrullingsbekreftelse. + Ingen migrasjon, kun `teecup_frontend` trenger redeploy. + Neste steg: -1. PWA-egenskaper (manifest, service worker, offline-cache) — ikke startet. -2. Fortsatt åpne beslutninger fra FEATURE_BACKLOG.md: kode-regenerering for +1. PWA er bygget, venter på utrullingsbekreftelse — se over. ANBEFAL + brukeren å browser-teste offline-flyten selv (DevTools Offline-modus) + uavhengig av om/når den rulles ut. +2. Fire UI-/UX-hull notert 2026-07-19 (se over) — ingen fikset ennå. + Rewrite/medlemsside-bugen (#2) er den mest presserende siden siden er + helt utilgjengelig i dag. +3. Fortsatt åpne beslutninger fra FEATURE_BACKLOG.md: kode-regenerering for ADR-020, korrigering-godkjenning fra motpart, video/1-til-1-meldinger (bevisst utsatt i ADR-025). -3. Flere turneringsformater utover Ryder Cup (Københavner/High-low-high/ +4. Flere turneringsformater utover Ryder Cup (Københavner/High-low-high/ Robbins/Try all, notert 2026-07-19) — ingen ADR-runde startet ennå. -4. **Merk for neste økt:** `deploy/Caddyfile`-endringen (ny `/ws/*`-rute, +5. **Merk for neste økt:** `deploy/Caddyfile`-endringen (ny `/ws/*`-rute, ADR-025) ligger uncommitted i det SEPARATE `/opt/teeoff`-repoet, ikke i `teecup`-repoet — samme fallgruve som ADR-016-runden sin Caddy-endring, lett å glemme siden denne økten ellers kun har jobbet i `/opt/teecup`. -5. Ved fremtidige nye V0-skjermer/-design i V0 (fortsett i samme prosjekt), +6. Ved fremtidige nye V0-skjermer/-design i V0 (fortsett i samme prosjekt), FORVENT en full re-eksport hver gang — diff mot live-treet i et scratch-område før noe pakkes ut over eksisterende filer, og sjekk om V0-skjermen bygger inn handlinger backend ikke støtter ennå FØR diff --git a/FEATURE_BACKLOG.md b/FEATURE_BACKLOG.md index a41a9ce..98fc49b 100644 --- a/FEATURE_BACKLOG.md +++ b/FEATURE_BACKLOG.md @@ -806,12 +806,101 @@ manuelt tildelt makt»-designet). --- +## Rapporterte UI-/UX-hull (2026-07-19) — notert, IKKE fikset ennå + +Fire punkter rapportert av brukeren fra faktisk bruk av `teecup.teeoff.no`. +Root cause funnet ved kodegjennomgang for de tre første (ikke bare gjettet); +det fjerde er et reelt manglende UI-element, ingen kodefeil. Ingen av de fire +er fikset i denne runden — kun dokumentert slik at de ikke går i glemmeboken. + +1. **Dashboard-turneringskortet viser "Ingen datoer satt" selv om øktene + (rundene) har dato/klokkeslett satt.** `components/tournament-card.tsx` + leser `tournament.start_date`/`end_date` — et EGET, frittstående felt på + selve turneringen (ADR-015), atskilt fra `session.scheduled_at` (dato per + økt/runde). Grep bekrefter: disse to tournament-feltene LESES tre steder + i frontend (`dashboard.tsx`, `public-tournament.tsx`, `public-club.tsx`), + men skrives INGEN steder — det finnes ingen UI for å sette dem i det hele + tatt. Kortet vil derfor alltid vise "ingen datoer satt", uansett hvor mange + økter som har fått en `scheduled_at`, fordi feltet det leser aldri kan bli + satt gjennom UI-et. To mulige retninger: (a) bygg et faktisk + `start_date`/`end_date`-skjemafelt på turneringen, eller (b) la kortet + utlede visningsdatoen fra øktenes `scheduled_at`-spenn i stedet for et + eget, separat felt — sistnevnte er trolig det organisatoren faktisk + forventer. + +2. **`/orgs/{id}/members`-siden gir en rå API-404 (`{"detail":"Not + Found"}`), ikke medlemssiden.** Bekreftet root cause, ikke bare + reprodusert: `frontend/next.config.mjs` sin `rewrites()` returnerer en + PLAIN ARRAY (implisitt "afterFiles"-semantikk i Next.js) — det betyr at + ikke-dynamiske filer/sider sjekkes FØR rewrites, men DYNAMISKE sider + (som `app/orgs/[id]/members/page.tsx`) sjekkes ETTER. Rewrite-regelen + `{ source: "/orgs/:path*", destination: ".../orgs/:path*" }` (satt opp i + ADR-016 for å proxye API-kall) fanger derfor `/orgs/{id}/members` FØR + Next.js noensinne når frem til den faktiske siden, og sender kallet til + FastAPI i stedet — som naturligvis ikke har noen `GET /orgs/{id}/members`- + rute (kun `/orgs/{id}/memberships`), derav den rå FastAPI-404-formen + (ikke engang appens egen `app_error`-kontrakt, siden ruten ikke matcher + noe sted i det hele tatt). Selve siden (`org-members.tsx`, lenken fra + dashbordet) er ellers riktig bygget — dette er en ren + rewrite/dynamisk-rute-presedens-krasj, samme klasse fallgruve som + ADR-016 sin opprinnelige "alt nytt API-prefiks må inn i rewrites"-lærdom, + bare i motsatt retning (en frontend-SIDE ble skjult AV en rewrite). Dette + er den FØRSTE frontend-siden som noensinne har blitt nestet direkte under + et allerede proxyet prefiks (`/orgs/*`) — ingen tidligere skjerm har + truffet dette. Sannsynlig fiks: flytt siden til en ikke-proxyet sti + (f.eks. `/organizations/[id]/members`), ELLER gjør rewrites-regelen mer + presis (kun kjente API-undermønstre som `/orgs/:id/tournaments`, + `/orgs/:id/memberships` osv., ikke et bredt `:path*`). + +3. **Dashboard: ingen vei til å opprette/legge til en ANDRE organisasjon.** + Bekreftet i `dashboard.tsx`: `CreateOrganizationState` + (opprett-organisasjon-skjemaet) vises KUN når `hasOrg` er `false`, altså + når brukeren har null organisasjoner fra før. Har brukeren allerede én + org, finnes ingen knapp/lenke noe sted i UI-et for å opprette en til — + selv om backend-et støtter dette fullt ut og uten begrensning (ADR-021, + bekreftet: `POST /orgs` har ingen grense på antall org-er én bruker kan + eie). Ren manglende UI, ikke en backend-begrensning. + +4. **Ingen måte å se, på ett blikk, at ALLE runder/økter i en turnering har + fått dato/klokkeslett satt.** `tournament-program.tsx` viser + `scheduled_at` per øktkort hvis satt, ingenting spesielt (ingen + fremhevet "mangler dato"-tilstand) hvis ikke. Ingen sammendrag/telling + noe sted ("X av Y runder har dato") — organisatoren må åpne + program-skjermen og lese hvert kort manuelt. Ren UX-mangel, ingen + bakenforliggende datamodell-begrensning (all nødvendig data finnes + allerede i `GET .../sessions`). + +**Ingenting av dette er fikset ennå** — kun diagnostisert og notert på +brukerens eksplisitte instruks, for å ikke gå i glemmeboken mens PWA-runden +prioriteres. + +--- + ## UX / frontend (senere fase) - 📋 Høy kontrast, dark/light, store +/- knapper, stor «Neste hull»-knapp (banebruk i sollys/med solbriller). -- ✅ Offline-first (ADR-006) — prinsipp besluttet; implementasjon senere. -- 📋 PWA: manifest, service workers, «Legg til på hjemskjerm». +- ✅ Offline-first (ADR-006) — BYGGET 2026-07-19 (ADR-028), se eget punkt + under. Scoreregistrering (hole-scores/hole-results) fungerer nå offline + med automatisk synk. +- ✅ PWA: manifest, service worker, «Legg til på hjemskjerm» — BYGGET + 2026-07-19 (ADR-028). + +### PWA — ✅ BYGGET 2026-07-19 (ADR-028), IKKE ENNÅ RULLET UT + +Full design i ARCHITECTURE_DECISIONS.md ADR-028. Kort: + +| Del | Status | Notat | +|---|---|---| +| Installerbar app (manifest + ikoner + «Legg til på hjemskjerm») | ✅ bygget | `app/manifest.ts` (Next.js sin innebygde manifest-generator), `components/sw-register.tsx`, `appleWebApp`-metadata for iOS. | +| Ikoner | ✅ bygget, **MIDLERTIDIG** | Enkelt grønt golf-flagg generert programmatisk (`public/icons/*`, `public/apple-icon.png`) — **skal erstattes med ekte design senere.** Erstattet samtidig den gamle v0.app-plassholderlogoen som lå i `apple-icon.png` fra før (var aldri TeeCup-merkevare). | +| Service worker: cache app-navigasjon + `/orgs/*`-GET-er | ✅ bygget | `public/sw.js`, nettverk-først/cache-fallback (bevisst IKKE stale-while-revalidate, se ADR-028). `public/offline.html` som siste utvei. | +| Offline scoreregistrering (hole-scores/hole-results) | ✅ bygget | `lib/offline-queue.ts` (IndexedDB-kø) + `components/session-scorecard.tsx`. Synker automatisk ved `window`s `online`-event, pluss manuell "Synkroniser nå"-knapp. Bevisst IKKE Background Sync API (iOS Safari støtter den ikke). | +| Andre skrivehandlinger offline (walkover, chat/feed, oppsett) | 💤 bevisst utenfor omfang | Kun de to scoreregistrerings-endepunktene er køet — se ADR-028 Beslutning B for begrunnelse per type. | +| Faktisk browser-testet (DevTools Offline-modus) | ❌ IKKE gjort | Kun verifisert med typesjekket build + container-boot/curl. Ingen nettleserverktøy tilgjengelig denne runden — **anbefales sterkt at brukeren selv tester offline-flyten i Chrome DevTools før tillit i skarp bruk.** | + +**Ikke rullet ut ennå** — venter på brukerens eksplisitte +utrullingsbekreftelse (ingen migrasjon, kun `teecup_frontend`). --- diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 27ce342..5e123f7 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata, Viewport } from 'next' import { Nunito } from 'next/font/google' +import { SwRegister } from '@/components/sw-register' import './globals.css' const nunito = Nunito({ @@ -33,6 +34,14 @@ export const metadata: Metadata = { ], apple: '/apple-icon.png', }, + // PWA-installasjon (ADR-028). iOS Safari leser ikke manifest.json for + // «Legg til på hjemskjerm»-oppførsel -- disse Apple-spesifikke metafeltene + // trengs i tillegg til selve manifestet (app/manifest.ts). + appleWebApp: { + capable: true, + statusBarStyle: 'default', + title: 'TeeCup', + }, } export const viewport: Viewport = { @@ -50,7 +59,10 @@ export default function RootLayout({ }>) { return ( -
{children} + +