This nightly adds per-stream equalizer overrides, an enable/disable toggle for audio streams, and a fix that makes detection.occurred alert rules work for all detections (not just new species). A batch of settings hot-reload and persistence fixes ensures that UI changes take effect immediately and survive restarts, especially in Docker deployments with custom config paths. Audio playback reliability improves with ETag-based cache invalidation that eliminates stale-cache 416 errors.
New Features
Per-Stream Audio Equalizer
Audio streams can now have their own equalizer overrides, mirroring the per-source EQ that was already available for sound cards. Each stream card in the UI exposes an expandable EQ panel. A bug where per-source EQ silently fell back to the global profile (the resolver matched on auto-generated registry IDs instead of the human-readable display name) is also fixed (#2860).
Audio Stream Enable/Disable Toggle
Configured audio streams can now be individually enabled or disabled without removing them from settings. Disabled streams are excluded from capture, monitoring, scheduling, and sound-level reporting. Legacy configs without the field default to enabled. The UI shows a distinct visual state for disabled streams (#2836 by @alexjurkiewicz).
Detection Events for All Detections
detection.occurred alert rules now fire for every detection, not just new species. Previously, ordinary detections returned early before reaching the event bus, making the event type unusable for alerting. New species detections additionally emit detection.new_species so both event types can be used independently. The alert rule editor now shows event descriptions in the dropdown (#2865, fixes #2699).
Security
- Settings validation hardening - reject zero realtime interval (would suppress all detections), strip HTML tags from site name, reject path traversal (
..) in audio export path while allowing absolute paths (Docker volumes, NAS mounts), validate EQ frequency/Q factor ranges, enforce dynamic threshold cross-field constraints, reject negative retention values (#2876, #2893).
Bug Fixes
Configuration & Settings Persistence
- Settings lost on restart in Docker deployments -
FindConfigFile()now checks the--configCLI flag path before searching defaults, fixing a silent data loss bug where the API returned 200 but wrote to the wrong config file (#2874). - Settings not persisting to disk after range filter rebuild - the API controller's publish-to-global check used pointer identity, which broke after any out-of-band
StoreSettingscall. Replaced with a stable ownership flag (#2877, #2884). - Processor settings stale after UI changes - detection threshold, false positive filter, dog bark filter, privacy filter, daylight filter, extended capture, and all other processor-level settings now read from the latest published snapshot instead of a cached copy from startup (#2866).
- Range filter mutations violated immutable-snapshot contract - converted to clone-mutate-publish pattern, fixing a race where concurrent readers could observe torn writes on the species slice (#2869).
- Hot-reload of coordinates/API key and spectrogram settings - weather service and spectrogram generation now sample fresh config per request (#2841 by @bo0tzz).
- PATCH support for logging, alerting, backup, output sections - these sections previously returned 400 "unknown settings section" (#2875).
- PATCH support for perch, models, taxonomySynonyms sections - same issue; taxonomy synonym merges now preserve existing entries (#2886).
Audio & Streaming
- Extension-less clip paths in database - three validation gaps in the audio export settings flow combined to produce
clip_namerows ending in a bare., causing 404s on download and spectrogram generation. Fixed with defense-in-depth across frontend format switching, backend validation, and path construction (#2817). - Stale audio cache producing 416 Range Not Satisfiable - added ETag header (mtime + file size) to SecureFS-served files, invalidating browser cache entries left over from the pre-gzip-fix era (#2858).
- Source/stream rename not reflected in audio registry - renaming a source or stream in the UI now updates the registry DisplayName and fires a reconfigured event, so EQ overrides resolve against the new name (#2861).
Dashboard, Wizard & Authentication
- Guest users behind Cloudflare Tunnel redirected on navigation -
fetchWithCSRF()no longer treats 401 as "session expired" for guest-mode users; guests get a handled API error instead of a disruptive redirect (#2827, fixes #2779). - Pre-auth API calls firing before login - settings load and restart-status check are now guarded behind an auth check, eliminating 4 console errors per unauthenticated page load (#2889).
- Wizard reappearing after dismissal with auth enabled - localStorage check now runs before the
freshInstallbranch for all wizard flows (#2889). - Notification button accessibility - Edit/Delete icon-only buttons on the notifications settings page now have
aria-labelattributes (#2889). - Missing basepath on images and spectrograms - several asset URLs were not wrapped in
buildAppUrl, breaking display behind reverse proxies (#2873 by @rexxars). - Dark spectrogram text on live stream page - spectrogram overlay now uses a light foreground color for readability (#2863 by @rexxars).
Notifications
- User notification templates ignored on alert path - detection notifications dispatched via the alerting engine used hardcoded fallback messages instead of rendering user-configured templates. Shoutrrr providers (Telegram, Pushover, ntfy) now correctly apply custom title/message templates (#2842).
Telemetry & Reliability
- Diskmanager log flooding - default module log level changed from debug to info, preventing 100+ MB/day log files from per-clip debug entries (#2825).
- Duplicate Sentry events from unhandled promise rejections - Modal confirm and sidebar logout handlers no longer re-throw errors that are already captured via the structured logging path (#2818).
- Sentry noise from network errors when backend offline - dashboard API calls now check connection state before fetch, and all three Sentry capture paths filter network-level TypeErrors (#2851, #2854, #2855).
- Duplicate request guard for detection quick-review - rapid clicks on Correct/Incorrect no longer fire concurrent verification POSTs for the same detection (#2816).
Internationalization
- Estonian BirdNET labels updated - English common names replaced with official Estonian names from EOÜ (Estonian Ornithological Society) naming sources, matched by Latin taxon (#2822 by @alarmatwork).