bible270 1.1.3 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45fdbb1df9bc7d830bbaf95bf41a49c0bb027faeb5a98da183b5baeda39c30fe
4
- data.tar.gz: 844cc4dfb784a197321910c5a606e092e57f0a0437890a8cbe1c4bcbe30c146a
3
+ metadata.gz: a1d5f984616aed86326fe24bdb808b539bbd6cc8fe28e61f58838e6fba6573ca
4
+ data.tar.gz: 661d698c8eaacee191eb27eda7fa75bc83cf0df94ef5dfc85ae18974a26ead0d
5
5
  SHA512:
6
- metadata.gz: b9c688571b688a9fd9cba621bd3bb816561751af80d59568ba7f54df5598bbd6543efbf28bac976c57deb1d390f7440aad08d9fc51ecc7e047563a85554cf266
7
- data.tar.gz: f5ffea7599e4526ea4f6184bafb0b7253a5c86ef24a0adf8781abccc410d8135b96781b3b079b856699272a02b6a6ce95c6f6a699fa3864e6ffd9331cc521fc6
6
+ metadata.gz: 60ccdd3d39fadb44e75c0eef05e18bc304032636e440f6d8d9b3b0a941b6c1a2a73bdc5231ae353de27d8b5aa8923f028e4dbd16d259ea9079bce20bf6b7bd9b
7
+ data.tar.gz: 0c41c6c130e93d42bc1d45dd1757c2f2216a378bf77cf5118dce25f28e72fceeb813f5064bb5ec93397afc077091f1a649b068b69f0616c964cfb2f700ab5cce
data/CHANGELOG.md CHANGED
@@ -4,7 +4,16 @@ All notable changes to bible270. Format follows [Keep a Changelog](https://keepa
4
4
 
5
5
  ## Unreleased Changes
6
6
 
7
- **[Full Changelog](https://github.com/avonderluft/bible270/compare/v1.1.2...main)**
7
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v1.1.3...main)**
8
+
9
+ ## [1.1.3] - 2026-08-23
10
+
11
+ ### Changed
12
+
13
+ - align Profile reminder choices with the recommended 15-minute scheduler interval while preserving existing off-quarter preferences
14
+ - document concrete host cron and Docker Compose setup, output logging, verification, and one-scheduler-per-database guidance
15
+
16
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v1.1.2...v1.1.3)**
8
17
 
9
18
  ## [1.1.2] - 2026-08-22
10
19
 
