Superadmin control surface spec and access matrix

Define the minimal superadmin control surface, permissions, and mapping to tenant/guest responsibilities. Document scope and non-goals.

Duplicate of fotospiel-app-ihd after beads re-init


Tenant announcements / release notes

Broadcast announcements to tenants/admins with targeting and scheduling.


SEC-BILL-02 Signature freshness + retry policies for Paddle webhooks


Tenant admin onboarding: inline checkout integration in welcome flow


Paddle migration: design Paddle data mappings for packages/products/prices


Live Show: decide queue model & multi-screen ordering

# Decision: Queue model & multi-screen ordering

## Context
The Live Show player needs a steady stream of approved photos.

Constraints:
- High volume bursts (dozens/minute).
- Multiple display devices may be used (projector + TV).
- We want a simple, resilient model that supports reconnect/resync.

## Options
### A) Derived queue from `photos` + stable ordering (recommended for MVP)
- Store Live Show eligibility on each photo (`live_status=approved`, `live_approved_at`, etc.).
- The server provides an **ordered feed** by `(live_approved_at, id)` (or by a monotonic sequence).
- The player maintains its own playback queue and “recently shown” cooldown in memory.

**Pros**
- Minimal schema complexity.
- Easy resync: fetch “approved since cursor”.
- Works well with realtime events + polling fallback.

**Cons**
- Playback is not perfectly deterministic across multiple displays (they may be at different offsets), unless we add a shared cursor/session.

### B) Explicit queue table (deterministic)
- Introduce `live_show_queue_entries` with an ordered list, plus `played_at`, `skipped_at`, `position`.
- Player reads from server-defined queue, optionally per “show session”.

**Pros**
- Deterministic ordering across screens.
- Great for analytics (“what was shown when”).

**Cons**
- More complex writes and concurrency.
- Requires extra rules for retention/pruning and backpressure.

### C) Server-driven playback session
- Server owns playback ticks and pushes “now playing” to all screens.

**Pros**
- Perfect sync.

**Cons**
- Overkill; introduces tight coupling, more infra and failure points.

## Recommended decision
Choose **Option A** for MVP.

### Ordering recommendation
Use stable ordering `ORDER BY live_approved_at ASC, id ASC` (or DESC depending on query usage).

If we later need strict determinism across displays, add one of:
- A monotonic per-event `live_sequence` assigned at approval time (stored on the photo), or
- A queue table (Option B).

## Multi-screen behavior (expected)
- Multiple displays are supported; they will show the **same set** of approved photos.
- They are not guaranteed to be frame-synchronized.
- New photos should appear on all screens shortly after approval.

## API/feed shape (to guide downstream work)
- Player boot: `GET /api/v1/live-show/{token}/photos?limit=50` (latest approved within retention window).
- Incremental resync: `GET .../photos?after_id=...&limit=200` or `after_approved_at=...`.
- Realtime event includes `photo_id` and enough metadata to enqueue, with client fetching the image asset.

## Edge cases & rules
- **Burst control**: if >N new photos arrive quickly, player shows a “grid burst” or increases pace temporarily.
- **No new photos**: player rotates through backlog (curated/balanced modes) without repeating too frequently.
- **Moderation reversal**: if a photo becomes rejected, it is removed from future rotation (do not hard-cut current frame).

## Decision needed from product
- Is “not perfectly synchronized across multiple screens” acceptable for V1? (recommended: yes)
- Do we need analytics of exactly what was shown on screen? (if yes, consider Option B)

- Options A/B/C compared with pros/cons\n- Clear recommendation (derived queue) and stable ordering defined\n- Expected multi-screen behavior documented\n- Product questions captured (sync requirement, analytics)

Decision: V1 uses derived queue from photos with stable ordering; multi-screen is allowed but not frame-synchronized (acceptable).


Coupon ops: expand fraud tooling with IP/device reputation


Paddle catalog sync: SyncPackageToPaddle job create/update


Live Show: define trusted uploader rules & default retention window

# Decision: Trusted uploader rules & default retention window

## Context
Moderation is required for many events, but we also want a fast “auto-approve trusted sources” mode.

We currently track photo ingestion sources in `photos.ingest_source` (e.g. `tenant_admin`, `photobooth`, `sparkbooth`, `guest_pwa`). Guest uploads are token-based and do not have strong identity guarantees.

