Remove series detail panel
The series show page replaced the slide-over panel for viewing series
details. The panel route, views, and tests are now dead code.

Assisted-by: Claude Opus 4.6 via Claude Code
change szvvmvwmykwouwqvqqpkqltmsunxowzp
commit b4653c29c995f803fb6be8b06ab98205437613e5
author Alpha Chen <alpha@kejadlen.dev>
date
parent nqpntqsu
diff --git a/lib/ketchup/views/series_detail.rb b/lib/ketchup/views/series_detail.rb
deleted file mode 100644
index cbc8bd2..0000000
--- a/lib/ketchup/views/series_detail.rb
+++ /dev/null
@@ -1,131 +0,0 @@
-# frozen_string_literal: true
-
-require "phlex"
-
-module Views
-  class SeriesDetail < Phlex::HTML
-    def initialize(series:, csrf: nil)
-      @series = series
-      @csrf = csrf
-    end
-
-    def view_template
-      active_task = @series.active_task
-      div(class: "panel-inner", "x-data": "{ editing: false }") do
-        div(class: "panel-header") do
-          a(href: "/", class: "panel-close", "aria-label": "Close") { "←" }
-          div(class: "panel-actions") do
-            button(
-              class: "panel-action",
-              "x-show": "!editing",
-              "x-on:click": "editing = true; $dispatch('start-editing')"
-            ) { "Edit" }
-            button(
-              class: "panel-action",
-              "x-show": "editing",
-              "x-on:click": "editing = false; $dispatch('stop-editing')"
-            ) { "Done" }
-          end
-        end
-
-        div(class: "panel-body") do
-          div(
-            id: "series-note-detail",
-            class: "series-note",
-            "data-value": @series.note || "",
-            "data-series-id": @series.id.to_s
-          )
-
-          dl(class: "detail-fields") do
-            dt { "Repeat every" }
-            dd("x-show": "!editing") do
-              plain interval_text(@series.interval_count, @series.interval_unit)
-            end
-            dd(
-              class: "detail-edit-interval",
-              "x-show": "editing",
-              "x-cloak": true,
-              "x-data": "intervalEditor(#{@series.id}, #{@series.interval_count}, '#{@series.interval_unit}')",
-            ) do
-              input(
-                type: "number",
-                class: "detail-input detail-input-count",
-                min: 1,
-                "x-model.number": "count",
-                "x-on:change": "save()"
-              )
-              select(
-                class: "detail-input detail-input-unit",
-                "x-model": "unit",
-                "x-on:change": "save()"
-              ) do
-                INTERVAL_OPTIONS.each { |val, label| option(value: val) { label } }
-              end
-            end
-
-            if active_task
-              dt { "Due date" }
-              dd(
-                "x-show": "!editing",
-                "x-text": "new Date('#{active_task[:due_date]}T00:00').toLocaleDateString()"
-              ) { active_task[:due_date].to_s }
-              dd(
-                "x-show": "editing",
-                "x-cloak": true,
-                "x-data": "dueDateEditor(#{@series.id}, '#{active_task[:due_date]}')"
-              ) do
-                input(
-                  type: "date",
-                  class: "detail-input detail-input-date",
-                  "x-model": "dueDate",
-                  "x-on:change": "save()"
-                )
-              end
-
-              if active_task.urgency > 0
-                dt { "Urgency" }
-                dd { "#{format("%.1f", active_task.urgency)}×" }
-              end
-            end
-          end
-
-          unless @series.completed_tasks.empty?
-            div(class: "task-history") do
-              h3 { "History" }
-              ul do
-                @series.completed_tasks.each do |ct|
-                  li(
-                    class: "task-history-item",
-                    "x-data": "historyNote(#{@series.id}, #{ct[:id]}, #{ct[:note] ? "true" : "false"})"
-                  ) do
-                    div(class: "task-history-row") do
-                      span(class: "task-history-check") { "✓" }
-                      span(class: "task-history-date") { ct[:completed_at].strftime("%Y-%m-%d") }
-                      span(
-                        class: "task-history-add-note",
-                        "x-show": "!hasNote && !editing",
-                        "x-on:click": "edit()"
-                      ) { "add a note..." }
-                    end
-                    div(
-                      class: "task-history-note-editor",
-                      "data-value": ct[:note] || "",
-                      "x-show": "hasNote || editing",
-                      "x-ref": "editor"
-                    )
-                  end
-                end
-              end
-            end
-          end
-        end
-      end
-    end
-
-    private
-
-    def interval_text(count, unit)
-      "#{count} #{count == 1 ? unit : "#{unit}s"}"
-    end
-  end
-end
diff --git a/lib/ketchup/views/series_panel.rb b/lib/ketchup/views/series_panel.rb
deleted file mode 100644
index 0d21d45..0000000
--- a/lib/ketchup/views/series_panel.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require "phlex"
-
-require_relative "series_detail"
-
-module Views
-  class SeriesPanel < Phlex::HTML
-    def initialize(series:, csrf: nil)
-      @series = series
-      @csrf = csrf
-    end
-
-    def view_template
-      render SeriesDetail.new(series: @series, csrf: @csrf)
-    end
-  end
-end
diff --git a/lib/ketchup/web.rb b/lib/ketchup/web.rb
index 4c43e7c..5c75eae 100644
--- a/lib/ketchup/web.rb
+++ b/lib/ketchup/web.rb
@@ -10,7 +10,6 @@ require_relative "views/calendar"
 require_relative "views/agenda"
 require_relative "views/series/new"
 require_relative "views/series/show"
