DailyRoundup

Card Action Scheduler

Summarised in the README; this page is the detail.

Moving cards by date

The card-action scheduler (issue #334) is a background daemon thread inside roundup-server that drives the “move cards by date” feature:

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.

Keeping cards at the top

The scheduler also orders lists that ask for it, under two per-list toggles that the server treats independently:

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.

Draining list reorders to Trello

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.

How it wakes up

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

Configuration is stored on the accounts table and is set via PUT /dailyroundup/account. Set DAILYROUNDUP_SCHEDULER_ENABLED=0 to disable the scheduler.