## Definitions
- **Trusted uploader**: uploads that can bypass Live Show manual moderation.
- **Retention window**: time window for which approved photos remain eligible for rotation in the Live Show.

## Options (trusted rules)
### A) Trust by ingestion source only (recommended for V1)
Auto-approve for Live Show only when `ingest_source` is one of:
- `tenant_admin` (authenticated staff actions)
- `photobooth` / `sparkbooth` (controlled integrations)

All `guest_pwa` uploads require manual approval when moderation is enabled.

**Pros**
- Harder to spoof; aligns with real security boundaries.
- Simple to explain and operate.

**Cons**
- Guests never auto-approve; more moderator work.

### B) Trust by guest device id (not recommended without stronger proof)
Use `created_by_device_id` / `X-Device-Id` to whitelist devices.

**Risk**
- Device IDs are not cryptographically bound; a motivated guest could spoof the header.

If we want this later, we should introduce a **server-issued signed device token** (pairing flow) and validate it on upload.

### C) Trust by invitation/QR (future)
Guests who joined with a special “staff QR/pairing token” become trusted.

## Recommended decision
Choose **Option A** for V1.

### Moderation mode semantics (proposed)
- `off`: all photos with “submit to live show” become `approved` immediately *except* photos that are already flagged/removed by other moderation pipelines.
- `manual`: all guest PWA photos become `pending`; trusted sources auto-approve.
- `trusted_only`: same as manual, but UI copy emphasises that only booth/staff are automatic.

## Retention window (defaults)
### Recommendation
Default `retention_window_hours = 12` (configurable per event).

Rationale:
- Keeps the “eligible set” bounded for performance.
- Fits most event durations; avoids showing very old photos late in the night.

### Notes
- Even with a retention window, we can still show older photos via “curated” mode (e.g. featured/top-liked) if product wants.

## Edge cases
- **High-volume**: moderators may not keep up → allow temporary switch to “trusted_only” + announce to guests.
- **Abuse**: if a trusted integration misbehaves, operator can disable trusted auto-approve.
- **Reversal**: approving a previously rejected photo must be tracked with audit info (who/when).

## Decision needed from product
- Confirm the default retention window: 12h vs 6h vs “entire event”.
- Confirm whether “trusted_only” should auto-approve `tenant_admin` uploads (recommended: yes).
- Confirm whether guest auto-approve is desired in V1 (recommended: no, unless we build pairing).

- Trusted rules options listed, with security risk called out for device-id trust\n- Clear V1 recommendation (trust by ingest_source only)\n- Moderation mode semantics defined\n- Default retention window recommendation + product decision questions

Decision: V1 trusted auto-approve uses ingest_source only (tenant_admin/photobooth/sparkbooth). Default retention_window_hours = 12.


Security review: payments/webhooks code audit (signatures, idempotency, linkage)


Paddle catalog sync: PackageResource sync status badges + timestamp


Security review: marketing/API controller+validation review


Security review checklist: Guest PWA dynamic tests


Paddle catalog sync: surface last sync error/log context in admin


SEC-API-03 Synthetic monitoring + alert config


Security review: event admin code audit (policies, PKCE, file handling)


SEC-BILL-03 Failed capture notifications + ledger hook


Security review: map roles/data


SEC-IO-02 Refresh-token management UI + audit logs

Obsolete: authentication now uses Sanctum PATs; OAuth/refresh-token tables removed and no refresh-token flow remains. See docs/archive/prp/13-backend-authentication.md and docs/archive/prp/marketing-checkout-payment-architecture.md.


Live Show: public player view with effects engine


Live Show player shell + routing + data layer

Add /show/{token} route + guest player page shell, Live Show API client, SSE/polling subscription and state model.


Live Show playback engine (queue, pacing, layouts)

Implement player playback scheduler, queue management, and layout rendering for single/split/grid.


Live Show effects presets + background modes

Map effect presets/intensity to animations and implement background modes (blur last, gradient, solid, brand).


Live Show player UX polish (fullscreen, states, performance)

Add fullscreen/keyboard controls, loading/empty/offline states, and performance safeguards (preload, memory).


