Unify seed data between rake seed and snapshot capture
Snapshots built data through browser form automation while rake seed
generated random data. Both now share one deterministic dataset in
Ketchup::Seed::DATA, so snapshots show realistic content without
brittle click-through sequences.

Assisted-by: Claude Opus 4.6 via Claude Code
change qtzpyzlmtuqkunxyrszrslzrwkmmlwyl
commit e0f9ddfc0aae3763f803532c66f519668dccd526
author Alpha Chen <alpha@kejadlen.dev>
date
parent pswtvvww
diff --git a/Rakefile b/Rakefile
index 11471a8..85d90aa 100644
--- a/Rakefile
+++ b/Rakefile
@@ -30,71 +30,8 @@ task :seed do
 
   user = User.first || abort("No users yet — visit the app first to create one")
 
-  notes = [
-    "Call Mom",
-    "Water the plants\n\nCheck soil moisture *before* watering",
-    "Clean the kitchen",
-    "Back up laptop\n\n- Time Machine to external drive\n- Sync cloud photos\n- Verify **offsite** backup",
-    "Review finances",
-    "Dentist appointment\n\n**Dr. Chen**, 10am\n555-0142 to reschedule",
-    "Oil change",
-    "Haircut",
-    "Replace HVAC filter\n\nSize: **20x25x1**",
-    "Check smoke detectors",
-  ]
-
-  max_count = {
-    "day" => 14, "week" => 4, "month" => 6, "quarter" => 2, "year" => 2
-  }
-  overdue_spread = {
-    "day" => 7, "week" => 21, "month" => 60, "quarter" => 120, "year" => 180
-  }
-  completion_notes = [
-    "Done, no issues",
-    "Rescheduled from **last week**",
-    "Took longer than expected — **2 hours** instead of 1",
-    "Had to call back *twice*",
-    "All good\n\n- Changed filter\n- Reset thermostat",
-  ]
-
-  series_data = notes.map do |note|
-    unit = max_count.keys.sample
-    count = rand(1..max_count.fetch(unit))
-    spread = overdue_spread.fetch(unit)
-    due_date = Date.today + rand(-spread..spread)
-
-    interval_days = case unit
-                    when "day" then count
-                    when "week" then 7 * count
-                    when "month" then 30 * count
-                    when "quarter" then 91 * count
-                    when "year" then 365 * count
-                    end
-
-    history = if [true, false].sample
-                rand(1..4).times.map do |i|
-                  past_date = due_date - (interval_days * (i + 1))
-                  {
-                    due_date: past_date,
-                    completed_at: past_date.to_time + rand(0..3) * 86400,
-                    note: rand < 0.5 ? completion_notes.sample : nil
-                  }
-                end
-              else
-                []
-              end
-
-    {
-      note: note,
-      interval_unit: unit,
-      interval_count: count,
-      due_date: due_date,
-      history: history
-    }
-  end
-
-  Seed.call(user: user, series: series_data)
-  puts "Seeded #{notes.length} series for #{user.name} (#{user.login})"
+  Ketchup::Seed.call(user: user, series: Ketchup::Seed::DATA)
+  puts "Seeded #{Ketchup::Seed::DATA.length} series for #{user.name} (#{user.login})"
 end
 
 namespace :snapshots do
diff --git a/lib/ketchup/seed.rb b/lib/ketchup/seed.rb
index 9bdc6a6..98c3518 100644
--- a/lib/ketchup/seed.rb
+++ b/lib/ketchup/seed.rb
@@ -2,24 +2,128 @@
 
 require_relative "models"
 
