Score-fanen: mindre luft under headeren + skjul passerte hindringer i avstandsmåleren

To brukerpåpekte funn: (1) unødvendig stort tomrom mellom RoundHeader og
hull-navigatoren når ingen varsel-banner vises -- main sin toppadding
stablet seg oppå seksjonens egen mt-5 i stedet for å erstatte den.
(2) hindringer skal kun vises når de fortsatt ligger mellom spiller og
green -- passerte hindringer var unødvendig støy. Se CHANGELOG.md
#136/#137 for detalj og verifisering. Punkt 2 ikke rullet ut ennå.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Erol Haagenrud 2026-08-20 07:26:29 +02:00
parent 839bed4dd9
commit f10121beaf
3 changed files with 78 additions and 3 deletions

View file

@ -13388,3 +13388,47 @@ Neste steg:
**Rullet ut 2026-08-20** -- bruker bekreftet. Ren frontend. **Rullet ut 2026-08-20** -- bruker bekreftet. Ren frontend.
`docker compose build teecup_frontend && up -d`, rene logger, 200 OK. `docker compose build teecup_frontend && up -d`, rene logger, 200 OK.
136. **Score-fanen: mindre luft under RoundHeader, 2026-08-20.** Bruker
viste et skjermbilde fra live: unødvendig stort tomrom mellom
fanerad-headeren og "Hull 1"-boksen når ingen varsel-banner
(offline/pending-sync) var synlig. Årsak: `<main>` sin egen
`py-6 sm:py-8`-toppadding la seg OVENPÅ hull-seksjonens allerede
eksisterende `mt-5` lenger ned i stedet for å erstatte den -- disse
stablet seg til 44-52px kombinert, mens resten av siden bruker en
tettere rytme (16-20px). Rettet: `round-detail.tsx` sin `<main>`
bruker nå `pt-3 sm:pt-4` (matcher RoundHeader sin egen interne
`py-3 sm:py-4`-rytme) i stedet for `py-6 sm:py-8` -- kun toppen
redusert, bunnpadding uendret.
137. **Hindringer bak spilleren skal ikke lenger vises, 2026-08-20.**
Bruker påpekte (uavhengig av skjermbildet over): avstander til
hindringer skal kun vises når hindringen faktisk ligger MELLOM
spilleren og greenen -- en hindring spilleren allerede har gått forbi
er unødvendig informasjon.
`hole-target-distance.tsx`:
- `groupDiagramHazards()` (brukt av det vanlige diagram-sporet, når
banen har tee-koordinater) tar nå imot spillerens `alongPercent`
(posisjon langs tee-green-aksen) og filtrerer bort enhver hindring
der YTTERSTE kant (bakkanten for et parret forkant+bakkant-hinder,
selve punktet for et enkeltstående) ligger BAK spilleren. Et parret
hinder regnes altså som passert først når spilleren er forbi BEGGE
kantene, ikke bare forkanten. En liten toleranse
(`HAZARD_PASSED_TOLERANCE_PCT = 1,5`) hindrer at en hindring rett
ved spillerens fot blinker inn/ut mellom to GPS-oppdateringer.
- Den sjeldnere fallback-listen (bane uten tee-koordinater, ingen
akse å projisere langs) filtrerer med en enklere proxy: hindringen
må ligge nærmere green-midten enn spilleren selv gjør.
**Verifisert i scratch:** en frittstående forhåndsvisning av
`HoleTargetDistance` (compact + full) med stubbet `navigator.
geolocation.watchPosition` (synkron, deterministisk testposisjon --
ikke Chrome DevTools sin posisjons-emulering, som var upålitelig
tidligere i denne økten) og to hindringer på rett linje langs hullet,
én bak og én foran en fast spillerposisjon. Bekreftet: kun hindringen
foran spilleren ("Vannhinder") vises, i BÅDE kompakt liste og fullt
diagram -- hindringen bak ("Fjellknaus") er helt fraværende i begge.
`tsc --noEmit` rent.
**Ikke rullet ut ennå** -- venter på bekreftelse fra bruker.

View file