Tenant admin onboarding: add Paddle error UX + test coverage


Paddle catalog sync: extend PaddleClient tests/mocks for catalog endpoints


Tenant admin onboarding: legacy asset audit + component inventory


Live Show: tests (backend + UI smoke)


Paddle catalog sync: PaddleCatalogService scaffold


Fix staging coupon seed 500 for E2E

Resolved elsewhere; staging coupon seed 500 no longer reproducible after recent backend changes.


Help docs: Live Show how-to + recommended hardware (DE/EN)


Security review: confirm env/header defaults


Localized SEO: canonical/hreflang tags + localized navigation


Ensure checkout step 3 requires login for Paddle checkout

Problem: Paddle checkout on step 3 fails when user is not logged in. Step 3 must enforce authentication before initializing Paddle checkout.\n\nSuggestions:\n- Protect step 3 route/controller with auth middleware and redirect to login with intended return URL.\n- Gate step 3 UI/CTA on auth state; show inline login prompt and disable Paddle until authenticated.\n- Require auth in backend endpoint that creates Paddle transaction/session; return 401 and send user to login.\n- Optionally preflight at end of step 2 to prompt login before advancing.


SEC-FE-01 CSP nonce/hashing rollout

Completed in codebase (verified) - duplicate of fotospiel-app-zli


Coupon ops enhancements (redemption service, preview endpoint, widget, export)


Security review: media pipeline code audit (AV/EXIF, signed URLs, storage separation)


Paddle migration: register sandbox webhooks + document events consumed


Live Show: Admin app settings & effect presets


Paddle migration: extend config/env handling for Paddle keys/webhook secrets


Paddle catalog sync: PaddlePackagePull job


Paddle migration: admin catalog sync UI for packages


Paddle migration: marketing checkout uses Paddle-hosted checkout


Fix German welcome phrasing with article-safe app_name


Security review checklist: Marketing/API dynamic tests


Paddle migration: review current billing implementation


SEC-API-02 Public API incident response playbook


SEC-FE-02 Consent-gated analytics loader


Ops mail default + withdrawal legal routes


Paddle migration: define mobile/native billing strategy (RevenueCat vs Paddle)


Tenant admin onboarding: update PRP/docs + handoff notes


Guest policy settings (toggles, rate limits, retention defaults)

Global guest feature toggles, rate limits, and retention defaults. Settings page + persistence.

Fix: WatermarkSettingsPage now defines form(Schema ) to register . Ran vendor/bin/pint --dirty and php artisan test tests/Unit/SuperAdminNavigationGroupsTest.php.


Security review checklist: Media pipeline/storage dynamic tests


Security review: trust boundaries/entrypoints mapped


SEC-IO-01 Document PAT revocation/rotation playbook


Superadmin control surface roadmap

Roadmap to implement practical superadmin control over tenant admin + guest experience. Tracks lifecycle, moderation, policies, ops health, compliance, audit, announcements, integrations.


Checkout refactor: auth/login/register flow alignment


Paddle catalog sync: unit tests for service + jobs


Checkout refactor: Stripe/Paddle payment integration + webhooks


Paddle catalog sync: migration for paddle sync columns


Paddle catalog sync: configure log channel/Slack hook for sync outcomes


Integrations health: unified Paddle/RevenueCat/webhook status dashboard

Add a superadmin integrations health dashboard for Paddle/RevenueCat/webhooks.
Scope: show latest webhook processing status/lag, recent failures, retry backlog, and config presence (env set) without exposing secrets.
Include per-provider status badges and time-window filters, plus links to related logs/actions.


Superadmin control surface spec and access matrix


SEC-API-01 Signed URL middleware + asset migration


SEC-BILL-01 Checkout session linkage + idempotency locks


Security review checklist: Event Admin dynamic tests


SEC-IO-03 Subnet/device matching config + tests

Obsolete: subnet/device matching was tied to legacy OAuth/refresh-token approach; current auth is Sanctum PAT-only with no device/subnet binding in code/config.


SEC-MS-03 Checksum validation + alert thresholds


Tenant admin onboarding: welcome flow + routing guard + UI steps


Live Show: guest PWA submit-to-live toggle + copy


Security review: guest PWA code audit (join tokens, uploads, SW cache)


