Add flash and undo for archive
Matches the existing completion pattern so archiving is reversible.

Assisted-by: Claude Opus 4.6 via pi
change wsrvtttkkxuwurkyzmkrzopqrvssqqqw
commit b1785cc8f3ba6a4b8c1283dc7e84dab53b7c7a87
author Alpha Chen <alpha@kejadlen.dev>
date
parent uxzxwusp
diff --git a/lib/ketchup/web.rb b/lib/ketchup/web.rb
index 94090bc..24853d3 100644
--- a/lib/ketchup/web.rb
+++ b/lib/ketchup/web.rb
@@ -96,12 +96,31 @@ module Ketchup
         r.on Integer do |series_id|
           @series = @user.series_dataset.where(id: series_id).sole
 
-          r.post "archive" do
-            DB.transaction do
-              @series.active_task&.destroy
-              @series.update(archived_at: Time.now)
+          r.on "archive" do
+            r.post do
+              DB.transaction do
+                @series.active_task&.destroy
+                @series.update(archived_at: Time.now)
+              end
+
+              note_title = @series.note.lines.first&.strip || @series.note
+              archive_path = "/series/#{series_id}/archive"
+              session["flash"] = { "message" => "Archived \u201c#{note_title}\u201d", "undo_path" => archive_path }
+              r.redirect "/"
+            end
+
+            r.delete do
+              DB.transaction do
+                @series.update(archived_at: nil)
+                unless @series.active_task
+                  last_completed = @series.completed_tasks.first
+                  due_date = last_completed ? @series.next_due_date(last_completed[:completed_at].to_date) : Date.today
+                  Task.create(series_id: @series.id, due_date: due_date)
+                end
+              end
+              response.status = 204
+              ""
             end
-            r.redirect "/"
           end
 
           r.is do
diff --git a/test/test_web.rb b/test/test_web.rb
index 4b05911..2fb2238 100644
--- a/test/test_web.rb
+++ b/test/test_web.rb
@@ -741,6 +741,8 @@ class TestWeb < Minitest::Test
     series = Ketchup::DB[:series].first
     archive_series(series[:id])
 
+    # First request shows the flash; second request clears it.
+    get "/", {}, auth_headers
     get "/", {}, auth_headers
     refute_includes last_response.body, "Call Mom"
   end
@@ -783,6 +785,52 @@ class TestWeb < Minitest::Test
     assert_includes [403, 404], last_response.status
   end
 
+  def test_archive_shows_flash_with_undo
+    create_series(note: "Call Mom", interval_unit: "week", interval_count: "2",
+                  first_due_date: (Date.today - 3).to_s)
+
+    series = Ketchup::DB[:series].first
+    archive_series(series[:id])
+
+    get "/", {}, auth_headers
+    body = last_response.body
+    assert_includes body, "Archived"
+    assert_includes body, "Call Mom"
+    assert_includes body, "data-undo-path=\"/series/#{series[:id]}/archive\""
+  end
+
+  def test_unarchive_restores_series
+    create_series(note: "Call Mom", interval_unit: "week", interval_count: "2",
+                  first_due_date: (Date.today - 3).to_s)
+
+    series = Ketchup::DB[:series].first
+    archive_series(series[:id])
+
+    refute_nil Ketchup::DB[:series].first(id: series[:id])[:archived_at]
+    assert_nil Ketchup::DB[:tasks].first(series_id: series[:id], completed_at: nil)
+
+    delete "/series/#{series[:id]}/archive", {}, auth_headers
+    assert_equal 204, last_response.status
+
+    assert_nil Ketchup::DB[:series].first(id: series[:id])[:archived_at]
+    active_task = Ketchup::DB[:tasks].first(series_id: series[:id], completed_at: nil)
+    refute_nil active_task
+  end
+
+  def test_unarchive_requires_own_series
+    create_series(
+      note: "Alice task", interval_unit: "day", interval_count: "1",
+      first_due_date: "2026-03-01",
+      headers: auth_headers(login: "alice@example.com")
+    )
+
+    series = Ketchup::DB[:series].first
+    archive_series(series[:id], login: "alice@example.com")
+
+    delete "/series/#{series[:id]}/archive", {}, auth_headers(login: "bob@example.com")
+    assert_includes [403, 404], last_response.status
+  end
+
   def test_series_detail_has_archive_button
     create_series(note: "Call Mom", interval_unit: "week", interval_count: "2",
                   first_due_date: "2026-03-01")