Skip to content

Count /schedule edit timing from the edit; refuse dead one-offs - #141

Merged
NinjaLikesCheez merged 3 commits into
mainfrom
fix/schedule-edit-timing
Oct 19, 2026
Merged

NinjaLikesCheez merged 3 commits into
mainfrom
fix/schedule-edit-timing

Conversation

@NinjaLikesCheez

Copy link
Copy Markdown
Contributor

What was wrong

  1. A timing edit could post straight away. A weekly Mon 19:00 schedule last ran Mon 12 Oct 19:00. On Thu 15 Oct 19:20 the owner runs /schedule edit days:thu (no when). start and lastRunAt stayed the same, so the runner's next occurrence after lastRunAt was Thu 15 Oct 19:00. That was only 20 minutes late, under the one-hour limit, so it posted on the next tick. The "✅ Updated" reply counts from now, so it said Thu 22 Oct. Changing weekly to monthly had the same problem.
  2. One-offs that can never post were kept, each taking one of the 50 slots:
    • /schedule resume on a one-off whose time passed while it was paused replied "it has no more posts to make" but kept it.
    • /schedule edit repeat:once with no when on a weekly schedule left a past start ("Next posts: None").
  3. The when option description always said "Europe/Amsterdam", even though the zone actually used comes from PIXEL_TIMEZONE.

What changed (src/features/schedules/index.ts only)

  1. If an edit changes start or recurrence, it now sets lastRunAt to max(stored, now), the same rule resume already used (now shared in a fromNow helper). Edits that leave the timing alone (text, name, or a repeat/days identical to what's stored) don't touch lastRunAt.
  2. Both cases are refused, and nothing is deleted automatically:
    • A timing edit that would leave nothing to post is refused before the form opens and again on submit: "a one-off needs a time that's still to come. Add when…".
    • resume refuses a one-off that has nothing left to post, says to use /schedule delete, and leaves it paused.
    • Why refuse instead of remove: deleting someone's schedule because an edit was wrong would surprise them. Refusing changes nothing, says exactly what to do, and treats both paths the same way.
  3. The description is now built from the configured zone. Option descriptions are sent at registration, so a deployment whose zone isn't Europe/Amsterdam needs just register before Discord shows the new text. With Europe/Amsterdam the text is unchanged. (just register was not run.)

Docs (docs/commands.md, docs/architecture.md) and the CHANGELOG (Fixed) are updated.

Not covered here

A one-off the runner auto-pauses because its creator lost access (paused_no_access) is still kept until someone deletes it. It shows as paused in /schedule list, and resume now refuses it with the delete hint. Removing these automatically would mean changing runner.ts, which is being changed in another PR, so I left it alone.

Testing

  • New tests in schedules.test.ts use a fake CommandContext through the dispatcher, a settable clock, and a single runner tick through feature.start():
    • the days and repeat edits above no longer post and report the right next time
    • edits that don't change the timing keep lastRunAt
    • repeat:once with no when is refused by both the form and the command, and works with when
    • resume refuses a past one-off and keeps it paused
    • resume of a repeat doesn't post a missed occurrence
    • the when description names a non-Amsterdam zone
  • Before the fix, 5 of the new tests fail. With the old code and the lastRunAt assertion removed, the edit tests show the post really going out right away.
  • just check passes.

No file listed in AGENTS.md rule 14 is touched.

🤖 Generated with Claude Code

A timing edit (when, repeat or days) kept the old lastRunAt, so the runner
could post an occurrence of the new timing that fell up to an hour before
the edit, right away, while the reply said the next one. Moving the time
now sets lastRunAt to max(stored, now), as resume already does.

An edit that turns a repeat into a one-off whose start has passed, and a
resume of a one-off whose time passed while paused, are now refused with
a clear message instead of keeping a schedule that can never post.

The `when` option description names the configured PIXEL_TIMEZONE
instead of a hard-coded Europe/Amsterdam.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@NinjaLikesCheez NinjaLikesCheez added the bug Something isn't working label Oct 18, 2026
@NinjaLikesCheez
NinjaLikesCheez merged commit 44722ce into main Oct 19, 2026
3 of 4 checks passed
@NinjaLikesCheez
NinjaLikesCheez deleted the fix/schedule-edit-timing branch October 19, 2026 07:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant