Skip to main content
Petanque Life
← Back to all features
15

Mobile & Fan Experience

149 features · 13 subsystems

Mobile applications, spectator/fan features, gamification, and community engagement tools.

Mobile Application

F15.01
Shipped
How it works
  • F15.01.01 Shipped

    Cross-platform app (iOS, Android, Web) (React Native/Expo)

    ✅ PL-F1501a
  • F15.01.02 Shipped

    Player mobile app (profile, competitions, results, rankings)

    ✅ PL-F1501a
  • F15.01.03 Shipped

    Official mobile app (match management, score entry)

    ✅ PL-F1501a
  • F15.01.04 Shipped

    Club admin mobile app (member management, communication)

    ✅ PL-F1501a
  • F15.01.05 Shipped

    Spectator mobile app (live scores, schedule, venue info)

    ✅ PL-F1501a
  • F15.01.06 Shipped

    Offline mode (score entry without connectivity, sync later)

    ✅ PL-F1501b
  • F15.01.07 Shipped

    Push notifications

    ✅ PL-F1501b · Go-ombyggnad: transporten LEVERERAD i 4776ad23.m-1 (D-PUSH) — POST /v1/push/devices + POST /v1/push/dispatch (202-jobbväg för bred fan-out) mot APNs/FCM v1. app-sidan LEVERERAD i 4776ad23.m-3 (task 4776ad23.m-3.t-2): kontextuell behörighets-onboarding efter genomförd anmälan (aldrig vid kallstart), registrering med råa APNs-/FCM-token (getDevicePushTokenAsync), token-rotation och avregistrering vid logout före sessionsrivningen, skärmen Notiser & enheter (/notiser — maskerad token, avregistrering med bekräftelse) med permanent ingång i Inställningar, foreground-/kallstartsmottagning, fast kategorikatalog (packages/shared/src/push/categories.ts, inkl. Godkänn/Avslå), säker deep-link-navigering (endast interna routes, fallback Hem) och badge-nollställning vid appöppning — vy-spec specs/app/views/notiser-enheter.md, bevisad av L4-drivrutinen tools/gate/dpush-m3-app-e2e.mjs mot körande stack + riktig Postgres. Ärlig gräns (A-3/A-5): riktig OS-dialog, bakgrundsrendering och åtgärdsknappar på fysisk enhet verifieras först i molnet med skarpa APNs-/FCM-nycklar, och notis-preferenser/tysta tider/sammanfattningar ägs av D-COMM (ej byggda — vyn säger det rakt ut, ingen död knapp)
  • F15.01.08 Shipped

    QR code scanner (license verification, check-in)

    ✅ PL-F1501b
  • F15.01.09 Shipped

    GPS-based nearby venue/competition finder

    ✅ PL-F1501b
  • F15.01.10 Shipped

    Dark mode and accessibility settings

    ✅ PL-F1501b

Live Match Experience

F15.02
Planned
How it works
  • F15.02.01 Shipped

    Real-time live scores across all courts

    ✅ PL-F1502a
  • F15.02.02 Shipped

    Match timeline (point-by-point history)

    ✅ PL-F1502a
  • F15.02.03 Shipped

    Court view (visual representation of ongoing matches)

    ✅ PL-F1502a
  • F15.02.04 Shipped

    Follow specific player/team (personalized updates)

    ✅ PL-F1502a
  • F15.02.05 Shipped

    Live commentary feed

    ✅ PL-F1502b
  • F15.02.06 Shipped

    Score alerts and notifications

    ✅ PL-F1502b
  • F15.02.07 Shipped

    Multi-match view (watch multiple courts)

    ✅ PL-F1502b
  • F15.02.30 Shipped

    Adaptive bitrate stream profile (HLS/DASH renditions, server-managed)

    ✅ PL-T219
  • F15.02.31 Shipped

    Picture-in-picture support flag per stream profile

    ✅ PL-T219
  • F15.02.32 Shipped

    Commentary language toggle (Accept-Language sortering, default-track)

    ✅ PL-T219
  • F15.02.33 Shipped

    Stream geo-block policy (ISO-3166-1 alpha-2, expiry, requires_auth)

    ✅ PL-T219

Community Features

