HydraIssues

Decouple kiosk catalog from body availability — fetch from hydraexperiencelibrary via hydracluster proxy
done bug Project: hydraheadflatscreen Reporter: cederik 29 Apr 2026 19:16

Description

Symptom

Kiosks at venues with no online body hang forever on Loading experiences…. Hit today on cheeky-cactus-86 after we updated its venue to bxl1/ad6 (no body enrolled there). v6.1.25 kiosk runs in correct fullscreen game-mode, but the spinner never resolves.

Root cause

The agent's GET /api/v1/experiences (hydraheadflatscreen/pkg/client/localapi.go:66–103) does:

  1. discoverBody(district) — query hydracluster /api/v1/bodies/eligible, probe LAN+WG
  2. fetchSunshineApps(body) — poll the body's Sunshine :47990/api/apps
  3. Return []SunshineApp

When step 1 fails, returns HTTP 503 {"error":"no bodies available"}. Qt KioskView.qml:105 checks data.length, which is undefined on the error object, so loading=true is never cleared.

This is wrong by design. Catalog should come from hydraexperiencelibrary keyed by venue, not from live bodies. Bodies are runtime substrate; they are not the source of truth for which experiences are available at a venue.

Fix (full plan in ~/.claude/plans/partitioned-launching-liskov.md)

Two-part change:

hydracluster — new proxy endpoint

GET /api/v1/heads/{id}/experiences in a new pkg/api/handlers_head_experiences.go:

  • Look up head's district + venue from node store
  • Call hydraexperiencelibrary GET /api/v1/experiences/live?district=X&venue=Y with the existing library token
  • Intersect with Node.AllowedExperiences (pkg/store/store.go:49) if non-empty
  • Return [{name,label,orientation,stream}]

Centralizes "what experiences should this kiosk see" logic. No new credentials on Mac Minis.

hydraheadflatscreen — agent change

  • New pkg/client/catalog.go with fetchVenueCatalog() calling the new hydracluster endpoint
  • Last-known-good cache at ~/.hydraheadflatscreen/catalog_cache.json for offline resilience
  • handleExperiences always returns HTTP 200 + JSON array ([] when empty), never {error}. Empty grid is distinguishable from broken kiosk.
  • Body discovery (discoverBody, fetchSunshineApps) stays unchanged — used only by /api/v1/stream/start

Qt app

No code change needed. KioskView.qml:101–112 already iterates experiences[index].name; new response shape is backward compatible.

Verification

  • curl new hydracluster endpoint with cheeky-cactus's id, expect catalog for bxl1/ad6 (likely empty until experiences planted)
  • Plant a test experience at bxl1/ad6 in library, re-curl, see it
  • Update cheeky-cactus agent (auto-update via 6h tick, or manual), re-screenshot, grid resolves to experiences (or empty grid) — no more infinite spinner
  • Tap a tile while a body is online — stream still works (body path untouched)
  • Block kiosk's connection to hydracluster — verify cache fallback serves last-known catalog

Out of scope

  • Cross-venue-by-owner eligibility for body discovery (parked issue #113)
  • Richer catalog UI (thumbnails, descriptions) — Phase 2
  • Bodies advertising their Sunshine app list to the library (currently experience name = sunshine app name, 1:1)

Sequencing

  1. hydracluster endpoint first (verify via curl)
  2. agent change second (release v2.0.30; auto-update propagates)
  3. Once verified on cheeky-cactus, this also makes future catalog drift self-healing once issue #118's Qt-app auto-updater lands.