-require_relative "views/series_panel"
 require_relative "views/user_panel"
 
 class Web < Roda
@@ -130,10 +129,6 @@ class Web < Roda
       r.on Integer do |series_id|
         @series = @user.series_dataset.where(id: series_id).sole
 
-        r.get "panel" do
-          Views::SeriesPanel.new(series: @series, csrf: method(:csrf_token)).call
-        end
-
         r.is do
           r.get do
             Views::Series::Show.new(series: @series, current_user: @user, csrf: method(:csrf_token)).call
diff --git a/test/test_web.rb b/test/test_web.rb
index 817ed49..0ef34ae 100644
--- a/test/test_web.rb
+++ b/test/test_web.rb
@@ -272,23 +272,6 @@ class TestWeb < Minitest::Test
     assert_includes last_response.body, "2 weeks"
   end
 
-  def test_get_series_panel_shows_completed_history
-    create_series(note: "Call Mom", interval_unit: "week", interval_count: "1",
-                  first_due_date: "2026-03-01")
-
-    task = DB[:tasks].first
-    series = DB[:series].first
-    csrf_post "/series/#{series[:id]}/tasks/#{task[:id]}/complete", {}, auth_headers
-
-    completed_task = DB[:tasks].first(id: task[:id])
-    patch "/series/#{series[:id]}/tasks/#{completed_task[:id]}/note", { note: "Left a message" }, auth_headers
-
-    get "/series/#{series[:id]}/panel", {}, auth_headers
-    assert last_response.ok?
-    assert_includes last_response.body, "Left a message"
-    assert_includes last_response.body, "task-history"
-  end
-
   def test_get_series_requires_own_series
     create_series(
       note: "Alice task", interval_unit: "day", interval_count: "1",
@@ -494,31 +477,6 @@ class TestWeb < Minitest::Test
     assert_equal 404, last_response.status
   end
 
-  def test_get_series_panel_returns_fragment
-    create_series(note: "Call Mom", interval_unit: "week", interval_count: "2",
-                  first_due_date: "2026-03-01")
-
-    series = DB[:series].first
-    get "/series/#{series[:id]}/panel", {}, auth_headers
-    assert last_response.ok?
-    assert_includes last_response.body, "Call Mom"
-    assert_includes last_response.body, "panel-inner"
-    # Fragment — no <html>, no Layout wrapper
-    refute_includes last_response.body, "<!DOCTYPE"
-  end
-
-  def test_get_series_panel_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 = DB[:series].first
-    get "/series/#{series[:id]}/panel", {}, auth_headers(login: "bob@example.com")
-    assert_equal 404, last_response.status
-  end
-
   def test_layout_includes_footer
     get "/", {}, auth_headers
     assert_includes last_response.body, "site-footer"