screentinker/frontend/js/views
ScreenTinker 3e37d33b80 QA: close four ways a control or an asset lied about itself
Found by driving the real server and a real browser, not by reading. Each fix has a
test that fails without it.

1. A missing upload answered 200 with the DASHBOARD. express.static falls through on a
   miss and the SPA catch-all caught it, so GET /uploads/content/<gone>.mp4 returned
   15KB of index.html as text/html — under the `immutable, max-age=30d` header the mount
   sets before it knows the file exists. Every player downloader treats 200 as success,
   so a panel stores the HTML page AS the video and caches it for a month, rendering a
   black frame with nothing in any log. Reachable exactly when it hurts: a content
   replace writes a new random filename and unlinks the old one. The mount now
   terminates a miss with a 404 and drops the cache header.

2. Four dashboard->device socket handlers had no capability gate. dashboard:device-command
   has always refused a command the panel cannot honour, and the comment above it is right
   about why ("hiding the button is not enforcement — this socket is reachable directly").
   Every word applied to the four handlers immediately above it, which had none: a display
   declaring [] still received screenshot-request, remote-touch, remote-key and
   remote-start. Measured, not inferred. They now refuse on remote.screenshot /
   remote.input / remote.stream and name the capability in the ack; remote-stop stays
   ungated for the same reason set_debug does. The undeclared fleet is unaffected — an
   absent declaration still resolves to its platform baseline and keeps everything.

   The wall panel list (#235) made this visible: it offered a Screenshot button for every
   panel, including a BrightSign, which has no screenshot capability at all, and popped a
   toast promising an image that was never coming. GET /api/devices now ships the RESOLVED
   capability array rather than the raw column ('[]' as a STRING, which Array.isArray reads
   as "pre-capability server, show everything" — wrong in the one case that matters), so
   the wall list and the fleet cards can hide what a panel cannot do. The remote pad's
   Scrn Off / Scrn On were gated on remote.input while the Info tab gated the same two
   commands on display.power; both now agree.

3. A register with no `platform` ERASED the stored one. captureIdentity coerces a missing
   field to the literal 'unknown' and persistIdentity wrote it straight over. That column
   is load-bearing: platformFamily() reads it, so one reconnect from an older build turned
   a Tizen panel into a browser tab and handed it a volume slider the .wgt has no handler
   for — the exact control BASELINE.tizen exists to hide — while a BrightSign lost screen
   power and reboot and gained screenshots it cannot take. platform and client_type are
   now preserved (physical facts); client_version and contract_version still decay, because
   there "we no longer know" is the truthful answer. client_type 'wgt' is also read as a
   second signal for a Tizen TV.

4. PUT /api/content/:id/replace carried its own shorter copy of the ingest logic. Replacing
   a video left duration_sec at the OLD clip's length and nulled width/height, so #237's
   brand-new "default an item to the clip's own length" then handed out the wrong number
   for every later add — 32s scheduled for a 5s video is 27s of frozen frame. Replacing an
   image measured it with raw sharp metadata and thumbnailed without .rotate(),
   re-introducing the EXIF-orientation bug #172 had just fixed at ingest. Both paths now
   share lib/content-ingest.deriveMediaMetadata.

Verified working and NOT changed: all six item-duration insert paths (a 31.7s clip stores
32 everywhere, an explicit value always wins, and no path can store a 0); the content
revision bump + filepath refresh reaching a real device socket; a landscape wall producing
byte-identical geometry to the pre-#236 expression; a portrait wall reaching the player as
side-by-side halves; cross-workspace isolation across 29 probes.

Full suite green (1319).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
2026-08-06 16:12:29 -05:00
..
activity.js Stop eight dashboard views reporting success for requests the server refused 2026-07-30 21:21:56 -05:00
admin-player-debug.js Add player debug overlay and server-side error telemetry sink 2026-05-15 15:20:42 -05:00
admin.js Stop eight dashboard views reporting success for requests the server refused 2026-07-30 21:21:56 -05:00
billing.js i18n batch 5: wire layout-editor + video-wall + billing (~85 keys) 2026-04-29 20:13:38 -05:00
content-library.js Stop the content edit dialog rewriting types it cannot represent 2026-07-30 21:09:59 -05:00
dashboard.js QA: close four ways a control or an asset lied about itself 2026-08-06 16:12:29 -05:00
designer.js Translate the labels that never went through t() 2026-07-28 19:52:52 -05:00
device-detail.js QA: close four ways a control or an asset lied about itself 2026-08-06 16:12:29 -05:00
force-password-change.js feat(admin): admin-provisioned user creation + first-login gate (#10) 2026-06-05 11:03:56 -05:00
help.js Describe a portrait video wall as portrait, and stop a wall hiding its screens 2026-08-06 09:46:31 -05:00
kiosk.js Stop eight dashboard views reporting success for requests the server refused 2026-07-30 21:21:56 -05:00
layout-editor.js Portrait templates, a canvas that matches the layout, and a playlist mockup 2026-08-05 13:23:15 -05:00
login.js feat(auth): self-service password reset 2026-07-27 11:19:39 -05:00
no-workspace.js feat(signup): optional org-on-create for self-service signups (#12) 2026-06-05 11:16:27 -05:00
onboarding.js Default a video playlist item to the clip's own length (#237) 2026-08-06 09:36:53 -05:00
playlists.js Merge #239: let the playlist preview skip to any item 2026-08-06 09:52:20 -05:00
reports.js Stop eight dashboard views reporting success for requests the server refused 2026-07-30 21:21:56 -05:00
schedule.js Stop eight dashboard views reporting success for requests the server refused 2026-07-30 21:21:56 -05:00
settings.js Make help tips reachable, and explain the pages that had none 2026-07-28 19:37:49 -05:00
teams.js Stop eight dashboard views reporting success for requests the server refused 2026-07-30 21:21:56 -05:00
video-wall.js QA: close four ways a control or an asset lied about itself 2026-08-06 16:12:29 -05:00
widgets.js Stop eight dashboard views reporting success for requests the server refused 2026-07-30 21:21:56 -05:00
workspace-members.js feat(admin): admin-provisioned user creation + first-login gate (#10) 2026-06-05 11:03:56 -05:00