screentinker/server
Claude 14367af5f1 Keep a workspace on schedules that outlive their device group
Deleting a device group converts its group schedules into per-device ones so the screens keep their
programming. That INSERT omitted workspace_id, which is nullable with no default, so every converted
row landed with workspace_id = NULL.

A null workspace does not merely look untidy — it makes the row unreachable in three directions at
once, and they compound into the worst possible combination:

  invisible   the schedule list and the all-screens calendar both filter on workspace_id
  undeletable PUT and DELETE refuse a row with no workspace (403)
  still live  services/scheduler.js has no workspace filter, so it keeps firing every 60 seconds

"I deleted the group but the screens still switch content at 9am, and there is nothing in the
calendar to remove." The only way out was direct database access.

The conversion now carries the workspace, preferring the schedule's own and falling back to the
group's so a legacy group schedule that itself predates workspace_id still converts into a reachable
row. A boot migration repairs rows already orphaned in the field by recovering the workspace from
the device each one targets; anything still unresolvable is left alone rather than guessed at.

4 tests: the converted row keeps its workspace, is visible to the query the list and calendar use,
preserves the actual programming rather than just the ownership, and the repair recovers a row
orphaned before this fix existed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
2026-07-30 20:53:21 -05:00
..
config fix(logging): gate CF-Connecting-IP on a Cloudflare peer, not any trusted proxy 2026-07-26 10:23:04 -05:00
db Keep a workspace on schedules that outlive their device group 2026-07-30 20:53:21 -05:00
lib Serve a beta APK alongside the stable one, and let a display move between them 2026-07-30 19:12:46 -05:00
middleware Merge branch 'fix/recovery-grants' into release/auth-campaign 2026-07-26 16:22:59 -05:00
player Web player: re-render a widget whose content was edited 2026-07-30 20:17:22 -05:00
routes Keep a workspace on schedules that outlive their device group 2026-07-30 20:53:21 -05:00
scripts fix(content+android): rotation-aware media — portrait upright on dashboard AND player (#170) (#172) 2026-07-12 22:05:11 -05:00
services Report zero event-loop lag when a window recorded no samples 2026-07-28 14:11:32 -05:00
test Keep a workspace on schedules that outlive their device group 2026-07-30 20:53:21 -05:00
ws Text widgets: stop losing text off the bottom, and show an edit without an app restart 2026-07-30 19:44:12 -05:00
.gitignore feat(email): Microsoft Graph send + alert spam protection + preferences UI 2026-05-12 18:16:40 -05:00
config.js Add an operator override for self-update on MDM-managed panels 2026-07-28 23:07:30 -05:00
package-lock.json chore(release): v1.9.27 2026-07-30 19:16:55 -05:00
package.json chore(release): v1.9.27 2026-07-30 19:16:55 -05:00
server.js Serve a beta APK alongside the stable one, and let a display move between them 2026-07-30 19:12:46 -05:00
smoke-ui.js Keep the smoke test out of npm test, and update the lockfile 2026-07-28 20:34:34 -05:00
version.js chore(version): single-source VERSION, env-configurable data paths, bump tooling 2026-06-10 12:56:03 -05:00