-module Seed
-  def self.call(user:, series:)
-    series.each do |s|
-      created = Series.create_with_first_task(
-        user: user,
-        note: s[:note],
-        interval_unit: s[:interval_unit],
-        interval_count: s[:interval_count],
-        first_due_date: s[:due_date]
-      )
+module Ketchup
+  module Seed
+    DATA = [
+      # Overdue — high urgency (short interval, many days past)
+      {
+        note: "Water the plants\n\nCheck soil moisture *before* watering",
+        interval_unit: "day",
+        interval_count: 3,
+        due_date: Date.today - 6,
+        history: [
+          { due_date: Date.today - 9, completed_at: (Date.today - 9).to_time, note: "Done, no issues" },
+          { due_date: Date.today - 12, completed_at: (Date.today - 11).to_time, note: nil },
+        ]
+      },
+      # Overdue — moderate urgency
+      {
+        note: "Clean the kitchen",
+        interval_unit: "week",
+        interval_count: 1,
+        due_date: Date.today - 4,
+        history: []
+      },
+      # Overdue — low urgency (long interval, few days past)
+      {
+        note: "Review finances",
+        interval_unit: "month",
+        interval_count: 1,
+        due_date: Date.today - 3,
+        history: [
+          { due_date: Date.today - 33, completed_at: (Date.today - 32).to_time, note: "All good\n\n- Checked statements\n- Updated budget" },
+        ]
+      },
+      # Overdue — quarterly, just past due
+      {
+        note: "Dentist appointment\n\n**Dr. Chen**, 10am\n555-0142 to reschedule",
+        interval_unit: "quarter",
+        interval_count: 1,
+        due_date: Date.today - 1,
+        history: [
+          { due_date: Date.today - 92, completed_at: (Date.today - 92).to_time, note: "Rescheduled from **last week**" },
+          { due_date: Date.today - 183, completed_at: (Date.today - 182).to_time, note: nil },
+        ]
+      },
+      # Upcoming — soon
+      {
+        note: "Call Mom\n\nAsk about *weekend plans*\n- Bring **birthday cake**\n- Check flight times",
+        interval_unit: "week",
+        interval_count: 2,
+        due_date: Date.today + 2,
+        history: [
+          { due_date: Date.today - 12, completed_at: (Date.today - 12).to_time, note: "Had to call back *twice*" },
+          { due_date: Date.today - 26, completed_at: (Date.today - 25).to_time, note: nil },
+        ]
+      },
+      # Upcoming — next week
+      {
+        note: "Oil change",
+        interval_unit: "month",
+        interval_count: 3,
+        due_date: Date.today + 8,
+        history: []
+      },
+      # Upcoming — a couple weeks out
+      {
+        note: "Replace HVAC filter\n\nSize: **20x25x1**",
+        interval_unit: "quarter",
+        interval_count: 1,
+        due_date: Date.today + 18,
+        history: [
+          { due_date: Date.today - 73, completed_at: (Date.today - 73).to_time, note: "All good\n\n- Changed filter\n- Reset thermostat" },
+        ]
+      },
+      # Upcoming — far out
+      {
+        note: "Back up laptop\n\n- Time Machine to external drive\n- Sync cloud photos\n- Verify **offsite** backup",
+        interval_unit: "month",
+        interval_count: 1,
+        due_date: Date.today + 24,
+        history: [
+          { due_date: Date.today - 6, completed_at: (Date.today - 6).to_time, note: "Took longer than expected — **2 hours** instead of 1" },
+          { due_date: Date.today - 36, completed_at: (Date.today - 35).to_time, note: nil },
+        ]
+      },
+      # Completed history only — daily
+      {
+        note: "Check smoke detectors",
+        interval_unit: "year",
+        interval_count: 1,
+        due_date: Date.today + 140,
+        history: [
+          { due_date: Date.today - 225, completed_at: (Date.today - 225).to_time, note: "Replaced batteries in **hallway** unit" },
+          { due_date: Date.today - 590, completed_at: (Date.today - 589).to_time, note: nil },
+        ]
+      },
+      # Simple upcoming with no history
+      {
+        note: "Haircut",
+        interval_unit: "month",
+        interval_count: 2,
+        due_date: Date.today + 45,
+        history: []
+      },
+    ].freeze
 
