screentinker/frontend
ScreenTinker 7eab9c6092 PiP overlay MVP: push image/web overlays to a device or group (#109)
Implements the #109 MVP from docs proposal: a floating overlay PUSHED to a device or
group in real time, rendered above the playlist without disturbing it. Scope is the
MVP only — video/RTSP, MQTT, offline-queue, and the priority/stacking system are
deferred to follow-up PRs as the proposal specifies.

Protocol (/device socket, player-agnostic):
- device:pip-show { pip_id, type:image|web, uri, position, width, height, duration,
  title?, title_color?, background_color?, opacity?, border_radius?, close_button? }
- device:pip-clear { pip_id? }
The player fetches uri itself (same trust model as remote_url content; server never
proxies). type:web is full-trust by design, hence the 'full' token scope.

Server (server/routes/pip.js, new; mounted in config/api-surface.js PUBLIC_ROUTERS):
- POST /api/pip and POST /api/pip/clear + DELETE /api/pip, all requireScope('full').
- Resolves device_id to a device OR a group, expands a group to members, and emits
  per-device — reusing the group command route's room-size online check and
  {device_id, name, status: sent|offline} result shape. Generates pip_id.
- Validates type/position allowlists, uri http(s), numeric bounds on
  width/height/duration/opacity/border_radius, colors via the existing VALID_COLOR
  (#RRGGBB; transparency is the separate opacity field).
- Workspace-isolated: every target query is scoped to req.workspaceId, so a token
  bound to workspace A can't address workspace B (404). Offline devices are reported,
  never queued (PiP is ephemeral).

Player overlay layer (Tizen; tizen/js/pip-overlay.js, new):
- A #pip sibling ABOVE #stage that PlaylistPlayer/ZoneRenderer never touch.
- applyOrientation now applies the SAME transform to #pip as #stage, so corner
  positions track the visible CONTENT in all four orientations.
- image -> <img>, web -> <iframe> (muted by default: empty allow= denies autoplay),
  sized/positioned/styled per payload, optional title bar.
- Single overlay slot, last-show-wins; duration timer (0 = until cleared); pip-clear
  (id-aware) or timer tears down; teardown wrapped so a malformed payload can't wedge
  the layer. Reports show/clear over device:log (tag 'pip').

Dashboard: a minimal "Send overlay" / "Clear overlay" tester on the device-detail
controls (device/group via the open device, type, uri, position, duration), calling
POST /api/pip through the api helper.

Tests (server suite green, 161/161):
- api.test.js: PiP tier — authz (read/write 403, full passes), workspace isolation
  (wsA token -> wsB device 404), payload validation, device + group targeting, clear;
  plus the PUBLIC_ROUTERS snapshot-firewall updated for /api/pip.
- pip-overlay.test.js: loads the real player.js + pip-overlay.js in a vm with a DOM
  shim; proves the overlay shows, auto-dismisses on the duration timer, and never
  changes the playlist signature / touches #stage; web->iframe, last-show-wins,
  id-aware clear, malformed-payload safety.

Not in this PR (intentional):
- Android player overlay — fast-follow. Protocol + server are player-agnostic; the
  Android layer (an overlay View above the player, orientation-matched to MainActivity's
  rootView rotation) is the same shape and lands next.
- OpenAPI docs for POST /api/pip — the contract test's scope heuristic only treats
  'command' paths as full-scope, so documenting a full-scope non-command route there
  needs that heuristic extended first; deferred with the docs item (proposal §8.6).
- video/rtsp types, MQTT, offline queue-on-reconnect, priority/stacking, arbitrary
  (x,y)/selector positioning (proposal §6).

Refs #109

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:42:32 -05:00
..
assets Landing: replace iframe mock with dashboard screenshot 2026-04-21 19:47:13 -05:00
compare fix(landing): correct comparison-table claims, mobile image, media-query bug 2026-06-11 08:38:38 -05:00
css fix(ui): make sidebar nav scrollable on short screens 2026-06-08 20:41:15 -05:00
guides chore(discord): update Discord invite link 2026-05-14 12:26:20 -05:00
js PiP overlay MVP: push image/web overlays to a device or group (#109) 2026-06-18 14:42:32 -05:00
legal docs(privacy): disclose error and diagnostic telemetry from players 2026-05-15 15:31:21 -05:00
vendor test(api): fix spec scope drift + guard it in CI; Redoc provenance 2026-06-12 18:45:09 -05:00
agency.html feat(ui): the size-guidance card container in the agency portal (#73) 2026-06-14 16:10:25 -05:00
api-docs.html docs(api): OpenAPI spec, Redoc at /docs, CI spec-lint 2026-06-12 18:45:09 -05:00
index.html feat(config): HIDE_BILLING flag to hide the Subscription/billing UI (#116) 2026-06-16 09:19:24 -05:00
landing.html fix(landing): correct comparison-table claims, mobile image, media-query bug 2026-06-11 08:38:38 -05:00
manifest.json Initial open source release 2026-04-08 12:14:53 -05:00
robots.txt SEO: add meta tags, sitemap, robots.txt, comparison pages, guides, internal linking 2026-04-28 20:54:32 -05:00
sitemap.xml SEO: add meta tags, sitemap, robots.txt, comparison pages, guides, internal linking 2026-04-28 20:54:32 -05:00
sw-admin.js fix(sw): stop the admin service worker from breaking video playback 2026-06-08 23:08:13 -05:00