screentinker/server/lib
ScreenTinker 52ab04204a Preview a rotated display the way people see it, not the way its framebuffer is
#238: the dashboard preview of a 90/270 display was sideways while the panel on the
wall was right — the split that makes a preview useless, because a designer checking
portrait content can no longer tell a real fault from an artefact of the tool.

A portrait panel is a landscape framebuffer that the player rotates content INSIDE
(+90), hung turned the other way (-90); the two cancel and the viewer sees upright
portrait. The dashboard modelled only the first half. It iframed the player into a
box it had already given the finished 9/16 shape, so the player rotated a second time
inside a box that was pretending to be the finished picture, and nothing anywhere
stood in for the mount. Screenshots had the opposite half missing: they are the raw
framebuffer, shown untouched, so every portrait screen looked wrong on the cards and
in Now Playing too.

So each surface now has a stage (the panel's face) and a frame (its framebuffer),
with the frame turned by the INVERSE of the player's angle. Turning it the same way
is the tempting mistake and the worst kind of wrong: 90+90 lands upside-down, which
reads as nearly-right. The dimension swap is not cosmetic either — composing into the
real framebuffer shape is what makes the player lay content out in the same portrait
box the panel uses; hand it a portrait viewport instead and every zone and object-fit
decision is computed for a canvas no panel has.

The geometry is the players' own rule (server/lib/orientation-style.js), served to the
dashboard rather than re-derived, since a second copy of a rotation rule is exactly how
the two came to disagree. Covers the device preview modal, the playlist preview's
portrait toggle (same fault), Now Playing and the device cards. The Remote canvas stays
raw on purpose: taps are sent as fractions of it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
2026-08-06 09:38:31 -05:00
..
agency-layouts.js feat: agency zone-grant issuance UI + reactive placement card (#73) 2026-06-14 15:12:55 -05:00
agency-targets.js feat(agency): per-token upload folder — auto-created, subtree-confined (#158) (#171) 2026-07-12 21:23:25 -05:00
apk-cache.js Serve a beta APK alongside the stable one, and let a display move between them 2026-07-30 19:12:46 -05:00
apk-signature.js feat(device-owner): tier foundation + QR provisioning + content-expiry & device enhancements (#168) 2026-07-12 19:41:07 -05:00
app-settings.js feat(#146): always-on devices_connected + admin-toggleable /api/status debug block 2026-07-01 18:45:40 -05:00
billing-token.js feat(#146): owner-only CLI to mint billing:read tokens (scripts/mint-billing-token.js) 2026-07-01 21:31:11 -05:00
billing.js feat(#146): usage metering + admin-gated Billable Screens report (contract system-of-record) 2026-07-01 20:45:27 -05:00
bounded-snapshot-store.js fix(widgets): bound the unauthenticated telemetry store, and stop it writing rows 2026-07-26 10:36:09 -05:00
branding.js fix(security): patch quick-win findings from the codebase review 2026-06-08 19:02:19 -05:00
brightsign-package.js autorun.zip must be STORED and opened with roBrightPackage 2026-08-05 11:36:18 -05:00
brightsign-update.js Remote screenshots use the framebuffer, and an opted-in tester can move forward 2026-08-05 12:33:34 -05:00
chunked-prune.js fix(#146) A: non-blocking maintenance — chunked+yield+band-gate all sweeps 2026-06-30 20:55:05 -05:00
command-queue.js feat(socket): delivery queue for offline-device emits 2026-05-14 13:06:43 -05:00
content-ack-limiter.js fix(#146) E: log/write self-protection — coalesced logs, batched telemetry, bounded maps 2026-06-30 21:34:01 -05:00
content-ingest.js fix(uploads): derive stored type from file content, and never serve uploads as documents 2026-07-25 11:47:09 -05:00
device-identity.js fix(#146) P1.1: resolveIdentity short-circuits on device_id (zero-lookup hot path) 2026-06-30 21:57:55 -05:00
device-sanitize.js Scope device serialization to what each endpoint actually needs 2026-07-27 20:40:40 -05:00
device-settings.js Apply a saved device snapshot only inside the workspace it was taken in 2026-07-30 20:56:59 -05:00
device-timezone.js Store a schedule in the timezone its screen runs in 2026-07-28 09:43:46 -05:00
emailVerify.js feat(auth,tizen): TOTP 2FA UI, email verification on signup, Tizen SSSP install 2026-07-22 21:08:50 -05:00
flap-limiter.js fix(#148) Item 1: exempt paired+authenticated devices from the flap-limiter quarantine 2026-07-02 14:59:25 -05:00
ghcr-check.js feat(dashboard): version indicator + GHCR update check (#165) 2026-07-10 22:40:06 -05:00
image-gen.js feat(ai): generate background + foreground images for signs (#41 Phase 2) 2026-06-09 13:40:14 -05:00
incident-classify.js feat(diagnostics): device incident log — offline cause, network-vs-reboot, display-sleep (#175) 2026-07-13 11:26:04 -05:00
limiter-telemetry.js Record auth rate-limit rejections so they can be measured 2026-07-28 14:01:00 -05:00
liveness.js feat: app-ending signal (exit-signal contract v1) — server + APK + .wgt + /player 2026-07-08 15:32:40 -05:00
log-coalescer.js fix(#146) P3.7: coalescer carries the PEAK numeric over the window 2026-06-30 22:10:45 -05:00
login-lockout.js feat(auth): bound password login per account, not only per IP 2026-07-26 14:07:16 -05:00
media-mute.js Mute reaches YouTube items — it never did, and failed opposite ways per player 2026-08-05 10:50:59 -05:00
media-orientation.js fix(content+android): rotation-aware media — portrait upright on dashboard AND player (#170) (#172) 2026-07-12 22:05:11 -05:00
numeric-code.js fix(devices): generate access-gating six-digit codes with a CSPRNG 2026-07-26 14:24:03 -05:00
orientation-style.js Preview a rotated display the way people see it, not the way its framebuffer is 2026-08-06 09:38:31 -05:00
ota-breaker.js Serve a beta APK alongside the stable one, and let a display move between them 2026-07-30 19:12:46 -05:00
ota-download-guard.js feat(#146): /api/status.debug throughput counters (gauges -> gauges + work done) 2026-06-30 23:24:32 -05:00
pair-lockout.js fix(pairing): expire a pairing code on device liveness, not row age 2026-07-27 10:59:38 -05:00
parse-size.js Parse MAX_FILE_SIZE, and document what else caps an upload 2026-07-28 20:40:14 -05:00
passwordReset.js feat(auth): self-service password reset 2026-07-27 11:19:39 -05:00
permissions.js feat(roles): add cross-org platform_operator staff role (#13) 2026-06-05 10:30:21 -05:00
player-cache-policy.js Offline media on every player, and a revision so the cache can still be updated 2026-08-05 15:27:36 -05:00
player-capabilities.js Merge: capability persistence, dashboard gating, server-side refusal 2026-08-05 14:29:09 -05:00
player-media-health.js fix(#146): web player — reconnect drops video to "Waiting for content" (idle reset over live playback) 2026-07-01 22:03:44 -05:00
reconnect-throttle.js fix(#146): reconnect/heartbeat storm containment (beta5) 2026-06-29 22:12:13 -05:00
recovery-grant.js fix(auth): back break-glass recovery with a revocable, auditable grant 2026-07-26 14:49:03 -05:00
rolling-counter.js feat(#146): /api/status.debug throughput counters (gauges -> gauges + work done) 2026-06-30 23:24:32 -05:00
safe-socket.js fix(#146): crash-hardening — one device's handler throw can't take down the fleet 2026-06-29 23:38:11 -05:00
schedule-eval.js feat(scheduling): per-item schedule blocks (#74 dayparting, #75 auto-expire) 2026-06-11 15:46:41 -05:00
schema-check.js fix(db): observable migrations + fail-fast schema verification (#37) 2026-06-09 09:31:52 -05:00
secretbox.js feat(ai): AI content design in the Designer, BYO endpoint (#41 Phase 1) 2026-06-09 12:23:55 -05:00
session-settle.js fix(#148) patch2: per-device session-settle debounce — absorb duplicate-socket storms 2026-07-02 19:12:46 -05:00
settings-pin.js The settings PIN can be rotated and set from the dashboard 2026-08-05 13:44:15 -05:00
socket-rooms.js feat(socket): Phase 2.3 workspace-scoped dashboard socket rooms + per-command permission gates. Dashboard namespace was previously a flat broadcast - every connected dashboard received every device's status/screenshot/playback events platform-wide (foreign device names + IPs included). Inbound socket commands gated by a legacy admin/superadmin role check that was dead code post-Phase-1 rename. 2026-05-12 11:34:24 -05:00
ssrf-guard.js feat: transition engine — GL wipes across web, Tizen & Android (+ image↔video) (#204) 2026-07-20 16:45:32 -05:00
status-log-writer.js feat(diagnostics): device incident log — offline cause, network-vs-reboot, display-sleep (#175) 2026-07-13 11:26:04 -05:00
sync-backend.js Drop the user-agent fallback — it could never fire 2026-08-05 10:30:10 -05:00
tcp-keepalive.js fix(#148) Items 2-4: mark-offline closes the socket + tighten ping + TCP keepalive 2026-07-02 14:59:25 -05:00
tenancy.js refactor(auth): drop the unused optionalAuth middleware 2026-07-24 21:03:25 -05:00
tenant-cascade-migration.js fix(db): cascade tenant resources on workspace/org delete (#18 follow-up) 2026-06-08 16:01:52 -05:00
totp-lockout.js feat(server): TOTP primitives - encrypted secret, hashed recovery codes, verify lockout (#100) 2026-06-13 20:48:55 -05:00
totp.js feat(auth,tizen): TOTP 2FA UI, email verification on signup, Tizen SSSP install 2026-07-22 21:08:50 -05:00
transition-bundle.js feat: transition engine — GL wipes across web, Tizen & Android (+ image↔video) (#204) 2026-07-20 16:45:32 -05:00
transition-config.js feat: transition engine — GL wipes across web, Tizen & Android (+ image↔video) (#204) 2026-07-20 16:45:32 -05:00
upload-sniff.js fix(uploads): derive stored type from file content, and never serve uploads as documents 2026-07-25 11:47:09 -05:00
user-deletion.js feat(#150): preserve per-device settings across delete+re-pair (fingerprint-keyed) 2026-07-07 12:40:47 -05:00
wgt-cache.js feat(auth,tizen): TOTP 2FA UI, email verification on signup, Tizen SSSP install 2026-07-22 21:08:50 -05:00
zone-validate.js fix: per-item mute round-trip + multi-zone orphan-zone fallback & warnings 2026-06-22 23:16:29 -05:00