- Tracking configs: filter by name + provider type
- Template configs: filter by name + provider type
- Command configs: filter by name + provider type
- Notification trackers: filter by name + provider
- Command trackers: filter by name + provider
- Targets: filter by name (type filtering already existed)
- Nav badge counts include system-owned entities (user_id=0)
- Shows "no items match filter" vs "no items yet" empty states
Scheduler provider:
- Virtual provider (no external service) that emits SCHEDULED_MESSAGE
events on user-defined intervals or cron expressions
- Custom variables stored in tracker filters, flattened into template context
- fire_count persists across triggers via tracker state
- APScheduler CronTrigger support for cron-mode schedules
- Default templates (EN+RU), seeded on startup
Multi-provider UX fixes:
- Tracking config hides Immich-specific sections (periodic, scheduled,
memory, asset display) for non-Immich providers
- Command config driven by provider capabilities — hides commands/settings
for providers without bot commands
- Template config hides empty "Scheduled Messages" group
- Test menu on tracker targets is provider-aware (Immich shows all 4 test
types, others show only basic)
- Removed redundant Test button from tracker card
- System-owned tracking configs (user_id=0) seeded for Gitea + Scheduler
- Fixed ownership checks to allow system configs in tracker-target links
- Capabilities cache shared across template-configs and command-configs
- Command tracker bot selector uses EntitySelect instead of raw select
- Sample context includes Gitea + Scheduler variables for template preview
First webhook-based provider integration (Immich uses polling).
Gitea pushes events via POST /api/webhooks/gitea/{provider_id} with
HMAC-SHA256 signature validation.
- 9 event types: push, issue opened/closed/commented, PR opened/closed/merged/commented, release published
- Generic filters system on NotificationTracker (collections, senders, exclude_senders)
- Provider capabilities include supported_filters and webhook_based flag
- Gitea API client for connection testing and repository listing
- 18 default Jinja2 notification templates (EN + RU)
- Frontend: conditional provider forms, Gitea event toggles in tracking config
- Auto-migration for filters column and Gitea tracking flags
- Locale-aware templates: CommandTemplateSlot now has a locale column,
allowing each slot to have per-language variants (EN/RU). Templates
are resolved at runtime from the Telegram user's language_code.
- Merged system configs: "Default Commands (EN)" and "(RU)" merged
into a single "Default Commands" config with locale-aware slots.
Migration handles existing data automatically.
- Configurable command descriptions: hardcoded COMMAND_DESCRIPTIONS
replaced with desc_* template slots (desc_status, desc_help, etc.)
that users can customize per locale. setMyCommands registers all
locales explicitly.
- Removed locale from CommandConfig: no longer needed since locale
is derived from the Telegram user's language at runtime.
- Debounced command auto-sync: after command config/tracker changes,
affected bots are marked dirty and synced after a 30s debounce
window. Manual "Sync with Telegram" button still works.
- Entity pickers in LinkedTargetsSection: replaced 6 plain <select>
elements with EntitySelect components (search, icons, keyboard nav).
Added onselect callback and size="sm" props to EntitySelect.
Frontend route renamed from /telegram-bots to /bots. All nav links,
CrossLinks, SearchPalette hrefs updated. API endpoints remain as
/api/telegram-bots, /api/email-bots, /api/matrix-bots (backend unchanged).
Replace `import * as mdi from '@mdi/js'` (loads ~5MB of SVG paths
synchronously into every HMR update) with a lazy async import that
loads once and caches. MdiIcon and IconPicker now use getMdiPath()
and getAllMdiNames() from the shared mdi-lookup module.
- Template seed now re-creates missing system templates on every startup
(not just first boot), using raw SQL to handle legacy NOT NULL columns
- Tracking configs: add provider type selector (was missing)
- All config forms: provider type uses IconGridSelect during creation,
read-only text during editing (immutable after creation)
- Pages: tracking-configs, command-configs, command-template-configs,
template-configs
- Tracking Configs: add provider type field (was missing entirely)
- All 4 config pages: provider type uses IconGridSelect during creation,
shown as read-only text during editing (provider type is immutable)
- Pages: tracking-configs, command-configs, template-configs,
command-template-configs
URL param timing was unreliable with SvelteKit client-side routing.
Now CrossLink calls requestHighlight(id) setting a global variable
before goto(), and highlightFromUrl() reads it after data loads.
Double requestAnimationFrame ensures DOM has rendered after loaded=true.
Falls back to ?highlight= URL param for direct links.
CSS animation was interfering with stagger animation on cards.
Now uses setInterval-based box-shadow pulse with computed primary
color from CSS variables. Pulses glow on/off every 400ms for 2.5s,
then fades out via transition.
Compact search bar button between sidebar header and nav. Shows
magnifying glass icon + placeholder text + ⌘K kbd hint when expanded,
just the icon when collapsed. Clicking opens the search palette.
Use inline style.animation instead of CSS class to avoid triggering
stagger-children fadeSlideIn re-animation when highlight is removed.
Restores original inline styles on cleanup.
When clicking a CrossLink, the target entity ID is passed as
?highlight=<id> in the URL. The destination page:
1. Shows a semi-transparent dim overlay (z-index: 10)
2. Finds the card with data-entity-id matching the ID
3. Scrolls to it smoothly (block: center)
4. Applies a pulsing primary-color box-shadow animation (z-index: 11)
5. Cleans up overlay + animation after 2 seconds
If the card isn't in DOM yet (data still loading), a MutationObserver
waits up to 5 seconds for it to appear.
Changes:
- New highlight.ts utility with highlightFromUrl(), MutationObserver,
dim overlay management
- Card component accepts entityId prop → data-entity-id attribute
- CrossLink accepts entityId prop → appends ?highlight=<id> to href
- All 9 entity pages: Card elements have entityId, highlightFromUrl()
called after data loads
- CSS: cardHighlight keyframe animation + nav-dim-overlay styles
- Add $state-based entity cache layer with 30s TTL, request deduplication,
and local mutation helpers (entity-cache.svelte.ts + caches.svelte.ts)
- Wire all 10 page components to use shared caches for cross-page data
- Add slide animation for nav tree expand/collapse with rotating chevron
- Remove aggregate count badges from container nav nodes (keep on leaves)
- Convert Targets from flat leaf to group with per-type children
(Telegram, Webhook, Email, Discord, Slack, ntfy, Matrix)
- Add URL-based type filtering on Targets page with per-type descriptions
- Add Bots group children for Email and Matrix alongside Telegram
- Tab-based routing for bots page (?tab=telegram/email/matrix)
- Add per-type target counts and email/matrix bot counts to /status/counts
- Split CLAUDE.md into focused context files under .claude/docs/
- Fix .gitignore: scope lib/ to root, allow .claude/docs/ tracking
- Clear all caches on logout
- Reset form state when switching target type tabs
Navigation:
- Restructure flat nav into grouped tree: Notification (Trackers,
Configs, Templates), Commands (same), Bots (Telegram), Settings
(Common, Users)
- Collapsible groups with expand/collapse state persisted in localStorage
- Auto-expand group containing the active page
- Counter badges on groups (sum of children) and individual items
- New /api/status/counts endpoint for nav badge data
- Mobile bottom nav uses flattened key pages
Dashboard:
- Rename "Recent Events" to "Events"
- Move chart under Events section (after filters, before event list)
- Filters (event type, provider, search) now affect both the event
list AND the chart simultaneously
- Add event_type, provider_id, search filter params to /api/status/chart
- Remove ALL hardcoded EN/RU fallback strings from handler.py — every
command response now renders through CommandTemplateSlot templates
- _render_cmd_template now returns error placeholder instead of None
when template is missing, ensuring no silent failures
- Fix register_commands_with_telegram tuple unpacking bug (was ignoring
cmd_template_slots from _resolve_command_context)
- Auto-assign system default template (matching locale) on command
config creation when none specified
- Add command_template_config_id to CommandConfigCreate model
- Remove "no template" option from frontend dropdown — template is
now required for command configs
- Auto-select first matching template when creating new command config
- Fix || vs ?? for command_template_config_id, default_count, and
rate_limits in frontend edit function (0 was treated as falsy)
- Discord, Slack, ntfy, Matrix notification target types with clients and dispatch
- MatrixBot model + API + frontend in Bots tab
- Command template system fully wired into all handler commands
- Default command templates seeded (EN/RU, 14 slots each)
- Command template editor with variables reference including child fields
- Delete protection on all 10 entity types (409 with consumer details)
- Provider type selector on template config forms
- Target type selector as dropdown with all 7 types
- Response template selector on command config form
- CLAUDE.md: mandatory server restart rule, child properties rule
- Fix sanitizePreview regex to match literal quotes instead of " entities
- Default telegram chat_action to "typing" in model and frontend
- Change "photo(s)" to "file(s)" in default templates (EN/RU)
- Remove redundant album URL line from assets_added templates
- Auto-refresh system-owned templates from files on server startup
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rework entity schema: rename Tracker→NotificationTracker, add CommandConfig/
CommandTracker/CommandTrackerListener entities for decoupled command handling.
Commands now resolve through CommandTracker→CommandConfig instead of
TelegramBot.commands_config. Smart ref-counted bot polling based on active
listeners. Add chat_action to telegram targets. Full frontend CRUD pages
for command configs and command trackers. Idempotent SQLite migrations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
- Replace 3 test buttons with unified dropdown menu (basic/periodic/scheduled/memory)
- Send text message first, then assets as reply (not combined caption+media)
- Pass all target config settings to Telegram client (disable_url_preview, max_media, chunk_delay, etc.)
- Real data test notifications for periodic/scheduled/memory (fetch from Immich)
- Provider card URL is now a clickable hyperlink
- Localized test type labels (EN/RU)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Show entity icons on all cards with fallback defaults (providers, trackers, targets, bots)
- Enrich EventLog with provider_name, tracker_name, assets_count; add DB migration
- Dashboard events: filtering (type, provider, search), sorting, pagination, dynamic page size
- Friendly chat names on telegram target cards (resolve from TelegramChat table)
- Test message button on bot chat items with locale-aware messages
- Album public link validation on tracker save with auto-create dialog
- Support albums without public links: conditional <a href> in templates
- Fetch shared links during poll, enrich events with public_url/protected_url
- Per-asset public_url in template context ({share_url}/photos/{asset_id})
- Common date/location detection: common_date + common_location context vars
- Dual date formats: date_format (datetime) + date_only_format (date only)
- Template clone button, HTML link rendering in template preview
- Fix Telegram asset download 401: pass x-api-key headers through client
- Fix provider external_url matching for API key scoping
- Fix event timestamp timezone (append Z suffix for UTC)
- Localize event filter controls, test messages (EN/RU)
- Template variable UI helpers updated with all new fields
- CLAUDE.md: template system sync rules documentation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
- Scheduler lifecycle sync: create/update/delete tracker now syncs APScheduler jobs
- Test-periodic/test-memory endpoints render actual Jinja2 templates with sample data
- Cascade cleanup on tracker delete (TrackerState removed, EventLog nullified)
- Fix user_id=0 FK violation for system-owned TemplateConfig (removed FK constraint)
- Fix API key leak: only attach x-api-key header for internal provider URLs
- Validate config ownership in tracker_targets create/update
- Fix _response() double-emit of created_at in template/tracking configs
- Add per-target-link test endpoints (test, test-periodic, test-memory)
Frontend:
- Fix orphaned provider on test exception in providers/new
- Add submitting guard + disabled state to targets save button
- Move test buttons from tracker card to per-target-link rows
- Fix Svelte 5 async $state reactivity (spread reassignment for all Record mutations)
- i18n for dashboard timeAgo and event type badges (EN + RU)
- Add required attribute to chat select dropdown in targets
- Fix font CSS vars to prioritize imported DM Sans / JetBrains Mono
- Standardize empty states with centered icon + text across all 6 list pages
- Add stagger-children animation class to all list containers
- Fix slide transition duration consistency (200ms everywhere)
- Standardize border-radius to rounded-md across all form inputs
- Fix providers/new page structure (h2 + mb-8 spacing)
- Fix tracker card action row overflow (flex-wrap justify-end)
- JinjaEditor dark mode reactivity (recreate editor on theme change)
- Add aria-labels to mobile nav items
- Make ConfirmModal confirm button label/icon configurable
- Remove double error reporting on providers page
- Add telegram bot edit functionality (name editing via PUT)
- i18n for External Domain label on provider forms
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend API (38 routes):
- providers: full CRUD + test connection + list collections + API key masking
- trackers: full CRUD + trigger + history + test-periodic/memory
- tracking-configs: full CRUD with Pydantic models, provider_type filter
- template-configs: full CRUD + preview + preview-raw with two-pass validation
- targets: full CRUD + test notification + config masking
- telegram-bots: full CRUD + chat discovery + token endpoint
- users: full admin CRUD + password reset + self-delete protection
- status: dashboard endpoint with providers/trackers/targets/events counts
Frontend pages updated:
- Dashboard with animated stat cards and event timeline
- Providers with proper components, delete confirm, snackbar
- Trackers/targets/tracking-configs/template-configs/telegram-bots/users
all use PageHeader, Card, Loading, MdiIcon with correct i18n keys
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The API client was redirecting to /login on any 401 response,
including the checkAuth() call in the layout. This caused a
redirect loop that cleared the token. Auth redirects are now
handled solely by the layout's checkAuth() flow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Layout now checks auth on mount and redirects:
- No users exist -> /setup
- Not authenticated -> /login
- Shows loading state while checking
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace Google Fonts with @fontsource/dm-sans and @fontsource/jetbrains-mono
- Add SVG favicon
- Add autocomplete attributes to login/setup form inputs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Architecture documentation for HAOS integration with Notify Bridge:
- Polling-based event stream API (recommended for Phase 1)
- WebSocket option for future real-time enhancement
- Feature obsolescence analysis: what moves to Bridge vs stays in HAOS
- New HAOS integration flow: Bridge URL -> JWT auth -> poll events
- Entity mapping: sensors, binary sensors, cameras, buttons
- API contract: /api/events/stream, /api/trackers/{id}/state
- 4-phase migration path from direct Immich to Bridge client
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire all components into a working application:
- Scheduler service: APScheduler loads enabled trackers, polls at intervals
- Watcher service: orchestrates poll -> detect -> notify flow
- Eagerly loads DB data, then creates aiohttp session for provider
- Saves tracker state after each poll
- Logs events to EventLog table
- Dispatches notifications to targets with template rendering
- Manual trigger endpoint: POST /api/trackers/{id}/trigger
- Scheduler starts on app lifespan startup
- Full end-to-end flow verified: server starts cleanly
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract and generalize notification dispatch:
- TelegramClient: full Bot API client with photo/video/document/media group support
- TelegramFileCache: TTL and thumbhash-based file_id caching
- WebhookClient: simple JSON POST client
- NotificationQueue: persistent deferred notification queue for quiet hours
- NotificationDispatcher: routes ServiceEvent to targets, renders templates
- StorageBackend protocol + JsonFileBackend for persistence
- TargetConfig dataclass for target configuration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement ImmichServiceProvider as first concrete ServiceProvider:
- ImmichClient: async API client (ping, albums, shared links, search, thumbnails)
- ImmichAssetInfo/ImmichAlbumData: Immich-specific models with from_api_response()
- Change detector: produces generic ServiceEvent from album diffs
- Asset utils: filter, sort, URL building for Immich assets
- 12 Immich-specific template variables registered in global VariableRegistry
- Provider config schema (url, api_key, external_domain)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Define the generic provider/event/variable system:
- ServiceProvider ABC with connect, disconnect, poll, list_collections
- ServiceProviderType enum (IMMICH first)
- EventType enum (assets_added/removed, collection_renamed/deleted, sharing_changed)
- MediaAsset, MediaCollection, CollectionState dataclasses
- TemplateVariableDefinition and VariableRegistry with 11 base variables
- All models use extra: dict for provider-specific data passthrough
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Set up the Notify Bridge project structure:
- packages/core (notify_bridge_core) with provider, model, notification, template packages
- packages/server (notify_bridge_server) with FastAPI skeleton and health endpoint
- frontend with SvelteKit 2, Svelte 5, Tailwind CSS v4, static adapter
- Root configs: .gitignore, README.md, CLAUDE.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>