Fix snapshot visual issues in overdue sort and calendar
Overdue tasks sorted identically by date and urgency because
due dates happened to correlate with interval length. Shifting
"Review finances" earlier breaks the correlation.
Headless Chrome skips rendering beyond the viewport, so
selector-based calendar screenshots were either too tall or
blank. Temporarily resizing the viewport forces a full render,
then an explicit area clip captures just the column.
Assisted-by: Claude Opus 4.6 via Claude Code
diff --git a/lib/ketchup/seed.rb b/lib/ketchup/seed.rb
index 98c3518..a8ed1a1 100644
--- a/lib/ketchup/seed.rb
+++ b/lib/ketchup/seed.rb
@@ -24,14 +24,14 @@ module Ketchup
due_date: Date.today - 4,
history: []
},
- # Overdue — low urgency (long interval, few days past)
+ # Overdue — low urgency (long interval, many days past)
{
note: "Review finances",
interval_unit: "month",
interval_count: 1,
- due_date: Date.today - 3,
+ due_date: Date.today - 7,
history: [
- { due_date: Date.today - 33, completed_at: (Date.today - 32).to_time, note: "All good\n\n- Checked statements\n- Updated budget" },
+ { due_date: Date.today - 37, completed_at: (Date.today - 36).to_time, note: "All good\n\n- Checked statements\n- Updated budget" },
]
},
# Overdue — quarterly, just past due
diff --git a/lib/ketchup/snapshots.rb b/lib/ketchup/snapshots.rb
index ea86082..ac9cb87 100644
--- a/lib/ketchup/snapshots.rb
+++ b/lib/ketchup/snapshots.rb
@@ -138,15 +138,25 @@ module Ketchup
# 5. Upcoming column
entries << snap("upcoming", selector: '[x-data="upcoming"]')
- # 6. Upcoming column, calendar view (top)
+ # 6. Upcoming column, calendar view (top visible in viewport)
wait_for('[x-data="upcoming"] .sort-toggle button').click
wait_for(".calendar-day-empty")
- entries << snap("upcoming-calendar", selector: '[x-data="upcoming"]')
-
- # 7. Upcoming column, calendar view (scrolled to bottom)
- @browser.evaluate("window.scrollTo(0, document.body.scrollHeight)")
- entries << snap("upcoming-calendar-bottom", selector: '[x-data="upcoming"]')
- @browser.evaluate("window.scrollTo(0, 0)")
+ col = @browser.evaluate('document.querySelector(\'[x-data="upcoming"]\').getBoundingClientRect().toJSON()')
+ viewport_h = @browser.evaluate("window.innerHeight")
+ entries << snap("upcoming-calendar", area: { x: col["x"], y: 0, width: col["width"], height: viewport_h })
+
+ # 7. Upcoming column, calendar view (bottom)
+ #
+ # HACK: headless Chrome doesn't render content outside the viewport,
+ # so scrollTo + screenshot produces a blank image. Resizing the
+ # viewport to the full page height forces Chrome to paint everything,
+ # then we clip to the bottom viewport-sized slice of the column.
+ # There's probably a better way — captureBeyondViewport, full-page
+ # screenshot with crop, etc. — but this works for now.
+ page_h = @browser.evaluate("document.body.scrollHeight")
+ @browser.resize(width: 1280, height: page_h)
+ entries << snap("upcoming-calendar-bottom", area: { x: col["x"], y: page_h - viewport_h, width: col["width"], height: viewport_h })
+ @browser.resize(width: 1280, height: 900)
# 8. Sidebar (new series)
entries << snap("new-series", selector: ".column-aside")
@@ -253,11 +263,13 @@ module Ketchup
end
end
- def snap(name, selector: nil)
+ def snap(name, selector: nil, area: nil)
yield if block_given?
file = @output_dir / "#{name}.png"
- if selector
+ if area
+ @browser.screenshot(path: file.to_s, area: area)
+ elsif selector
@browser.screenshot(path: file.to_s, selector: selector)
else
@browser.screenshot(path: file.to_s)