@@ -12,13 +21,17 @@ All notable changes to bible270. Format follows [Keep a Changelog](https://keepa
12
21
 
13
22
  - include complete 1.1.0 and 1.1.1 release history in the packaged changelog
14
23
 
24
+ ### Fixed
25
+
26
+ - make the mobile navigation menu toggle reliably on iOS Safari with an explicit accessible button, synchronized `aria-expanded` state, and controlled menu visibility
27
+
15
28
  **[Full Changelog](https://github.com/avonderluft/bible270/compare/v1.1.1...v1.1.2)**
16
29
 
17
30
  ## [1.1.1] - 2026-08-22
18
31
 
19
- ### Fixed
32
+ ### Changed
20
33
 
21
- - make the mobile navigation menu toggle reliably on iOS Safari by relying on native `details` disclosure behavior instead of overriding it with JavaScript
34
+ - replace the first custom mobile-menu handler with native `details` disclosure behavior
22
35
 
23
36
  **[Full Changelog](https://github.com/avonderluft/bible270/compare/v1.1.0...v1.1.1)**
24
37
 
@@ -508,6 +521,7 @@ This is the initial public release.
508
521
 
509
522
  * Email sign-in requires working Action Mailer delivery in the host application. Set `config.mailer_from`; delivery is inline by default, or set `email_sign_in_deliver_later` when a queue backend is available.
510
523
 
524
+ [1.1.3]: https://github.com/avonderluft/bible270/releases/tag/v1.1.3
511
525
  [1.1.2]: https://github.com/avonderluft/bible270/releases/tag/v1.1.2
512
526
  [1.1.1]: https://github.com/avonderluft/bible270/releases/tag/v1.1.1
513
527
  [1.1.0]: https://github.com/avonderluft/bible270/releases/tag/v1.1.0
data/README.md CHANGED
@@ -318,7 +318,7 @@ config.allow_reader_start_date = true # allow personal per-reader calendars
318
318
  | a date | false | Everyone is pinned to the community date; stored individual dates are ignored |
319
319
  | nil | false | Fully undated — no calendar anywhere |
320
320
 
321
- Dated plans show each day's date with a **Today** badge and a "Go to today" link. Administrators may re-map an individual reader's calendar from the admin panel; check-offs and reflections remain keyed to day numbers, so nothing in their history moves.
321
+ Dated plans show each day's date with a **Today** badge and a "Go to today" link. `config.start_date` is the deploy-time default; an administrator may override the current run's shared date from the Admin panel without a deploy, or reset it to the configured value. The shared-date change immediately re-maps calendar dates, **Today**, and reminders for readers following the shared calendar. Administrators may also re-map an individual reader's calendar. Check-offs and reflections remain keyed to day numbers, so nothing in their history moves.
322
322
 
323
323
  Date helpers are pure functions, usable anywhere:
324
324
 
data/Rakefile CHANGED
@@ -160,7 +160,7 @@ namespace :test do
160
160
  Rake::Task['test:serial'].enhance(['test:prepare_serial'])
161
161
 
162
162
  task :parallel do
163
- workers = ENV.fetch('PARALLEL_WORKERS', '2')
163
+ workers = ENV.fetch('PARALLEL_WORKERS', '6')
164
164
  FileUtils.rm_rf('coverage')
165
165
  env = {
166
166
  'PARALLEL_COVERAGE' => 'true',
@@ -28,10 +28,40 @@ module Bible270
28
28
  end
29
29
  end
30
30
 
31
+ def update_run_start
32
+ if Setting.set_run_start_date!(params[:start_date])
33
+ date = Setting.run_start_date
34
+ redirect_to admin_path, notice: "Current run start date changed to #{date.strftime('%B %-d, %Y')}."
35
+ else
36
+ redirect_to admin_path, alert: 'Choose a valid start date.'
37
+ end
38
+ end
39
+
40
+ def reset_run_start
41
+ Setting.clear_run_start_date!
42
+ date = Setting.run_start_date
43
+ notice = if date
44
+ "Current run now uses the configured start date, #{date.strftime('%B %-d, %Y')}."
45
+ else
46
+ 'Current run now uses the configured undated schedule.'
47
+ end
48
+ redirect_to admin_path, notice: notice
49
+ end
50
+
31
51
  def index
32
52
  @enrollment_closed = Setting.enrollment_closed?
33
53
  @enrollment_closed_at = Setting.enrollment_closed_at
34
54
  @readers = Reader.all.to_a.sort_by(&:sort_name)
55
+ @run_start_date = Setting.run_start_date
56
+ @configured_start_date = Bible270.config.start_date
57
+ @run_start_date_overridden = Setting.run_start_date_overridden?
58
+ if Bible270.config.allow_reader_start_date
59
+ @shared_calendar_readers = @readers.count { |reader| reader.started_on.nil? }
60
+ @personal_calendar_readers = @readers.size - @shared_calendar_readers
61
+ else
62
+ @shared_calendar_readers = @readers.size
63
+ @personal_calendar_readers = 0
64
+ end
35
65
  counts = Checkoff.group(:reader_id, :day).count
36
66
  @days_completed = Hash.new(0)
37
67
  counts.each { |(rid, day), n| @days_completed[rid] += 1 if n >= Plan.total_parts(day) }
@@ -8,7 +8,7 @@ module Bible270
8
8
  @start_day = current_reader&.current_day || 1
9
9
  # nil outside the plan's window, so no "Go to today" before it begins
10
10
  @today_day = current_reader&.today_day
11
- @community_start_date = Bible270.config.start_date
11
+ @community_start_date = Setting.run_start_date
12
12
  end
13
13
 
14
14
  def show
@@ -90,7 +90,7 @@ module Bible270
90
90
 
91
91
  # Every reflection is visible when written; these are the moderation actions.
92
92
  def hide! = update!(approved: false, moderated_at: Time.current)
93
- def unhide! = update!(approved: true, moderated_at: Time.current)
93
+ def unhide! = update!(approved: true, moderated_at: Time.current)
94
94
  def hidden? = !approved
95
95
  def reply? = parent_id.present?
96
96
 
@@ -515,14 +515,13 @@ module Bible270
515
515
 
516
516
  # The start date that actually governs this reader. A reader's own
517
517
  # started_on wins when per-reader dates are allowed; otherwise (or if they
518
- # haven't got one) the community-wide config.start_date applies. Nil means
519
- # the plan is undated for this reader and no calendar mapping exists.
518
+ # haven't got one) the current run's shared date applies. Nil means the plan
519
+ # is undated for this reader and no calendar mapping exists.
520
520
  def effective_start_date
521
- config = Bible270.config
522
- if config.allow_reader_start_date && started_on
521
+ if Bible270.config.allow_reader_start_date && started_on
523
522
  started_on
524
523
  else
525
- config.start_date
524
+ Setting.run_start_date
526
525
  end
527
526
  end
528
527
 
@@ -607,10 +606,9 @@ module Bible270
607
606
  # Called when a reader first participates. Only stamps a personal start date
608
607
  # when per-reader dates are enabled and a shared date isn't already in force.
609
608
  def ensure_started!
610
- config = Bible270.config
611
- return unless config.allow_reader_start_date
609
+ return unless Bible270.config.allow_reader_start_date
612
610
  return if started_on.present?
613
- return if config.start_date.present?
611
+ return if Setting.run_start_date.present?
614
612
 
615
613
  update!(started_on: Bible270.today)
616
614
  end
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bible270
4
- # Runtime state an admin can change without a deploy. Currently just whether
5
- # this run of the plan is open to new readers.
4
+ # Runtime state an admin can change without a deploy: enrollment and the
5
+ # current run's shared calendar date.
6
6
  class Setting < ApplicationRecord
7
7
  self.table_name = 'bible270_settings'
8
8
 
9
9
  ENROLLMENT_CLOSED_AT = 'enrollment_closed_at'
10
+ RUN_START_DATE = 'run_start_date'
10
11
 
11
12
  validates :key, presence: true, uniqueness: true
12
13
 
@@ -25,6 +26,35 @@ module Bible270
25
26
  where(key: key.to_s).delete_all
26
27
  end
27
28
 
29
+ # ---- current run ------------------------------------------------------
30
+
31
+ def self.run_start_date
32
+ run_start_date_override || Bible270.config.start_date
33
+ end
34
+
35
+ def self.run_start_date_override
36
+ Plan.to_date(read(RUN_START_DATE))
37
+ rescue ActiveRecord::StatementInvalid
38
+ nil
39
+ end
40
+
41
+ def self.run_start_date_overridden?
42
+ run_start_date_override.present?
43
+ end
44
+
45
+ def self.set_run_start_date!(value)
46
+ date = Plan.to_date(value)
47
+ return false if date.nil?
48
+
49
+ write(RUN_START_DATE, date.iso8601)
50
+ true
51
+ end
52
+
53
+ def self.clear_run_start_date!
54
+ delete_key(RUN_START_DATE)
55
+ true
56
+ end
57
+
28
58
  # ---- enrolment --------------------------------------------------------
29
59
 
30
60
  # Closed either because an admin closed it, or because the app was configured
@@ -7,11 +7,11 @@
7
7
  <div class="b270-panel">
8
8
  <h2 class="b270-subhead">This run of the plan is <%= @enrollment_closed ? 'closed' : 'open' %> to new readers.</h2>
9
9
  <% if @enrollment_closed %>
10
- <% if @enrollment_closed_at %>
11
- <p class="b270-startline">Enrollment closed
12
- on <%= b270_date(@enrollment_closed_at) %><% end %>.
10
+ <p class="b270-startline">
11
+ <% if @enrollment_closed_at %>Enrollment closed on <%= b270_date(@enrollment_closed_at) %>.<% end %>
13
12
  The <%= @readers.size %> reader<%= 's' unless @readers.size == 1 %> already taking part can
14
- still sign in and carry on.</p>
13
+ still sign in and carry on.
14
+ </p>
15
15
  <%= button_to 'Open to new readers', admin_enrollment_path, method: :patch,
16
16
  params: { state: 'open' }, class: 'b270-btn' %>
17
17
  <% else %>
@@ -22,11 +22,52 @@
22
22
  <% end %>
23
23
  </div>
24
24
 
25
+ <div class="b270-panel">
26
+ <h2 class="b270-subhead">Current run start date</h2>
27
+ <% if @run_start_date %>
28
+ <p class="b270-startline">
29
+ This run <%= @run_start_date > Bible270.today ? 'starts' : 'started' %>
30
+ <strong><%= @run_start_date.strftime('%B %-d, %Y') %></strong> and ends
31
+ <strong><%= Bible270::Plan.end_date_for(@run_start_date).strftime('%B %-d, %Y') %></strong>.
32
+ <% if @run_start_date_overridden %><span class="b270-badge">Admin override</span><% end %>
33
+ </p>
34
+ <% else %>
35
+ <p class="b270-startline">This run is undated.</p>
36
+ <% end %>
37
+
38
+ <%= form_with url: admin_run_start_path, method: :patch, class: "b270-startform",
39
+ data: { turbo_confirm: "Change the current run start date? Calendar dates, Today, and reminders will be re-mapped. Check-offs and reflections will not change." } do %>
40
+ <label class="b270-field">
41
+ <span>Start date</span>
42
+ <%= date_field_tag :start_date, @run_start_date, required: true %>
43
+ </label>
44
+ <%= submit_tag(@run_start_date ? 'Change start date' : 'Set start date', class: 'b270-btn') %>
45
+ <% end %>
46
+
47
+ <p class="b270-hint">
48
+ This affects <%= pluralize(@shared_calendar_readers, 'reader') %> following the shared calendar.
49
+ <% if @personal_calendar_readers.positive? %>
50
+ <%= pluralize(@personal_calendar_readers, 'reader') %> with a personal calendar will not move.
51
+ <% end %>
52
+ Check-offs and reflections remain attached to their existing day numbers.
53
+ </p>
54
+
55
+ <% if @run_start_date_overridden %>
56
+ <%= button_to 'Use configured date', admin_run_start_path, method: :delete, class: 'b270-linkbtn',
57
+ form: { data: { turbo_confirm: 'Remove the Admin override and use the configured start date?' } } %>
58
+ <p class="b270-hint">
59
+ Configured date:
60
+ <%= @configured_start_date ? @configured_start_date.strftime('%B %-d, %Y') : 'undated' %>.
61
+ </p>
62
+ <% end %>
63
+ </div>
64
+
25
65
  <% if @readers.any? %>
26
66
  <div class="b270-list">
27
67
  <% @readers.each do |reader| %>
28
68
  <div class="b270-li">
29
- <%= b270_avatar(reader, size: 72) %>
69
+ <%= link_to b270_avatar(reader, size: 72), admin_reader_path(reader),
70
+ "aria-label": reader.display_name %>
30
71
  <div>
31
72
  <div class="name"><%= link_to reader.display_name, admin_reader_path(reader), style: 'text-decoration:none' %>
32
73
  <% if Bible270.config.admin?(reader) %> <span class="sub"><strong>[admin]</strong></span> <% end %>
@@ -58,8 +99,7 @@
58
99
  <%= @reachable == 1 ? 'reader' : 'readers' %> individually, not as one message with
59
100
  everyone in bcc — so each is greeted by name.
60
101
  <% if @last_broadcast_at.present? %>
61
- <br>Last sent <%= b270_datetime(@last_broadcast_at) %><%
62
- %><% if @last_broadcast_subject.present? %>: <em><%= @last_broadcast_subject %></em><% end %>.
102
+ <br>Last sent <%= b270_datetime(@last_broadcast_at) %><% if @last_broadcast_subject.present? %>: <em><%= @last_broadcast_subject %></em><% end %>.
63
103
  <% end %>
64
104
  </p>
65
105
 
@@ -3,6 +3,6 @@
3
3
  <h1><%= Bible270.config.app_name %> <em>— read God's whole story</em></h1>
4
4
  <p class="lede">Daily passages from the Old Testament, New Testament, and Psalms or Proverbs — portions sized to take about the same time to read. Check off what you read; your place is saved for you, and you can share reflections and encourage one another along the way.</p>
5
5
 
6
- <%= render "bible270/shared/progress" %>
6
+ <%= render "bible270/shared/progress", show_start_date: true %>
7
7
 
8
8
  <p style="margin:12px 0 0"><%= link_to "Meet your fellow readers →", community_path %></p>
@@ -1,4 +1,4 @@
1
- <%# locals: (reader: nil, start_day: nil, today_day: nil, community_start_date: :unset) %>
1
+ <%# locals: (reader: nil, start_day: nil, today_day: nil, community_start_date: :unset, show_start_date: false) %>
2
2
  <%#
3
3
  The progress panel.
4
4
 
@@ -21,7 +21,8 @@
21
21
  panel_reader = reader || current_reader
22
22
  panel_start_day = start_day || panel_reader&.current_day || 1
23
23
  panel_today_day = today_day || panel_reader&.today_day
24
- panel_community_date = community_start_date == :unset ? Bible270.config.start_date : community_start_date
24
+ panel_community_date = community_start_date == :unset ? Bible270::Setting.run_start_date : community_start_date
25
+ panel_start_date = panel_reader&.effective_start_date if show_start_date
25
26
  day_verb = panel_start_day == 1 ? 'Start' : 'Continue'
26
27
  panel_behind = panel_today_day.present? && panel_start_day < panel_today_day
27
28
  %>
@@ -29,7 +30,11 @@
29
30
  <% if panel_reader %>
30
31
  <div class="b270-bignum">
31
32
  <span class="n"><%= panel_reader.days_completed %></span>
32
- <span class="of">of <%= Bible270::Plan::DAYS %> days complete</span>
33
+ <span class="of">
34
+ of <%= Bible270::Plan::DAYS %> days complete<% if panel_start_date %>
35
+ · plan <% if panel_start_date == Bible270.today %>starts today<% elsif panel_start_date > Bible270.today %>starts on <%= panel_start_date.strftime("%b %-d, %Y") %><% else %>started on <%= panel_start_date.strftime("%b %-d, %Y") %><% end %>
36
+ <% end %>
37
+ </span>
33
38
  <span class="pct"><%= panel_reader.completion_percent %>%</span>
34
39
  </div>
35
40
 
@@ -112,7 +112,7 @@
112
112
 
113
113
  .b270-startline{margin:0;font-size:13.5px;color:var(--muted)}
114
114
  .b270-startline strong{color:var(--ink)}
115
- .b270-startform{display:flex;align-items:center;gap:10px;margin-top:10px;flex-wrap:wrap}
115
+ .b270-startform{display:flex;align-items:flex-end;gap:10px;margin-top:10px;flex-wrap:wrap}
116
116
  .b270-startform input[type=date]{font:inherit;font-size:14px;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:#fff;color:var(--ink)}
117
117
  .b270-linkbtn{background:none;border:none;padding:0;margin-top:8px;font:inherit;font-size:12.5px;color:var(--oxblood);cursor:pointer;text-decoration:underline}
118
118
  .b270-hint{margin:8px 0 0;font-size:12px;color:var(--faint);max-width:111ch}
data/config/routes.rb CHANGED
@@ -29,6 +29,8 @@ Bible270::Engine.routes.draw do
29
29
 
30
30
  post 'admin/broadcast', to: 'admin#broadcast', as: :admin_broadcast
31
31
  patch 'admin/enrollment', to: 'admin#update_enrollment', as: :admin_enrollment
32
+ patch 'admin/run-start', to: 'admin#update_run_start', as: :admin_run_start
33
+ delete 'admin/run-start', to: 'admin#reset_run_start'
32
34
  get 'admin/comments', to: 'admin#comments', as: :admin_comments
33
35
  patch 'admin/comments/:id/hide', to: 'admin#hide_comment', as: :admin_hide_comment
34
36
  patch 'admin/comments/:id/unhide', to: 'admin#unhide_comment', as: :admin_unhide_comment
data/lib/bible270/plan.rb CHANGED
@@ -493,14 +493,14 @@ module Bible270
493
493
  pinned.merge(pinned_from_breaks(chapters))
494
494
  end
495
495
 
496
- def psalm_parts = memo(:psalm_parts) { pp_parts.first(psalm_chapters.size) }
496
+ def psalm_parts = memo(:psalm_parts) { pp_parts.first(psalm_chapters.size) }
497
497
  def proverbs_parts = memo(:proverbs_parts) { pp_parts.last(proverbs_chapters.size) }
498
498
 
499
- def psalm_readings = memo(:psalm_readings) { expand_readings(psalm_chapters, psalm_parts) }
499
+ def psalm_readings = memo(:psalm_readings) { expand_readings(psalm_chapters, psalm_parts) }
500
500
  def proverbs_readings = memo(:proverbs_readings) { expand_readings(proverbs_chapters, proverbs_parts) }
501
501
 
502
502
  # Readings grouped by chapter, so a divided chapter stays together.
503
- def psalm_groups = memo(:psalm_groups) { group_by_chapter(psalm_readings) }
503
+ def psalm_groups = memo(:psalm_groups) { group_by_chapter(psalm_readings) }
504
504
  def proverbs_groups = memo(:proverbs_groups) { group_by_chapter(proverbs_readings) }
505
505
 
506
506
  def group_by_chapter(readings)
@@ -530,7 +530,7 @@ module Bible270
530
530
  end
531
531
  end
532
532
 
533
- def pp_plan = memo(:pp_plan) { pp_readings.map { |seg| format_segment(seg) } }
533
+ def pp_plan = memo(:pp_plan) { pp_readings.map { |seg| format_segment(seg) } }
534
534
  def pp_verse_loads = memo(:pp_verse_loads) { pp_readings.map { |seg| segment_length(seg) } }
535
535
 
536
536
  # 1-indexed days carrying a Proverbs reading.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bible270
4
- VERSION = '1.1.3'
4
+ VERSION = '1.1.4'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bible270
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew vonderLuft