SEC-MS-04 Storage health widget in Super Admin


Paddle migration: automated tests for checkout/webhooks/sync


Localized SEO: validate hreflang via Search Console/Lighthouse


Live Show: decide display access model (token link vs kiosk session)

# Decision: Display access model (public player view)

## Context
We need a *hands-off* playback surface for projectors/TVs (“Live Show player view”). It must be easy to open on any device, safe to share with staff, and revocable if leaked.

We already use tokenized public routes for the guest PWA (`/g/{token}`, `/e/{token}/{path?}` in `routes/web.php`). We also use Laravel signed URLs for some one-off documents (gift voucher print), but that pattern is not ideal for long-running displays.

## Options
### A) Token-only show link (recommended)
- Admin generates a **random, unguessable token** per event: e.g. `/show/{token}`.
- Player view is publicly accessible *only* with the token.
- Admin can **rotate** token to invalidate all current displays.
- Optional: add **expires_at** for temporary links (off by default for convenience).

**Pros**
- Fastest operator workflow; no login needed on projector device.
- Matches existing public-token patterns in this repo.
- Simple to rotate/kill in case of leakage.

**Cons**
- Anyone with the link can view the show (mitigated by rotation and not showing PII).

### B) Admin-authenticated “kiosk session”
- Projector device logs in as an admin (or receives a kiosk session after admin auth) and then shows the player view.

**Pros**
- Strong access control.

**Cons**
- Operationally painful at venues (login, MFA, account management).
- Higher support burden and more failure points.

### C) Temporary signed URL (Laravel signed routes)
- Generate a time-limited signed URL and use that for the player.

**Pros**
- No DB token storage required.

**Cons**
- Long-running displays need long expiry; link rotation/revocation is awkward.
- Not great UX when a signature expires mid-event.

## Recommended decision
Choose **Option A: Token-only show link**.

### Security posture
- Token is treated as a secret: do not log full token; never display it in error messages.
- Public player view must not display PII (no guest names, no device IDs).
- Add rate limiting middleware for the show endpoints (lightweight protection against brute force).

## Operational workflow
1. Admin enables Live Show for an event.
2. Admin copies the show link and opens it on the playback device.
3. If link leaks: admin clicks **Rotate link** → all old links stop working.

## Implementation notes (to guide downstream work)
- Add `events.live_show_token` (random 32+ bytes, base64url/hex) and `events.live_show_token_rotated_at`.
- New public route e.g. `GET /show/{token}` → serves the player SPA shell.
- New public API endpoint resolved by token (read-only): `GET /api/v1/live-show/{token}/state` (settings + initial photo batch).
- Realtime channel uses token to subscribe (or resolves event id server-side after token validation).

## Decision needed from product
- Confirm whether we want optional **expires_at** (default: no expiry, rely on rotation).
- Confirm if multiple simultaneous display devices are supported (default: yes; token works naturally).

- Issue records options A/B/C with pros/cons\n- Clear recommendation (token-only) with security considerations\n- Concrete implementation notes (DB fields, route shape, revocation workflow)\n- Explicit product decisions listed (expiry + multi-screen)

Decision: token-only show link per event; no expiry by default; rely on rotation/invalidation. Multi-screen supported naturally via token.


Moderation queue for guest content

Queue for flagged guest content (photos, feedback). Bulk actions to hide/delete/resolve with audit.

Land the plane: tests run (FilamentPanelNavigationTest, PhotoModerationQueueTest, TenantFeedbackModerationQueueTest, TenantLifecycle*), migrations added for photo + feedback moderation, no follow-up blockers.


Added superadmin control surface + access matrix to docs/ops/operations-manual.md (Section 1.1), including non-goals and role scope.


Legal: disclose checkout/coupon fraud IP/device signals

Update Legal Pages (privacy policy) to disclose IP/device capture for coupon fraud signals and retention window.


Superadmin audit log for admin actions

Audit trail for superadmin actions without PII payloads.


Security review follow-ups: signed URL TTLs, guest asset throttles, CORS allowlist, logging hygiene


Checkout refactor: CheckoutController + marketing route alignment


Tenant admin onboarding: Playwright skeleton for welcome flow


Security review checklist: Webhooks/Billing dynamic tests


