Scheduling: a move re-asks everyone and carries the series with it; pencilled in → a vote; why it was cancelled (D99, D100, D101, #804, lane 4) #809
Loading…
Reference in a new issue
No description provided.
Delete branch "build/lane4-scheduling"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #804. Lane 4 (#730), server work plus one dialog.
A move re-asks everyone (D99)
end_timewas left behind.The series moves with it (#804)
The rule.
series_service.plan_series_movewalks the later sessions. Each must sit on a regular day at least half an interval after the one before; the first that already does ends the walk. It compares in the campaign's wall-clock time, so DST can't shave the boundary.Where the walk stops without moving anything:
A detached session is outside the series and is never touched.
A moved successor is re-asked and notified, and keeps its number, notes and title.
The materialiser doesn't refill vacated slots. A new occurrence must be at least half an interval after the latest session the series already has. That takes one grouped query and no bookkeeping. The hourly re-run is tested, and it fails without the rule.
GET /api/sessions/{id}/reschedule-preview?to=(GM only) returns what would move and where the walk would stop, and why. It uses the sameplan_moveas the real move, and a test checks the two agree.PATCHreturns the samerippleafter a move.Pencilled in → a vote (D100), and restarting a vote
reopen-for-votingalso accepts pencilled-in sessions and open votes. It clears every answer, sendsvote_opened, and reschedules the auto-close and reminder.PATCH proposed_timeskeeps every vote on times still offered.Why it was cancelled (D101)
cancel_reason,cancelled_at,cancelled_by_idanduncancelled_at.POST /api/sessions/{id}/cancel {reason?}.DELETEstill works, with no reason.extra.reason. There is no bot contract bump. Rendering the reason in Discord is lane 10's.How long it ran
sessions.recorded_seconds, from the bot's existingduration_seconds. A zero from an older bot never overwrites a stored value.Migration
d7e8f9a0b1c3, chained afterc5d6e7f8a9b1(#808). The single-head pin moved totest_cancel_reason_migration.Checks, on the tree merged with main (#807, #808; the code conflicts were resolved by hand)
f3a4 → c5d6 → d7e8, down one and up again.test_scheduling_lane4.py(34) andtest_cancel_reason_migration.py(7).Behaviour worth knowing
countanduntilwhen it picks days. So a count-limited series whose early sessions are pushed on can end up making fewer sessions than its count.🤖 Generated with Claude Code
Migration d7e8f9a0b1c3 adds five nullable columns to sessions, as raw DDL with no backfill: cancel_reason, cancelled_at, cancelled_by_id (-> users, ON DELETE SET NULL), uncancelled_at and recorded_seconds. Old cancellations keep NULLs rather than an invented who or when. The model gains a selectin-loaded `canceller` and a `cancelled_by` property ({id, name}) that reads only what is loaded, so building a response never lazy-loads in async. The one-head pin moves from test_audio_processed_at_migration to the new migration's test, which runs the real upgrade and downgrade in a scratch schema. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>The session page's Cancel Session used the browser's confirm and DELETE. It now opens the drawing's dialog — "Everyone who said yes is told, on Discord and by email if they asked for it…", the field "Why, for the people you are telling" (placeholder "Three of us are out and I have no voice"), [Keep it] [Cancel the session] — and calls the new POST /api/sessions/{id}/cancel with the reason. cancelSession(id, reason) in api/sessions.js is the one caller's new shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>