0.2.68 (unreleased)

  • Fall back to the plain theme selector fragment for HTMX requests when a theme omits the optional select_theme.html template.

  • Report missing soft-required theme templates as cast.W002 system check warnings, including configured themes and templates provided by custom loaders. Template syntax errors also produce warnings rather than aborting startup checks.

  • Rename generic Speaker N: cue prefixes as well as voice tags through Transcript.rewrite_speaker_labels. Renamed prefixes become voice tags for subsequent renames and public speaker sanitization.

  • Resolve Voxhelm API base and token as a pair from one configuration level. Incomplete pairs no longer inherit deployment credentials. Move mixed-level credentials to one level before upgrading; changing the Wagtail API base requires re-entering its token. Saving with both credential fields blank removes the site pair so deployment credentials can be used.

  • Exclude view-restricted pages from navigation links in blog, post and feed contexts, including for signed-in visitors. Pages beneath a restricted page are also omitted; member-only sites must supply their own navigation. Which pages are candidates for the navigation is unchanged.

  • Render shared feed descriptions with the blog/site default theme, ignoring visitor session, query and internal preview theme overrides.

  • Rate-limit comment posting. Both comment post views used to accept any number of comments from one client. They now allow CAST_COMMENTS_POST_RATE_LIMIT posts (default 20) per CAST_COMMENTS_POST_RATE_WINDOW seconds (default 600) per logged-in user or anonymous client address and answer further posts with 429 and Retry-After; the AJAX view returns the usual JSON error shape so the bundled script shows the message at the comment field. Previews are not counted, and 0 disables the limit. Sites behind a reverse proxy need REST_FRAMEWORK["NUM_PROXIES"] for the client address to be correct; see CAST_COMMENTS_POST_RATE_LIMIT.

  • media_replace --yes now exits with CommandError (non-zero status) when at least one replacement failed. It used to print errors=<n> in the summary and still exit successfully, so scripts could not tell a failed or partial run from a clean one. The remaining paths are still attempted and the summary is still printed first. Automation that relied on the old exit status needs to handle the failure.

  • Add media_backup --refresh-changed. media_backup skips every path that already exists in the backup, so a file changed in place (for example by media_replace) kept its old bytes in the backup forever and media_restore would have brought the old file back. The new option compares existing files by content (size as a shortcut for “different”, SHA-256 of both copies when the sizes match) and overwrites backup copies that differ. The new bytes are staged in the backup storage first and the previous copy is never deleted, so a production file that cannot be read, a staged copy that cannot be written, or a backup storage that does not overwrite existing names leaves the previous backup copy in place. If the final overwrite itself fails, what remains under the path depends on the backup storage backend: object storage keeps the previous object, a backend that writes in place (FileSystemStorage with allow_overwrite) can leave a partial file. Failures are reported, the remaining paths are still processed, and the command exits non-zero. The default mode and media_restore are unchanged.

  • Send known-speaker voice references whenever a diarized transcript request names only the audio, such as generate_transcripts --audio-id. The episode is now resolved from the audio when it belongs to exactly one episode; before, such requests silently ran without references and returned anonymous diarization labels only.

  • Warn when known-speaker recognition is enabled but a diarized request sends no voice references. generate_transcripts prints the warning, and the service logs it on cast.voxhelm.service for queued requests.

  • Name a guest without a voice reference when applying known-speaker suggestions. Before, such a guest’s lines stayed blank, and on episodes whose anonymous Speaker N labels had approved contributor mappings a known-speaker re-run lost the guest’s name. When exactly one assigned contributor got no reference and exactly one diarization label is tied to no known speaker, the uncertain segments on that label now get the contributor’s name. Ambiguous cases still stay blank. The label map comes from Voxhelm’s raw_diarization_labels summary or, for older sidecars, from the same confident-segment vote.

  • recalc_video_posters now exits with CommandError (non-zero status) when at least one poster could not be recalculated. It used to print errors=<n> and still exit successfully. All videos are still attempted, healthy ones are saved, and the summary is printed first. Failed poster generation is now counted at all: Video.create_poster() logs and swallows errors so uploads succeed without a poster, which made the command report errors=0 for them. The command asks for the error (create_poster(raise_errors=True), default unchanged) and also counts a video that ends up without a wanted poster. Automation that relied on the old exit status needs to handle the failure.

  • styleguide_prefetch --with-renditions no longer leaves CAST_STYLEGUIDE_GENERATE_RENDITIONS switched on. The command changed the process-wide setting and never put it back, so after call_command in a long-running process the styleguide kept generating renditions, also when the command had failed. The previous value (or the absence of the setting) is now restored on success and on failure.

  • scripts/benchmark_typeahead.py (development benchmark, not shipped) checks its target before it migrates or writes. seed and all ran the migrations, pointed the default Site at the page tree root and created the benchmark user before refusing a database with real Blogs. A read-only preflight now refuses first: any settings module other than scripts.typeahead_benchmark_settings, Blogs outside the typeahead-benchmark- prefix, pages outside those Blogs (other than the tree root and Wagtail’s untouched default home page), and user accounts other than typeahead-benchmark. Empty, freshly migrated and previously seeded benchmark databases work as before. scripts/benchmark_feeds.py seeds through the same check.

  • Clear the cache before every test. A feed response cached by one test was served to a later test that requested the same feed URL, so test_get_only_published_entries failed depending on the random test order.

  • Protect media_stale cleanup with a configurable minimum storage-file age (CAST_MEDIA_STALE_MIN_AGE_DAYS, default 7 days). Skip unknown ages, re-check age and exact database file references before each deletion, and report files skipped as too young or newly referenced. Listing remains the default; the command does not delete database rows.