Require explicit date in Task#complete!
Callers must pass today: so the reference date is always
intentional. The route passes Date.today at request time.
Assisted-by: Claude Opus 4.6 via pi
diff --git a/lib/ketchup/models.rb b/lib/ketchup/models.rb
index d3e4ecf..024c5ef 100644
--- a/lib/ketchup/models.rb
+++ b/lib/ketchup/models.rb
@@ -81,11 +81,9 @@ class Task < Sequel::Model
days_overdue.to_f / interval
end
- def complete!
+ def complete!(today:)
DB.transaction do
update(completed_at: Time.now)
-
- today = Date.today
next_date = case series.interval_unit
when "day"
today + series.interval_count
diff --git a/lib/ketchup/web.rb b/lib/ketchup/web.rb
index b581fa2..a09063d 100644
--- a/lib/ketchup/web.rb
+++ b/lib/ketchup/web.rb
@@ -122,7 +122,7 @@ class Web < Roda
r.post "complete" do
r.halt 422 unless @task[:completed_at].nil?
- @task.complete!
+ @task.complete!(today: Date.today)
r.redirect "/series/#{series_id}"
end
diff --git a/test/test_web.rb b/test/test_web.rb
index a33318f..0ae58ee 100644
--- a/test/test_web.rb
+++ b/test/test_web.rb
@@ -182,6 +182,17 @@ class TestWeb < Minitest::Test
assert_equal Date.today >> 3, new_task[:due_date]
end
+ def test_complete_task_advances_from_given_date
+ create_series(note: "Call Mom", interval_unit: "week", interval_count: "2",
+ first_due_date: "2026-03-01")
+
+ task = Task.first
+ task.complete!(today: Date.new(2026, 4, 1))
+
+ new_task = Task.where(completed_at: nil).first
+ assert_equal Date.new(2026, 4, 15), new_task.due_date
+ end
+
def test_complete_task_requires_own_task
post "/series", {
note: "Alice task", interval_unit: "day", interval_count: "1",