screentinker/server/test
ScreenTinker 0030acc526 Store a schedule in the timezone its screen runs in
Creation and playback disagreed about which clock a schedule's hours are on.
The player evaluated blocks in the device's zone — an operator override, else
whatever the player's OS reported. Creation defaulted to a bare 'UTC', because
the dialog never asked for a zone and the server filled the silence with one.

So hours typed as "09:00 to 17:00" were stored as UTC and evaluated somewhere
else. For anyone outside UTC the schedule was correct and appeared to do
nothing, opening hours later than intended, with nothing on screen to explain
why. A user in Asia/Tokyo hit exactly this and reported it as "I added
something and it didn't appear".

Both sides now resolve through lib/device-timezone, so they cannot drift: an
explicit device override wins, then the OS-reported zone, then null. A legacy
'UTC' override counts as unset, since that was the old default rather than a
deliberate choice and a genuine UTC deployment is indistinguishable from an
unconfigured one.

A new schedule inherits its target's zone — the device's, or for a group its
leader's, falling back to the oldest member that reports one. A zone named
explicitly by the caller still wins; this only fills the silence. A target that
has never reported one still lands on UTC, which is the previous behaviour made
explicit rather than assumed.

The dialog now states which clock the hours are on, and says so differently when
that clock is not the operator's own. Stating it is the other half of the fix:
the server can pick the right zone, but the user still has to be able to see it.

