- 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.
Notifications:
- Add shared http_base, redact, and SSRF hardening modules
- Refactor dispatcher, queue, receiver and per-provider clients
(telegram, discord, email, matrix, ntfy, slack, webhook) to use
the shared base, with bounded queue and redacted error logs
- Tests for ssrf, redact, http_base, queue bounds, dispatcher
aggregation, telegram media partition, email and matrix clients
Frontend:
- Settings: log level / log format selectors now use IconGridSelect
with per-option icons and i18n descriptions
- Minor providers page and entity-cache store updates
Tooling:
- Document code-review-graph MCP usage in CLAUDE.md
- Ignore .code-review-graph/, register .mcp.json
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.
Boot-time logging was a three-line basicConfig stub with no timestamps, no
correlation, and silent drops at every layer of the Telegram send path — a
/random command that delivered text but no media left zero evidence in the
log. This replaces the setup and closes every silent drop encountered end-to-end.
New infrastructure:
- notify_bridge_core.log_context: request_id/command/chat_id/bot_id/dispatch_id
ContextVars with a bind_log_context() context manager so deep call sites
(TelegramClient, NotificationDispatcher) inherit the correlation tag without
threading args through.
- notify_bridge_server.logging_setup: dictConfig-based setup with a
LogRecordFactory that tags every record, a SecretMaskingFilter that redacts
/botN:TOKEN plus Authorization/x-api-key/password/secret in messages AND
tracebacks, a JSON formatter for aggregators, text formatter with grep-friendly
[req=... cmd=... bot=... chat=... disp=...] prefix, and default dampening
for sqlalchemy/aiohttp/apscheduler/urllib3/PIL.
Runtime control:
- NOTIFY_BRIDGE_LOG_LEVEL / _FORMAT / _LEVELS env vars (boot).
- DB-backed log_level / log_format / log_levels AppSettings, applied on
boot after migrations and live via apply_log_levels() when edited in
the settings UI (format still requires restart, logs a WARN).
- Frontend settings page gains a Logging card (level dropdown, format
dropdown, per-module overrides); en/ru i18n keys added.
Call-site fixes (/random media-group blind spot and adjacent):
- TelegramClient._fetch_asset: every silent drop now WARN-logs with reason
(missing url, HTTP non-200, size/dimension limits, ClientError).
- TelegramClient._send_media_group: WARN on "chunk had N items but 0 usable",
ERROR on sendMediaGroup non-ok/transport with full context; returns
success=False + "no_items_delivered" instead of success=True with an empty
message_ids list so callers can distinguish.
- TelegramClient.send_message / _upload_media / _send_from_cache: ERROR on
non-ok + transport failures with status/code/desc; DEBUG for cache-hit
fallbacks.
- NotificationDispatcher.dispatch: generates a dispatch_id, binds it, logs
start/finish with failure count, uses exc_info for target failures.
- commands/handler: missing/failed templates -> ERROR + exc_info; send_reply
and send_media_group errors upgraded WARNING -> ERROR with chat/error_code
context; rate-limit and truncation cases logged with full context.
- commands/webhook and services/telegram_poller: bind_log_context(request_id
=tg:<update_id>, command, chat_id, bot_id), INFO on receive/dispatch/
completion with duration, exc_info on raise, INFO when commands disabled.
- commands/immich: INFO when album scope is empty; WARN per asset dropped
from media payload and a summary WARN when "N assets in, 0 out".
Cache engine:
- TelegramFileCache: configurable max_entries (LRU cap applies in both TTL
and thumbhash modes), ttl_seconds<=0 disables TTL, stats() method.
- Dispatcher builds an asset.id -> thumbhash resolver from event.added_assets
(Immich populates thumbhash in extra) and passes it to TelegramClient, so
asset-cache entries invalidate on visual change rather than age.
- Watcher wires app settings into cache init: URL cache = TTL + LRU cap,
asset cache = thumbhash + LRU cap. Adds soft-reset (in-memory only) used
when cache params change.
Settings:
- New key telegram_asset_cache_max_entries (default 5000).
- telegram_cache_ttl_hours default bumped 48 -> 720 (30d); now URL-only.
- PUT /settings resets in-memory caches when cache keys change (files kept).
- New endpoints: GET/POST /settings/telegram-cache/stats and /clear.
Settings page:
- Cache stats card (count + size + oldest/newest per bucket) with a hint
explaining that the size is cumulative uploaded-to-Telegram bytes.
- Clear-cache button behind a confirm modal.
- New TimezoneSelector + LocaleSelector components replace raw inputs.
- max-entries input, TTL range updated (0..8760, 0 = disabled).
Mobile nav:
- "More" panel now mirrors the full sidebar tree (groups + subnodes) so
every destination is reachable on mobile; previously flat hand-picked list.
- Nav height uses env(safe-area-inset-bottom); panel bottom + z-index fixed
so content can't visually overlay the bottom bar.
A11y / DOM warnings:
- Password-change form has a hidden username field for password-manager
association; autocomplete hints on all three password inputs.
- Telegram webhook secret wrapped in a no-op form + autocomplete=off.
Bug fix:
- update_settings used any(await ... for ...) which raised TypeError at
runtime (async generator not an iterator); replaced with explicit loop.
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.
- Remove top paginator from dashboard events, keep only bottom
- Fix test message locale: pass UI locale to email/matrix bot tests
- Convert webhook auth mode from text input to icon grid selector
- Generate secure UUID tokens for webhook URLs instead of sequential IDs
- Move Recent Payloads into per-provider expandable container (lazy-loaded)
- Make template config languages dynamic via app settings instead of hardcoded
- Change default dev port to 5175
Adds telegram bot command system with 13 commands (search, latest, random, etc.),
webhook/polling handlers, rate limiting, app settings page, and various UI/UX
improvements across all entity pages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>