screentinker/server/routes
Claude e4c25c39df Describe a portrait video wall as portrait, and stop a wall hiding its screens
#236: the wall canvas was secretly framebuffer space rather than the wall as
the audience sees it. Invisible while every panel is the normal way up, and
actively misleading the moment one isn't — two portrait-mounted panels standing
side by side had to be STACKED VERTICALLY in the editor, with a pre-rotated copy
of every video, before the output came out right. It worked, but only after
trial and error, and it meant a portrait wall could never reuse content as-is.

Each panel now carries a mounting rotation (0/90/180/270 clockwise, the same
convention as the per-device orientation setting), the canvas means the physical
wall, and the player works out the mapping. The geometry lives in one place,
server/lib/wall-geometry.js, because four players have to agree on it to the
pixel across a seam.

Existing walls need no migration and do not move. Every wall in the field is
rotation 0, and that case takes the original expression verbatim on all three
players rather than the algebraically-equal centre-based one — the two differ in
the last float bit, and a float's worth of disagreement between two panels is a
hairline seam down a wall that was aligned yesterday. Pinned by the first test
in wall-geometry.test.js and by wall-payload.test.js.

While a display is in a wall its panel rotation replaces its own orientation:
both describe the same physical fact, so honouring both turned the content twice.

#235: a wall replaced its members' cards, so one dead panel of a four-panel wall
was invisible from the dashboard, and inspecting a single screen meant pulling it
out of the live wall and putting it back. The wall screen now lists its panels
with live online state, a per-panel screenshot request, and a link to each
device's page; the dashboard wall card carries per-member status chips that track
socket updates.

Tests: wall-geometry.test.js re-simulates the CSS box independently and asserts
each panel's viewport maps onto exactly its own rect of wall space, for every
rotation, plus a mixed wall and the Tizen player's hand-ported copy executed
against the canonical rule. Full server suite green (1260).

Not verified here: the Android and Tizen renders on real hardware. Kotlin
compiles clean; the maths is shared/tested, the view plumbing is not.
2026-08-06 09:46:31 -05:00
..
activity.js Phase 2.1: tenancy middleware, permission helpers, JWT workspace context, frontend + backend role-rename compat 2026-05-11 20:02:00 -05:00
admin.js Show every plan on the admin tab, with who is on each 2026-07-29 19:36:10 -05:00
agency.js fix(uploads): derive stored type from file content, and never serve uploads as documents 2026-07-25 11:47:09 -05:00
ai.js feat(ai): separate optional image API key (#41) 2026-06-09 13:47:47 -05:00
assignments.js fix(server): floor duration_sec to prevent widget zero-duration player loop (#199) 2026-07-17 13:41:52 -05:00
auth.js feat(auth): self-service password reset 2026-07-27 11:19:39 -05:00
billing.js feat(#146): billing:read scoped token — dual-path auth for the Usage Report (Option C) 2026-07-01 21:16:21 -05:00
contact.js fix(landing): replace broken Custom pricing card with enterprise contact form 2026-05-14 13:52:24 -05:00
content.js Offline media on every player, and a revision so the cache can still be updated 2026-08-05 15:27:36 -05:00
device-groups.js Show only the controls a display can actually honour 2026-08-05 14:24:52 -05:00
devices.js Show only the controls a display can actually honour 2026-08-05 14:24:52 -05:00
folders.js Phase 2.2c: content_folders gets workspace_id (schema + backfill); folders.js scoped; content.js folder-move strict same-workspace 2026-05-11 21:04:03 -05:00
kiosk.js Validate kiosk style values as CSS rather than as HTML 2026-07-27 22:28:47 -05:00
layouts.js Save a layout by diffing its zones, not by deleting and re-inserting them 2026-07-30 20:51:28 -05:00
media.js feat: transition engine — GL wipes across web, Tizen & Android (+ image↔video) (#204) 2026-07-20 16:45:32 -05:00
pip.js Hold overlay pushes to the same write check as every other fleet action 2026-07-30 20:46:03 -05:00
player-debug.js Add player debug overlay and server-side error telemetry sink 2026-05-15 15:20:42 -05:00
playlists.js Portrait templates, a canvas that matches the layout, and a playlist mockup 2026-08-05 13:23:15 -05:00
provisioning.js fix(api): consolidate device pairing to /pair, remove vestigial bare endpoint (#90) 2026-06-12 20:13:16 -05:00
reports.js Phase 2.2g: reports.js scoped to workspace_id; fixes pre-existing /export and /uptime cross-tenant leaks 2026-05-11 21:36:54 -05:00
schedules.js Make a content-only schedule actually put that content on the screen 2026-07-30 22:26:58 -05:00
status.js Merge branch 'fix/pin-generation-csprng' into release/auth-campaign 2026-07-26 16:22:59 -05:00
stripe.js feat(stripe): enable promotion codes on checkout sessions (#227) 2026-07-23 23:44:18 -05:00
subscription.js Initial open source release 2026-04-08 12:14:53 -05:00
teams.js feat(teams): temporarily disable Teams API while feature is redesigned 2026-05-12 13:30:55 -05:00
tokens.js feat(agency): per-token upload folder — auto-created, subtree-confined (#158) (#171) 2026-07-12 21:23:25 -05:00
video-walls.js Describe a portrait video wall as portrait, and stop a wall hiding its screens 2026-08-06 09:46:31 -05:00
white-label.js fix(security): patch quick-win findings from the codebase review 2026-06-08 19:02:19 -05:00
widgets.js Stop shrinking hand-written text widgets into illegibility 2026-07-30 20:28:28 -05:00
workspaces.js fix(workspaces): use APP_URL env var for invite-accept URL generation 2026-05-17 15:26:07 -05:00