Paddle catalog sync: add DTO helpers for Paddle product/price responses


SEC-MS-02 Streaming upload refactor + tests

Current state (code scan)
- Guest uploads: App\\Http\\Controllers\\Api\\EventPublicController@upload uses Storage::disk()->putFile (stream-friendly) but still does watermark/thumbnail work inline.
- Tenant admin uploads: App\\Http\\Controllers\\Api\\Tenant\\PhotoController@store and @uploadDirect use Storage::disk()->put($path, file_get_contents(...)) which loads entire file into memory.
- Photobooth ingest already streams from import disk via readStream -> Storage::disk()->put($path, $stream).
- Presigned upload flow is stubbed to a local upload-direct endpoint; no true presigned S3 handling yet.
- No tenant upload feature tests exist; guest upload tests exist and cover limits/security.

Goal
- Stream uploads to disk (avoid full in-memory buffers) for tenant-admin upload endpoints and keep behavior consistent across sources.

Plan
1) Introduce a small streaming upload helper/service
   - New service (e.g. App\\Services\\Storage\\UploadStreamService) that accepts UploadedFile + disk + destination path.
   - Use fopen on UploadedFile::getRealPath (or $file->getStream()) and Storage::disk($disk)->put($path, $stream) / writeStream.
   - Always close stream; return stored size and checksum (hash_file on stored path) for asset metadata.

2) Refactor tenant upload endpoints to use streaming
   - Update PhotoController@store and @uploadDirect to use the helper instead of file_get_contents.
   - Use Storage::disk()->putFileAs (or helper) to preserve deterministic paths without buffering.
   - Keep existing validation, watermark, thumbnail, asset recording, and package usage logic.

3) Optional consistency pass on guest upload
   - Consider routing EventPublicController@upload through the same helper for consistent storage + checksum handling, while keeping current validation/limits.

4) Tests
   - Add Feature tests for tenant upload endpoints:
     - /api/v1/tenant/events/{slug}/photos (store) uploads a fake image and persists Photo + EventMediaAsset with expected path/size.
     - /api/v1/tenant/events/{slug}/upload-direct (presigned) uploads a fake image and stores asset + thumbnail.
   - Ensure existing guest upload tests still pass (no behavioral changes).

5) Safety/ops
   - Verify streaming logic handles empty/invalid files gracefully and still reports errors via ApiError.
   - Keep request-time processing (thumb/watermark) unchanged for now; consider queuing in a follow-up if CPU spikes persist.


Paddle customer success metrics (tenant ↔ Paddle sync, sandbox seeding, rollout/rollback)


Session changes 2025-09-08 (PRP split, PWA scaffolding, Filament resources, API)


Registration flow fixes: JSON redirect, error clearing, role handling


SEC-GT-02 Join-token analytics dashboard (Grafana)

Logging + Filament summaries exist; Grafana dashboard still missing.


Paddle catalog sync: announce workflow change to admin users


SEC-GT-01 Hash join tokens + data migration


Remove legacy registration page assets


Integrations health (Paddle/RevenueCat/webhooks)

Health/status dashboard for payment and webhook integrations.


SEC-GT-03 Tighten gallery/photo rate limits + alerting


Coupon ops: wire analytics into Matomo dashboard


Checkout refactor: test coverage + rollout notes


Localized SEO: sitemap updated with locale alternates


Security review: configure env assumptions for dynamic testing


Paddle migration: draft production cutover procedure


Paddle catalog sync: add Link existing Paddle entity action in admin


Paddle catalog sync: Package model casts/fillable + factory


Audit PayPal SDK migration doc vs code (PayPal integration missing)


Paddle catalog sync: seed sandbox catalog via MCP


Paddle catalog sync: Playwright smoke for admin sync action


Paddle migration: tenant admin billing pages (Paddle transactions/portal)


Security review: seed test identities/fixtures


Security review: route/middleware inventory for marketing/API


Security review checklist: Cross-cutting headers/CSRF/rate limits


Checkout refactor: wizard foundations + updated steps


Legal: disclose checkout/coupon fraud IP/device signals


Paddle catalog sync: verify legacy packages mapped before auto-sync


Live Show: realtime delivery channel (WS/SSE) + fallback polling