-      s[:history].each do |h|
-        Task.create(
-          series_id: created.id,
-          due_date: h[:due_date],
-          completed_at: h[:completed_at],
-          note: h[:note]
+    def self.call(user:, series:)
+      series.each do |s|
+        created = Series.create_with_first_task(
+          user: user,
+          note: s[:note],
+          interval_unit: s[:interval_unit],
+          interval_count: s[:interval_count],
+          first_due_date: s[:due_date]
         )
+
+        s[:history].each do |h|
+          Task.create(
+            series_id: created.id,
+            due_date: h[:due_date],
+            completed_at: h[:completed_at],
+            note: h[:note]
+          )
+        end
       end
     end
   end
diff --git a/lib/ketchup/snapshots.rb b/lib/ketchup/snapshots.rb
index 24243dd..8291251 100644
--- a/lib/ketchup/snapshots.rb
+++ b/lib/ketchup/snapshots.rb
@@ -9,6 +9,7 @@ require "ferrum"
 require "puma"
 require "puma/configuration"
 
+require_relative "seed"
 require_relative "web"
 
 module Ketchup
@@ -42,51 +43,36 @@ module Ketchup
       private
 
       def run_capture
-        # Empty dashboard
-        snap("empty-dashboard") do
+        # 1. Dashboard — populated overdue + upcoming columns
+        snap("dashboard") do
           goto @base
           wait_for(".home")
         end
 
-        # New series form filled with markdown, before creating
+        # 2. Series detail — pick one with completed history
+        series_with_history = Series.first(
+          id: Task.exclude(completed_at: nil).select(:series_id)
+        ) || Series.first
+
+        snap("series-detail") do
+          goto "#{@base}/series/#{series_with_history.id}"
+          wait_for("#series-note-detail")
+        end
+
+        # 3. New series form with markdown, snap sidebar only
         goto @base
         wait_for("#new-series-form")
         fill_new_series(
-          note: "Call Mom\n\nAsk about *weekend plans*\n- Bring **birthday cake**\n- Check flight times",
+          note: "Call the vet\n\nAsk about *vaccination schedule*\n- Bring **shot records**\n- Check flea meds",
           interval_count: 2,
           interval_unit: "week"
         )
         snap("new-series-editing", selector: ".column-aside")
 
-        # Submit and capture series detail
-        @browser.at_css("#create-series-btn").click
-        wait_for("#series-note-detail")
-        snap("series-detail")
-
-        # Create more series for a populated dashboard
-        [
-          { note: "Water the plants", interval_count: 3, interval_unit: "day" },
-          { note: "Clean the kitchen", interval_count: 1, interval_unit: "week" },
-          { note: "Review finances", interval_count: 1, interval_unit: "month" },
-          { note: "Dentist appointment", interval_count: 1, interval_unit: "quarter" },
-        ].each do |series|
-          goto @base
-          wait_for("#new-series-form")
-          fill_new_series(**series)
-          @browser.at_css("#create-series-btn").click
-          wait_for("#series-note-detail")
-        end
-
-        snap("dashboard") do
-          goto @base
-          wait_for(".home")
-        end
-
-        # Complete a task
+        # 4. Complete a task, snap the resulting detail page
         goto @base
         wait_for(".complete-btn").click
         wait_for(".task-history")
-
         snap("after-complete")
 
         entries = @names.map { |name| { name: name } }
@@ -112,6 +98,8 @@ module Ketchup
 
         url = "http://127.0.0.1:#{launcher.connected_ports.first}"
         user = User.find_or_create(login: "snapshot@example.com") { |u| u.name = "Snapshot User" }
+        Ketchup::Seed.call(user: user, series: Ketchup::Seed::DATA)
+
         browser.headers.set(
           "Tailscale-User-Login" => user.login,
           "Tailscale-User-Name" => user.name
diff --git a/test/test_seed.rb b/test/test_seed.rb
index 1942311..49a8bbd 100644
--- a/test/test_seed.rb
+++ b/test/test_seed.rb
@@ -25,7 +25,7 @@ class TestSeed < Minitest::Test
       }
     ]
 
-    Seed.call(user: user, series: series_data)
+    Ketchup::Seed.call(user: user, series: series_data)
 
     assert_equal 1, Series.count
     assert_equal 1, Task.count
@@ -51,7 +51,7 @@ class TestSeed < Minitest::Test
       }
     ]
 
-    Seed.call(user: user, series: series_data)
+    Ketchup::Seed.call(user: user, series: series_data)
 
     assert_equal 3, Task.count
     assert_equal 2, Task.exclude(completed_at: nil).count