summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-28 09:08:22 +0300
committerPaul Buetow <paul@buetow.org>2026-06-28 09:08:22 +0300
commit94479238557adcab33a95fefbf752a3742b8a8ba (patch)
tree8a97bb2d616c58409617c4b5a3b5ed928246e827
parentfef68fe90d7f00d7d831c335b03119f11c2c927b (diff)
nr0: dry-run reuses real paging/redirect logic via shared helpers
collect_dry_run_page_plan re-derived page_count via the ceil formula and redirect_count via a magic "*4+2", duplicating logic owned by album_page_records (pagination) and render_page_view_redirects (redirect files). The preview could silently drift from a real --generate. Single source of truth: - album_page_count_for_image_count (album-photo-select.source.sh) owns the MAXPREVIEWS-per-page grouping count that album_page_records realises. - ALBUM_REDIRECTS_PER_PAGE=4 / ALBUM_REDIRECTS_LAST_PAGE_EXTRA=2 + album_redirect_count_for_page_count (album-render.source.sh) own the per-page (4) plus last-page-extra (2) redirect tally that render_page_view_redirects actually emits. dry-run now predicts both counts through these helpers (no dist files touched, side-effect free). Confirmed the real redirect count is page_count*4+2, so output is byte-identical: partial-final-page (3 preview pages / 14 navigation redirects) and empty album (0 / 0) unchanged. Regenerated bin/shuriken; just test/shellcheck/check-generated all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-rwxr-xr-xbin/shuriken64
-rw-r--r--src/lib/album-photo-select.source.sh22
-rw-r--r--src/lib/album-render.source.sh31
-rw-r--r--src/lib/dry-run.source.sh11
4 files changed, 120 insertions, 8 deletions
diff --git a/bin/shuriken b/bin/shuriken
index 1076527..a85c44e 100755
--- a/bin/shuriken
+++ b/bin/shuriken
@@ -2582,8 +2582,15 @@ collect_dry_run_page_plan() {
plan_ref["details_count"]=0
if (( image_count > 0 )); then
- page_count=$(( (image_count + MAXPREVIEWS - 1) / MAXPREVIEWS ))
- redirect_count=$(( page_count * 4 + 2 ))
+ # Predict the page and redirect counts from the SAME helpers a real
+ # --generate uses (task nr0), so the preview can't drift from the actual
+ # output. album_page_count_for_image_count (album-photo-select) owns the
+ # MAXPREVIEWS-per-page grouping that album_page_records realises, and
+ # album_redirect_count_for_page_count (album-render) owns the per-page +
+ # last-page redirect tally that render_page_view_redirects emits. No dist
+ # files are touched here, so dry-run stays side-effect free.
+ page_count=$(album_page_count_for_image_count "$image_count")
+ redirect_count=$(album_redirect_count_for_page_count "$page_count")
plan_ref["details_count"]="$image_count"
plan_ref["page_count"]="$page_count"
plan_ref["redirect_count"]="$redirect_count"
@@ -3227,12 +3234,32 @@ album_photo_files() {
| maybe_shuffle
}
+# Pagination single source of truth (task nr0): how many preview pages a given
+# number of album photos splits into, with at most MAXPREVIEWS photos per page.
+# album_page_records below realises exactly this many records by grouping the
+# actual (shuffled) photo list, and the dry-run plan calls this helper to predict
+# the page count from the incoming-image tally WITHOUT enumerating dist files. So
+# the preview and a real --generate can never disagree on the page count: both
+# express "ceil(image_count / MAXPREVIEWS)" through this one definition. An empty
+# album yields 0 pages.
+album_page_count_for_image_count() {
+ local -ri image_count="$1"; shift
+
+ if (( image_count <= 0 )); then
+ printf '0\n'
+ return
+ fi
+ printf '%d\n' "$(( (image_count + MAXPREVIEWS - 1) / MAXPREVIEWS ))"
+}
+
# Group the album's photos into pages of at most MAXPREVIEWS, in their final
# (shuffled/sorted) order. The result is emitted one line per page as a
# tab-separated record "<page_num>\t<photo>\t<photo>..." so the caller can walk
# pages without keeping every page in memory at once. Order is fully
# deterministic (album_photo_files already applies the seeded shuffle), so the
-# downstream parallelism only changes timing, never which photo lands where.
+# downstream parallelism only changes timing, never which photo lands where. The
+# number of records produced here equals album_page_count_for_image_count of the
+# photo count (same MAXPREVIEWS-per-page grouping); see that helper.
album_page_records() {
local -r photos_dir="$1"; shift
local photo
@@ -3613,13 +3640,42 @@ record_rendered_view_page() {
last_views_ref["$page"]="$preview"
}
+# Navigation-redirect count single source of truth (task nr0). Every view page
+# gets ALBUM_REDIRECTS_PER_PAGE wrap-around redirect files: the prev stub
+# (N-0.html) and its details twin, plus the next stub (N-(last+1).html) and its
+# details twin -- four files, emitted by render_page_view_redirects below for
+# every page. The LAST page additionally emits ALBUM_REDIRECTS_LAST_PAGE_EXTRA
+# files: the 0-MAXPREVIEWS / 0-MAXPREVIEWS-details entry stubs that bounce into
+# the album. Keep these two numbers in lockstep with render_page_view_redirects;
+# the dry-run plan predicts redirect_count from them via
+# album_redirect_count_for_page_count instead of a magic "*4+2".
+declare -gri ALBUM_REDIRECTS_PER_PAGE=4
+declare -gri ALBUM_REDIRECTS_LAST_PAGE_EXTRA=2
+
+# Total navigation redirects a run produces for a given number of preview pages:
+# four per page plus the last page's extra entry stubs. Zero pages -> zero
+# redirects (render_view_redirects returns early on an empty album). This is the
+# count render_page_view_redirects actually writes across all pages, expressed
+# once so the dry-run plan cannot drift from real generation.
+album_redirect_count_for_page_count() {
+ local -ri page_count="$1"; shift
+
+ if (( page_count <= 0 )); then
+ printf '0\n'
+ return
+ fi
+ printf '%d\n' "$(( page_count * ALBUM_REDIRECTS_PER_PAGE \
+ + ALBUM_REDIRECTS_LAST_PAGE_EXTRA ))"
+}
+
# Render every navigation redirect for a single view page (the prev/next
# wrap-around stubs that bounce N-0 / N-(last+1) to the neighbouring page).
# Each redirect is its own self-contained file (the template overwrites it), so
# this whole group is safe to run as one independent background job; only the
# files for distinct pages are produced here. The wrap-around redirects for the
# very last page (0-MAXPREVIEWS and the loop-to-1 links) are emitted as part of
-# that page's group.
+# that page's group. Per-page / last-page file counts are fixed by
+# ALBUM_REDIRECTS_PER_PAGE / ALBUM_REDIRECTS_LAST_PAGE_EXTRA above.
render_page_view_redirects() {
local -r html_dir="$1"; shift
local -ri page="$1"; shift
diff --git a/src/lib/album-photo-select.source.sh b/src/lib/album-photo-select.source.sh
index 2cb2e88..06190f7 100644
--- a/src/lib/album-photo-select.source.sh
+++ b/src/lib/album-photo-select.source.sh
@@ -20,12 +20,32 @@ album_photo_files() {
| maybe_shuffle
}
+# Pagination single source of truth (task nr0): how many preview pages a given
+# number of album photos splits into, with at most MAXPREVIEWS photos per page.
+# album_page_records below realises exactly this many records by grouping the
+# actual (shuffled) photo list, and the dry-run plan calls this helper to predict
+# the page count from the incoming-image tally WITHOUT enumerating dist files. So
+# the preview and a real --generate can never disagree on the page count: both
+# express "ceil(image_count / MAXPREVIEWS)" through this one definition. An empty
+# album yields 0 pages.
+album_page_count_for_image_count() {
+ local -ri image_count="$1"; shift
+
+ if (( image_count <= 0 )); then
+ printf '0\n'
+ return
+ fi
+ printf '%d\n' "$(( (image_count + MAXPREVIEWS - 1) / MAXPREVIEWS ))"
+}
+
# Group the album's photos into pages of at most MAXPREVIEWS, in their final
# (shuffled/sorted) order. The result is emitted one line per page as a
# tab-separated record "<page_num>\t<photo>\t<photo>..." so the caller can walk
# pages without keeping every page in memory at once. Order is fully
# deterministic (album_photo_files already applies the seeded shuffle), so the
-# downstream parallelism only changes timing, never which photo lands where.
+# downstream parallelism only changes timing, never which photo lands where. The
+# number of records produced here equals album_page_count_for_image_count of the
+# photo count (same MAXPREVIEWS-per-page grouping); see that helper.
album_page_records() {
local -r photos_dir="$1"; shift
local photo
diff --git a/src/lib/album-render.source.sh b/src/lib/album-render.source.sh
index fa8173f..0d0f6a0 100644
--- a/src/lib/album-render.source.sh
+++ b/src/lib/album-render.source.sh
@@ -310,13 +310,42 @@ record_rendered_view_page() {
last_views_ref["$page"]="$preview"
}
+# Navigation-redirect count single source of truth (task nr0). Every view page
+# gets ALBUM_REDIRECTS_PER_PAGE wrap-around redirect files: the prev stub
+# (N-0.html) and its details twin, plus the next stub (N-(last+1).html) and its
+# details twin -- four files, emitted by render_page_view_redirects below for
+# every page. The LAST page additionally emits ALBUM_REDIRECTS_LAST_PAGE_EXTRA
+# files: the 0-MAXPREVIEWS / 0-MAXPREVIEWS-details entry stubs that bounce into
+# the album. Keep these two numbers in lockstep with render_page_view_redirects;
+# the dry-run plan predicts redirect_count from them via
+# album_redirect_count_for_page_count instead of a magic "*4+2".
+declare -gri ALBUM_REDIRECTS_PER_PAGE=4
+declare -gri ALBUM_REDIRECTS_LAST_PAGE_EXTRA=2
+
+# Total navigation redirects a run produces for a given number of preview pages:
+# four per page plus the last page's extra entry stubs. Zero pages -> zero
+# redirects (render_view_redirects returns early on an empty album). This is the
+# count render_page_view_redirects actually writes across all pages, expressed
+# once so the dry-run plan cannot drift from real generation.
+album_redirect_count_for_page_count() {
+ local -ri page_count="$1"; shift
+
+ if (( page_count <= 0 )); then
+ printf '0\n'
+ return
+ fi
+ printf '%d\n' "$(( page_count * ALBUM_REDIRECTS_PER_PAGE \
+ + ALBUM_REDIRECTS_LAST_PAGE_EXTRA ))"
+}
+
# Render every navigation redirect for a single view page (the prev/next
# wrap-around stubs that bounce N-0 / N-(last+1) to the neighbouring page).
# Each redirect is its own self-contained file (the template overwrites it), so
# this whole group is safe to run as one independent background job; only the
# files for distinct pages are produced here. The wrap-around redirects for the
# very last page (0-MAXPREVIEWS and the loop-to-1 links) are emitted as part of
-# that page's group.
+# that page's group. Per-page / last-page file counts are fixed by
+# ALBUM_REDIRECTS_PER_PAGE / ALBUM_REDIRECTS_LAST_PAGE_EXTRA above.
render_page_view_redirects() {
local -r html_dir="$1"; shift
local -ri page="$1"; shift
diff --git a/src/lib/dry-run.source.sh b/src/lib/dry-run.source.sh
index 75a0655..b02cc23 100644
--- a/src/lib/dry-run.source.sh
+++ b/src/lib/dry-run.source.sh
@@ -30,8 +30,15 @@ collect_dry_run_page_plan() {
plan_ref["details_count"]=0
if (( image_count > 0 )); then
- page_count=$(( (image_count + MAXPREVIEWS - 1) / MAXPREVIEWS ))
- redirect_count=$(( page_count * 4 + 2 ))
+ # Predict the page and redirect counts from the SAME helpers a real
+ # --generate uses (task nr0), so the preview can't drift from the actual
+ # output. album_page_count_for_image_count (album-photo-select) owns the
+ # MAXPREVIEWS-per-page grouping that album_page_records realises, and
+ # album_redirect_count_for_page_count (album-render) owns the per-page +
+ # last-page redirect tally that render_page_view_redirects emits. No dist
+ # files are touched here, so dry-run stays side-effect free.
+ page_count=$(album_page_count_for_image_count "$image_count")
+ redirect_count=$(album_redirect_count_for_page_count "$page_count")
plan_ref["details_count"]="$image_count"
plan_ref["page_count"]="$page_count"
plan_ref["redirect_count"]="$redirect_count"