F15.03
Planned
How it works
  • F15.03.01 Shipped

    Player profile pages (public)

    ✅ PL-F1503a
  • F15.03.02 Shipped

    Club profile pages (public)

    ✅ PL-F1503a
  • F15.03.03 Shipped

    Follow players and clubs

    ✅ PL-F1503a
  • F15.03.04 Shipped

    Activity feed (followed players' results, upcoming matches)

    ✅ PL-F1503a
  • F15.03.05 Shipped

    Find-a-player (looking for team members)

    ✅ PL-F1503b
  • F15.03.06 Shipped

    Find-a-club (new player looking for local club)

    ✅ PL-F1503b
  • F15.03.07 Shipped

    Forums / discussion boards per club, region, topic

    ✅ PL-F1503b
  • F15.03.08 Shipped

    Event reviews and photos sharing

    ✅ PL-F1503b
  • F15.03.20 Shipped

    Multilingual forums (locale-default + i18n_strings, BCP-47-validering)

    ✅ PL-T219
  • F15.03.21 Shipped

    Cross-locale forum discovery (locale=* aggregerar alla språk)

    ✅ PL-T219
  • F15.03.22 Shipped

    Forum auto-moderation (regel-baserad: profanity-regex, link-spam, all-caps, spam-score)

    ✅ PL-T219
  • F15.03.23 Shipped

    Forum mentions + reactions (@user → push-pipeline, emoji-toggle på posts)

    ✅ PL-T219

Gamification

F15.04
Planned
How it works
  • F15.04.01 Shipped

    Achievement badges (first competition, 100 matches, etc.) — PL-F1504a

    ✅ PL-F1504a
  • F15.04.02 Shipped

    Milestone tracking and celebrations — PL-F1504a

    ✅ PL-F1504a
  • F15.04.03 Shipped

    Prediction game (predict competition outcomes) — PL-F1504a

    ✅ PL-F1504a
  • F15.04.04 Shipped

    Fantasy petanque (build dream team, earn points) — PL-F1504a

    ✅ PL-F1504a
  • F15.04.05 Shipped

    Leaderboards (most active player, most competitions, etc.) — PL-F1504a

    ✅ PL-F1504a
  • F15.04.06 Shipped

    Challenge system (challenge another player to a match) — PL-F1504b

    ✅ PL-F1504b
  • F15.04.07 Shipped

    Streak tracking (consecutive wins, active weeks) — PL-F1504b

    ✅ PL-F1504b
  • F15.04.08 Shipped

    Daily/weekly challenges (community-wide skill challenges) — PL-F1504b

    ✅ PL-F1504b
  • F15.04.09 Shipped

    Club challenges (club vs club in weekly mini-competitions) — PL-F1504b

    ✅ PL-F1504b
  • F15.04.10 Shipped

    Seasonal quests (play X matches, visit Y venues, enter Z tournaments) — PL-F1504b

    ✅ PL-F1504b
  • F15.04.11 Shipped

    XP and level system (based on activity, not just wins)

    ✅ PL-F1504c
  • F15.04.12 Shipped

    Personal bests tracking (best ELO, best tournament result, longest streak)

    ✅ PL-F1504c
  • F15.04.13 Shipped

    Fan predictions for live matches (predict score, earn points)

    ✅ PL-F1504c
  • F15.04.14 Shipped

    Pick'em brackets (predict entire tournament outcomes)

    ✅ PL-F1504c
  • F15.04.15 Shipped

    Celebration UX motor — proportional confetti / banner / badge primitives, registry-governed events, max-1-major-per-session rate limit, audit gate

    ✅ PL-T163
  • F15.04.16 Shipped

    Reduced-motion + background-safe celebration playback (web prefers-reduced-motion + native AppState)

    ✅ PL-T163
  • F15.04.17 Shipped

    Personal-best comparison primitive with stagger animation

    ✅ PL-T163

Accessibility & Inclusion

F15.05
Planned
How it works
  • F15.05.01 Shipped

    Multi-language interface (French, English, Spanish, + federation languages)

    ✅ PL-F1505a, ♻️ Rebuild fa798ce7.m-4.t-2 (D-LOCALE FR-2: appens språkkedja är WIRAD — I18nProvider i app/app/_layout.tsx med resolvad locale + runtime-bundles, upplösning kontots locale → enhetsspråk (expo-localization / navigator.languages) → aktiva tenantens default_locale → en, NAV-etiketterna ur microcopy (appNav.*) i stället för svenska literaler, och språkfältet i Inställningar (app-language-picker) som PATCH:ar kontot (PATCH /v1/me) och byter UI:t utan omladdning. Valet följer kontot till web utan delade cookies; utloggad är fältet ärligt avstängt; offline/404 degraderar tyst till de statiska bundlarna. Se specs/app/views/installningar.md)
  • F15.05.02 Shipped

    Screen reader compatibility (WCAG 2.1 AA)

    ✅ PL-F1505a
  • F15.05.03 Shipped

    High contrast mode

    ✅ PL-F1505a
  • F15.05.04 Shipped

    Font size adjustment

    ✅ PL-F1505a
  • F15.05.05 Shipped

    Simplified interface mode (for non-technical users)

    ✅ PL-F1505b
  • F15.05.06 Shipped

    Age-appropriate interfaces (youth vs. adult)

    ✅ PL-F1505b
  • F15.05.07 Shipped

    Right-to-left language support

    ✅ PL-F1505b

Onboarding & Discovery

F15.06
Planned
How it works
  • F15.06.01 Shipped

    New player onboarding wizard — PL-F1506a

    ✅ PL-F1506a, ✅ PL-T308 (premium editorial multi-step flow: animerad welcome-sekvens, swipe-stack mobil + split-screen desktop med crossfade <StepIllustration>, persistent draft i AsyncStorage, tenant-aware licens-katalog, konfetti-celebration via shared useCelebrate('onboarding.complete') med reduced-motion-glow, ProgressDots med accessibilityRole="progressbar", focus-shift per steg-shift, skip-affordance på notifikations- och preferenssteg)
  • F15.06.02 Shipped

    "Getting started with petanque" content — PL-F1506a

    ✅ PL-F1506a
  • F15.06.03 Shipped

    Find your nearest club (location-based) — PL-F1506a

    ✅ PL-F1506a
  • F15.06.04 Shipped

    Try petanque events listing (beginner-friendly) — PL-F1506a

    ✅ PL-F1506a
  • F15.06.05 Shipped

    Rules overview for newcomers — PL-F1506b

    ✅ PL-F1506b
  • F15.06.06 Shipped

    Equipment guide for beginners — PL-F1506b

    ✅ PL-F1506b
  • F15.06.07 Shipped

    Sport ambassador program — PL-F1506b

    ✅ PL-F1506b
  • F15.06.10 Shipped

    Public club discovery map (PublicClubProfile + 2dsphere, bbox-search, antimeridian-split)

    ✅ PL-T219
  • F15.06.11 Shipped

    Pickup-game schedule per club (PickupSlot + DateOverride, klubblokal-tid)

    ✅ PL-T219
  • F15.06.12 Shipped

    Visitor-friendly club flag (filter på publik karta + i app)

    ✅ PL-T219
  • F15.06.20 Shipped

    Viewer preference center (ViewerPreference: follow + push + digest, get-or-default)

    ✅ PL-T219
  • F15.06.21 Shipped

    Per-player follow + match alerts (cap 200 / 100 / 50, score-tröskel)

    ✅ PL-T219
  • F15.06.22 Shipped

    Per-club follow + news feed (digest-källa)

    ✅ PL-T219
  • F15.06.23 Shipped

    Personalised digest (DigestBuilderService: daily/weekly, idempotent dispatch-log)

    ✅ PL-T219
  • F15.06.24 Shipped

    Quiet hours + push-cadence grouping (IANA-tz, batched_5min/15min/60min)

    ✅ PL-T219

My Equipment

F15.07
Planned

Inspired by golf (club fitting) and bowling (ball specs) — track your boules and equipment.

How it works
  • F15.07.01 Shipped

    Boule inventory (brand, model, weight, diameter, hardness, striation pattern) — PL-F1507a

    ✅ PL-F1507a
  • F15.07.02 Shipped

    Boule serial number tracking (for homologation verification) — PL-F1507a

    ✅ PL-F1507a
  • F15.07.03 Shipped

    Equipment photo gallery — PL-F1507a

    ✅ PL-F1507a
  • F15.07.04 Shipped

    Performance notes per boule set (which set for which conditions) — PL-F1507a

    ✅ PL-F1507a
  • F15.07.05 Shipped

    Equipment wear tracking (usage log, matches played, surface type, total matches counter) — PL-F1507b

    ✅ PL-F1507b
  • F15.07.06 Shipped

    Equipment wishlist and comparison (wishlist CRUD, compare 2–5 sets side-by-side) — PL-F1507b

    ✅ PL-F1507b
  • F15.07.07 Shipped

    "What boules do the pros use" — equipment profiles of top players (search, filter, verified badge) — PL-F1507b

    ✅ PL-F1507b

Weather & Conditions

F15.08
Planned

Inspired by golf and sailing — outdoor sport conditions matter.

How it works
  • F15.08.01 Shipped

    Weather forecast for upcoming competitions (integrated from weather API) — PL-F1508

    ✅ PL-F1508
  • F15.08.02 Shipped

    Weather conditions at venue (temperature, wind, humidity, rain risk) — PL-F1508

    ✅ PL-F1508
  • F15.08.03 Shipped

    Terrain condition reporting by venue (crowdsourced: type, condition, hardness, wind, notes, photo) — PL-F1508

    ✅ PL-F1508
  • F15.08.04 Shipped

    "Best time to play" suggestions (playability score 0-100, recommended hours per day) — PL-F1508

    ✅ PL-F1508
  • F15.08.05 Shipped

    Weather-related tips (condition-triggered: surface, wind, temperature, rain matching engine) — PL-F1508

    ✅ PL-F1508
  • F15.08.06 Shipped

    Historical weather data linked to venue analysis (aggregate statistics per club) — PL-F1508

    ✅ PL-F1508

Social & Community Engagement

F15.09

Inspired by Strava, fitness apps, and social sport platforms.

How it works
  • F15.09.01 Shipped

    PL-F1509a

    ✅ PL-F1509a
  • F15.09.02 Shipped

    PL-F1509a

    ✅ PL-F1509a
  • F15.09.03 Shipped

    PL-F1509a

    ✅ PL-F1509a
  • F15.09.04 Shipped

    PL-F1509a

    ✅ PL-F1509a
  • F15.09.05 Shipped

    PL-F1509b

    ✅ PL-F1509b
  • F15.09.06 Shipped

    PL-F1509b

    ✅ PL-F1509b
  • F15.09.07 Shipped

    PL-F1509b

    ✅ PL-F1509b
  • F15.09.08 Shipped

    PL-F1509b

    ✅ PL-F1509b
  • F15.09.09 Shipped

    PL-T098

    Group membership (auto-provision per klubb, opt-in distrikt/federation)
  • F15.09.10 Shipped

    PL-T098

    Nested comments (max djup 2) med @-mentions och reaktioner
  • F15.09.11 Shipped

    PL-T098

    Moderator-roller + ban + promote/demote med admin-only guards
  • F15.09.12 Shipped

    PL-T098

    Join request-flöde för distrikt/federation-grupper
  • F15.09.13 Shipped

    PL-T098

    Content report + moderator-resolve (actioned/dismissed)
  • F15.09.14 Shipped

    PL-T098

    Per-grupp settings (visibility, who-can-post, profanity-flag)
  • F15.09.15 Shipped

    PL-T098

    Push fan-out policy (members/mentions-only/off) — dispatcher log-only, FCM deferred
  • F15.09.16 Shipped

    PL-T098

    GDPR-anonymisering av användarens inlägg + kommentarer
  • F15.09.17 Shipped

    PL-T286

    ✅ PL-T286
  • F15.09.18 Shipped

    PL-T286

    ✅ PL-T286

Augmented Reality & Camera Features

F15.10

AR-powered tools for measuring, learning, detecting, and replaying petanque action on mobile.

How it works
  • F15.10.01 Shipped

    PL-F1510

    ✅ PL-F1510
  • F15.10.02 Shipped

    PL-F1510

    ✅ PL-F1510
  • F15.10.03 Shipped

    PL-F1510

    ✅ PL-F1510
  • F15.10.04 Shipped

    PL-F1510

    ✅ PL-F1510
  • F15.10.05 Shipped

    PL-F1510

    ✅ PL-F1510

Premium Mobile-First UX

F15.11
Planned

Mobile-first redesign av spelar-appen: dual-mode shell (bottom-tab på mobil, sidebar på desktop), hero-baserade startvyer, offline-first licenskort och tenant-branded theming.

How it works
  • F15.11.01 Shipped

    Dual-mode app shell — useDeviceClass({ cutoff: 640 }) växlar mellan mobile bottom-tab-nav (5 slottar: Hemma / Matcher / Ranking / Licens / Mer) och desktop sidebar (260 px) + centrerat innehåll (max 1024 px) utan route-byte eller data-remount. app/src/components/navigation/AppShell.tsx, BottomTabNav.tsx, SidebarNav.tsx.

    ✅ PL-T157
  • F15.11.02 Shipped

    Hero-baserad startsida — nästa match som full-bredd-hero med LinearGradient (primary→secondary), Fraunces display-titel, countdown, CalendarClock/MapPin-meta och sticky CTA. Två-up MiniCards (Licens + Ranking) + senaste resultat + discover-getting-started. Pull-to-refresh. app/src/components/home/PlayerHome.tsx.

    ✅ PL-T157
  • F15.11.03 Shipped

    Fullscreen licenskort — hologram-gradient-ring (primary→secondary→accent), Fraunces display-titel, 220 px QrPlaceholder, valid-until + status-pin, offline-verification-banner. Läser från verifyLicense() som faller tillbaka på signerad JWT-bundle från cache när offline. app/app/(tabs)/license.tsx, app/src/components/license/QrPlaceholder.tsx.

    ✅ PL-T157
  • F15.11.04 Shipped

    Rankings med "Du är #N"-highlight + sparklines — highlight Card ovanför leaderboard med aktuell position + Movement + mini-sparkline; varje RankingRow har position, namn, klubb, Movement-indikator (ArrowUp/Down/Minus) och 56×18 SVG-sparkline. Current-user row har primary-tint-bakgrund + border. app/app/(tabs)/rankings.tsx.

    ✅ PL-T157
  • F15.11.05 Shipped

    Matcher-lista med status-badges — MatchRow med Round-badge (eller LIVE-pin för live-matcher), W/L-badge för finished, CalendarClock+MapPin-meta, score + chevron-CTA. Separata sektioner "Upcoming" och "Recent". app/app/(tabs)/matches.tsx.

    ✅ PL-T157
  • F15.11.06 Shipped

    "Mer"-fliken — overflow-meny för sekundära sektioner (Profile, Equipment, Discover, Search, Community, Weather, Settings). Logged-in identity-card med avatar + tenant-badges, sign-in-CTA för gäster, grupperade rows med ikon-bubbla + description. Logout-knapp längst ned. app/app/(tabs)/more.tsx.

    ✅ PL-T157
  • F15.11.07 Shipped

    Offline-banner på root-nivå — prominent <Banner variant="offline"> med WifiOff-ikon, "senast synkad HH:MM"-text och optional "Sync now"-CTA när pendingCount > 0. Separat från det tunna OfflineIndicator sync-strippen. app/src/components/OfflineBanner.tsx.

    ✅ PL-T157
  • F15.11.08 Shipped

    Tenant-branded ThemeProvider — useTenantBranding()-hook hämtar /me/tenant-profile vid activeTenantId-byte och applicerar theme-objektet via applyTenantTheme(). Caching per tenantId via lastAppliedRef — best-effort, inga fel bubblar upp. app/src/hooks/useTenantBranding.ts.

    ✅ PL-T157
  • F15.11.09 Shipped

    Banner-primitiv — ny <Banner>-komponent med variants info/success/warning/error/offline, optional icon (lucide), title, message, action-button och dismiss. Matchar admin-appens Banner-API men med offline-variant (warning-palett) unikt för app-paradigmet. app/src/components/ui/Banner.tsx.

    ✅ PL-T157
  • F15.11.10 Shipped

    Haptic-feedback-gating per interaktion — useHaptics().fire('selection') på alla bottom-tab-trycky, match-row-trycky, more-entry-trycky; fire('warning') på logout. Respekterar både accessibility-toggle och OS reduce-motion. app/src/hooks/useHaptics.ts.

    ✅ PL-T157
  • F15.11.11 Shipped

    QrPlaceholder — deterministisk 25×25 SVG-bitmap från licens-payload, tre klassiska QR-finder-patterns + FNV-1a-hash-seedad LCG. Dokumenterat ej scanbar; federationens skannare läser license-number från visibel text. Swappbar mot riktig QR-komponent utan ändring av omgivande layout. app/src/components/license/QrPlaceholder.tsx.

    ✅ PL-T157
  • F15.11.12 Shipped

    Pull-to-refresh på alla primärvyer — RefreshControl på Home, Matches, Rankings, License. Tint = colors.primary. Samma callback som initial load() — enhetligt refresh-beteende.

    ✅ PL-T157

Digital scoreboard / live-display (D-SCORE)

F15.12
Planned

> Initiativ **D-SCORE** (`docs/product/scoreboard-analysis.md`), milstolpar `0ad09f7c.m-1` (t-1 backend,

  • F15.12.01 Planned

    Display-datamodell — rena tenant-skopade tabeller display/display_court/display_pairing/display_event (migration 00074_display_core.sql). Enum-CHECKs för status/mode/theme/distance_profile; mjuka event_id/court_ref-referenser (ingen hård FK över domängräns); ingen sponsor/rental-modell i m-1.

    0ad09f7c.m-1.t-1
  • F15.12.02 Planned

    Display-administration (/v1/displays…) — skapa (idempotent, med initial pairing-kod) / lista / hämta / PATCH (If-Match, optimistisk lås → 409) / soft-delete (retired) / PUT …/courts / POST …/pairing / GET …/events. Capability-gated display:manage/display:view; tenant-isolering (404 för främmande tenant).

    0ad09f7c.m-1.t-1
  • F15.12.03 Planned

    Pairing & klient-bootstrap — POST /v1/display-pairings/{code}/claim (oautentiserad device → claim_token + config, engångs, hashad hemlighet), GET …/config (claim_token/display:view), POST …/heartbeat (idempotent, online/offline härlett ur last_seen_at).

    0ad09f7c.m-1.t-1
  • F15.12.04 Planned

    Sys tvärs-tenant flottöversikt — GET /v1/sys/displays (operatör-scope, läs-only): aggregerade antal online/offline/unpaired/retired + rad per display i alla tenants.

    0ad09f7c.m-1.t-1
  • F15.12.05 Planned

    AuthZ-capabilities display:manage/display:view i katalogen, seedade på clubadmin/federation_admin. OpenAPI-först + genererad TS-klient i synk (packages/shared).

    0ad09f7c.m-1.t-1
  • F15.12.06 Shipped

    admin Displayer-sektion (admin/app/displayer) — lista med status-badge färg+ikon+text (online/offline/unpaired/retired) + filter, skapa/para-stepper (namn → org-nod → banor → visningsläge → tema/distans → generera & visa pairing-kod med regenerera), redigera-konfig (PATCH med If-Match) och händelse-tidslinje. Verklig data via den genererade klienten (displaysOps); inga stubbar. Sponsor-steg (m-4) och renderad live-vy (m-2) är medvetet inte med.

    0ad09f7c.m-1.t-2
  • F15.12.07 Shipped

    sys tvärs-tenant flottöversikt (sys/app/flotta) — operatör-scope, läs-only, ingen branding: aggregerade antal + tabell över alla tenants displayer ur GET /v1/sys/displays via genererad klient. Ingen beslutsrätt i tenants konfig.

    0ad09f7c.m-1.t-2
  • F15.12.08 Planned

    SSE live-stream-relä — GET /v1/displays/{id}/stream (text/event-stream): leverantörsneutral in-process-relä av D-COMP:s frysta vokabulär snapshot\

    score|match_status|standings (§5d.2, aldrig score_update/mene_completed/connected/update), projicerat/filtrerat per court_ref mot displayens display_court-mängd. Handshake (claim_token eller same-tenant display:view) före strömmen öppnas → 403 utan auth, 404 främmande tenant. snapshot först (subscribe-före-send); config-PATCH/PUT courts pushar färsk snapshot till anslutna klienter. Mock-stream (?mock=1&mock_courts=…, test/scaffolding) kör D-COMP-fritt. Spec: specs/api/sse/events-display.md. | 0ad09f7c.m-2.t-1
  • F15.12.09 Planned

    Snapshot polling-fallback — GET /v1/displays/{id}/snapshot: materialiserad visnings-ställning (kort-TTL ETag), samma handshake som strömmen, svarar även utan aktivt live-event (senast kända / tom), endast visnings-data (inga internfält). OpenAPI + genererad TS-klient i synk.

    0ad09f7c.m-2.t-1
  • F15.12.10 Shipped

    Fullscreen display-klient (web/app/d/[displayId], shell-lös BYO-skärm) — pairing-vy ("Ange koden"), device-claim (POST /v1/display-pairings/{code}/claim, claim_token i localStorage + X-Display-Claim-Token), SSE-konsument via fetch+ReadableStream.getReader() av det frysta vokabuläret (snapshot\

    score|match_status|standings), tre display-axlar ur config (visningsläge single|hall_overview|rotation|feature_ticker · distans-profil near|medium|far · tema indoor|outdoor), upplösnings-oberoende (vw/vh/clamp) med enorma tabular-nums-siffror, status-badge färg+ikon+text (WCAG 2.2 AA), vit-etikett-branding {logo,name,primary,accent}, offline-resiliens (cachad ställning + "återansluter…" + backoff-reconnect + /snapshot-polling, aldrig blank), cast-mottagar-läge. Sponsor-region reserverad men tom (m-4-gräns). | 0ad09f7c.m-2.t-2
  • F15.12.11 Shipped

    Admin renderad förhandsvisning (admin Displayer-sektion) — samma display-variant-rendering mot displayens config (mode/theme/distans/banor) driven av /stream (admin-bärare) eller mock-strömmen (?mock=1) när inget live-event är kopplat; tydligt märkt demo/tom-ställning, inga hårdkodade siffror, sponsor-region tom. Fyller den yta m-1 medvetet lämnade.

    0ad09f7c.m-2.t-2
  • F15.12.12 Shipped

    Publik oautentiserad projektions-yta — GET /public/scoreboard/{id} + /stream: ett publik-projektions-lager ovanpå m-2:s relä/snapshot, nås av vem som helst med displayens UUID (ingen bearer/claim_token). 200 med endast en dokumenterad allowlist av visnings-data (mode/theme/distance_profile/rotation/event_id, banor, matcher {match_id,status,court_ref,score}, standings publik projektion) + white-label branding-tokens {logo,name,primary,accent}; inga internfält (PII/tenant-interna/aktör-id/responder/pager/audit) — en strikt internfält-strippad delmängd av device-ytan. Kort-TTL cachebar (ETag + Cache-Control max-age); okänd/retired → 404; utan live-data → 200 tom (aldrig blank). Publika strömmen bär samma frysta vokabulär + allowlist. OpenAPI + genererad TS-klient i synk. Registrerad via sanktionerat /public/*-mönster (F1-4, aldrig --no-verify). Ingen ny tabell/kolumn. Spec: specs/api/endpoints/displays.md, specs/api/sse/events-display.md. web/app-konsument-vyer levereras i m-3 t-2/t-3. Sponsor-slot + impression-rollup till F11.03 = m-4 (backend sponsor_slot/impression-beacon/rollup = m-4 t-1; frontend-delen = m-4 t-2: sponsor-regionen renderas ur verklig sponsor-data i display-variantens sponsor-region på device-fullscreen (ScoreboardView), publik web-live-vy (PublicScoreboardClient) och app-spectator-ytan — aldrig platshållare, tom när ingen aktiv slot — och varje visning kallar den idempotenta impression-beaconen (device POST /v1/displays/{id}/sponsor-impression, publik/app POST /public/scoreboard/{id}/sponsor-impression) med en stabil dedup_key per (slot, session, tids-bucket) så reconnect/poll/re-render aldrig dubbelräknas; admin kopplar/redigerar/frikopplar sponsor-slot via ett sponsor-steg i Displayer-steppern + redigera-panelen med live-förhandsvisning, allt via den genererade TS-klienten).

    0ad09f7c.m-3.t-1
  • F15.12.13 Shipped

    Publika konsument-vyer (web + app) ovanpå F15.12.12:s projektion. web: oautentiserad SSR/SEO full-white-label publik event-live-vy (web/app/scoreboard/[displayId]), castbar/fullscreen, aldrig blank (cachad ställning + "återansluter…" + /public-snapshot-polling + backoff). app: publik-spårets Live-matchlista (LIVE-pin ikon+text) + per-match mène-progression härledd ur det frysta vokabuläret via den återanvända scoring-presenter; follow som PERSISTERAS via den rebuildade viewer-follow-primitiven POST/GET/DELETE /v1/me/follow (F15.06.20–22: follow + push + digest, notify_push/notify_digest) så D-PUSH kan konsumera den — notis-LEVERANSEN själv byggs ej här (D-PUSH/D-COMM). + liten klubbadmin para/justera-display on-the-go-yta (capability-gatad display:manage, POST /v1/displays/{id}/pairing + PATCH med If-Match) via den genererade klienten. Migration 00075_viewer_follow.sql (self-scopad /me-yta, ingen display/sponsor/hyr-tabell). Spec: specs/api/endpoints/me-follow.md.

    0ad09f7c.m-3.t-2

Socialspel: spontant gängspel med QR-join (D-SOCIALSPEL)

F15.13
Planned

> Initiativ **D-SOCIALSPEL** (ägarbeslut 2026-07-23) — gratisnivåns flaggskepp i player-appen

  • F15.13.01 Planned

    Lottningsmotorn (api/internal/socialplay, ren/DB-fri/klockfri) — matchformerna 1v1 (valbar, default av) / 2v2 / 2v3 / 3v3 med täckningsmatten (alla N ≥ 4 utom exakt 7 med 2v3 på), vilorotationens hårda invariant (ingen vilar två gånger innan alla vilat en), de fyra parningslägena (omlottning+variation, prestandagrupper, Monrad med omötesgaranti, fasta lag+variation), A/B-splitten med separat balansering per pool och deterministiskt seed per runda. Låst testsvit. Inkopplad i m-3 (F15.13.14): motorfilerna är oförändrade, sessionsadaptern ligger i en ny fil.

    659e6203.m-1.t-1/t-2
  • F15.13.02 Planned

    Datamodell social_play_session + social_play_member (migration 00138_dsocialspel_m2_sessions.sql) — ingen tenant_id, enda externa FK: auth_identity; CHECK identitet eller gästnamn, joined_via IN ('qr','manual','web'), unikt share_token, partiellt unikt index (session_id, auth_identity_id) som gör konto-joinet idempotent på DB-nivå. rests/pool anstådda till m-3 (inget oanvänt schema).

    659e6203.m-2.t-1
  • F15.13.03 Planned

    Starta & redigera session — POST/GET/PATCH /v1/social-play/sessions[/{id}] med visionens hela inställningsuppsättning i settings jsonb (forms, allow_2v3, allow_1v1 default av, rounds 1–10 eller null="öppen", target_score default 13, team_mode, pairing_mode, ab_split, split_round) — exakt m-1-motorns enumvärden, partiella objekt fylls med defaults/befintliga värden, fältvalidering med stabila 422-koder. Default-namn "Socialspel [datum]"; ägaren ur bearer-claimet, aldrig ur bodyn. settings_locked förbereder m-3:s låsning utan API-brott.

    659e6203.m-2.t-1
  • F15.13.04 Planned

    Join-token + QR-URL (POST …/{id}/join-token, api/internal/socialplay/jointoken) — check-in-tokenets HMAC-mönster med härledd nyckel (kryptografisk domänseparation: ett check-in-token kan aldrig spelas upp som join-token, även med samma råa hemlighet i båda env-nycklarna), flergångs (hela gänget skannar samma skärm) och roterande (skärmen präglar om före utgång; äldre giltigt token fortsätter gälla), TTL strukturellt klampad ≤ 10 min, ingen PII i payloaden, tom nyckel ⇒ typat 5xx (aldrig osignerat token). join_url = <web>/socialspel/<share_token>?t=<token>.

    659e6203.m-2.t-1
  • F15.13.05 Planned

    Gå med med konto (POST …/{id}/join) — idempotent (samma identitet igen ⇒ 200 och SAMMA rad), ägaren får gå med utan token (arrangör ≠ spelare), typad felsplit 401 manipulerat / 409 utgånget / 409 fel session / 409 stängd anslutning / 409 medlemstak (200 per session). Signatur verifieras före utgång.

    659e6203.m-2.t-1
  • F15.13.06 Planned

    Gäst utan konto (POST /v1/public/social-play/shared/{share_token}/join) — helt oautentiserad: färskt join-token + ETT fält (namn, trimmat 1–80 tecken) ⇒ deltagare utan identitet (joined_via='web'). share_token ensamt räcker aldrig (läsbehörighet ≠ skrivbehörighet) — separationen är det som håller delningslänken skrivskyddad. Stabilt rad-id för framtida gäst-claim.

    659e6203.m-2.t-1
  • F15.13.07 Planned

    Ägarens deltagarhantering — manuell inskrivning (POST …/{id}/members, fungerar även när anslutningen är stängd — sena ankomster), borttag (DELETE …/members/{member_id}, sessionsskopat ⇒ främmande rad 404) och öppna/stäng anslutning (POST …/{id}/joining, skilt från status så en pågående session kan öppna igen mellan rundor).

    659e6203.m-2.t-1
  • F15.13.08 Planned

    Publik skrivskyddad delningsläsning (GET /v1/public/social-play/shared/{share_token}) + åtkomstmodellen: handler-grindar (ej RLS) med normativ 404-före-403-ordning (utomstående får aldrig ett existensbesked; bara en verklig deltagare kan se 403; oautentiserad ⇒ 401). Publika medlemsraden är vitlistad (id, display_name, is_guest, joined_via) — aldrig e-post, aldrig auth_identity_id.

    659e6203.m-2.t-1
  • F15.13.09 Planned

    Deltagarlistan live (SSE GET …/{id}/live för ägare/deltagare + GET /v1/public/social-play/shared/{token}/live för gästvyn) — varje frame är event: snapshot med HELA läget {session, members, member_count}, vid connect och vid varje medlems-/anslutnings-/namn-/inställningsändring (≤ 5 s). Fulla snapshots gör återanslutning trivialt korrekt; icke-blockerande fan-out + 15 s heartbeat (husets hubb-mönster).

    659e6203.m-2.t-1
  • F15.13.10 Planned

    "Mina socialspel" (GET /v1/social-play/sessions) — sessioner jag äger eller deltar i, nyast först, med role och member_count; tom lista i stället för 404. Underlaget för appens ingång från hemskärmen.

    659e6203.m-2.t-1
  • F15.13.11 Planned

    Kontrakts- och integritetsvakter — felkoderna registrerade i problem.Registry (openapi x-pl-codes-driftvakt), openapi-först + genererad TS-klient i synk (packages/shared), gratisnivån bevisad av en schema-assertion (en tenant_id på socialspelstabellerna är ett kontraktsbrott), GDPR-erasure-domäner (personens egna deltagarrader + de sessioner hen startade; gästrader rörs ej) och det dokumenterade no-audit-beslutet (audit_log.tenant_id är NOT NULL och socialspelet är tenant-löst — resultatens ändringshistorik får egna tabeller i resultatmilstolpen).

    659e6203.m-2.t-1
  • F15.13.12 Shipped

    Ytorna. App (app/app/socialspel/ + hemskärmsingången home-socialspel): *Mina socialspel* (ägda eller deltagna, senast-först, ärligt tomläge), *Starta socialspel* (hela inställningsuppsättningen med defaults — matchformer, 2v3 på, 1v1 av, rundor 1–10/"öppen", målpoäng 13, lagbildning, rundparning, A/B + splitrunda), *sessionsvyn* med stor lokalt ritad QR som förnyar sig själv (om-prägling ~60 s före exp, join-länkens element byts aldrig ut), live deltagarlista, alla fyra ägarkontrollerna (manuell inskrivning som fungerar även vid stängd anslutning, borttag, öppna/stäng och redigera inställningar — samma delade fältuppsättning som starten, förifylld med sessionens FAKTISKA settings och skickad som PATCH med skapandets body, ärligt spärrad vid tomt namn eftersom servern då behåller det gamla) plus "Jag spelar också" för arrangören som själv spelar (ägar-joinet utan token ⇒ deltagarrad med identitet; läget härlett ur serverns member_count kontra members, aldrig gissat) och ingen "Lotta"-CTA (m-3:s yta stubbas inte in), samt *join-rutten* /socialspel/ga-med?st=…&t=… bakom QR-länken (publik delningsläsning → identitetsskopat, idempotent join → sessionsvyn). Web (web/app/socialspel/[share_token]): kontolös mobil-först gästvy — skrivskyddad live-läsning på delningsnyckeln, "Gå med som gäst" med ETT fält när tokenet är färskt och anslutningen öppen, alla ärliga lägen (stängd anslutning släcks live utan omladdning; utgånget token upptäcks före klicket — exp avkodas ur tokenets osignerade, PII-fria payload med serverns egen gräns now >= exp plus en timer på utgången, så ett dött token aldrig renderar formuläret medan servern förblir auktoritet; saknat/oläsbart token, okänd nyckel, typade join-fel), deltagarräknaren som räknar exakt raderna i listan (personantalet inkl. arrangören redovisas separat och benämnt) "Öppna i appen" som riktig länk i appens schema (petanquelife://socialspel/ga-med?st=…&t=…, A-4:s realisering av visionens universella länk — den som har appen går med med sin identitet i stället för som identitetslös gäst, och länken blockerar aldrig gästvägen under den) och rekryteringskroken CTA "Skapa konto" som riktig länk till husets individual-signup; noindex. Live på båda ytorna via t-1:s SSE-snapshotkanaler genom transportens requestEventStream (≤5 s utan omladdning, uppmätt i L4) med ärlig polling-fallback var 2:e sekund där strömmen inte kan hållas (Hermes). All trafik via den genererade klienten (socialPlayOps, ny barrel-rad) — L3d-ratchet oförändrad på baseline 0. Vy-specar: specs/app/views/socialspel.md, specs/web/views/socialspel-gastvy.md. Bevis: L4-drivrutinen tools/gate/socialspel-m2-e2e.mjs (hela resan + axe på gästvyn i 375×812 + DB-facit) samt enhetstester för de rena härledningarna (app/lib/__tests__/socialspel-presenter.test.ts — formulär ⇄ settings som inverser; web/lib/__tests__/socialspel-links.test.ts — app-djuplänkens schema/path/parametrar).

    659e6203.m-2.t-2
  • F15.13.13 Planned

    Kvällens avslut — backend (migration 00140_dsocialspel_m4_close.sql: ended_reason/rematch_of_session_id/last_activity_at på sessionen, emit-bokföringen social_play_result_event och anspråkstabellen social_play_guest_claim; fortfarande ingen tenant_id, enda externa FK-mål auth_identity). Avslutet är EN kodväg (api/internal/socialplayend): ägarens POST …/{id}/end och autostängningsjobbet social_play_autoclose_tick (varje timme, singleton, 24 h utan aktivitet, ended_reason='auto' och tom aktör i loggen) delar transaktion, session_end-loggpost och emit — avslutet är idempotent (andra anropet ⇒ 200, oförändrad stämpel, ingen ny logg, ingen andra emit). Skrivskyddet efter avslut ligger i den gemensamma sessionsupplösningen, före kroppsvalideringen (409 social_play_session_ended på hela mutationsytan; acceptanstestet genererar listan ur openapi, så en glömd rutt fäller sig själv). Summeringen rider med i sessionsdetaljen, den publika share-vyn och varje live-snapshot — men bara när kvällen är avslutad (ingen tom stub under spel): pall ur serverns tabellordning med place/tied (två pallar och två vinnare vid A/B), kuriosa (längsta vinstsvit där vila varken bryter eller förlänger, Fanny = förlorarens nolla) — båda sektionerna utelämnade när de saknar innehåll — plus kvällens siffror inkl. ärligt redovisade orapporterade matcher. Revansch (POST …/{id}/rematch) duplicerar kvällen i en transaktion (samma inställningar, nytt share_token, spår tillbaka; konton behåller identitet, gäster kopieras som gäster, den som lämnat kopieras inte, fasta lag kopieras aldrig) och svarar 200 på dubbeltryck i stället för att skapa en dubblett. §5d.2-emitten sker efter commit, bakom recover(), med persisterad idempotensbokföring skriven före emitten — en rad ⇒ aldrig en andra emit, i någon process eller kodväg — och en match där en sida saknar kontodeltagare emitteras inte alls (men bokförs). Gäst-claim (POST /v1/social-play/shared/{share_token}/guest-claims med bearer — aldrig en ny publik mutationsrutt — plus ägarens lista och beslut): ett godkännande sätter identiteten på samma medlemsrad (id:t ändras aldrig), supersedar konkurrenter, loggas — och emitterar ingenting (ingen retroaktiv ELO i v1); tak: ett väntande anspråk per anspråkare och session (partiellt unikt index) + 50 per session. GDPR: anspråkets identitet är registrerad som subject_direct i raderingsdomänerna och i täckningsmanifestet — raderas personen försvinner anspråket, medan gästraden i någon annans kväll aldrig rörs. Gränsdragning: ytorna för avslutet (summeringsvy, pall, Revansch-knapp, gästvyns summeringsläsning) är t-2 och stubbades inte in här — de är levererade i F15.13.16.

    659e6203.m-4.t-1
  • F15.13.14 Planned

    Spelet — backend (migration 00139_dsocialspel_m3_rounds.sql: social_play_round, social_play_match, social_play_team, social_play_change_log, banetiketter + rests/pool/status/team_id på medlemsraden; fortfarande ingen tenant_id, enda externa FK-mål auth_identity). Motorn inkopplad: POST …/{id}/rounds bygger RoundInput ur inställningarna (formmängd forms ∪ 2v3/1v1, preferens härledd ur forms), de aktiva deltagarna/de fasta lagen, den ackumulerade historiken och ett crypto/rand-seed, och persisterar runda + matcher + vilo-räknare + (första lottningen) status='playing'/joining_open=false/settings_locked=true i EN transaktion; unikheten (session_id, round_no) är samtidighetsvakten (två parallella lottningar ⇒ exakt en runda, förloraren får typad 409). Motorns *ValidationError mappas 1:1 till sju stabila 422-koder — aldrig ett läckt Go-fel, aldrig en 500. Fasta lag bildas vid första lottningen med laglig sidstorlek (överblivna = "utan lag", deltar inte) och byts därefter atomiskt med PUT …/teams. Resultat (POST …/matches/{id}/result): deltagaren rapporterar sin EGEN match, ägaren alla; sist skrivna gäller; målpoängvalidering med ägar-åsidosättande (lika poäng avvisas alltid); varje skrivning appendas till den append-only ändringsloggen (GET …/changes, aktör = visningsnamn, aldrig e-post/identitets-id). Tabellen aggregeras i ett rent steg och ordnas av motorns EGEN komparator (individuell/lagvis, separata A- och B-tabeller efter splitrundan) och rider med i sessionsdetaljen, den publika share-vyn och varje live-snapshot tillsammans med aktuell runda — ingen /standings-rutt, ingen andra sorteringsregel. A/B klassificeras med ClassifySplit i samma ögonblick som splitrundan blir färdigrapporterad; ägarens flytt är en override som alltid vinner. Deltagarförändringar under spel: sen ankomst startar på minsta vilo-räknaren, en deltagare som HAR spelat raderas aldrig utan markeras "har lämnat" (resultat och tabellrad bevaras). Determinism: rundans seed (decimalsträng på tråden) + input_member_ids/input_team_ids gör en persisterad runda mekaniskt replaybar. Gästvyn får GET /v1/public/social-play/shared/{token}/rounds — läsning, aldrig skrivning; ingen ny publik mutationsrutt (mekaniskt vaktat i openapi-testet).

    659e6203.m-3.t-1
  • F15.13.15 Shipped

    Spelet — ytorna. App: ägarens lottnings-CTA i sessionsvyn (socialspel-lotta "Lotta första/nästa runda" — eller, när lottning inte är möjlig, förklaringen socialspel-lotta-blockerad i dess ställe: för få deltagare för minsta lagliga matchform, uppnått rundtak, avslutad session; ingen död knapp någonstans) med serverns 409 social_play_round_incomplete som enda källa till "föregående runda är orapporterad" ⇒ "Lotta ändå" (avbruten match/tidsstopp), och låsta inställningar efter första lottningen (redigeringen visar bara det ändringsbara och PATCH-bodyn bantas mekaniskt till de olåsta nycklarna — servern 409:ar på en låst nyckel även med oförändrat värde). Rundvyn (app/app/socialspel/[id]/runda.tsx): rundans matcher med lag, bana (nummer + arrangörens namn), ställning, "Rapporterad av …"/"Rättad av …", omöten och grupp vid A/B — egna matchen överst som fältkontrakt (socialspel-egen-match först i DOM-ordningen; "vilken rad är jag" härleds ur mitt eget visningsnamn mot kontoraderna och bara när matchningen är entydig, eftersom deltagar-DTO:n är PII-fri) — resultatinmatning bara för den som får skriva (kontodeltagaren sin egen match, ägaren alla; inget inaktiverat fält för övriga), målpoängvalidering med ägarens åsidosättande (lika poäng aldrig), vilande med rotationsregeln utskriven, och ägarens justeringar (byt sida, ställ vilande, sätt in vilande i en orapporterad match, bannamn) som alla loggas i den append-only ändringsloggen. Tabellen ([id]/tabell.tsx): raderna i serverns ordning (motorns komparator är produktens enda sorteringsregel), individuell/lagvis enligt läge, A/B-flikar med ägarens manuella pool-flytt (overriden vinner alltid), rundhistorik och en accessibilityLabel per rad som läses som en mening (m-2:s öppna a11y-fynd (a) stängt för m-3:s ytor). Rundtimern (FR-9): nedräkning på arrangörens skärm — default 45 min, start/paus/nollställ, tydligt slutläge — helt lokal: ingen rutt, inget serverfält, ingen synk, ingen koppling till D-COMP-TIMER, och tillståndet bor utanför snapshot-härledd state så en inkommande live-frame aldrig nollställer den (vald längd minns i AsyncStorage — bara en siffra). Web: gästvyn utvidgad med den skrivskyddade läsytan — aktuell runda, live-tabellen (A/B-växling) och rundhistoriken, allt utan omladdning; ytan har ingen resultatinmatning, inte ens inaktiverad (visionens FR-4-ägarbeslut: rekryteringsincitament + missbruksskydd), utan säger i klartext att rapportering kräver konto (socialspel-gast-rapport-info) intill "Skapa konto" — mekaniskt vaktat: L4 asserterar att socialspel-resultat-* saknas helt i gästvyns DOM. Live på alla tre ytorna ur samma snapshot (rundan + tabellen ligger i varje frame; rundhistoriken bakom sin egen läsrutt, omläst när rundnyckeln ändras — spec A-12) med ärlig polling-fallback. Ärlig gräns: ingen "Avsluta"-CTA, ingen pall, ingen Revansch — avslutet är F15.13.13 och stubbas inte in. All trafik via den genererade klienten; L3d-ratchet oförändrad på baseline 0. Vy-specar: specs/app/views/socialspel.md (inkl. testid-kontraktet), specs/web/views/socialspel-gastvy.md. Bevis: L4-drivrutinen tools/gate/socialspel-m3-e2e.mjs (descriptor e2e.d/680-socialspel-m3.mjs) — hela resan mot körande stack + riktig Postgres: lottning → rundvy → deltagarens rapport från egen inloggning → ägarens rättning → live ≤5 s hos ägare, deltagare och gäst → gästvy utan inmatning → timern räknar ned → runda 2 med vilande → rundhistorik → axe wcag2a/aa/21aa/22aa i 375×812 utan critical/serious → DB-facit; plus enhetstester för de rena härledningarna (app/lib/__tests__/socialspel-spel-presenter.test.ts, web/lib/__tests__/socialspel-presenter.test.ts).

    659e6203.m-3.t-2
  • F15.13.16 Shipped

    Avslutet — ytorna. App: *"Avsluta kvällen"* (socialspel-avsluta) på sessions- och rundvyn med bekräftelsesteg (socialspel-avsluta-bekrafta) som säger exakt vad som händer — skrivskyddat efteråt, orapporterade matcher räknas ingenstans — och som bara finns för ägaren (en deltagare ser ingen knapp alls, inte ens inaktiverad; drivrutinen asserterar frånvaron). *Summeringen* (app/app/socialspel/[id]/summering.tsx): pall (två pallar och två vinnare vid A/B, placering i text inkl. "Delad 2:a plats"), kuriosa (utelämnad när servern inte bär någon), kvällens siffror med ärligt redovisade orapporterade matcher, sluttabell i serverns ordning, rundhistorik, avslutsorsak ("Avslutades automatiskt efter 24 timmar utan aktivitet" vid ended_reason='auto'), delningslänken ur appens ENDA härledning (shareLink() — serverns joinURL utan join-beviset; basadressen konfigurerad i varje eas.json-byggprofil som EXPO_PUBLIC_WEB_URL, mekaniskt asserterat så inget riktigt bygge delar ut en localhost-länk) med ärligt utfall på kopieringen (lyckat, misslyckat, och ingen knapp alls där urklippsväg saknas) och Revansch (ny kväll med samma deltagarlista, dubbeltryck ⇒ ingen dubblett). *Mina socialspel* delad i pågående (→ spelet) och avslutade (→ summeringen, med vinnaren ur serverns pall och tyst där summeringen inte lästs). *Gäst-anspråket*: samma rutt /socialspel/ga-med utan färskt ?t= blir anspråksvyn — anspråkaren väljer sin gästrad (socialspel-claim-valj) och skickar (socialspel-claim-skicka), ägaren godkänner/avslår i summeringen (socialspel-claim-godkann/-avsla); vyn läser kvällen via den publika delningsläsningen och skickar med bearer, eftersom anspråkaren per definition inte är deltagare (404-före-403). Web: gästvyn blir efter avslut kvällens permanenta resultatsida på samma URL — avslutsorsak, pall, kuriosa, sluttabell, rundhistorik och raden om att ett konto gör det möjligt att göra anspråk på gästresultaten via arrangören (socialspel-gast-claim-info) — fortfarande med noll socialspel-resultat-* i DOM:en och utan join-formulär (kvällen är stängd; ingen attrapp). Live på alla ytor ur samma snapshot: växeln är serverns summary (finns först vid status='ended'), aldrig en lokal "jag tryckte avsluta"-flagga — därför byter ägarens, deltagarens och gästens skärm samtidigt utan omladdning. All trafik via den genererade klienten; L3d-ratchet oförändrad på baseline 0. Vy-specar: specs/app/views/socialspel.md (Del III + testid-kontraktet), specs/web/views/socialspel-gastvy.md (summeringsytan efter avslut). Bevis: L4-drivrutinen tools/gate/socialspel-m4-e2e.mjs (descriptor e2e.d/690-socialspel-m4.mjs) — hela AC-12-resan mot körande stack + riktig Postgres: end utan auth ⇒ 401 → avslut via bekräftelsen → summeringen komplett → gästens fönster växlar live utan omladdning → delningslänken i ett utloggat fönster → anspråk skickat av en icke-deltagare och godkänt → Revansch med samma deltagarlista → pågående/avslutade → axe wcag2a/aa/21aa/22aa i 375×812 utan critical/serious → DB-facit (ended_reason, §5d.2-raderna, rematch_of_session_id, den godkända gästraden, 0 väntande anspråk, ingen tenant_id); plus enhetstester för de rena härledningarna (app/lib/__tests__/socialspel-summering-presenter.test.ts, app/lib/__tests__/socialspel-links.test.ts, web/lib/__tests__/socialspel-summering-presenter.test.ts).

    659e6203.m-4.t-2