summaryrefslogtreecommitdiff
path: root/src/lib
AgeCommit message (Collapse)Author
2026-06-16wn0 batch preview-thumbnail rendering per pagePaul Buetow
Each thumbnail on a preview page (page-N.html) used to be rendered by its own "template preview" call, paying the full source_template_file cost -- an "env -i bash" invocation -- per thumbnail. With MAXPREVIEWS thumbnails per page that was N template renders per page just for the grid. render_full_preview_page now builds the markup for ALL of a page's thumbnails in bash (build_preview_thumbnail / append_preview_thumbnail) and emits the whole grid in ONE render via a new previewpage.tmpl that takes the pre-built HTML through a context_raw "preview_thumbs" field -- the same pattern the stats filter galleries (camera.tmpl) already use. Per-thumbnail markup is byte-identical to the old preview.tmpl output: same <a name=... href=...><img class='thumb <anim>' .../></a> structure, order, HTML escaping and seeded "slow" animation class. Header and footer stay as their own template calls, so a page now costs ~1 previewpage render + header/footer instead of N + chrome. The parallel job-pool integration and failure contract are unchanged: each preview page is still one background render job. Added the render_preview_thumbs_html field spec (hn0 dispatch pattern, context_raw kind), registered previewpage in the validate_template_dir required templates and in the required-context-vars test expectations, and pointed the four generation template-failure tests at previewpage.tmpl (generation no longer renders preview.tmpl). The standalone "template preview" engine unit tests keep exercising preview.tmpl, which still ships. Verified byte-identical output: generated the fixture album (including a spaces/special-char filename) twice with RANDOM_SEED=42 using the parent commit's bin/shuriken vs the new bin; every .html file is identical. just test, just shellcheck, just check-generated and git diff --check all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16jn0 split bootstrap.source.sh into logging/paths/startup modulesPaul Buetow
Separate the mixed concerns in bootstrap.source.sh per [SRP]: - logging.source.sh: output_is_quiet, output_is_verbose, log_info, log_verbose, log_warning (the output/logging concern). - paths.source.sh: resolve_default_rc_file, resolve_source_root, resolve_default_template_dir, resolve_default_asset_dir, template_dir_uses_default, apply_template_dir_default, init_config (install/source-root/default-dir + rc-file/template-dir resolution). - bootstrap.source.sh keeps the CLI usage text and the shared resolve_config_array parser used by the config modules. Pure code move, verified byte-identical function bodies. LIB_SOURCES now lists logging.source.sh first, then bootstrap, then paths; matching source directives added to src/shuriken.sh so --check-sourced sees the new modules. just test/shellcheck/check-generated and git diff --check all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16cn0 split stats.source.sh into aggregate/render/filter-album modulesPaul Buetow
Pure code move: split the ~1248-line stats.source.sh (SRP+SoC) into three cohesive modules, all functions and STATS_* constants/globals moved verbatim: - stats-aggregate.source.sh: EXIF parsing, bucketing, _stats_tally, reset/collect_photo_exif_stats and the STATS_* maps. - stats-render.source.sh: stats overview page (sections, bars, leaderboard, render_stats_page, _stats_build_body, _stats_filter_link, background pickers). - stats-filter-album.source.sh: per-filter mini-albums (render_filter_pages, gallery/view/thumbnail builders, _stats_enqueue_filter_album). Updated LIB_SOURCES accordingly. No logic change (verified: all 55 functions and top-level globals preserved; full suite, shellcheck, check-generated, diff all pass). The SoC point (HTML built in bash rather than templates) is left for a follow-up (nn0/qn0). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16in0 split album.source.sh into cohesive modulesPaul Buetow
Refactor the ~1442-line album.source.sh ([SRP] violation) into three cohesive modules, leaving album.source.sh a thin coordinator. Pure code move, no behavior change -- whole functions and their doc comments moved verbatim; the assembled bin/shuriken contains the same function set. - image-pipeline.source.sh: ImageMagick derivative generation (create_photo_derivatives, create_all_photo_derivatives) and photo asset prep (prepare_generation_photo_assets). - album-metadata.source.sh: EXIF identify caching, exif tooltip/details helpers, file counts, shuriken.json metadata, EXIF cache clearing and the dry-run plan. - album-render.source.sh: preview/view/details/splash/redirect page rendering, the parallel render-job orchestration and the ALBUM_VIEW_PAGE_BY_PHOTO global. - album.source.sh: thin coordinator (generate, refresh_splash, generate_stats_pages, site-asset/archive/html-clean helpers). Add the three modules to LIB_SOURCES (Justfile) and to the SHURIKEN_LIB_SOURCES markers in src/shuriken.sh, ordered after random and before album.source.sh / stats.source.sh. just build, just test, just shellcheck, just check-generated and git diff --check all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16hn0 make template render kind dispatch extensible (OCP)Paul Buetow
Replace the hardcoded `case "$kind"` block in prepare_template_render_vars with a name-based registration/dispatch pattern. Each render field kind is now implemented by one prepare_template_render_var__<kind> handler; the core loop resolves the handler by name (prepare_template_render_var__$kind), verifies it exists via `declare -F`, calls it with a uniform signature (out_nameref, context_name, source_name), and reports a config_error for an unknown kind (no matching handler) -- preserving the previous error behavior. Adding a new kind now means defining a new handler function only; the loop never changes. Handlers cover all existing kinds: context_css, context_html, context_raw, current_date_html, config_html (keeps its inner source_name dispatch for HEIGHT/MAXPREVIEWS/TITLE/etc. and the same :- defaults), original_basepath_is_set, preview_num_next_html, preview_num_prev_html, and tarball_include. Escaping and defaults are unchanged, so rendered output is byte-identical (verified by diff -r of a full --generate album, before vs after). Add test_template_render_var_dispatch_is_extensible proving every declared kind resolves to a handler and that a newly defined handler is dispatched without touching the core loop (OCP). just test, just shellcheck, just check-generated and git diff --check all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16vn0 parallelize preview pages and navigation redirectsPaul Buetow
Only per-photo view/detail pages were backgrounded; preview pages (page-*.html) and navigation redirects rendered serially in the main loop, serializing album generation. - render_full_preview_page assembles a complete preview page (header, optional prev link, ordered thumbnails, next/footer) in one call so the per-page appends stay sequential; queue_preview_page_render_job backgrounds each whole page via the shared template render pool, throttled to IMAGE_JOBS. - render_album_pages now materializes the full deterministic page layout up front (album_page_records) and enqueues both view jobs and one preview-page job per page into the same pool; per-photo bookkeeping moved to _album_record_view_photo. - render_view_redirects enqueues one job per view page (render_page_view_redirects) and blocks until all redirects exist, preserving its synchronous contract while parallelizing the work. - Removed obsolete advance_album_preview_page and render_album_page_thumbnail. Parallelism only affects timing: seeded random choices are context-keyed, so output is byte-identical across runs. A failed preview or redirect job flips render_failed and makes generation exit non-zero, matching the view-job failure contract. The IMAGE_JOBS cap still bounds concurrent renders. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16un0 build template context file without declare -f or a bash subprocessPaul Buetow
source_template_file previously built the BASH_ENV context file by piping "declare -p ...; declare -f; serialize_template_render_context ...;" into a fresh "bash -euo pipefail" subprocess for every rendered page. That dumped all ~5000 lines of shuriken functions and spawned a subprocess per page just to run the serializer - a large per-page cost for albums with hundreds-to-thousands of pages. Now serialize_template_render_context runs in the current shell with stdout redirected straight into the context tempfile, followed by an appended "unset BASH_ENV". The serializer returns its own non-zero status explicitly so the failure is detected via an "if" status-test (which returns normally through the RETURN trap and cleans up the partial context file), robust even when source_template_file runs inside a status-tested "if template ..." call chain where bash would otherwise suppress an inner errexit abort. The trap-based cleanup (RETURN plus INT/TERM/HUP re-raising to $BASHPID) is preserved unchanged. Two serializer test mocks that relied on the removed "| bash" errexit now return non-zero explicitly. Rendered HTML output is unchanged; just test, just shellcheck, just check-generated and git diff --check all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15Make the favicon configurable via FAVICON config / --favicon flagPaul Buetow
The generated pages link a favicon.ico that was always the bundled shuriken favicon (copy_site_favicon hard-copied share/shuriken/assets/favicon.ico). Add a FAVICON config variable and a --favicon PATH CLI flag: when set, that file is published as favicon.ico instead of the bundled default; when empty, the bundled favicon is used as before. Plumbed through apply_config_defaults, CLI_OPTION_SPEC + override allowlist, usage, print_config, the action config list and effective-setting log, and validated (a non-empty FAVICON must be a readable file) before generation. shuriken.default.conf and the README document it; a test covers a custom favicon, its appearance in --print-config, and rejection of a missing file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15Stats backgrounds: fit the category instead of album-wide randomPaul Buetow
The filter mini-album pages all drew their blurred background from the whole album, so a filter page's background did not relate to its category. Mirror the main album instead: - A filter gallery's background is now a random photo from THAT filter's own set (like the album preview pages pick a random album photo). - A filter view page's background is the photo it shows (like the album view pages). - The stats overview still uses a random album-wide photo. Adds _stats_pick_background (random pick from a filter's photo list) and drops the now-unused album-wide background preload from render_filter_pages. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15Fix dist root permissions so --sync publishes a readable directoryPaul Buetow
The dist directory is the swapped-in staging dir, which mktemp -d creates mode 0700. The published album root therefore stayed 0700, so the first `shuriken --sync` created the remote album directory 0700 and the web server (daemon) could not read it -- requiring a manual chmod 755 on each mirror. Relax the staging dir to the umask-default directory mode right after mktemp -d (what mkdir would have produced), so the dist root matches its subdirectories and is served/synced with sane permissions. Add a regression test asserting the dist root mode equals its photos/ subdirectory mode. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15Move the stats site into a stats/ subdir to keep the album root smallPaul Buetow
The filter mini-albums put thousands of HTML files directly in the album root. Reorganise so only the main album lives in DIST_DIR and all stats content goes under stats/: stats/index.html - the stats overview (was stats.html) stats/<pagebase>/index.html - each filter gallery (was <pagebase>.html) stats/<pagebase>/<index>.html - each filter view page (was <pagebase>--N.html) Each mini-album is its own directory, so no single directory holds more than one filter's pages. Links updated accordingly: the overview links to <pagebase>/, the gallery thumbnails link to sibling <index>.html, view prev/next/gallery are same-dir, and assets/album-details/back-to-stats resolve via the ../.. backhref. The header Stats link and camera.tmpl back link point at stats/index.html. Mini-albums still reuse the shared photos/thumbs/blurs assets and render in parallel. Tests and the dry-run plan updated for the new layout; README documents the stats/ subdirectory structure. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15Persist the EXIF cache in a volatile ./cache dir parallel to ./distPaul Buetow
The per-photo `identify -verbose` cache lived in $DIST_DIR/.shuriken-cache, which is inside the throwaway staging dir and the published dist: a fresh/cleared dist lost it (forcing a full, slow re-identify of every original) and it got deployed to web servers. Move it to a volatile ./cache/exif directory parallel to ./dist (dirname of DIST_DIR resolves to the working dir in both the staging and direct contexts). Now an unchanged photo skips identify on every regenerate, the cache survives a cleared dist, and it is never deployed. --force clears ./cache/exif once up front (clear_exif_cache) and the run repopulates it, so force still does exactly one identify per photo. Dropped .shuriken-cache from the staging-copy loop. Tests and the audit doc updated for the new cache location. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15Cache the background photo list for filter pagesPaul Buetow
_stats_random_background scanned the photos directory once per page; with a filter mini-album per stat bucket that is thousands of scans. Load the sorted photo list once into a global (STATS_BG_PHOTOS) that render_filter_pages populates before forking the render jobs, so each background subshell inherits the cached array and just indexes into it. The seeded pick is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15Make every stat bucket a clickable filter mini-album, rendered in parallelPaul Buetow
Generalises the per-camera mini-albums to every stats category: clicking any row on the stats page (camera, lens, year, month, aperture, shutter, ISO, focal length, megapixels, aspect, orientation, format, exposure program, metering, white balance, flash) now opens a filter mini-album of just the matching photos, with view pages whose prev/next cycle within the filter. Aggregation: a single _stats_tally now records, for every tallied bucket, the bucket's photo list and a unique filename-safe "pagebase" (camera-canon-eos-r5, iso-400, year-2023, ...) with collision resolution, exposed via STATS_FILTER_* maps. The old camera-only STATS_CAMERA_SLUGS/PHOTOS/SLUG_OWNERS are gone. Render: every bar/leaderboard row is wrapped in a link to its bucket's pagebase via _stats_filter_link. render_filter_pages replaces render_camera_pages and renders ALL filter mini-albums (gallery + per-photo view pages) reusing camera.tmpl/cameraview.tmpl. The galleries/view pages reuse the album's shared photos/thumbs/blurs assets -- only the HTML differs. Parallelism: filter pages render through the shared job pool, throttled to IMAGE_JOBS (the same pool the album view pages use). The album render warms every photo's EXIF cache (via details pages) before the parallel filter render, so the filter jobs only read the cache. Tests updated for the new STATS_FILTER_* structures and extended to assert non-camera filter mini-albums (orientation, ISO) and their stats-page links. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15Per-camera mini-albums: full-width grid + camera-scoped view navigationPaul Buetow
Two fixes to the per-camera pages: - The camera thumbnail grid is now full-width and reflows with the window like the main album. Previously it sat inside the 900px-wide stats column, so wide thumbnails only fit ~2 per row. camera.tmpl now puts the grid in a full-width sibling block (centered by the body, like the album's direct thumbnail children); the heading/back-link stay in the narrow stats column. - Each camera is now a self-contained mini album. Thumbnails link to per-camera view pages (camera-<slug>--<index>.html) whose prev/next cycle only through that camera's photos, instead of linking into the shared album view pages (where next/prev walked the whole album and left the camera). The view pages reuse the album's look (blurred background, EXIF tooltip, animation) and offer Gallery / Details (to the album details page) / Direct-link nav. The "--" index separator can never collide with a gallery name since slugs never contain "--". Adds cameraview.tmpl + its render field, _stats_render_camera_{gallery,views} and the cameraview body builders. Tests updated for the new thumbnail links and extended to assert the per-camera view pages and their camera-scoped navigation; required-context-vars test now also covers the camera and cameraview templates. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15Stats/camera pages: blurred background, album-like camera thumbs, spacingPaul Buetow
Three refinements to the stats site: - Stats and per-camera pages now get a random blurred background image (same seeded pick the album preview pages use) instead of a plain black page. Adds _stats_random_background + STATS_BLURS_DIR; falls back to black when no photos exist (e.g. isolated unit tests). - Per-camera thumbnails now behave like the main album: they link to each photo's album view page (navigation, details, EXIF tooltip) and carry the same seeded animation class, instead of linking to the raw image. render_album_pages records a photo -> "<page>-<preview>" map (ALBUM_VIEW_PAGE_BY_PHOTO) that the camera pages read; photos with no recorded view page fall back to the image. - Spacing: the camera leaderboard rows get padding + a separator line so the long wrapping camera names are distinguishable, and the stats sections, headings, bar rows, back-link, and camera grid get moderate extra spacing so nothing looks cramped. Tests updated for the new camera-thumbnail markup and extended to assert the stats background and the album view-page links; camera.tmpl comment corrected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15Add EXIF mouse-over tooltip to the normal image viewPaul Buetow
The details view already showed a title="" hover summary (camera, aperture, ISO, shutter, date) on its image; the normal image view did not. Wire the same tooltip into view.tmpl: - render_view_page now computes the EXIF tooltip via photo_exif_tooltip_text (reusing the shared identify cache, so no extra ImageMagick work) and passes it as the exif_tooltip context var. - exif_tooltip is now a required render field for the view template too. - view.tmpl adds the title="" attribute to its <img> when the summary is non-empty, mirroring details.tmpl (no title when there is no EXIF). - Tests: extend the EXIF render test to assert the view image carries the same tooltip, and update the required-context-vars expectation for view. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14rm0 surface stats page in the dry-run planPaul Buetow
Review follow-up: collect_dry_run_plan stored plan["stats_page"] but print_dry_run_plan never rendered it, so --dry-run gave no signal about the STATS_PAGE toggle or the stats/camera pages generate() would write (dead assignment, breaking parity with splash_page). Print a "Stats page: <yes|no>" line and, when enabled, list stats.html and camera-*.html under "Planned generated files" (camera pages are a wildcard since dry-run does not run EXIF aggregation). Add a --dry-run --no-stats test asserting the stats plan is omitted, and extend the existing dry-run test to assert the stats lines when enabled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14rm0 wire stats feature into generate with STATS_PAGE togglePaul Buetow
Final integration of the stats-site feature: - Add STATS_PAGE config (default yes) mirroring SPLASH_PAGE through every layer: shuriken.default.conf, apply_config_defaults, CLI_OPTION_SPEC (--stats/--no-stats), the CLI_CONFIG_OVERRIDE_TARGETS allowlist (without which the flag was silently ignored), usage text, yes/no validation, print_config, the action config list + effective-setting log, the generation metadata, and the dry-run plan. - generate() now calls a gated generate_stats_pages helper after the album pages (and before archiving, so the stats/camera pages are tarballed): collect_photo_exif_stats + render_stats_page . . + render_camera_pages . . - Emit "stats_page" in shuriken.json next to "splash_page". - Gate the header Stats nav link behind STATS_PAGE via a new render_stats_page_html config_html field, so it is hidden (no 404) when stats are disabled. - Document STATS_PAGE and --stats/--no-stats in the README. - Tests: full --generate produces stats.html + camera-<slug>.html + the nav link and stats_page=true; --no-stats suppresses all three and sets stats_page=false; print-config and metadata expectations updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14um0 add per-camera filter pagesPaul Buetow
Implement render_camera_pages <html_dir> <backhref> in stats.source.sh: one camera-<slug>.html per camera in STATS_CAMERA_SLUGS, reusing the aggregation's collision-resolved slugs and per-camera photo lists so filenames match pm0's leaderboard links. Each page reuses the shared header/footer chrome plus a new camera.tmpl that renders an HTML-escaped camera heading, a pre-built thumbnail grid, and a back-to-stats link. Thumbnails reuse preview.tmpl's markup style (thumb image from thumbs/) but link to the full-size image under photos/ rather than a per-photo view page, since the view-page name is not derivable outside the album pagination loop. The photos and thumbs dir names are fixed to match generate()'s render_album_pages call. Cameras are walked in LC_ALL=C label order for reproducible output, and cameras with no recorded photos are skipped. Adds the camera_name/camera_thumbs render field specs and a tests/cli.sh case covering per-camera files, slug collisions, HTML escaping, thumbnail link targets, and determinism. This provides the function + template + tests only; wiring into generate() and the STATS_PAGE toggle remain for rm0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14pm0 pin leaderboard sort collation for reproducible outputPaul Buetow
Review follow-up: _stats_keys_by_count_desc sorted the count tie-break under the caller's locale, so the generated stats page could differ byte-for-byte across locales/machines. Pin the sort with LC_ALL=C to keep the static output reproducible, consistent with the project's seeded / deterministic generation behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14pm0 add stats page template and render functionPaul Buetow
Render the aggregated STATS_* counters from the om0 module into a static stats.html. render_stats_page builds the variable-length body (camera leaderboard, temporal/exposure/dimension histograms, format and enum breakdowns) as HTML and hands it to the new stats.tmpl through a raw context field, wrapping it with the shared header/footer the way view/details pages do. Bars are pure CSS so the page stays JavaScript-free. - new template share/templates/default/stats.tmpl (page chrome + body) - render_stats_page <html_dir> <backhref> [page_name] plus small section builders in src/lib/stats.source.sh; EXIF-derived labels are HTML-escaped - new TEMPLATE_RENDER_FIELD_SPECS field render_stats_body_html (context_raw) and 'stats' added to render_backhref_html's required list - camera leaderboard entries link to camera-<slug>.html (um0 owns those) - Stats nav link wired into the shared header bar - tests cover leaderboard links, counts/percentages, a histogram section, &/< escaping, and omission of empty categories Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14om0 fix camera slug collisions and align ISO fallback orderPaul Buetow
Review follow-up on the stats aggregation module: - Distinct camera labels that sanitized to the same slug (e.g. two models differing only in punctuation) shared one STATS_CAMERA_PHOTOS list and one camera-<slug>.html link, so um0 would render a merged/ambiguous per-camera page. Add _stats_resolve_camera_slug, which uniquifies colliding slugs with a numeric suffix and tracks ownership in a new STATS_SLUG_OWNERS reverse map. It returns via a nameref (not command substitution) so the global mutation persists in the caller's shell. - Align the stats ISO fallback order with album.source.sh's tooltip builder (ISOSpeedRatings -> PhotographicSensitivity -> ISO) so a photo with multiple ISO tags buckets the same value it displays. - Document that the format breakdown trusts the file extension. - Add test_stats_distinct_cameras_get_unique_slugs covering the collision fix and slug reuse on re-encounter. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14om0 add stats aggregation modulePaul Buetow
Add src/lib/stats.source.sh implementing the aggregation layer for the stats site feature. collect_photo_exif_stats() iterates the album's incoming photos, reads each one's cached identify -verbose output via album.source.sh's cache helper, and fills global STATS_* associative arrays for the camera leaderboard, per-camera photo lists, temporal, exposure, dimension, format and enum stats. Builds exactly the v1 stat set recommended by docs/stats-exif-audit.md (rendering is left to the sibling tasks pm0/rm0/um0). Wire stats.source.sh into the Justfile LIB_SOURCES list (after album, before config) and regenerate bin/shuriken. Add unit tests exercising synthetic identify fixtures, rational/bucket boundaries, DateTimeOriginal substring parsing, missing-field tolerance, and the cached-output path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-146m0 re-raise interrupt to $BASHPID, not $$, in render subshellPaul Buetow
The signal handler in source_template_file re-raised the caught signal with `kill -s $sig $$`. Because the function commonly runs inside backgrounded render subshells (queue_album_view_render_job), $$ expands to the main shuriken PID, not the subshell's own. On interrupt the handler therefore signalled the main shell -- which had already cleared its own staging traps -- killing it mid-cleanup (leaking the staging dir) instead of terminating just the render subshell. Re-raise to $BASHPID, the current (sub)shell's real PID, which equals $$ in the foreground case so it is correct everywhere. Extend test_template_interrupt_removes_context_file to run the render in a backgrounded subshell and assert the parent shell survives the re-raise; verified it fails against a $$-based build and passes with $BASHPID. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-146m0 clean up BASH_ENV context tempfile via traps on interruptPaul Buetow
source_template_file created its context tempfile with mktemp but only ran rm -f on the success and explicit-failure paths. When a render was interrupted by a signal (terminate_active_generation SIGTERMs the backgrounded render subtree on Ctrl-C/abort) the tempfile leaked. Register cleanup in exactly one place, inline in source_template_file's body: - A RETURN trap covers normal and error returns and clears all of these traps (including itself) so it cannot linger and fire on an enclosing function's return against the out-of-scope context_file local (set -u). The trap must be set in the function body, not a helper: without functrace a RETURN trap is not function-scoped and would fire when a helper returns, deleting the file before the render runs. - INT/TERM/HUP handlers remove the file, clear the traps and re-raise so the process still exits with the signal's default disposition. PIPE is not trapped (the internal context-build pipeline emits legitimate SIGPIPE); SIGKILL is untrappable, leaving only the OS-reaped KILL-escalation case. The redundant rm -f calls are removed. Adds test_template_interrupt_removes_context_file, which runs an in-flight render and SIGTERMs it, asserting the context file is gone. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14lm0 harden resolve_config_array callers against set -ePaul Buetow
The shared resolve_config_array helper returns non-zero when the source config var was never declared. Both callers (resolve_tar_opts, resolve_sync_destinations) invoked it unconditionally and ignored the return value, so under set -euo pipefail a genuinely-unset TAR_OPTS or SYNC_DESTINATIONS would abort the function before the empty-array fallback could run. Currently masked because apply_config_defaults always declares both vars, but the contract was unsafe to rely on. Guard both call sites with `|| true` and document why: the empty array the helper leaves behind is exactly what each caller's fallback expects. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14lm0 de-duplicate imagemagick detection and config-array parsingPaul Buetow
Introduce a shared resolve_config_array helper in bootstrap.source.sh that parses a config variable declared as either a Bash array or a whitespace-separated scalar into a named output array. resolve_tar_opts and resolve_sync_destinations now both delegate to it, with resolve_tar_opts keeping its "-c" default for the empty/unset case. validate_imagemagick now reuses resolve_imagemagick_command instead of duplicating the magick/convert probing, reporting failures through config_error so the validation output is unchanged. Add a print-config test covering the empty TAR_OPTS (scalar and array) fallback to the default. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14km0 harden parallel job poolPaul Buetow
2026-06-13Refactor helper modules for im0Paul Buetow
2026-06-13dm0: split dry-run planning from outputPaul Buetow
2026-06-13jm0: allow minimal refresh configsPaul Buetow
2026-06-13jm0: centralize runtime config defaultsPaul Buetow
2026-06-13Refactor generation metadata writing for cm0Paul Buetow
2026-06-12Refactor EXIF tooltip parsing for em0Paul Buetow
2026-06-12Make run_action status-safe for bm0Paul Buetow
2026-06-12Harden status-tested validation paths for bm0Paul Buetow
2026-06-12Fix remaining errexit masking for bm0Paul Buetow
2026-06-12Capture action and template setup failures for bm0Paul Buetow
2026-06-12Clean template context files on failure for bm0Paul Buetow
2026-06-12Guard template render setup failures for bm0Paul Buetow
2026-06-12Propagate dispatcher validation failures for bm0Paul Buetow
2026-06-12Unify validator fail-fast contract for bm0Paul Buetow
2026-06-12Consolidate CLI option specsPaul Buetow
Task: am0
2026-06-12Fix next template required vars for gm0Paul Buetow
2026-06-12Co-locate template required context vars for gm0Paul Buetow
2026-06-11fm0 deduplicate escape wrappersPaul Buetow
2026-06-11Fix HEIGHT photo scaling for task 5m0Paul Buetow
2026-06-11Refactor template render fields for task 7m0Paul Buetow
2026-06-11Make main CLI state explicit for task 8m0Paul Buetow