Tests pin both directions and, most importantly, that creation and playback
resolve identically from the same device row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 09:43:46 -05:00
..
helpers test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
148-eviction-storm.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
148-half-open.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
156-schedule-read-path.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
admin-users.test.js feat(admin): Delete Organization + Workspace with cascade (#36) 2026-06-09 09:22:21 -05:00
agency-digest.test.js feat(api): batched email digest for agency uploads (#73) 2026-06-14 13:59:37 -05:00
agency-folder.test.js feat(agency): per-token upload folder — auto-created, subtree-confined (#158) (#171) 2026-07-12 21:23:25 -05:00
agency-gate.test.js feat(api): agency portal endpoints + router.param target seam (#73) 2026-06-13 22:48:42 -05:00
agency-layouts.test.js feat(api): GET /api/agency/layouts - device-free layout geometry (#73) 2026-06-14 13:53:30 -05:00
agency-list.test.js feat(api): GET /api/agency/playlists - a token's designated targets (#73) 2026-06-14 13:08:07 -05:00
agency-scope.test.js feat(api): agency-token security primitive - off-ladder scope + agencyGate (#73) 2026-06-13 21:30:38 -05:00
agency.test.js fix(uploads): derive stored type from file content, and never serve uploads as documents 2026-07-25 11:47:09 -05:00
ai-design.test.js fix(ai): de-overlap generated text + layer shapes behind text (#41) 2026-06-09 12:57:41 -05:00
api.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
apitoken-unit.test.js test(api): close #92 follow-up coverage gaps 2026-06-12 20:10:36 -05:00
apk-signature.test.js feat(device-owner): tier foundation + QR provisioning + content-expiry & device enhancements (#168) 2026-07-12 19:41:07 -05:00
billing-authz.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
billing-endpoint.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
billing-token-mint.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
billing-unit.test.js feat(#146): usage metering + admin-gated Billable Screens report (contract system-of-record) 2026-07-01 20:45:27 -05:00
block-authz.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
boot-health.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
branding.test.js feat(branding): instance-level default white-label branding (#15) 2026-06-08 16:55:22 -05:00
client-ip-attribution.test.js fix(logging): gate CF-Connecting-IP on a Cloudflare peer, not any trusted proxy 2026-07-26 10:23:04 -05:00
config-paths.test.js chore(version): single-source VERSION, env-configurable data paths, bump tooling 2026-06-10 12:56:03 -05:00
content-ack-dedup.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
content-ack-flood.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
content-ack-limiter.test.js fix(#143): content-ack flood control — per-device rate budget + loop-lag valve 2026-06-27 22:21:57 -05:00
content-ack-valve.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
content-batch-ops.test.js feat(content): batch operations — multi-select, batch delete, batch move (#224) 2026-07-23 12:29:44 -05:00
content-expiry.test.js feat(device-owner): tier foundation + QR provisioning + content-expiry & device enhancements (#168) 2026-07-12 19:41:07 -05:00
content-multi-upload.test.js feat(content): multi-file upload (#222) 2026-07-23 11:38:37 -05:00
content-search-filter-sort.test.js feat(content): server-side search, type filter, and sort (#221) 2026-07-23 11:38:22 -05:00
content-subtitles.test.js feat(content): subtitle/caption support as a content property (#223) 2026-07-23 12:33:35 -05:00
content-unstable-connection.test.js feat(content): unstable-connection mode — cap YouTube at 720p for weak WiFi (#220) 2026-07-23 11:38:18 -05:00
debug-throughput.test.js feat(#146): /api/status.debug throughput counters (gauges -> gauges + work done) 2026-06-30 23:24:32 -05:00
device-block-and-auth.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
device-events.test.js feat(diagnostics): device incident log — offline cause, network-vs-reboot, display-sleep (#175) 2026-07-13 11:26:04 -05:00
device-owner-qr.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
device-pairing-notify.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
device-serialization-scope.test.js Scope device serialization to what each endpoint actually needs 2026-07-27 20:40:40 -05:00
device-zone-contract.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
directory-board-render.test.js Directory board: JSON/CSV import + logo-replaces-title + fix images on player (#195) 2026-07-16 13:53:34 -05:00
directory-search.test.js Keep a solo widget mounted, and size its keyboard to the viewport 2026-07-27 20:39:59 -05:00
email-transport.test.js feat(email): SMTP transport as an alternative to Microsoft Graph [#173] (#179) 2026-07-13 15:56:22 -05:00
email-verify.test.js feat(auth,tizen): TOTP 2FA UI, email verification on signup, Tizen SSSP install 2026-07-22 21:08:50 -05:00
evicted-socket-rearm.test.js fix(#148) patch2: per-device session-settle debounce — absorb duplicate-socket storms 2026-07-02 19:12:46 -05:00
fingerprint-reclaim.test.js fix(pairing): reinstalled panel reclaims its device row instead of being blocked [Bold] (#180) 2026-07-13 23:07:50 -05:00
flap-limiter.test.js fix(#146) B: sustained flap-rate limiter (the trigger fix), SNAT-safe identity chain 2026-06-30 20:59:53 -05:00
flap-paired-exemption.test.js fix(#148) Item 1: exempt paired+authenticated devices from the flap-limiter quarantine 2026-07-02 14:59:25 -05:00
flap-quarantine.test.js fix(#146) P0: auto-quarantine is in-memory + time-limited, never a DB block 2026-06-30 21:56:20 -05:00
ghcr-check.test.js feat(dashboard): version indicator + GHCR update check (#165) 2026-07-10 22:40:06 -05:00
group-sync-clock.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
i18n-tokens.test.js test(api): close #92 follow-up coverage gaps 2026-06-12 20:10:36 -05:00
identity-resolver.test.js fix(#146) P1.1: resolveIdentity short-circuits on device_id (zero-lookup hot path) 2026-06-30 21:57:55 -05:00
kill-switches.test.js fix(#146) P1.3: per-feature env kill switches + fallout doc section 2026-06-30 22:03:00 -05:00
kiosk-style-containment.test.js Validate kiosk style values as CSS rather than as HTML 2026-07-27 22:28:47 -05:00
log-selfprotect.test.js fix(#146) P3.7: coalescer carries the PEAK numeric over the window 2026-06-30 22:10:45 -05:00
login-lockout.test.js feat(auth): bound password login per account, not only per IP 2026-07-26 14:07:16 -05:00
loop-lag-integration.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
loop-lag.test.js feat(#142): event-loop lag telemetry (perf_hooks) + bounded storage 2026-06-27 19:01:08 -05:00
media-orientation.test.js fix(content+android): rotation-aware media — portrait upright on dashboard AND player (#170) (#172) 2026-07-12 22:05:11 -05:00
media-proxy.test.js feat: transition engine — GL wipes across web, Tizen & Android (+ image↔video) (#204) 2026-07-20 16:45:32 -05:00
mute.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
numeric-code.test.js fix(devices): generate access-gating six-digit codes with a CSPRNG 2026-07-26 14:24:03 -05:00
observability-units.test.js feat(#146): always-on devices_connected + admin-toggleable /api/status debug block 2026-07-01 18:45:40 -05:00
openapi-contract.test.js docs(api): document /api/pip and the assignments muted field (#109/#129) 2026-06-18 17:36:12 -05:00
operator-block.test.js fix(#146) D: operator block — close the device_id-less gap + dashboard toggle 2026-06-30 21:28:04 -05:00
operator-permissions.test.js fix(roles): make platform_operator assignable + add deny/assign regression tests 2026-06-05 12:44:39 -05:00
ota-breaker-device-forgive.test.js Clear a device's OTA rate state when it proves its identity 2026-07-27 22:28:47 -05:00
ota-breaker-patchn.test.js fix(ota): treat legacy -patchN as a released version so the old fleet is offered updates 2026-07-08 23:21:26 -05:00
ota-breaker.test.js fix(#144): OTA update-check circuit-breaker + phantom guard + per-device keying 2026-06-28 23:36:52 -05:00
ota-check.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
ota-hardening.test.js fix(#146) P1.2: band-aware download caps — serve freely when healthy 2026-06-30 22:00:28 -05:00
pair-lockout.test.js fix(api): harden device pairing against brute-force (#87) 2026-06-12 20:16:12 -05:00
pairing-code-liveness.test.js fix(pairing): expire a pairing code on device liveness, not row age 2026-07-27 10:59:38 -05:00
pairing-race.test.js fix(pairing): close deferred-offline reclaim race + idempotent same-code adopt (#192) 2026-07-15 16:05:50 -05:00
password-reset.test.js feat(auth): self-service password reset 2026-07-27 11:19:39 -05:00
pip-overlay.test.js fix(tizen): decode-gated image double-buffer to kill the black flash between stills [#187] (#193) 2026-07-15 18:29:39 -05:00
play-log-reference-resolution.test.js Resolve proof-of-play references instead of trusting the reported id 2026-07-27 21:15:44 -05:00
player-media-health.test.js fix(#146): web player — guard PlayerMediaHealth calls by METHOD, not object (stale-module TypeError) 2026-07-01 22:19:22 -05:00
playlist-build-cost.test.js test(#146) P2.5: buildPlaylistPayload cost under mass reconnect — mitigated 2026-06-30 22:08:55 -05:00
provisioning-cleanup.test.js fix(#146) A: non-blocking maintenance — chunked+yield+band-gate all sweeps 2026-06-30 20:55:05 -05:00
provisioning.test.js fix(api): consolidate device pairing to /pair, remove vestigial bare endpoint (#90) 2026-06-12 20:13:16 -05:00
reconnect-storm-load.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
reconnect-throttle-integration.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
reconnect-throttle.test.js fix(#142): load-aware per-device reconnect throttle (the outage fix) 2026-06-27 19:18:00 -05:00
recovery-grant.test.js fix(auth): back break-glass recovery with a revocable, auditable grant 2026-07-26 14:49:03 -05:00
register-insert-crash.test.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.test.js feat(scheduling): per-item schedule blocks (#74 dayparting, #75 auto-expire) 2026-06-11 15:46:41 -05:00
schedule-timezone-inheritance.test.js Store a schedule in the timezone its screen runs in 2026-07-28 09:43:46 -05:00
scheduled-reboot.test.js feat(device-owner): tier foundation + QR provisioning + content-expiry & device enhancements (#168) 2026-07-12 19:41:07 -05:00
schema-check.test.js fix(db): observable migrations + fail-fast schema verification (#37) 2026-06-09 09:31:52 -05:00
screenshot-authorization.test.js fix(devices): authorize the screenshot route on the device's workspace 2026-07-26 14:17:54 -05:00
security-fixes.test.js fix(security): patch quick-win findings from the codebase review 2026-06-08 19:02:19 -05:00
session-settle.test.js fix(#148) patch2: per-device session-settle debounce — absorb duplicate-socket storms 2026-07-02 19:12:46 -05:00
session-token-resolution.test.js Update sharp to 0.35.x, and repair the corrupt PNG fixture it exposed 2026-07-27 21:15:44 -05:00
socket-handler-isolation.test.js fix(#146): crash-hardening — one device's handler throw can't take down the fleet 2026-06-29 23:38:11 -05:00
ssrf-guard.test.js feat: transition engine — GL wipes across web, Tizen & Android (+ image↔video) (#204) 2026-07-20 16:45:32 -05:00
status-debug-toggle.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
status-log-prune-hardening.test.js test: widen CI-fragile event-loop-gap timing bars (flaky prune/storm asserts) 2026-07-08 17:31:27 -05:00
status-log-prune.test.js fix(#146) A: non-blocking maintenance — chunked+yield+band-gate all sweeps 2026-06-30 20:55:05 -05:00
storm-harness.test.js test: widen CI-fragile event-loop-gap timing bars (flaky prune/storm asserts) 2026-07-08 17:31:27 -05:00
stripe-checkout.test.js feat(stripe): enable promotion codes on checkout sessions (#227) 2026-07-23 23:44:18 -05:00
tcp-keepalive.test.js fix(#148) Items 2-4: mark-offline closes the socket + tighten ping + TCP keepalive 2026-07-02 14:59:25 -05:00
tenant-cascade-migration.test.js fix(db): cascade tenant resources on workspace/org delete (#18 follow-up) 2026-06-08 16:01:52 -05:00
thumbnail-proxy.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
tizen-eval-drift.test.js feat(scheduling): per-item schedule blocks (#74 dayparting, #75 auto-expire) 2026-06-11 15:46:41 -05:00
tizen-image-blackflash.test.js fix(tizen): decode-gated image double-buffer to kill the black flash between stills [#187] (#193) 2026-07-15 18:29:39 -05:00
totp-keyrotation.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
totp-unit.test.js feat(auth,tizen): TOTP 2FA UI, email verification on signup, Tizen SSSP install 2026-07-22 21:08:50 -05:00
totp.test.js feat(auth,tizen): TOTP 2FA UI, email verification on signup, Tizen SSSP install 2026-07-22 21:08:50 -05:00
transition-config.test.js feat: transition engine — GL wipes across web, Tizen & Android (+ image↔video) (#204) 2026-07-20 16:45:32 -05:00
trial-expiry.test.js fix(subscription): make the trial-expiry auto-downgrade actually fire (#228) 2026-07-24 15:51:46 -05:00
upload-content-type-safety.test.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.test.js feat(admin): Delete Organization + Workspace with cascade (#36) 2026-06-09 09:22:21 -05:00
v4-core-liveness.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
v4-core-refresh-gate.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
v4-exit-signal-phase3.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
v4-exit-signal.test.js test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176) 2026-07-13 09:51:40 -05:00
widget-render-xss.test.js fix(security): sanitize public widget render to close stored XSS 2026-06-08 19:11:14 -05:00
widget-telemetry-bounded.test.js fix(widgets): bound the unauthenticated telemetry store, and stop it writing rows 2026-07-26 10:36:09 -05:00
youtube-shorts.test.js fix(content): render YouTube Shorts in 9:16 instead of a landscape frame (#184) (#189) 2026-07-15 08:41:43 -05:00