screentinker/server/lib
ScreenTinker c1270599c3 Make the parity matrix true, and stop three controls that do nothing
The parity doc and the capability model had drifted from the players in both
directions, and nothing failed when they did. Auditing all four players against
their shipped sources turned up three controls a customer can press today that
change nothing, and a set of baselines that were partly too generous and partly
too stingy.

The three dead controls:

  - The volume slider works on Android only. The dashboard sends set_volume as
    { level: 0..1 }; the web player reads payload.value and Tizen reads
    payload.value ?? payload.volume, so on both the number is undefined and the
    handler quietly declines. Three complete, working volume implementations
    that cannot be driven. The fix is one line in each player and belongs to
    those files; audio.volume is out of the web and brightsign baselines until
    it lands, held there by a biconditional test that fails the moment a player
    starts reading `level`.

  - Every #161 Tier-2 command was refused for the entire fleet. lock_now,
    power_menu, status_bar, block_uninstall and unblock_uninstall were gated on
    system.device_owner, which no player declares and no baseline grants, so
    supports() was false everywhere -- including on the device-owner panels the
    feature was built for. The dashboard still drew the buttons because it also
    gates on device.tier === 2. Fixed here: those five now accept
    system.device_owner OR system.kiosk, which PlayerCapabilities.kt declares
    under `if (isOwner)` and nothing else, and which no non-Android player
    declares. Android should declare system.device_owner and retire the
    stand-in.

  - enable_system_capture required the capability it creates. It raises the
    MediaProjection consent dialog -- the way a panel GAINS capture -- and was
    gated on remote.screenshot, so the only panel that needs it was the one
    panel that could not be sent it. Now ungated. The dashboard still hides the
    button behind the same check; that half is a frontend change.

The baselines describe what an un-updated fielded display can do, and since
v1.9.29 is the first build in which any player declares anything, that means
v1.9.28. Every entry is now justified against `git show v1.9.28:<source>`:

  - android loses display.power (v1.9.28 answers screen_on with a logged no-op,
    so the ON half is dead on every fielded panel and one capability renders
    both buttons) and system.reboot (owner-only; off-owner it paints an
    accessibility power dialog over the signage). Scheduled reboots now skip
    undeclared Android panels rather than logging a reboot that never happened,
    which is the reason that gate exists.
  - tizen gains display.power: v1.9.28 implements both halves with no signing
    and no panel API, so withholding it hid a working control.
  - brightsign loses audio.volume, display.power, system.reboot,
    system.restart_player and offline.cache. All need a host bridge the unit is
    not known to have, and restart_player without one is the page reload that
    darkened a panel on 2026-07-28.

Also found, not fixed here because the files belong to others:
st-bridge.js computeCapabilities() is dead code -- nothing calls BS.capabilities()
-- and its 199 lines of passing tests constrain nothing a BrightSign actually
declares; the two disagree on six capabilities and the bridge is right about
most of them. BrightSign's "Force update" button is dead. PlayerCapabilities.kt
under-declares display.brightness.

The new test reads the player sources rather than the table: a dead-button rule
(every gated command has a branch somewhere), an unreachable-capability rule
(which would have caught system.device_owner), and biconditionals so a fix in a
player fails the test until the baseline follows. Claims that need hardware --
CEC reaching a display, a widget being allowed a service worker, SyncManager
holding frame lock -- are marked unverifiable in the document instead of
asserted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
2026-08-06 10:24:10 -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
item-duration.js Default a video playlist item to the clip's own length (#237) 2026-08-06 09:36:53 -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 Make the parity matrix true, and stop three controls that do nothing 2026-08-06 10:24:10 -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
wall-geometry.js Describe a portrait video wall as portrait, and stop a wall hiding its screens 2026-08-06 09:46:31 -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