@ -95,17 +95,32 @@ const _HAZARD_KIND: Record<string, HazardKind> = {
stone_fence: "stone_fence", stone_fence: "stone_fence",
} }
// Hvor mange prosent av hull-lengden en hindring får "henge igjen" etter at
// spilleren i teorien har passert den (ADR-085, brukerønske 2026-08-20:
// hindringer bak spilleren er unødvendig informasjon -- skal ikke lenger
// vises). Ren GPS-støy-margin, ikke en spilleregel -- uten denne ville en
// hindring rett ved spillerens fot kunne blinke inn/ut av listen mellom to
// GPS-oppdateringer idet spilleren går forbi.
const HAZARD_PASSED_TOLERANCE_PCT = 1.5
// Grupperer rå hindringspunkter til hindrings-ENTITETER for diagrammet. // Grupperer rå hindringspunkter til hindrings-ENTITETER for diagrammet.
// VIKTIG (bruker-rettelse, ADR-084): to punkter er KUN samme fysiske // VIKTIG (bruker-rettelse, ADR-084): to punkter er KUN samme fysiske
// hindring hvis de deler samme, ikke-null hazard_group -- ALDRI basert // hindring hvis de deler samme, ikke-null hazard_group -- ALDRI basert
// på poi_type+side_fairway alene (ville feilaktig slått sammen to // på poi_type+side_fairway alene (ville feilaktig slått sammen to
// atskilte hindringer av samme type på samme side). Punkter med // atskilte hindringer av samme type på samme side). Punkter med
// hazard_group=null forblir alltid enkeltstående. // hazard_group=null forblir alltid enkeltstående.
// `playerAlongPercent` (ADR-085) -- en hindring vises KUN hvis den fortsatt
// ligger MELLOM spilleren og greenen (brukerønske: passerte hindringer er
// unødvendig informasjon). For et parret forkant+bakkant-hinder brukes
// bakkanten (den ytterste kanten, nærmest green) -- hele hindringen regnes
// som passert først når spilleren er forbi BEGGE kantene, ikke bare
// forkanten.
function groupDiagramHazards( function groupDiagramHazards(
hazardPoints: ApiTargetPoint[], hazardPoints: ApiTargetPoint[],
livePosition: LatLng, livePosition: LatLng,
teeRef: LatLng, teeRef: LatLng,
greenRef: LatLng, greenRef: LatLng,
playerAlongPercent: number,
) { ) {
const grouped = new Map<string, ApiTargetPoint[]>() const grouped = new Map<string, ApiTargetPoint[]>()
const standalone: ApiTargetPoint[] = [] const standalone: ApiTargetPoint[] = []
@ -148,6 +163,8 @@ function groupDiagramHazards(
const kind = _HAZARD_KIND[primary.poi_type] const kind = _HAZARD_KIND[primary.poi_type]
if (!kind) continue if (!kind) continue
const alongValues = [front, back].filter((p): p is ApiTargetPoint => !!p).map(along) const alongValues = [front, back].filter((p): p is ApiTargetPoint => !!p).map(along)
const farEdgeAlong = Math.max(...alongValues)
if (farEdgeAlong < playerAlongPercent - HAZARD_PASSED_TOLERANCE_PCT) continue
entities.push({ entities.push({
key: `group-${groupKey}`, key: `group-${groupKey}`,
kind, kind,
@ -162,6 +179,7 @@ function groupDiagramHazards(
for (const p of standalone) { for (const p of standalone) {
const kind = _HAZARD_KIND[p.poi_type] const kind = _HAZARD_KIND[p.poi_type]
if (!kind) continue if (!kind) continue
if (along(p) < playerAlongPercent - HAZARD_PASSED_TOLERANCE_PCT) continue
entities.push({ entities.push({
key: `point-${p.latitude}-${p.longitude}-${p.poi_type}`, key: `point-${p.latitude}-${p.longitude}-${p.poi_type}`,
kind, kind,
@ -300,7 +318,16 @@ export function HoleTargetDistance({
// spilleren først. Avstandene er ALLTID live fra spilleren, uavhengig av // spilleren først. Avstandene er ALLTID live fra spilleren, uavhengig av
// om et geometrisk diagram kan tegnes under. // om et geometrisk diagram kan tegnes under.
const hazardPoints = points.filter((p) => p.poi_type in _HAZARD_LABELS) const hazardPoints = points.filter((p) => p.poi_type in _HAZARD_LABELS)
const hazards = hazardPoints // ADR-085 (brukerønske 2026-08-20): passerte hindringer skal ikke vises.
// Denne (tee-referanse mangler) grenen har ingen akse å projisere langs,
// så "mellom spiller og green" tilnærmes med "nærmere green enn
// spilleren selv" -- mindre presist enn along-percent-varianten under
// (tar ikke hensyn til sideveis avstand), men fungerer som en fornuftig
// proxy for det sjeldne tilfellet en bane mangler tee-koordinater.
const hazardPointsAhead = hazardPoints.filter(
(h) => haversineMeters({ lat: h.latitude, lng: h.longitude }, { lat: middle.latitude, lng: middle.longitude }) < distances.middle,
)
const hazards = hazardPointsAhead
.map((h) => { .map((h) => {
const base = _HAZARD_LABELS[h.poi_type] const base = _HAZARD_LABELS[h.poi_type]
const label = const label =
@ -330,7 +357,7 @@ export function HoleTargetDistance({
if (teeRef) { if (teeRef) {
const greenRef = { lat: middle.latitude, lng: middle.longitude } const greenRef = { lat: middle.latitude, lng: middle.longitude }
const player = projectOntoAxis(teeRef, greenRef, livePosition) const player = projectOntoAxis(teeRef, greenRef, livePosition)
const diagramHazards = groupDiagramHazards(hazardPoints, livePosition, teeRef, greenRef) const diagramHazards = groupDiagramHazards(hazardPoints, livePosition, teeRef, greenRef, player.alongPercent)
return ( return (
<HoleDiagram <HoleDiagram
size={size} size={size}

View file

@ -1494,7 +1494,11 @@ export function RoundDetail({ roundId }: { roundId: string }) {
return ( return (
<RoundPageShell roundId={roundId} activeTab="score"> <RoundPageShell roundId={roundId} activeTab="score">
<main className="mx-auto w-full max-w-3xl flex-1 px-5 py-6 sm:py-8"> {/* pt redusert fra py-6/py-8 (2026-08-20, brukerfunn: unødvendig
luft under RoundHeader når ingen varsel-banner vises -- padding
her la seg OVENPÅ hull-seksjonens egen mt-5 lenger ned i stedet
for å erstatte den). Kun toppen redusert, bunnen uendret. */}
<main className="mx-auto w-full max-w-3xl flex-1 px-5 pb-6 pt-3 sm:pb-8 sm:pt-4">
{conflictNotice && ( {conflictNotice && (
<div <div
role="alert" role="alert"