Summarised in the README; this page is the detail.
The card-action scheduler (issue #334) is a background daemon thread inside roundup-server that drives the “move cards by date” feature:
due - offset (default offset: 2 hours) and is applied eagerly, driven by due-date changes (issue #404): adding a due date (or creating a card with one) sets “Remind me”; changing the due date moves it; removing the due date clears it. The value is not rewritten while the due date is unchanged, so a user who hand-edits or clears “Remind me” keeps that value until the due date next changes. Enabling the feature (a 0 → 1 flip of auto_set_remind_enabled) backfills “Remind me” for every existing due-dated card in a background job. The transition is detected for due-date changes from both Trello and Reminders, and propagates to both sides plus any Live Activity.move_remind_target_list_id.move_due_target_list_id.Both destinations are per synced list (issue #580) and live on sync_lists. A NULL destination means “keep the card on this list” — a configuration in its own right, not an unfinished one — and a list’s own setting is the only one consulted for it. The destination must be an open Trello list on that list’s own board, which PATCH /dailyroundup/lists/{id} checks before storing, because Trello’s update_card cannot move a card across boards. The same two columns on accounts still govern the cards on the board’s unsynced lists (issue #373), which have no per-list row to read. A newly synced list therefore starts on “keep on this list” and stops obeying the account settings its cards followed while the Trello list was unsynced — choose its destinations in the app’s per-list options if you want the moves to continue. The due window (move_due_minutes_ahead) and auto-set “Remind me” remain account-wide and apply to both.
The scheduler also orders lists that ask for it, under two per-list toggles that the server treats independently:
move_due_to_top_enabled keeps the list’s due cards at its top, earliest due first, so the most overdue card is at the very top. “Due” uses the same earliest-unresolved-due rule as the move, so a checklist item’s due date counts and a ticked-off due date does not. The app offers this toggle only while the list keeps its due cards: with a destination set they are moved away, leaving nothing for it to order. The stored value is kept rather than cleared, so choosing “keep on this list” again restores the setting.move_pinned_to_top_enabled keeps 📌-prefixed cards beneath the due block, ordered by the moment the server first saw each card pinned, oldest pinned first. That moment is recorded set-once in card_action_index.pinned_at for every pinned card, and later pins land beneath the ones already there. Cards pinned before the server started recording the moment all record it at the same instant and are tie-broken on their current position, so a list with no previously recorded moments keeps the order already on screen. A list that already had pinned Live Activities is the exception: those cards carry a real, older pinned_at from issue #339 and sort above the ones recorded now, so its pinned block reshuffles once. Re-pinning a card puts it back at the bottom of the block, and so does archiving and unarchiving it: the archive takes the card out of the index along with the moment, and the unarchive records a fresh one.Everything else keeps its existing relative order beneath, and a card that is both due and pinned appears once, in the due block. Reordering is event-driven on Trello webhook events under the sync_lease, logging at INFO for each reorder (touched list, due-card and pinned-card counts, and cards moved), with startup and daily reconcile passes that correct any drift left by missed webhook events and log at WARN when they do. Checklist-item events are not among the Trello action types the server handles, so those passes are the only thing that picks up a checklist item’s due date.
A 📌 card without a due date can also surface on opted-in devices as a title-only Live Activity for a configurable window; a 📌 card that has a due date keeps the existing remind-to-due Live Activity, and keeps its place in the pinned block.
The scheduler also drains synced-list reordering to Trello. Dragging lists into a new order in Settings records the requested order and returns; the request makes no Trello calls at all.
Choosing the final board-aware positions needs each board’s live list order, so that planning runs in the drain alongside the pushes — on a background thread first, with the scheduler retrying anything left over on its next tick under the sync_lease and logging the applied and failed counts at INFO.
Three rules keep an interrupted reorder from becoming drift:
See Operations for the log lines to check when a reorder does not reach Trello.
The scheduler is event-driven: it maintains a card_action_index table populated from Trello webhook events and from configuration changes, sleeps until the earliest indexed wake-up moment, and acquires the global sync_lease before acting so concurrent writers cannot race. It also runs a reconciliation pass on startup and on a daily cadence that walks every applicable list, compares indexed timestamps against current Trello state, corrects any drift, and logs discrepancies at WARNING so missed webhook events stay visible to operators.
The index is a cache, and the card fetched when the wake-up comes is the authority on whether it is still on the board. Archiving a card deletes its row straight away, and unarchiving restores it — including an unarchive this server performs itself, whose own webhook is echo-suppressed. A row can outlive its card anyway — a dropped webhook, a card archived before that path existed, a later event on a card that is still archived, or one Trello call that both moves and archives a card, where the move is handled and returns first — so the tick re-reads closed and drops such a row rather than moving the card or notifying on it (issue #609). Any Live Activity the card was driving is ended before its row goes, on both paths: the row is the only handle on a running activity, so an activity outliving it would stay on the lock screen until iOS expired it.
Configuration is stored on the accounts table and is set via PUT /dailyroundup/account. Set DAILYROUNDUP_SCHEDULER_ENABLED=0 to disable the scheduler.