- Defer quiet-hours dispatches into new deferred_dispatch table; drain
job + periodic catch-up scan re-fire at window end with coalescing on
(link, event_type, collection_id).
- Add ON DELETE SET NULL migration on event_log_id and partial unique
index on (link_id, collection_id, event_type) WHERE status='pending'.
- Add release-check provider abstraction (Gitea/GitHub) with SSRF-safe
URL validation, settings UI cassette, and scheduled polling.
- Replace importlib-only version lookup with version.py helper that
prefers the higher of installed metadata vs source pyproject so stale
editable dev installs stop misreporting.
- Aurora frontend polish: MetaStrip component, ReleaseCassette,
EventDetailModal expansion, and i18n additions.
- Auto-refresh ticker is now silent: skips ``eventsLoading`` so the
loading placeholder no longer flashes, uses ``(event.id)`` key on
the events ``{#each}`` so unchanged rows reuse their DOM nodes, and
short-circuits the array reassignment when the visible page is
identical to what we already rendered. No-op refreshes leave the
list completely untouched.
- ``PageHeader`` crumbs (Routing · Notification, Operators · Bots, …)
were hard-coded literals. Moved to a new ``crumbs`` i18n namespace
with 9 keys; updated all 15 call sites to ``t('crumbs.*')`` so they
switch with the language.
- Tracker form's Immich feature-discovery banner now exposes both
``Open Tracking Config`` and ``Open Template Config``. Added the
``?edit=<id>`` auto-open hook to ``/template-configs`` (mirrors the
existing one on ``/tracking-configs``) so the new link lands users
directly on the editor.
Mirror the providers form pattern (defaultName tied to type) across
bots, targets, trackers, actions, and configs. Each form now derives
form.name from the selected type or provider while the user hasn't
manually edited it; switching to edit-mode flips the manualEdited
flag so existing names are preserved.
Defaults: bots → "<Type> Bot"; targets → type label; notification
trackers → "<provider> Tracker"; command trackers → "<provider>
Commands"; actions → "<provider> <Action Type>"; tracking/template/
command/command-template configs → "<descriptor.defaultName>
<Suffix>". TargetForm and TrackerForm grew an optional onnameinput
prop so parents can flag manual edits in subform inputs.
- Add overscroll-behavior: contain to all in-modal/popup scroll
containers (Modal body, EntitySelect, MultiEntitySelect, IconPicker,
IconGridSelect, SearchPalette, TimezoneSelector) so reaching the
inner scroll boundary no longer scrolls the page underneath.
- Telegram bot Discover Chats no longer collapses the existing chat
list into a "Loading…" placeholder. Split chatsLoading (initial)
from chatsRefreshing (Discover); rows are keyed by chat.id with
flip+fade animations; the list dims with a sweeping shimmer bar
while the Discover button shows a spinning icon and "Discovering
chats…" label. Honors prefers-reduced-motion.
Big batch — every secondary page now wears the same glass-card hero
that landed on Providers earlier:
- notification-trackers, tracking-configs, template-configs
- command-trackers, command-configs, command-template-configs
- targets (with active-tab title), actions
- bots (telegram / email / matrix tabs)
- settings, settings/backup, users
Each page picks an italic-em emphasis word, an editorial crumb (e.g.
'Routing · Notification', 'Operators · Bots', 'System · Maintenance'),
a count meter, and entity-specific status pills derived from live
data: 'X armed / Y paused' for trackers and actions, 'X types' for
configs/templates, 'X channels' or '$N receivers' for targets.
Other changes in this commit:
- Button.svelte: redesigned. Primary variant becomes a real Aurora
CTA — gradient lavender → orchid pill, 40px tall md / 34px sm,
inset highlight, lift + glow on hover. Secondary, danger, ghost
variants reworked to match. The 'Add <Type>' button on every page
now reads as the page's primary action instead of a flat lavender
rectangle.
- JinjaEditor: overrode oneDark's hardcoded background with
!important so the editor surface picks up var(--color-input-bg).
Gutters / scroller / selection / autocomplete tooltip all match
Aurora glass tokens now. Template editors stop visually clashing
with the surrounding panel.
- Aurora pulse dot: rewritten as a self-contained box-shadow glow
pulse (no transform, no pseudo-element). The dot's bounding box is
now stable so ancestors with overflow:hidden can never clip the
visible dot — only the (decorative) outer glow halo. Fixes the
'half-moon clipping' on the dashboard 'On watch' deck.
- topbar-action.svelte.ts left in tree but unused (topbar CTA was
reverted per your call). Will clean up in a later commit.
- Form input baseline styling moved into app.css (rounded 0.625rem,
glass background, hover/focus rings) so untouched filter inputs
on the per-type pages stop looking out of place.
i18n: emphasis / countLabel / armed / paused / receiver / receivers
/ channelsCount keys added across en + ru.
Build clean: 0 errors, 61 pre-existing a11y warnings unchanged.
Introduce a third update_mode option alongside polling/webhook. 'none'
disables both polling and webhook delivery — useful when another instance
owns the listener or when the bot is send-only. Switching into 'none' now
unschedules polling and unregisters any active webhook so Telegram stops
delivering updates.
New bots default to 'none' (safer when multiple bridges share a token).
Existing bots upgraded from a pre-update_mode schema keep 'polling' so
their behavior is unchanged.
Backend
- Per-chat album scope for Immich commands (search/latest/memory/...): new
allowed_album_ids on CommandTrackerListener, threaded listener/page kwargs
through ProviderCommandHandler.handle; PATCH listener-scope endpoint.
- /search and /find accept a trailing page number; Immich client search_smart
/ search_metadata take a page param.
- Immich person-asset lookup switched from removed GET /api/people/{id}/assets
to POST /api/search/metadata with personIds (fixes /person command and
auto_organize rules silently returning zero candidates on Immich 1.106+).
- Auto_organize rule now sets the target album's thumbnail to the first added
image when missing (falls back to any asset type); failures do not fail the
rule. add_assets_to_album surfaces the Immich error body on non-2xx.
- EventLog.user_id / action_id / action_name columns with defensive migration
+ backfill. Status query filters by user_id directly; Immich/webhook paths
emit user_id explicitly. action_runner writes an action_success/partial/
failed event on each non-dry-run.
- Dashboard DELETE /api/status/events (scoped to user_id) + rendering live
tracker/provider/action names via FK join with snapshot fallback.
- PATCH /api/users/{id} for username/role change with last-admin guard.
- Deletion protection returns structured {message, entity, blocked_by}
(ApiError carries .blockedBy; frontend opens BlockedByModal).
- Backup prepare-restore → AppSetting markers + atomic write of
pending_restore.json; lifespan hook applies on next startup and archives
under data/applied_restores/. apply-restart sends SIGTERM so the lifespan
shutdown runs; NOTIFY_BRIDGE_SUPERVISED env override gates the button.
Manual POST /api/backup/files (same format as scheduled).
- New periodic-summary test path reuses shared collect_scheduled_assets
(limit=0) so test and future production code go through one primitive.
- Per-receiver locale for Telegram test messages (resolves
TelegramChat.language_override per chat instead of applying the first
receiver's locale to everyone).
- Bounded concurrency (semaphores) in NotificationDispatcher._preload_asset_data
and _refresh_telegram_chat_titles; chat title sweep extended to 24h since
save_chat_from_webhook covers active chats opportunistically.
- Telegram poller detects the \"webhook is active\" 409 and auto-calls
deleteWebhook for bots whose DB update_mode is polling (throttled per bot).
- TelegramClient.get_chat added (CLAUDE.md rule 6); set_album_thumbnail added.
- Seeds: rename \"Default Commands\" → \"Default Immich Commands\";
track_assets_removed default False.
Frontend
- Global provider selector visible when there is only one provider.
- Clear-events button + i18n + ConfirmModal on the dashboard; new icons/
labels/filters/colors for action_success / action_partial / action_failed.
- Auto-select first available tracking/template/command/config + bot on
create forms (trackers, command-trackers, targets, template/command
configs).
- Telegram target disable_url_preview defaults to true.
- BlockedByModal wired into 8 deletion flows; fetchAuth helper for
multipart/binary calls (reuses api()'s refresh + ApiError mapping).
- Immich tracker 'Checking links' parallelised (concurrency cap 6).
- Backup page: pending-restore banner + Apply-now / Apply-later modal,
restarting overlay polling /api/health, manual 'Create backup' button.
- Command-trackers listener row gets an 'Edit album scope' modal with
inherit/explicit multiselect.
- Users page: Edit user modal (username + role).
- parseDate helper for consistent UTC date rendering.
Migrations / schema
- event_log: + user_id, action_id, action_name (+ backfill user_id from
notification_tracker).
- command_tracker_listener: + allowed_album_ids.
- Fix bot card header overflow by replacing "Sync with Telegram" text
button with icon button, add flex-wrap
- Rename sync button label to "Sync Commands"
- Remove decorative dashes from selector placeholders (— X — → X)
- Show selected provider name/icon in dashboard stat card when global
provider filter is active
- Add selector placeholder convention to frontend-architecture.md
- 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 commands_enabled field to TelegramChat (default off) with
migration, gating command dispatch in both poller and webhook
- Show toggle switch per chat in bot tab for enabling/disabling commands
- Fix listener response to include bot name instead of just type
- Replace listener "Enabled" label + "Edit" link with toggle switch
and crosslink to command-trackers page
- 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
Chat language:
- Added language_code field to TelegramChat model + migration
- Saved from message.from.language_code on webhook/polling
- Displayed as badge on bot chat cards and target receiver items
- Resolved from DB in target API response (works for existing receivers)
- Shown in chat picker dropdown (desc includes language)
EntitySelect improvements:
- Tracker-target link selector shows all targets, already-linked ones
appear disabled with "Already linked" hint
- Receiver chat picker shows already-added chats as disabled
Dev scripts:
- scripts/restart-backend.sh and restart-frontend.sh
- Updated .claude/docs/dev-servers.md to reference scripts