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
change tntrmkvvnqynmpzylspyqzlynrsxvmus
commit 5a6485f2d1d84654b094d1c82075bcf060b6d228
author Alpha Chen <alpha@kejadlen.dev>
date
parent orvkkony
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",