Context. The scheduler is purely deadline-driven greedy first-fit (D16): a flexible task lands in the earliest free slot that still meets its deadline, full stop. A task due next week gets placed today if there's room, crowding out work you actually mean to do now. Unschedule (D54) isn't a fit for this — it parks a task in status = 'inbox', and D54 says outright "it is not a snooze": nothing brings the task back, so using it here would mean remembering to manually re-schedule every deferred task.
Decision. Add tasks.deferred_until (nullable timestamp), independent of deadline. The placer (lib/scheduler/placer.ts) clips each candidate free slot to max(slot.start, deferredUntil) before evaluating footprint/placement — a slot entirely before the floor is skipped outright, one straddling it is entered late. No status change and no explicit "become eligible again" step: once deferred_until passes, the next sweep places the task like any other, because the check is a live comparison against now, not a one-time flag. Sections stay keyed on deadline (lib/tasks/sections.ts untouched) — a task due next week still shows under "This week", it just doesn't occupy calendar time until eligible; the Status pill (components/task-status-menu.tsx) shows a later <date> state in the meantime.
Three ways to set it: quick presets on the Status pill menu (Tomorrow / Next week / Weekend — plain wall-time floors), a persistent "Start" field in the task detail panel (components/task-detail-panel.tsx, mirrors the Deadline field exactly), and "Next available slot", which sets the floor to the end of the task's current scheduled block via the new deferTaskToNextSlot action (app/tasks/actions.ts).
That third preset exists because of a real bug this investigation surfaced: the reshuffler (lib/scheduler/reshuffler.ts) wipes and re-places every flexible task fresh each pass, deterministically, deadline-ordered. The existing "Find next slot" button (rescheduleBlock, app/calendar/actions.ts) just clears user_adjusted and waits for the next sweep — if nothing else about the schedule changed, the deterministic algorithm puts the task right back in the identical slot it just left. Fixed at the source: rescheduleBlock now also floors the task's deferred_until at the block's current end, so both it and the new "Next available slot" preset are guaranteed to move forward, not just churn.
Options considered. (A) Overload status = 'inbox' with an implicit resume date stored elsewhere — rejected: conflates with Unschedule's explicit "stay parked" contract from D54 and needs the same holding-pen bucket to mean two different things. (B) A new 'deferred' status with a cron to flip it back to 'scheduled' — rejected: an explicit state machine and a background flip for something a live now comparison already answers for free. (C) Leave "Find next slot" as-is and only fix it for the new "Next available slot" preset — rejected: identical root cause, identical fix; leaving the old button broken while shipping a new one that works right next to it is confusing for no savings.
Tradeoffs. ✅ The deadline and its placement priority are untouched — deferring changes when a task may start, not how urgently it's treated once eligible. ✅ Resuming is automatic; no manual re-schedule step, unlike Unschedule. ✅ One mechanism (a per-task floor the placer already has to check) serves fixed-date presets, custom dates, and "move forward" all at once. ✅ Fixes a genuine standing bug in "Find next slot" as a side effect. ⚠️ A past deferred_until is never cleared back to null — harmless (it's a permanent no-op once passed) but the column can carry stale values indefinitely. ⚠️ "Weekend" against showWeekends = false just means the real next slot is Monday — correct, if slightly surprising.
Reversibility. One nullable column, one clipping step in the placer's slot loop, one extra floor-set in rescheduleBlock. Dropping the column and the clipping step fully reverts; the Status pill and detail panel changes are additive UI.