An assignment remains attached with its saved settings whether Live or Disabled.
device_sources.enabled is INTEGER NOT NULL DEFAULT 1 CHECK(enabled IN (0,1)):
1 is Live; 0 is Disabled. Existing and new assignments default to Live; the add
form can explicitly attach a source as Disabled. There is no staged state or draft.
Run tools/enable-device-sources.ts before deploying readers of this column.
The migration is idempotent and preserves assignments and settings; branches share
the database. Do not add schema writes to feed reads.
The device resource root is Feed: all live sources composed with device presentation rules. This describes the server response at the next widget refresh, not confirmation of what WidgetKit currently displays.
Every assignment page shows its state, independent state controls, settings, and Source preview. Preview composes only that assignment, even when disabled, using saved settings and device presentation. Save and preview persists settings; live settings take effect on subsequent widget refreshes. Preview never changes state. Both browser views use the device's last reported timezone offset; if unavailable, sources use their defaults. Item timestamps render in the viewing browser's local timezone, as elsewhere in the administration UI.
State changes POST to /devices/:id/sources/:assignmentId/state, accepting exactly
enabled=1 or enabled=0. They require an assignment belonging to that device but
do not contact the source or validate unchanged settings. Descriptor/read failures
leave the state controls usable. Disabled sources remain unavailable in the add
picker because they are already attached. Removing an assignment discards settings.
Both resolver aliases and the reminder builder use getDeviceFeedConfiguration,
which selects only enabled assignments. Aggregate browser Feed also filters enabled
assignments. All-disabled devices retain presentation and return an empty feed with
zero effective-source diagnostics. Disabled source failures cannot break live reads.
At reminder delivery, the drainer verifies that the namespaced item's source is
still attached and live; otherwise the claimed row settles as void:SourceNotLive.
The builder cancels vanished items through the existing queue synchronization.
An APNs request already in flight cannot be recalled. Terminal reminder rows remain
terminal under the existing at-most-once policy; enabling a source does not revive
cancelled reminders for the same item/rule.
Assignment state changes request a widget refresh and reset reminder build staleness.
Saving disabled settings does not request a widget refresh. Status diagnostics retain
all attached sources and expose enabled; legacy activeSource selects a live source.
The widget/source protocol payloads are unchanged.
tools/source-state-check.ts uses disposable assignments to check disabled attachment,
both resolvers, all-disabled empty feeds, retained/editable settings, isolated previews,
device timezone/filtering, source failures, state validation, ownership, and queued
reminder suppression. tools/intraday-filter-check.ts, tools/source-settings-check.ts,
and tools/reminder-check.ts cover the surrounding contracts. Reminder checks scope
drain calls to fixture row IDs and never send APNs requests.