chat_action was stored in two places — the model column and config JSON —
and dispatch_helpers unconditionally overrode the config value with the
column. The frontend only ever wrote the JSON path, so the UI choice
silently had no effect on outgoing chat actions.
Make the column the single source of truth: frontend sends chat_action
top-level, dispatch_helpers reads from the column, and a one-time
backfill migrates existing config values to the column and strips the
legacy key.
Also fix a long-standing race where the keepalive's bare sleep(4) +
finally cancel could fire one last sendChatAction after the response
already arrived, leaving a phantom indicator for ~5s. Replace with a
stop event + wait_for so callers can signal stop cleanly via the new
stop_keepalive helper.
Display filters (Immich tracking config):
- favorites_only drops events with no favorited new assets, or filters
added_assets to favorites only
- assets_order_by/assets_order sort the rendered list
(date / name / rating / random / none)
- max_assets_to_show caps rendered+attached media (default 5 -> 10)
- include_tags strips people from event extras and tags from each asset
- include_asset_details strips city/country/state/lat/lon/is_favorite/
rating/description; load-bearing fields (thumbhash, file_size,
playback_size, cache keys) preserved
- New apply_tracking_display_filters helper in dispatch_helpers; wired
into watcher, webhooks, scheduled/periodic/memory, and manual
test-dispatch
- Targets sharing a TrackingConfig dispatch together; targets with
different TCs each see their own shaped event
Adaptive polling:
- Replace NotificationTracker.batch_duration with adaptive_max_skip
- Per-tracker opt-in: NULL/0 disables back-off (every tick runs);
positive N caps the skip factor at (N-1)-in-N after long idle
- Scheduler caches the cap in module state for the tick fast-path
- Migration adds the new column; API schemas/responses, frontend types,
i18n, and the tracker form updated to match
Add quiet_hours_enabled/start/end to TrackingConfig (HH:MM strings
interpreted in the app-level timezone AppSetting). The dispatch path
loads the app timezone once per run and passes it through
event_allowed_by_config -> in_quiet_hours, so overnight windows like
22:00-07:00 work correctly in any IANA tz.
Frontend exposes a Timezone field under Settings and a Quiet Hours
section on the Immich tracking-config form with time-picker inputs.
Add a new "webhook" provider type that accepts arbitrary HTTP POST payloads,
extracts template variables via user-defined JSONPath mappings, and dispatches
notifications through the existing pipeline. Supports three auth modes
(HMAC-SHA256, Bearer token, none), bounded JSONPath cache, and 1MB payload limit.
Full stack: core provider + event parser, API endpoint, DB migration,
capabilities, seeds, default templates (EN/RU), frontend descriptor, i18n.
- Tracker now has default_tracking_config_id and default_template_config_id
that apply to all linked targets unless overridden per-target
- Dispatch falls back to tracker defaults when per-link configs are null
- Email bot creation validates SMTP connection before saving
- Email notifications sent as HTML (links render properly)
- Linked target items are expandable: collapsed shows config CrossLinks,
expanded shows config selectors; action buttons always visible
- Fix email bot test button icon (mdiEmailSend → mdiSend)
- Fix target type icons in LinkedTargetsSection for all types
- Provider filter moved above search in sidebar
Add broadcast target type that fans out notifications to multiple
child targets. Dispatch expands broadcast into children in
load_link_data() — dispatcher stays unaware. Children can be
toggled on/off via disabled_child_ids in config.
Also: dashboard provider card smaller font for names, scroll-to-form
on target edit, broadcast nav tab with counter, flag_modified fix
for JSON column updates, CLAUDE.md nav tree docs.
- Introduce Receiver base class + typed subclasses (TelegramReceiver,
WebhookReceiver, EmailReceiver, etc.) in core/notifications/receiver.py
- Dispatcher uses typed Receiver objects instead of raw dicts, with
per-receiver locale-aware template rendering
- load_link_data resolves locale from TelegramChat.language_override at
load time: TargetReceiver.locale || chat.language_override || chat.language_code
- Add language_override field to TelegramChat (separate from auto-detected
language_code), with per-chat commands toggle and command dispatch using
override language
- Add locale field to TargetReceiver for explicit per-receiver overrides
- Add locale support to notification templates (matching command template
pattern): TemplateSlot now has locale field with (config_id, slot_name,
locale) uniqueness, nested API format {slot: {locale: template}}
- Migration merges separate EN/RU system configs into unified per-provider
configs; seeds create one config per provider with multi-locale slots
- Locale-aware dispatch with EN fallback in NotificationDispatcher
- Frontend locale tabs (EN/RU) on template config editor
- Fix tracking config cards not showing default provider icons
- Global provider filter, search palette, and various UX polish