- Public Live Show endpoints exist for state, updates, and SSE stream\n- Updates endpoint supports cursor (after_approved_at + after_id)\n- SSE emits photo.approved and ping, with settings updates when version changes\n- Feature tests cover state, updates, invalid token

Added LiveShowController with public endpoints: /api/v1/live-show/{token} (state), /updates (polling), /stream (SSE). Provides live-show settings (defaults + event.settings.live_show merge), settings_version hash, ordered approved photo feed with cursor. SSE emits photo.approved, settings.updated, ping. Added routes in routes/api.php. Added Photo live_status default. Tests: tests/Feature/LiveShowRealtimeTest.php. Ran Pint + test.


Security review kickoff mitigations (CORS allowlist, headers, upload hardening, signed URLs)


Compliance tools: data export + retention overrides

GDPR-compliant export requests and retention override workflows for tenants/events.


Live Show link sharing + QR in admin

Expose Live Show link in Event Admin with copy/share/open actions and embedded QR (use simplesoftwareio/simple-qrcode, no external service). Add API endpoints for link fetch/rotate, admin UI card with rotate confirmation, and tests.


Security review: replace public asset URLs with signed routes


Live Show: data model & status workflow (pending/approved/ready)

- DB migrations add event token + photo live fields + indexes\n- Token generation supports rotation (no expiry)\n- Photo live workflow methods set timestamps/reviewer consistently\n- Feature test covers token + workflow

Implemented Live Show data model: events.live_show_token + live_show_token_rotated_at; photos.live_status + timestamps/reviewer/rejection fields + indexes. Added PhotoLiveStatus enum and Photo workflow methods (markLivePending/approveForLiveShow/rejectForLiveShow). Added Event helpers (ensureLiveShowToken/rotateLiveShowToken). Tests: tests/Feature/LiveShowDataModelTest.php.


Tenant admin onboarding: staging E2E validation


Security review: data classes & retention baseline


Ops health dashboard (queues, storage, upload pipeline)

Superadmin ops dashboard showing queue backlog, failed jobs, storage thresholds, and upload pipeline health.

Implemented Ops Health dashboard with storage+queue widgets, new translations, and navigation wiring.


Paddle catalog sync: feature test for artisan command


Paddle migration: build Paddle API service layer


SEC-MS-01 AV + EXIF scrubber worker integration


Localized SEO: locale-prefixed routing + redirects


Paddle migration: seed sandbox catalog via MCP + validate activation flow


Localized SEO: add tests for hreflang/canonical tags


Localized SEO: update PRP/marketing playbooks for hreflang strategy


Registration flow fixes: JSON redirect, error clearing, role handling


Live Show: product spec & UX flow

# Live Show — Full Spec (including edge cases & moderation)

## Goal
Provide a “Live-Slideshow ready” mode that shows newly uploaded event photos on any screen (projector / TV / LED wall) within seconds, with stunning but tasteful effects and strong operational controls.

## Target outcomes
- **Delight**: looks premium on large screens; smooth transitions; branding-consistent.
- **Speed**: new uploads appear quickly (target: <5s after upload completes, subject to moderation rules).
- **Control**: event staff can moderate, pause, and tune effects/tempo live.
- **Resilience**: continues gracefully under network hiccups and high upload volume.

## User roles
- **Guest (Guest PWA)**: uploads photos; optionally opts into “show on Live Show”.
- **Moderator (Admin app)**: approves/rejects/holds photos; can do quick actions.
- **Event Operator (Admin app)**: configures show, effects, and playback behavior.
- **Display Device (public player view)**: full-screen playback; should be hands-off.

## Surfaces (UX)
1) **Guest PWA**
   - Post-upload confirmation includes a toggle: “Auf Live-Show zeigen” (default behavior is configurable per event).
   - If moderation is enabled: copy clarifies “wird nach Freigabe gezeigt”.

2) **Admin app: Live Show Control**
   - Start/stop show, generate/rotate show link (token), screen preview.
   - Live settings: effect preset, intensity, tempo, layout, branding.
   - Operational controls: pause, skip, emergency “safe mode”, clear queue.

3) **Admin app: Moderation Queue**
   - Inbox of newest photos that are eligible for Live Show but not yet approved.
   - Fast actions: Approve / Reject / Hold.
   - Optional assignment + SLA hints (e.g., “pending > 2min”).

