Round-up of feedback:
- Brand: drop italic-gradient 'Bridge' wordmark + 'SERVICE
NOTIFICATIONS' tag. Now plain 'Notify Bridge' bold sans + 'v0.5.2'
mono — exactly what the Aurora mockup shows.
- Event rows: replaced [collection_name] [event_tag] [count_tag] with
a sentence form — bold provider, muted verb, bold count, gradient
italic-feeling collection. Matches the mockup's 'Immich added 14
assets to «...»' pattern. Tracker → provider trail kept underneath.
- SearchPalette: re-skinned to Aurora glass — solid surface (was
using the now-translucent --color-card token and was nearly
invisible), backdrop blur, hairline section headers with
letterspaced mono labels, gradient active row, glass-bordered
result icons + detail pills.
- 'On watch' provider deck hidden when a global provider filter is
active (deck would only show that one provider — redundant). Two-col
grid collapses to single full-width column in that mode.
- Layout: dropped the max-w-7xl cap on the topbar and the page
container. Pages now stretch to full available width on wide
displays.
- Section labels in sidebar: dropped :global() wrapper since the
element is in the same component. Added font-mono for tighter
letterspacing match to the mockup.
Build clean.
Bringing the Aurora dashboard mockup to feature parity in the real app:
- NavIcon component: thin-stroke SVG icon set covering ~30 nav-tier
glyphs (dashboard, server, target, radar, bots, console, email,
matrix, webhook, slack, bullhorn, settings, theme, search, logout,
chevrons, plus/arrow). Falls back to MdiIcon for unknown names so
every existing usage keeps working.
- layout.svelte: sidebar nav swapped from MdiIcon to NavIcon — dropping
the dense filled glyphs in favour of soft 1.6px outlines that match
the mockup. Main container max-width bumped from 5xl (1024px) to
7xl (1280px) so the dashboard finally gets to breathe.
- +page.svelte (dashboard): full rewrite of the markup to project
every mockup section onto live data:
* Editorial Hero — 'Tonight, everything is flowing.' with gradient
italic emphasis, live/attention pill driven by failure count,
and a big mono throughput meter (24h events, armed/total,
providers, targets) on the right.
* Stats — kept the 4-card grid, refined accent palette per card.
* Two-column row — Signal stream (events with full routing trail:
tracker → provider, gradient avatar tile per event-type) on the
left, On-watch provider deck (per-provider activity bar fill +
live/idle pulse, derived from recent_events) on the right.
* Pulse panel wrapping the existing EventChart with the new title
treatment.
* Active wires — Sankey-style 'Tracker → Target' route summary
derived from notificationTrackers.tracker_targets + targetsCache
with event counts per route.
* Compose band — gradient call-to-action strip with new-tracker CTA.
- i18n: en.json + ru.json get the full set of new dashboard keys
(hero copy, panel titles, compose band, wires labels).
Build: 0 errors, 57 pre-existing warnings unchanged.
Foundation pass on the Aurora redesign:
- app.css: full token swap to lavender/orchid/mint/sky pastel palette,
vivid aurora gradient backdrop, frosted glass surface tokens, two
themes (Aurora dark + Pearl light), shadow recipe for floating glass.
- fonts: add Geist Sans / Geist Mono / Newsreader, drop DM Sans usage
(legacy fontsource entries kept in package.json until full migration).
- layout.svelte: sidebar becomes a floating glass rail with a conic-
gradient brand orb, Newsreader italic 'Bridge' wordmark, soft glass
hovers on nav links, gradient active indicator, gradient user avatar.
- Card.svelte: glass surface + inner highlight + soft hover lift.
- PageHeader.svelte: Newsreader serif display title.
- +page.svelte (dashboard): stat cards become glass with colored
accent 'orb', event timeline gets soft glass rows + slide-on-hover.
Build clean (0 errors, pre-existing a11y warnings unchanged).
Other pages still inherit old chrome via shared tokens but will need
component-specific passes; tracked separately.
CronTrigger.from_crontab was constructed without a timezone, so a cron like
'0 9 * * *' fired at 09:00 host-local instead of 09:00 in the admin-configured
timezone. Now all tracker/action cron triggers are built with the app tz, and
the setting endpoint rebuilds existing cron jobs when the tz changes (since
CronTrigger freezes its tz at construction time).
The scheduler provider also renders current_date/time/datetime/weekday in the
configured tz and exposes a new 'timezone' template variable.
EventLog entries for scheduled_message now include schedule_type,
cron_expression/interval_seconds, timezone, and fire_count, and the dashboard
shows the event type with a label/icon/color.
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.
- 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
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.
Replace all if/else chains keyed on provider type strings with a
descriptor-driven architecture. Each provider type (immich, gitea,
planka, scheduler, nut, google_photos) has a descriptor in
frontend/src/lib/providers/ that declares config fields, event
tracking fields, collection metadata, validation, and hooks.
Components now use getDescriptor(type) and render dynamically.
Dashboard provider card shows provider name + type when global
filter is active. Grid-items derived from registry.
- Dashboard providers card now shows count of providers matching the
global provider type filter instead of special name/type display
- Fix provider update sending empty config when only name/icon changed,
causing 400 validation error (api_key required)
- 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
- 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
- Added desc text to all 40+ grid items (EN + RU)
- compact prop on all IconGridSelect in compact form sections
- Fixed compact width to fill grid cells (removed width:auto)
- Replaced <select> filter dropdowns with IconGridSelect on config pages
- Replaced <select> provider filters with EntitySelect on tracker pages
- Dashboard filters constrained to fixed widths (not full row)
- Added filtering to command-template-configs and providers pages
- providerTypeFilterItems() with "All" option for filter contexts
- 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
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>
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>