Modernize Ruby style for 4.0
Ruby 4.0 freezes string literals by default, making the
frozen_string_literal magic comments redundant.
Assisted-by: Claude Opus 5 via Claude Code
diff --git a/Rakefile b/Rakefile
index 319ab37..547fb4c 100644
--- a/Rakefile
+++ b/Rakefile
@@ -35,7 +35,7 @@ task :seed do
end
namespace :snapshots do
- cache_dir = File.join(ENV.fetch("XDG_CACHE_HOME", File.expand_path("~/.cache")), "ketchup", "snapshots")
+ cache_dir = Pathname(ENV.fetch("XDG_CACHE_HOME", "~/.cache")).expand_path / "ketchup" / "snapshots"
css_sources = {
"public/css/reset.css" => "reset.css",
"public/css/utopia.css" => "utopia.css",
@@ -44,13 +44,13 @@ namespace :snapshots do
directory cache_dir
- css_targets = css_sources.map do |src, basename|
- target = File.join(cache_dir, basename)
+ css_targets = css_sources.map { |src, basename|
+ target = cache_dir / basename
file target => [cache_dir, src] do
cp src, target
end
target
- end
+ }
desc "Capture screenshots of the app in key states"
task :capture do
@@ -60,7 +60,7 @@ namespace :snapshots do
ENV["BUILD_DATE"] ||= "2025-01-01"
require "ketchup/snapshots"
- output_dir = File.join(cache_dir, "current")
+ output_dir = cache_dir / "current"
Ketchup::Snapshots::Capture.new(output_dir: output_dir).call
if ENV["CI"]
@@ -74,7 +74,7 @@ namespace :snapshots do
require "erb"
require "ketchup/snapshots"
- base_dir = Pathname(cache_dir)
+ base_dir = cache_dir
baseline_dir = base_dir / "baseline"
current_dir = base_dir / "current"
@@ -108,7 +108,7 @@ namespace :snapshots do
desc "Capture, diff, and open the viewer"
task review: :diff do
- system("open", (Pathname(cache_dir) / "diff.html").to_s)
+ system("open", (cache_dir / "diff.html").to_s)
end
desc "Generate gallery HTML from images in a directory"
@@ -116,19 +116,19 @@ namespace :snapshots do
require "erb"
require "ketchup/snapshots"
- images_dir = Pathname(args.fetch(:images_dir) { File.join(cache_dir, "current") })
- output_path = Pathname(args.fetch(:output_path) { File.join(cache_dir, "gallery.html") })
+ images_dir = Pathname(args.fetch(:images_dir) { cache_dir / "current" })
+ output_path = Pathname(args.fetch(:output_path) { cache_dir / "gallery.html" })
- css_sources.each_key { |src| cp src, output_path.dirname.to_s }
+ css_sources.each do |src, _basename|
+ cp src, output_path.dirname.to_s
+ end
title = "Ketchup Snapshots"
- images_by_viewport = Ketchup::Snapshots::VIEWPORTS.keys.each_with_object({}) do |viewport, result|
+ images_by_viewport = Ketchup::Snapshots::VIEWPORTS.to_h do |viewport, _size|
viewport_dir = images_dir / viewport
entries = Ketchup::Snapshots::Entry.read_manifest(viewport_dir)
images_rel = viewport_dir.relative_path_from(output_path.dirname)
- result[viewport] = entries.map do |entry|
- { entry: entry, filename: (images_rel / "#{entry.name}.png").to_s }
- end
+ [viewport, entries.map { |entry| { entry: entry, filename: (images_rel / "#{entry.name}.png").to_s } }]
end
template = (Pathname(__dir__) / "templates/snapshot_gallery.erb").read
diff --git a/config.ru b/config.ru
index 2ce7855..67059ef 100644
--- a/config.ru
+++ b/config.ru
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
$LOAD_PATH.unshift(File.expand_path("lib", __dir__))
unless ENV["COMMIT_SHA"]
diff --git a/db/migrate/001_create_users.rb b/db/migrate/001_create_users.rb
index 6bfa8b0..1e70607 100644
--- a/db/migrate/001_create_users.rb
+++ b/db/migrate/001_create_users.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
Sequel.migration do
change do
create_table(:users) do
diff --git a/db/migrate/002_create_series.rb b/db/migrate/002_create_series.rb
index 459dd6c..9388f46 100644
--- a/db/migrate/002_create_series.rb
+++ b/db/migrate/002_create_series.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
Sequel.migration do
change do
create_table(:series) do
diff --git a/db/migrate/003_create_tasks.rb b/db/migrate/003_create_tasks.rb
index cea2468..2e95663 100644
--- a/db/migrate/003_create_tasks.rb
+++ b/db/migrate/003_create_tasks.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
Sequel.migration do
change do
create_table(:tasks) do
diff --git a/db/migrate/004_add_note_to_tasks.rb b/db/migrate/004_add_note_to_tasks.rb
index b406de8..8ba3aed 100644
--- a/db/migrate/004_add_note_to_tasks.rb
+++ b/db/migrate/004_add_note_to_tasks.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
Sequel.migration do
change do
alter_table(:tasks) do
diff --git a/db/migrate/005_add_email_remove_name_from_users.rb b/db/migrate/005_add_email_remove_name_from_users.rb
index 69fd3aa..1a268a1 100644
--- a/db/migrate/005_add_email_remove_name_from_users.rb
+++ b/db/migrate/005_add_email_remove_name_from_users.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
Sequel.migration do
change do
alter_table(:users) do
diff --git a/db/migrate/006_add_archived_at_to_series.rb b/db/migrate/006_add_archived_at_to_series.rb
index 962c54c..43bc0b4 100644
--- a/db/migrate/006_add_archived_at_to_series.rb
+++ b/db/migrate/006_add_archived_at_to_series.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
Sequel.migration do
change do
add_column :series, :archived_at, DateTime
diff --git a/db/migrate/007_add_sharing.rb b/db/migrate/007_add_sharing.rb
index f98e9f8..f65e254 100644
--- a/db/migrate/007_add_sharing.rb
+++ b/db/migrate/007_add_sharing.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
Sequel.migration do
change do
alter_table(:series) do
diff --git a/lib/ketchup/config.rb b/lib/ketchup/config.rb
index bf3f77c..82c3614 100644
--- a/lib/ketchup/config.rb
+++ b/lib/ketchup/config.rb
@@ -1,5 +1,4 @@
# rbs_inline: enabled
-# frozen_string_literal: true
require "securerandom"
@@ -52,7 +51,7 @@ module Ketchup
default_user: env["DEFAULT_USER"],
commit_sha: env["COMMIT_SHA"],
change_id: env["CHANGE_ID"]&.slice(0, 8),
- build_date: env["BUILD_DATE"]
+ build_date: env["BUILD_DATE"],
)
end
end
diff --git a/lib/ketchup/db.rb b/lib/ketchup/db.rb
index 2a74ce0..306b0de 100644
--- a/lib/ketchup/db.rb
+++ b/lib/ketchup/db.rb
@@ -1,5 +1,4 @@
-# frozen_string_literal: true
-
+require "pathname"
require "sequel"
require_relative "config"
@@ -7,5 +6,5 @@ require_relative "config"
module Ketchup
DB = Sequel.sqlite(CONFIG.database_url)
Sequel.extension :migration
- Sequel::Migrator.run(DB, File.expand_path("../../db/migrate", __dir__))
+ Sequel::Migrator.run(DB, (Pathname(__dir__) / "../../db/migrate").expand_path)
end
diff --git a/lib/ketchup/dev_auth.rb b/lib/ketchup/dev_auth.rb
index 4b8ec1a..ceee140 100644
--- a/lib/ketchup/dev_auth.rb
+++ b/lib/ketchup/dev_auth.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
module Ketchup
class DevAuth
def initialize(app, login)
diff --git a/lib/ketchup/models.rb b/lib/ketchup/models.rb
index fc234e2..acd79eb 100644
--- a/lib/ketchup/models.rb
+++ b/lib/ketchup/models.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require_relative "db"
module Ketchup
@@ -24,7 +22,7 @@ module Ketchup
Sequel[:series][:note],
Sequel[:series][:interval_unit],
Sequel[:series][:interval_count],
- Sequel[:series][:shared]
+ Sequel[:series][:shared],
)
end
@@ -69,7 +67,7 @@ module Ketchup
streak = 0
on_time = 0
- completed.each_with_index do |t, i|
+ completed.each.with_index do |t, i|
on_time += 1 if t[:completed_at].to_date <= t[:due_date]
streak += 1 if i == streak && t[:completed_at].to_date <= t[:due_date]
end
@@ -101,12 +99,12 @@ module Ketchup
note: note,
interval_unit: interval_unit,
interval_count: interval_count,
- shared: shared
+ shared: shared,
)
Task.create(
series_id: series.id,
- due_date: first_due_date
+ due_date: first_due_date,
)
series
@@ -122,7 +120,7 @@ module Ketchup
# same day-of-month. Urgency only needs a rough ratio, so fixed counts
# are fine and avoid coupling to a specific start date.
INTERVAL_DAYS = {
- "day" => 1, "week" => 7, "month" => 30, "quarter" => 91, "year" => 365
+ "day" => 1, "week" => 7, "month" => 30, "quarter" => 91, "year" => 365,
}.freeze
def urgency
@@ -141,7 +139,7 @@ module Ketchup
DB.transaction do
update(
completed_at: Time.new(completed_on.year, completed_on.month, completed_on.day),
- completed_by_user_id: by.id
+ completed_by_user_id: by.id,
)
Task.create(series_id: series.id, due_date: series.next_due_date(completed_on))
end
diff --git a/lib/ketchup/seed.rb b/lib/ketchup/seed.rb
index 1cb9143..d91f446 100644
--- a/lib/ketchup/seed.rb
+++ b/lib/ketchup/seed.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require_relative "models"
module Ketchup
@@ -15,7 +13,7 @@ module Ketchup
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
{
@@ -34,7 +32,7 @@ module Ketchup
due_date: Date.today - 7,
history: [
{ 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
{
@@ -45,7 +43,7 @@ module Ketchup
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
{
@@ -56,7 +54,7 @@ module Ketchup
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
{
@@ -75,7 +73,7 @@ module Ketchup
shared: true,
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
{
@@ -86,7 +84,7 @@ module Ketchup
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
{
@@ -98,7 +96,7 @@ module Ketchup
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
{
diff --git a/lib/ketchup/snapshots.rb b/lib/ketchup/snapshots.rb
index 1a3be7c..9e7259f 100644
--- a/lib/ketchup/snapshots.rb
+++ b/lib/ketchup/snapshots.rb
@@ -1,8 +1,7 @@
-# frozen_string_literal: true
-
require "fileutils"
require "json"
require "logger"
+require "tempfile"
require "uri"
require "pathname"
@@ -11,6 +10,7 @@ require "puma"
require "puma/configuration"
require "rack/builder"
+require_relative "dev_auth"
require_relative "seed"
require_relative "web"
@@ -28,9 +28,9 @@ module Ketchup
manifest = dir / "manifest.json"
return [] unless manifest.exist?
- JSON.parse(manifest.read).map do |e|
+ JSON.parse(manifest.read).map { |e|
new(name: e.fetch("name"), path: e.fetch("path"), selector: e["selector"], viewport: e.fetch("viewport", "desktop"))
- end
+ }
end
end
@@ -44,10 +44,10 @@ module Ketchup
# Returns { "desktop" => [Comparison, ...], "mobile" => [Comparison, ...] }
def comparisons_by_viewport
- VIEWPORTS.keys.each_with_object({}) do |viewport, result|
+ VIEWPORTS.to_h do |viewport, _size|
baseline = read_entries(@baseline_dir / viewport)
current = read_entries(@current_dir / viewport)
- result[viewport] = compare(baseline, current)
+ [viewport, compare(baseline, current)]
end
end
@@ -59,13 +59,12 @@ module Ketchup
private
def read_entries(dir)
- Entry.read_manifest(dir).each_with_object({}) { |e, h| h[e.name] = e }
+ Entry.read_manifest(dir).to_h { [it.name, it] }
end
def compare(baseline, current)
return current.map { |name, entry| Comparison.new(name: name, baseline: baseline[name], current: entry) } if baseline.keys == current.keys
- require "tempfile"
baseline_file = Tempfile.new("baseline")
current_file = Tempfile.new("current")
baseline_file.write(baseline.keys.join("\n") + "\n")
@@ -73,7 +72,7 @@ module Ketchup
baseline_file.close
current_file.close
- `diff -U9999 #{baseline_file.path} #{current_file.path}`.lines.drop(2).filter_map do |line|
+ `diff -U9999 #{baseline_file.path} #{current_file.path}`.lines.drop(2).filter_map { |line|
name = line[1..].chomp
next if name.empty?
case line[0]
@@ -81,7 +80,7 @@ module Ketchup
when "-" then Comparison.new(name: name, baseline: baseline.fetch(name), current: nil)
when "+" then Comparison.new(name: name, baseline: nil, current: current.fetch(name))
end
- end
+ }
end
end
@@ -90,8 +89,12 @@ module Ketchup
class PageCheckError < StandardError
def initialize(snap_name:, console_errors:, page_errors:)
lines = ["Page errors during snap #{snap_name.inspect}:"]
- console_errors.each { |e| lines << " console.#{e[:type]}: #{e[:text]}" }
- page_errors.each { |e| lines << " page error: #{e}" }
+ console_errors.each do |e|
+ lines << " console.#{e[:type]}: #{e[:text]}"
+ end
+ page_errors.each do |e|
+ lines << " page error: #{e}"
+ end
super(lines.join("\n"))
end
end
@@ -115,7 +118,7 @@ module Ketchup
@browser = Ferrum::Browser.new(
headless: true,
window_size: VIEWPORTS.fetch("desktop"),
- browser_options: browser_options
+ browser_options: browser_options,
)
subscribe_page_errors
@@ -264,7 +267,7 @@ module Ketchup
interval_unit: "week",
interval_count: 1,
first_due_date: Date.today - 1,
- shared: true
+ shared: true,
)
Series.create_with_first_task(
@@ -273,7 +276,7 @@ module Ketchup
interval_unit: "week",
interval_count: 2,
first_due_date: Date.today + 2,
- shared: true
+ shared: true,
)
entries << snap("dashboard-shared") do
@@ -297,8 +300,6 @@ module Ketchup
end
def default_server(browser)
- require_relative "dev_auth"
-
app = Rack::Builder.app do
use Ketchup::DevAuth, "snapshot@example.com"
run Web.freeze.app
@@ -378,7 +379,7 @@ module Ketchup
def fill_new_series_form(note:, interval_count: 1, interval_unit: "day")
textarea = wait_for("#series-note-editor textarea")
textarea.focus
- note.each_line(chomp: true).with_index do |line, i|
+ note.lines(chomp: true).each.with_index do |line, i|
@browser.keyboard.type(:Enter) if i > 0
@browser.keyboard.type(line) unless line.empty?
end
@@ -432,7 +433,7 @@ module Ketchup
def subscribe_page_errors
@browser.on("Runtime.consoleAPICalled") do |params|
type = params["type"]
- text = params["args"].map { |a| a["value"].to_s }.join(" ")
+ text = params["args"].map { it["value"].to_s }.join(" ")
@console_messages << { type: type, text: text }
end
@@ -451,11 +452,11 @@ module Ketchup
yield
- console_errors = @console_messages.select { |m| %(error warning).include?(m[:type]) }
+ console_errors = @console_messages.select { %(error warning).include?(it[:type]) }
unless console_errors.empty? && @page_errors.empty?
raise PageCheckError.new(
- snap_name:, console_errors:, page_errors: @page_errors.dup
+ snap_name:, console_errors:, page_errors: @page_errors.dup,
)
end
end
diff --git a/lib/ketchup/views/dashboard.rb b/lib/ketchup/views/dashboard.rb
index fcb6183..d02b979 100644
--- a/lib/ketchup/views/dashboard.rb
+++ b/lib/ketchup/views/dashboard.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require "phlex"
require_relative "layout"
@@ -8,7 +6,7 @@ require_relative "task_card"
module Ketchup
module Views
- INTERVAL_OPTIONS = Series::INTERVAL_UNITS.map { |u| [u, "#{u}(s)"] }.freeze
+ INTERVAL_OPTIONS = Series::INTERVAL_UNITS.map { [it, "#{it}(s)"] }.freeze
AGENDA_DAYS = 7
@@ -20,7 +18,7 @@ module Ketchup
end
def view_template
- overdue = @current_user.overdue_tasks.all.sort_by { |t| -t.urgency }
+ overdue = @current_user.overdue_tasks.all.sort_by { -it.urgency }
upcoming = @current_user.upcoming_tasks.all
render Layout.new(current_user: @current_user, flash: @flash) do
@@ -79,8 +77,7 @@ module Ketchup
end
def render_agenda(upcoming, overdue_count: 0)
- tasks_by_date = {}
- upcoming.each { |t| (tasks_by_date[t[:due_date]] ||= []) << t }
+ tasks_by_date = upcoming.group_by { it[:due_date] }
today = Date.today
diff --git a/lib/ketchup/views/layout.rb b/lib/ketchup/views/layout.rb
index 8d9e785..4847bfe 100644
--- a/lib/ketchup/views/layout.rb
+++ b/lib/ketchup/views/layout.rb
@@ -1,21 +1,18 @@
-# frozen_string_literal: true
-
require "digest"
+require "pathname"
require "phlex"
module Ketchup
module Views
class Layout < Phlex::HTML
ASSET_VERSIONS = begin
- root = File.expand_path("../../../public", __dir__)
+ root = Pathname(__dir__) / "../../../public"
%w[
/css/reset.css
/css/utopia.css
/css/app.css
/js/app.js
- ].to_h {|path|
- [path, Digest::MD5.file(File.join(root, path)).hexdigest[0, 10]]
- }.freeze
+ ].to_h { [it, Digest::MD5.file(root / it.delete_prefix("/")).hexdigest[0, 10]] }.freeze
end
def initialize(current_user:, title: "Ketchup", active_view: nil, flash: nil)
diff --git a/lib/ketchup/views/series/new.rb b/lib/ketchup/views/series/new.rb
index 0c698f4..6c67f42 100644
--- a/lib/ketchup/views/series/new.rb
+++ b/lib/ketchup/views/series/new.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require "phlex"
require_relative "../layout"
@@ -25,7 +23,7 @@ module Ketchup
button(
class: "section-edit-btn",
id: "create-series-btn",
- disabled: true
+ disabled: true,
) { "Create" }
end
@@ -41,10 +39,12 @@ module Ketchup
div(class: "interval") do
input(
type: "number", id: "interval_count", name: "interval_count",
- min: 1, value: 1, required: true
+ min: 1, value: 1, required: true,
)
select(id: "interval_unit", name: "interval_unit", required: true) do
- Views::INTERVAL_OPTIONS.each { |val, label| option(value: val) { label } }
+ Views::INTERVAL_OPTIONS.each do |val, label|
+ option(value: val) { label }
+ end
end
end
end
@@ -53,7 +53,7 @@ module Ketchup
label(for: "first_due_date") { "First due date" }
input(
type: "date", id: "first_due_date", name: "first_due_date",
- value: Date.today.to_s, required: true
+ value: Date.today.to_s, required: true,
)
end
diff --git a/lib/ketchup/views/series/show.rb b/lib/ketchup/views/series/show.rb
index 50fca78..4caba65 100644
--- a/lib/ketchup/views/series/show.rb
+++ b/lib/ketchup/views/series/show.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require "phlex"
require_relative "../layout"
@@ -32,7 +30,7 @@ module Ketchup
button(
class: "section-edit-btn",
"x-show": "!editing",
- "x-on:click": "startEditing()"
+ "x-on:click": "startEditing()",
) do
plain "Edit"
end
@@ -40,7 +38,7 @@ module Ketchup
class: "section-edit-btn section-edit-btn--cancel",
"x-show": "editing",
"x-cloak": true,
- "x-on:click": "cancel()"
+ "x-on:click": "cancel()",
) do
plain "Cancel"
end
@@ -48,7 +46,7 @@ module Ketchup
class: "section-edit-btn",
"x-show": "editing",
"x-cloak": true,
- "x-on:click": "save()"
+ "x-on:click": "save()",
) do
plain "Save"
end
@@ -71,7 +69,7 @@ module Ketchup
dd(
class: "detail-edit-interval",
"x-show": "editing",
- "x-cloak": true
+ "x-cloak": true,
) do
input(
type: "number",
@@ -83,7 +81,9 @@ module Ketchup
class: "detail-input detail-input-unit",
"x-model": "unit"
) do
- INTERVAL_OPTIONS.each { |val, label| option(value: val) { label } }
+ INTERVAL_OPTIONS.each do |val, label|
+ option(value: val) { label }
+ end
end
end
@@ -91,7 +91,7 @@ module Ketchup
dd("x-show": "!editing") { @series.shared ? "Yes" : "No" }
dd(
"x-show": "editing",
- "x-cloak": true
+ "x-cloak": true,
) do
label(class: "detail-checkbox-label") do
input(
@@ -124,7 +124,7 @@ module Ketchup
class: "task-history-date",
"x-show": "!editingDate",
"x-on:click": "editingDate = true; $nextTick(() => $refs.dateInput.focus())",
- "x-text": "new Date(dueDate + 'T00:00').toLocaleDateString()"
+ "x-text": "new Date(dueDate + 'T00:00').toLocaleDateString()",
) { active_task[:due_date].to_s }
input(
type: "date",
@@ -134,7 +134,7 @@ module Ketchup
"x-model": "dueDate",
"x-ref": "dateInput",
"x-on:change": "save()",
- "x-on:keydown.escape": "dueDate = '#{active_task[:due_date]}'; editingDate = false"
+ "x-on:keydown.escape": "dueDate = '#{active_task[:due_date]}'; editingDate = false",
)
end
@@ -158,7 +158,7 @@ module Ketchup
completed_date = ct[:completed_at].strftime("%Y-%m-%d")
li(
class: "task-history-item",
- "x-data": "{ ...historyNote(#{@series.id}, #{ct[:id]}, #{ct[:note] ? "true" : "false"}), ...completedDateEditor(#{@series.id}, #{ct[:id]}, '#{completed_date}') }"
+ "x-data": "{ ...historyNote(#{@series.id}, #{ct[:id]}, #{ct[:note] ? "true" : "false"}), ...completedDateEditor(#{@series.id}, #{ct[:id]}, '#{completed_date}') }",
) do
div(class: "task-history-row") do
span(class: "task-history-check") { "✓" }
@@ -166,7 +166,7 @@ module Ketchup
class: "task-history-date",
"x-show": "!editingDate",
"x-on:click": "editingDate = true; $nextTick(() => $refs.dateInput.focus())",
- "x-text": "new Date(completedDate + 'T00:00').toLocaleDateString()"
+ "x-text": "new Date(completedDate + 'T00:00').toLocaleDateString()",
) { completed_date }
input(
type: "date",
@@ -177,7 +177,7 @@ module Ketchup
"x-ref": "dateInput",
"x-on:blur": "save()",
"x-on:keydown.enter": "$el.blur()",
- "x-on:keydown.escape": "cancel()"
+ "x-on:keydown.escape": "cancel()",
)
span(
class: "task-history-add-note",
@@ -189,7 +189,7 @@ module Ketchup
class: "task-history-note-editor",
"data-value": ct[:note] || "",
"x-show": "hasNote || editing",
- "x-ref": "editor"
+ "x-ref": "editor",
)
end
end
diff --git a/lib/ketchup/views/shared_icon.rb b/lib/ketchup/views/shared_icon.rb
index 27eb791..593e147 100644
--- a/lib/ketchup/views/shared_icon.rb
+++ b/lib/ketchup/views/shared_icon.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require "phlex"
module Ketchup
diff --git a/lib/ketchup/views/task_card.rb b/lib/ketchup/views/task_card.rb
index c8cb682..346bea4 100644
--- a/lib/ketchup/views/task_card.rb
+++ b/lib/ketchup/views/task_card.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require "phlex"
require_relative "shared_icon"
@@ -24,13 +22,13 @@ module Ketchup
button(
type: "submit", title: "Complete",
class: "complete-btn",
- **{ "aria-label": "Complete #{name}" }
+ **{ "aria-label": "Complete #{name}" },
) { "✓" }
end
div(class: "task-body") do
a(
href: "/series/#{@task[:series_id]}",
- class: "task-name stretched-link"
+ class: "task-name stretched-link",
) { name }
if @shared || @overdue
div(class: "task-meta") do
diff --git a/lib/ketchup/views/user/show.rb b/lib/ketchup/views/user/show.rb
index e094658..84868a8 100644
--- a/lib/ketchup/views/user/show.rb
+++ b/lib/ketchup/views/user/show.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require "phlex"
require_relative "../layout"
@@ -28,7 +26,7 @@ module Ketchup
button(
class: "section-edit-btn",
"x-show": "!editing",
- "x-on:click": "editing = true"
+ "x-on:click": "editing = true",
) do
plain "Edit"
end
@@ -36,7 +34,7 @@ module Ketchup
class: "section-edit-btn section-edit-btn--cancel",
"x-show": "editing",
"x-cloak": true,
- "x-on:click": "editing = false; location.reload()"
+ "x-on:click": "editing = false; location.reload()",
) do
plain "Cancel"
end
@@ -44,7 +42,7 @@ module Ketchup
class: "section-edit-btn",
"x-show": "editing",
"x-cloak": true,
- "x-on:click": "editing = false; document.getElementById('user-form').requestSubmit()"
+ "x-on:click": "editing = false; document.getElementById('user-form').requestSubmit()",
) do
plain "Save"
end
@@ -72,7 +70,7 @@ module Ketchup
form: "user-form",
class: "detail-input",
value: email,
- placeholder: "for notifications"
+ placeholder: "for notifications",
)
end
end
diff --git a/lib/ketchup/web.rb b/lib/ketchup/web.rb
index ea2eedd..8c537cb 100644
--- a/lib/ketchup/web.rb
+++ b/lib/ketchup/web.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require "json"
require "roda"
@@ -112,7 +110,7 @@ module Ketchup
"message" => "Archived",
"title" => note_title,
"path" => "/series/#{series_id}",
- "undo_path" => archive_path
+ "undo_path" => archive_path,
}
r.redirect "/"
end
@@ -179,7 +177,7 @@ module Ketchup
"message" => "Completed",
"title" => note_title,
"path" => "/series/#{series_id}",
- "undo_path" => complete_path
+ "undo_path" => complete_path,
}
return_to = r.params["return_to"]
diff --git a/lib/sequel/plugins/sole.rb b/lib/sequel/plugins/sole.rb
index 2765190..92cb2e4 100644
--- a/lib/sequel/plugins/sole.rb
+++ b/lib/sequel/plugins/sole.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
module Sequel
module Plugins
# The sole plugin adds a +sole+ dataset method that returns the
diff --git a/test/test_db.rb b/test/test_db.rb
index 83fa433..7898646 100644
--- a/test/test_db.rb
+++ b/test/test_db.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require_relative "test_helper"
require "minitest/autorun"
@@ -41,7 +39,7 @@ class TestDB < Minitest::Test
series_id = Ketchup::DB[:series].insert(
user_id: @user_id, note: "Call Mom",
interval_unit: "week", interval_count: 2,
- created_at: @now, updated_at: @now
+ created_at: @now, updated_at: @now,
)
Ketchup::DB[:tasks].insert(series_id: series_id, due_date: Date.new(2026, 3, 1), created_at: @now, updated_at: @now)
series_id
diff --git a/test/test_helper.rb b/test/test_helper.rb
index b53fc76..c860abe 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require "simplecov"
SimpleCov.start
diff --git a/test/test_seed.rb b/test/test_seed.rb
index df4d7d6..2ec3816 100644
--- a/test/test_seed.rb
+++ b/test/test_seed.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require_relative "test_helper"
require "minitest/autorun"
@@ -21,8 +19,8 @@ class TestSeed < Minitest::Test
interval_unit: "week",
interval_count: 2,
due_date: Date.new(2026, 3, 1),
- history: []
- }
+ history: [],
+ },
]
Ketchup::Seed.call(user: user, series: series_data)
@@ -46,9 +44,9 @@ class TestSeed < Minitest::Test
due_date: Date.new(2026, 3, 1),
history: [
{ due_date: Date.new(2026, 2, 26), completed_at: Time.new(2026, 2, 26, 10, 0, 0), note: "Done" },
- { due_date: Date.new(2026, 2, 23), completed_at: Time.new(2026, 2, 23, 10, 0, 0), note: nil }
- ]
- }
+ { due_date: Date.new(2026, 2, 23), completed_at: Time.new(2026, 2, 23, 10, 0, 0), note: nil },
+ ],
+ },
]
Ketchup::Seed.call(user: user, series: series_data)
diff --git a/test/test_sole.rb b/test/test_sole.rb
index 1830df0..fa4b9cf 100644
--- a/test/test_sole.rb
+++ b/test/test_sole.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require_relative "test_helper"
require "minitest/autorun"
diff --git a/test/test_web.rb b/test/test_web.rb
index b5644fa..b6a93e8 100644
--- a/test/test_web.rb
+++ b/test/test_web.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require_relative "test_helper"
require "minitest/autorun"
@@ -58,13 +56,13 @@ class TestWeb < Minitest::Test
create_series(
note: "Alice task", interval_unit: "week", interval_count: "1",
first_due_date: (Date.today - 1).to_s,
- headers: auth_headers(login: "alice@example.com")
+ headers: auth_headers(login: "alice@example.com"),
)
create_series(
note: "Bob task", interval_unit: "day", interval_count: "1",
first_due_date: (Date.today - 1).to_s,
- headers: auth_headers(login: "bob@example.com")
+ headers: auth_headers(login: "bob@example.com"),
)
get "/", {}, auth_headers(login: "alice@example.com")
@@ -91,7 +89,7 @@ class TestWeb < Minitest::Test
def test_create_series
create_series(
note: "Call Mom", interval_unit: "week", interval_count: "2",
- first_due_date: "2026-03-01"
+ first_due_date: "2026-03-01",
)
assert last_response.redirect?
@@ -105,7 +103,7 @@ class TestWeb < Minitest::Test
def test_create_series_creates_first_task
create_series(
note: "Call Mom", interval_unit: "week", interval_count: "2",
- first_due_date: "2026-03-01"
+ first_due_date: "2026-03-01",
)
series = Ketchup::DB[:series].first
@@ -118,7 +116,7 @@ class TestWeb < Minitest::Test
create_series(
note: "Dentist", interval_unit: "quarter", interval_count: "1",
first_due_date: "2026-06-01",
- headers: auth_headers(login: "dave@example.com")
+ headers: auth_headers(login: "dave@example.com"),
)
series = Ketchup::DB[:series].first
@@ -129,7 +127,7 @@ class TestWeb < Minitest::Test
def test_create_series_strips_whitespace
create_series(
note: " Trim me ", interval_unit: "day", interval_count: "1",
- first_due_date: "2026-03-01"
+ first_due_date: "2026-03-01",
)
assert_equal "Trim me", Ketchup::DB[:series].first[:note]
end
@@ -137,7 +135,7 @@ class TestWeb < Minitest::Test
def test_create_series_rejects_empty_note
create_series(
note: " ", interval_unit: "day", interval_count: "1",
- first_due_date: "2026-03-01"
+ first_due_date: "2026-03-01",
)
assert_equal 422, last_response.status
assert_equal 0, Ketchup::DB[:series].count
@@ -146,7 +144,7 @@ class TestWeb < Minitest::Test
def test_create_series_rejects_invalid_interval_unit
create_series(
note: "Nope", interval_unit: "fortnight", interval_count: "1",
- first_due_date: "2026-03-01"
+ first_due_date: "2026-03-01",
)
assert_equal 422, last_response.status
assert_equal 0, Ketchup::DB[:series].count
@@ -155,7 +153,7 @@ class TestWeb < Minitest::Test
def test_create_series_rejects_zero_interval_count
create_series(
note: "Nope", interval_unit: "day", interval_count: "0",
- first_due_date: "2026-03-01"
+ first_due_date: "2026-03-01",
)
assert_equal 422, last_response.status
assert_equal 0, Ketchup::DB[:series].count
@@ -164,7 +162,7 @@ class TestWeb < Minitest::Test
def test_create_series_rejects_invalid_due_date
create_series(
note: "Nope", interval_unit: "day", interval_count: "1",
- first_due_date: "not-a-date"
+ first_due_date: "not-a-date",
)
assert_equal 422, last_response.status
assert_equal 0, Ketchup::DB[:series].count
@@ -217,7 +215,7 @@ class TestWeb < Minitest::Test
create_series(
note: "Alice task", interval_unit: "day", interval_count: "1",
first_due_date: (Date.today - 1).to_s,
- headers: auth_headers(login: "alice@example.com")
+ headers: auth_headers(login: "alice@example.com"),
)
task = Ketchup::DB[:tasks].first
@@ -237,7 +235,7 @@ class TestWeb < Minitest::Test
def test_create_series_requires_auth
post "/series", {
note: "Nope", interval_unit: "day", interval_count: "1",
- first_due_date: "2026-03-01"
+ first_due_date: "2026-03-01",
}
assert_equal 403, last_response.status
end
@@ -285,7 +283,7 @@ class TestWeb < Minitest::Test
create_series(
note: "Alice task", interval_unit: "day", interval_count: "1",
first_due_date: "2026-03-01",
- headers: auth_headers(login: "alice@example.com")
+ headers: auth_headers(login: "alice@example.com"),
)
series = Ketchup::DB[:series].first
@@ -399,7 +397,7 @@ class TestWeb < Minitest::Test
create_series(
note: "Alice task", interval_unit: "day", interval_count: "1",
first_due_date: (Date.today - 1).to_s,
- headers: auth_headers(login: "alice@example.com")
+ headers: auth_headers(login: "alice@example.com"),
)
task = Ketchup::DB[:tasks].first
@@ -494,7 +492,7 @@ class TestWeb < Minitest::Test
create_series(
note: "Alice task", interval_unit: "day", interval_count: "1",
first_due_date: "2026-03-01",
- headers: auth_headers(login: "alice@example.com")
+ headers: auth_headers(login: "alice@example.com"),
)
series = Ketchup::DB[:series].first
@@ -579,7 +577,7 @@ class TestWeb < Minitest::Test
get "/", {}, auth_headers # establish session
post "/series", {
note: "No token", interval_unit: "day", interval_count: "1",
- first_due_date: "2026-03-01"
+ first_due_date: "2026-03-01",
}, auth_headers
assert_equal 403, last_response.status
end
@@ -710,7 +708,7 @@ class TestWeb < Minitest::Test
create_series(
note: "Alice task", interval_unit: "day", interval_count: "1",
first_due_date: (Date.today - 1).to_s,
- headers: auth_headers(login: "alice@example.com")
+ headers: auth_headers(login: "alice@example.com"),
)
task = Ketchup::DB[:tasks].first
@@ -777,7 +775,7 @@ class TestWeb < Minitest::Test
create_series(
note: "Alice task", interval_unit: "day", interval_count: "1",
first_due_date: "2026-03-01",
- headers: auth_headers(login: "alice@example.com")
+ headers: auth_headers(login: "alice@example.com"),
)
series = Ketchup::DB[:series].first
@@ -821,7 +819,7 @@ class TestWeb < Minitest::Test
create_series(
note: "Alice task", interval_unit: "day", interval_count: "1",
first_due_date: "2026-03-01",
- headers: auth_headers(login: "alice@example.com")
+ headers: auth_headers(login: "alice@example.com"),
)
series = Ketchup::DB[:series].first
@@ -845,7 +843,7 @@ class TestWeb < Minitest::Test
def test_create_shared_series
create_series(
note: "Team standup", interval_unit: "week", interval_count: "1",
- first_due_date: "2026-03-01", shared: true
+ first_due_date: "2026-03-01", shared: true,
)
assert last_response.redirect?
@@ -856,7 +854,7 @@ class TestWeb < Minitest::Test
def test_create_private_series_by_default
create_series(
note: "Call Mom", interval_unit: "week", interval_count: "2",
- first_due_date: "2026-03-01"
+ first_due_date: "2026-03-01",
)
series = Ketchup::DB[:series].first
@@ -868,7 +866,7 @@ class TestWeb < Minitest::Test
note: "Team standup", interval_unit: "week", interval_count: "1",
first_due_date: (Date.today - 1).to_s,
headers: auth_headers(login: "alice@example.com"),
- shared: true
+ shared: true,
)
get "/", {}, auth_headers(login: "bob@example.com")
@@ -879,7 +877,7 @@ class TestWeb < Minitest::Test
create_series(
note: "Secret task", interval_unit: "week", interval_count: "1",
first_due_date: (Date.today - 1).to_s,
- headers: auth_headers(login: "alice@example.com")
+ headers: auth_headers(login: "alice@example.com"),
)
get "/", {}, auth_headers(login: "bob@example.com")
@@ -891,7 +889,7 @@ class TestWeb < Minitest::Test
note: "Team standup", interval_unit: "week", interval_count: "1",
first_due_date: (Date.today - 1).to_s,
headers: auth_headers(login: "alice@example.com"),
- shared: true
+ shared: true,
)
task = Ketchup::DB[:tasks].first
@@ -910,7 +908,7 @@ class TestWeb < Minitest::Test
note: "Team standup", interval_unit: "week", interval_count: "1",
first_due_date: "2026-03-01",
headers: auth_headers(login: "alice@example.com"),
- shared: true
+ shared: true,
)
series = Ketchup::DB[:series].first
@@ -925,7 +923,7 @@ class TestWeb < Minitest::Test
note: "Team standup", interval_unit: "week", interval_count: "1",
first_due_date: "2026-03-01",
headers: auth_headers(login: "alice@example.com"),
- shared: true
+ shared: true,
)
series = Ketchup::DB[:series].first
@@ -940,7 +938,7 @@ class TestWeb < Minitest::Test
note: "Team standup", interval_unit: "week", interval_count: "1",
first_due_date: "2026-03-01",
headers: auth_headers(login: "alice@example.com"),
- shared: true
+ shared: true,
)
series = Ketchup::DB[:series].first
@@ -957,7 +955,7 @@ class TestWeb < Minitest::Test
note: "Team standup", interval_unit: "week", interval_count: "1",
first_due_date: "2026-03-01",
headers: auth_headers(login: "alice@example.com"),
- shared: true
+ shared: true,
)
series = Ketchup::DB[:series].first
@@ -972,7 +970,7 @@ class TestWeb < Minitest::Test
note: "Team standup", interval_unit: "week", interval_count: "1",
first_due_date: "2026-03-01",
headers: auth_headers(login: "alice@example.com"),
- shared: true
+ shared: true,
)
series = Ketchup::DB[:series].first
@@ -985,7 +983,7 @@ class TestWeb < Minitest::Test
create_series(
note: "Private", interval_unit: "week", interval_count: "1",
first_due_date: "2026-03-01",
- headers: auth_headers(login: "alice@example.com")
+ headers: auth_headers(login: "alice@example.com"),
)
series = Ketchup::DB[:series].first
@@ -997,7 +995,7 @@ class TestWeb < Minitest::Test
create_series(
note: "Team standup", interval_unit: "week", interval_count: "1",
first_due_date: (Date.today - 1).to_s,
- shared: true
+ shared: true,
)
get "/", {}, auth_headers
@@ -1007,7 +1005,7 @@ class TestWeb < Minitest::Test
def test_task_card_no_shared_icon_for_private_series
create_series(
note: "Private task", interval_unit: "week", interval_count: "1",
- first_due_date: (Date.today - 1).to_s
+ first_due_date: (Date.today - 1).to_s,
)
get "/", {}, auth_headers
@@ -1018,7 +1016,7 @@ class TestWeb < Minitest::Test
create_series(
note: "Team standup", interval_unit: "week", interval_count: "1",
first_due_date: "2026-03-01",
- shared: true
+ shared: true,
)
series = Ketchup::DB[:series].first
@@ -1091,7 +1089,7 @@ class TestWeb < Minitest::Test
_csrf: token,
note: note, interval_unit: interval_unit, interval_count: interval_count,
first_due_date: first_due_date,
- shared: shared ? "1" : nil
+ shared: shared ? "1" : nil,
}.compact, headers
end