4) **Public player view (Show Link)**
   - Full-screen slideshow with optional subtle UI chrome (small “Live” badge, event name, logo).
   - On reconnect/offline: non-intrusive overlay (“Verbindung… zeige zuletzt geladene Fotos”).

## Data & states
### Photo lifecycle (Live Show)
We need a clear status machine for each photo relative to Live Show.

**Proposed enum** `live_status`:
- `none` (not participating)
- `pending` (submitted / eligible but awaiting moderation)
- `approved` (eligible for playback)
- `rejected` (excluded; optionally with reason)
- `expired` (no longer in rotation due to policy or time window)

**Fields (minimum)**
- `live_status`
- `live_submitted_at`
- `live_reviewed_at`
- `live_reviewed_by` (admin user id)
- `live_rejection_reason` (short code; no PII)
- `live_priority` (optional; “featured”)

### Queue model
Two acceptable approaches:
- **Derived queue**: query `photos where live_status=approved` ordered by `live_submitted_at` (and other ranking signals). Player maintains an in-memory queue.
- **Explicit queue table**: store ordered entries with `position`, `state` (queued/played/skipped). Better for deterministic playback and analytics.

Start with derived queue unless we need strict deterministic ordering across multiple display devices.

### Live Show settings (per event)
- `enabled` (bool)
- `moderation_mode` (enum): `off`, `manual`, `trusted_only`
- `trusted_rules` (who counts as trusted; e.g., staff devices, whitelisted invites)
- `playback_mode` (enum): `newest_first`, `balanced`, `curated`
- `freshness_bias` (0..1)
- `pace_mode` (enum): `auto`, `fixed`
- `fixed_interval_seconds` (e.g., 6–12)
- `layout_mode` (enum): `single`, `split`, `grid_burst`
- `effect_preset` (enum)
- `effect_intensity` (0..100)
- `background_mode` (enum): `blur_last`, `gradient`, `solid`, `brand`
- `branding` (logo, accent color, typography choice)
- `content_rules` (e.g., exclude flagged/reported content)
- `retention_window_hours` (how long photos remain eligible for show)

## Realtime behavior
### Event types
- `photo.live_submitted` (guest opted in)
- `photo.live_pending` (needs review)
- `photo.live_approved`
- `photo.live_rejected`
- `live_show.settings_updated`

### Delivery
- Prefer **WebSockets** for low-latency updates.
- Provide **fallback polling** (5–10s) for environments where WS is blocked.
- Display device should handle:
  - reconnect with exponential backoff
  - resync on reconnect: fetch latest approved list and settings

### Consistency rules
- Player should de-duplicate by photo id.
- If an already-playing photo is later rejected, it must be removed from future rotation (don’t abruptly cut current frame unless “safe mode” is triggered).

## Playback & effects engine
### Visual goals
- “Stunning” but not distracting. Effects should be smooth and GPU-friendly.
- Provide a “Light Effects” preset for weaker hardware.

### Presets (initial)
- **Film Cut**: clean fades + slight grain overlay
- **Shutter Flash**: quick flash + slide-in
- **Polaroid Toss**: photo card with shadow + slight rotation settle
- **Parallax Glide**: slow zoom/pan (Ken Burns-like) with depth blur background
- **Light Effects**: minimal transitions, no heavy overlays

### Layout modes
- `single`: full-screen hero image (default)
- `split`: two photos alternating/paired (only if aspect ratios allow)
- `grid_burst`: quick mosaic of 4–9 images, then one becomes hero

### Performance constraints
- Always use optimized image sizes; preload next image.
- Use placeholder (blurhash/LQIP) to avoid white flashes.
- Cap memory: keep only N decoded images in memory (e.g., 3–5).

## Moderation rules
### Modes
1) **Moderation Off**
   - All submitted photos go directly to `approved`.
   - Still exclude photos that are flagged/reported.

2) **Manual Moderation**
   - Default: `pending` until staff approves.
   - SLA guidance: “pending > 2min” highlight.

3) **Trusted-only auto-approve**
   - If uploader matches trusted rules: `approved`, else `pending`.

### Actions
- **Approve**: sets `approved`, emits realtime event.
- **Reject**: sets `rejected` + reason code, emits event.
- **Hold**: stays `pending` (optionally substate `held`).

### Edge cases
- High-volume: moderation queue batching (“approve next 10”).
- Safety: “Safe Mode” instantly pauses new arrivals and switches to curated backlog.
- Disputes: allow reversing a decision (approve previously rejected) with audit trail.

## Security & access control
- Show link should be **tokenized** per event (unguessable), optionally with expiry.
- Allow **rotate token** (invalidate old displays).
- No PII on screen by default (no guest names, no device identifiers).
- Avoid introducing new tracking beyond existing anonymous session id policies.

## Failure modes & graceful degradation
- **No network**: keep showing last cached set; show subtle “offline” overlay.
- **Slow network**: delay effects until image is decoded; avoid stutter.
- **Blocked WebSockets**: auto-fallback to polling.
- **Projector handshake/resolution change**: player should reflow and continue.
- **Bad image/corrupt**: skip and mark as failed-to-render (internal), never block queue.
- **Time drift**: do not depend on exact wall-clock sync.

## Analytics (optional, privacy-safe)
- Aggregate only: photos shown count, average time-to-screen, moderation throughput.
- Do not log uploader identity or precise device fingerprints.

## Out of scope (hard boundaries)
- Videos.
- Facial recognition.
- Public profiles.
- Any new user tracking beyond documented anonymous session_id.

## Open questions (need decisions)
- Should the show link be public token-only, or require admin login with a “kiosk session”?
- Do we need deterministic playback ordering across multiple screens (explicit queue table), or is “eventually consistent” OK?
- Trusted rules definition: staff-only, invite whitelist, or per-device pairing?
- Maximum on-screen retention window default (e.g., 4h vs full event)?

- Live Show spec includes: surfaces, roles, states, realtime, playback/effects, moderation, security, failure modes\n- Explicit open questions listed for product decisions\n- Spec is implementation-ready and can directly feed downstream issues


SEC-FE-03 Cookie banner UX + localisation


Paddle catalog sync: document failure recovery playbook


Tenant lifecycle controls (status, limits, suspend/grace)

Superadmin controls for tenant status, grace periods, and hard limits (uploads/storage/events). Includes UI, policy checks, and audit events.

Delivered dedicated tenant lifecycle view with limits + audit timeline, added grace_period_ends_at field and tenant_lifecycle_events logging, wired lifecycle actions (activate/suspend/deletion/anonymize) + management actions (limits, grace, subscription expiry), enforced tenant photo/storage limits in PackageLimitEvaluator, added lifecycle/limits tests, ran Pint + targeted tests.


Paddle catalog sync: paddle:sync-packages command (dry-run/pull)


Security review: run dynamic testing harness (identities, DAST, fuzz uploads)


Live Show: Admin app moderation queue UI

- Dedicated Live Show moderation API endpoints exist for list + approve/reject/clear\n- Admin mobile UI exposes Live Show queue with status filter and actions\n- PhotoResource includes live_* fields for admin UI\n- Feature tests cover list + approve/reject/clear workflows

Added dedicated Live Show moderation API (tenant admin): /events/{slug}/live-show/photos + approve/reject/clear actions. Added LiveShowPhotoController + FormRequests. PhotoResource now exposes live_* fields. Admin app: new Live Show queue page, route, and Event detail shortcut tile. Admin API updated with Live Show functions + types. Added translations (EN/DE) for Live Show queue UI. Tests: tests/Feature/LiveShowPhotoControllerTest.php.


Paddle migration: tenant ↔ Paddle customer sync + webhook handlers


Compliance tools: superadmin data export + retention override UI

Add superadmin compliance tools for data exports and retention overrides.
Scope: list export requests, status, expiry, and allow manual retry/cancel; add per-tenant/event retention override UI with audit logging.
Ensure access is restricted to superadmins and no PII is exposed beyond existing export metadata.


Ops health widget visual polish

Replace Tailwind utility styling in ops health widget with Filament components and icon-driven layout.

Updated queue health widget layout to use Filament cards, badges, empty states, and grid utilities; added status strip and alert rail.


Tenant admin onboarding: PWA/Capacitor/TWA packaging prep


SEC-FE-01 CSP nonce/hashing rollout