Commit Graph

45 Commits

Author SHA1 Message Date
alexei.dolgolyov 10d30fc956 feat: production readiness — security, perf, bug fixes, bridge self-monitoring
Comprehensive multi-area pass driven by a parallel 8-agent production
review. Frontend, backend, database, security, performance, operational,
plus a new self-monitoring feature.

## Critical fixes
- Planka webhook: reads bounded raw body (was NameError on every call)
- HA quiet hours: ha_state_changed/automation_triggered/service_called/
  event_fired added to deferrable set (were silently dropped)
- DNS-rebinding SSRF: PinnedResolver wired into shared aiohttp session
- Telegram inbound webhook: secret now mandatory (401 without)
- Generic webhook: auth_mode="none" requires explicit
  acknowledge_unauthenticated=true; per-IP rate limit 60/min
- svelte-check: 5 null-narrowing errors in EventDetailModal fixed
- Provider hardcoding: Immich-only block extracted to descriptor
  featureDiscoveryHint
- command_sync: snapshot+expunge bot before exiting AsyncSession

## Bug fixes
- notifier asyncio.gather(return_exceptions=True) — one bad chat no longer
  cancels peer sends
- NotificationDispatcher hoisted out of per-tracker loop
- Provider credential resolution unified across all 5 dispatch sites
- HA asyncio.shield now drains inner task on cancellation
- Provider construction switched from if/elif ladder to factory registry
- NUT first poll seeds silently (no spurious ups_on_battery)
- Quiet-hours gate: event-type-disabled now wins over deferral
- APScheduler drain job ID resolution upgraded to seconds
- HA on_status_change wired through to EventLog
- Webhook payload rollback failures now logged (not swallowed)
- Batched receivers/chats/bots in load_link_data (was per-target N+1)
- flag_modified on JSON column reassignments in deferred_dispatch

## Database
- UNIQUE indexes on service_provider.webhook_token,
  telegram_bot.webhook_path_id, partial UNIQUE on telegram_bot.bot_id,
  telegram_chat(bot_id, chat_id), notification_tracker_target unique link,
  partial UNIQUE on bridge_self provider per user
- Composite ix_event_log_user_event_type_created index
- save_chat_from_webhook switched to ON CONFLICT DO UPDATE
- ondelete=CASCADE on user-id FKs (model annotation; app-side cascade
  delete added for existing data)
- delete_notification_tracker converted from N+1 to bulk DELETE/UPDATE
- Module-level asyncio.Lock replaced with lazy _get_lock() pattern
- VACUUM INTO snapshot now PRAGMA integrity_check verified

## Performance
- Jinja2 template compilation LRU cached (lru_cache maxsize=512)
- Per-locale render cache in NotificationDispatcher (skips re-rendering
  identical content for receivers sharing a locale)
- Tracker list cached per provider_id with 5s TTL + explicit invalidation
  on tracker CRUD (relieves HA chat-bus rate query pressure)
- Nav-counts collapsed from 16 round-trips to single UNION ALL
- HA event_log: skip persisting empty assets_added/removed events

## Security hardening
- Mass-assignment guard on Action create/update; cron sub-minute reject
- Backup JSON depth/node-count cap (depth ≤ 10, nodes ≤ 100k)
- _sanitize_config extended to all JSON-typed fields on backup import
- Telegram _safe_get walks redirects manually with SSRF revalidation
- Bcrypt 72-byte password length cap with clear 422
- Webhook payload body redaction; sensitive substring set extended with
  oauth/client_secret/webhook_secret/csrf in both header filter and
  template extras filter

## Frontend
- 76 catch (err: any) sites converted to errMsg(err) helper
- globalProviderFilter: pure getter; reconciliation moved to one-time
  $effect in +layout
- Provider-filter binding: removed paired $effects + _syncingFilter flag,
  now one-way derived
- entity-cache: separate _refreshing flag for background re-fetches
- api.ts 401 handling: AuthRedirectError class + dedup _redirecting flag,
  goto() instead of window.location.href
- a11y: aria-expanded on mobile More, role=switch + aria-checked on
  Telegram bot toggles

## Tests & operations
- CI pytest gate added to .gitea/workflows/build.yml + release.yml
  (wheel-built install to dodge editable-install slowness)
- /api/ready upgraded to deep healthcheck (db SELECT 1, scheduler.running,
  HA supervisor presence) returning {ready, checks, errors, version}
- /api/metrics endpoint with prometheus_client (deferred_pending,
  event_log_total, dispatch_duration, poll_failures, send_failures)
- New OPERATIONS.md covering deploy, healthchecks, metrics, backup/restore
  procedures, log handling, common scenarios, upgrade flow
- New tests: test_bridge_self (11), test_gitea_parser (9),
  test_planka_parser (6), test_immich_change_detector (6),
  test_backup_roundtrip (1)

## New feature: bridge self-monitoring
- New bridge_self provider type — internal sink for bridge health events
- Three event types: bridge_self_poll_failures (consecutive tracker poll
  failures), bridge_self_deferred_backlog (pending count crosses
  threshold), bridge_self_target_failures (consecutive 5xx/network
  failures per target)
- Per-user thresholds (defaults: 3 / 100 / 5) configurable via the
  provider config form
- Auto-seeded on user create + /setup + boot backfill for existing users
- Anti-spam: counters reset after emission; backlog uses transition latch
- Self-loop guard: bridge_self failures don't count toward target-failure
  thresholds (logged only) — wire to your own Telegram/Email/Matrix to
  get notified when polls/dispatches/sends fail
- 6 default templates (3 events × 2 locales), tracking config columns
  with backfill migration, frontend descriptor (excluded from "create
  provider" wizard since auto-managed)

Operator-visible behavior changes (call out in release notes):
- NOTIFY_BRIDGE_TELEGRAM_WEBHOOK_SECRET now REQUIRED for webhook mode
- Existing webhook providers with auth_mode="none" need explicit opt-in
- Generic webhook endpoint rate-limited 60/min per source IP
- HA disconnect/reconnect writes ha_status_* EventLog rows
- Every user gets a bridge_self provider — wire it to a target to
  receive failure alerts

Pre-existing test failures (test_ssrf, test_release_provider) on
Python 3.13 are unrelated; CI runs on 3.12.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 02:16:49 +03:00
alexei.dolgolyov 22127e2a59 feat: Home Assistant provider — WebSocket subscription + bot commands
Adds Home Assistant as a service provider with two coordinated surfaces:

Notifications (subscription):
- Long-lived WebSocket client (aiohttp ws_connect) with auth handshake,
  exponential-backoff reconnect, bounded event queue, and area-registry
  enrichment cached per (re)connect
- ServiceProvider ABC gains an optional `subscribe()` method for push-style
  providers; HomeAssistantServiceProvider uses it via a per-provider
  supervisor task started in the FastAPI lifespan
- 4 event types (state_changed, automation_triggered, call_service,
  event_fired), 4 default Jinja templates (en + ru), HA-specific
  tracker filters (entity_glob, domain_allowlist, exact entity ids)
- Extracted shared dispatch pipeline (api/webhooks.py → services/
  event_dispatch.py) so subscription and webhook ingest share the same
  event_log + deferred-dispatch + quiet-hours code path

Bot commands:
- /status, /entities [glob], /state <entity_id>, /areas
- Multi-command WS session so /status and /areas cost one handshake
- Sensitive-attribute blocklist (camera access_token, entity_picture, etc.)
  and 30-attribute cap to keep /state output safe and within Telegram's
  message size
- Error-message redaction strips URL userinfo before surfacing to chat

Frontend:
- HA descriptor with toggle ConfigField type (new) and tag-input filter
  mode for free-text glob/domain lists (new TagInput component)
- 15 command slots + 4 notification slots wired into the existing
  template-config UI
2026-05-13 14:31:56 +03:00
alexei.dolgolyov ba199f24bd feat: deferred dispatch, release-check provider, settings polish
- 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.
2026-05-12 02:58:07 +03:00
alexei.dolgolyov a666bad0c4 feat(frontend): group targets by bot, redesign backup settings
Targets page: collapse targets under a per-bot header (BotGroupHeader)
with a count chip and an "Open bot" cross-link. Receivers are hidden
by default and expand per group; non-bot types fall back to a "Direct
delivery" group. Telegram "Add receiver" now opens the EntitySelect
chat palette directly instead of an inline form — EntitySelect grew a
bindable `open` flag, `showTrigger`, and an `onclose` cancel signal.

Backup settings page: split the monolithic +page into focused panels
(BackupHero, BackupLedger, ExportPanel, ImportPanel, PendingStrip,
ScheduleCassette) and introduce a stepwise export/import flow with
category groups, secrets handling, conflict policy, and validation
gating. New i18n keys in both locales cover the bot grouping labels
and the backup step copy.
2026-05-10 23:51:48 +03:00
alexei.dolgolyov 35a3008896 feat: log bot command invocations to the event stream
Bot commands were the only user-initiated path that didn't surface in
the dashboard. They now produce ``command_handled`` /
``command_rate_limited`` / ``command_failed`` rows in ``EventLog``
alongside tracker and action events.

Backend
- ``EventLog`` gains nullable ``command_tracker_id`` / ``telegram_bot_id``
  FKs plus deletion-snapshot name columns (idempotent migration).
- New ``_log_command_event`` helper emits one row per invocation at the
  three branches in ``handle_command``. Logging failures are swallowed
  so they cannot block the user-visible reply.
- Telegram ``from`` is captured in poller and webhook, whitelisted to
  identity fields by ``_normalize_issuer`` (drops ``language_code`` and
  any future PII), persisted under ``details.issuer``.
- ``/api/status`` resolves live ``CommandTracker`` / ``TelegramBot``
  names (mirroring the action pattern) and exposes ``tracker_id``,
  ``command_tracker_id``, ``telegram_bot_id`` so the frontend can
  deep-link.

Frontend
- Event rows are now clickable and open a detail modal with full
  provenance (bot → chat → issuer → provider), raw ``details`` JSON,
  and per-entity action buttons.
- Buttons use the existing ``requestHighlight`` + ``goto`` crosslink
  pattern, so clicking lands on the entity's list page with that
  specific card scrolled into view and pulsing.
- Auto-refresh dropdown (Off / 10s / 30s / 1m / 5m) persisted in
  ``localStorage``; ticker pauses while the tab is hidden.
- Event-type filter, dashboard verb labels, and gradients extended for
  the three new ``command_*`` types.
- Filled in pre-existing missing i18n keys (``common.hide`` /
  ``common.show`` / ``commandConfig.noCommandsForProvider``).

Tests
- New ``test_command_event_logging.py`` covers subject formatting,
  issuer normalization, the three event branches, and graceful failure
  when the DB is unreachable. ``pytest packages/server/tests/`` → 96/96.
2026-05-07 22:22:41 +03:00
alexei.dolgolyov 9afd38e50e fix(redesign): contain modal scroll chaining and smooth Telegram chat refresh
- 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.
2026-04-28 18:52:20 +03:00
alexei.dolgolyov 1bfec521d8 fix(redesign): EntitySelect for language pickers + portal Timezone picker
- Template editors (notification & command) now use EntitySelect for
  locale switching and default to the configured primary locale
  instead of always 'en' when opening, editing, or cloning a config.
- LocaleSelector's add-flow uses EntitySelect for catalog pick;
  custom BCP-47 codes (e.g. de-CH) keep a small dedicated input.
- TimezoneSelector dropdown was being clipped by Card's overflow:hidden
  and backdrop-filter; portalled to <body> with an overlay backdrop and
  styled as a centered modal palette (same pattern as EntitySelect).
- Removed top padding on the timezone scroll list so sticky region
  group headers no longer leak rows above them.
- Extracted shared locale catalog to lib/locales.ts.
2026-04-27 14:18:58 +03:00
alexei.dolgolyov 711f218622 fix(redesign): a11y, mobile, perf polish for production push
Comprehensive pre-production sweep across the Aurora redesign — drives
svelte-check to 0 errors / 0 warnings (was 61) without changing visual
intent. Highlights:

- Mobile: hero title shrinks at 480px, signal-list stacks timestamp
  under sentence below 640px, sidebar icon buttons bumped to 40x40
- Light theme: muted-foreground darkened to #3a3560 to clear WCAG AA
  on glass surfaces and the modal close button
- Perf: topbar backdrop-filter 28→14px, mobile-more sheet 28→12px to
  cut concurrent blur layers on mid-tier mobile
- a11y: prefers-reduced-motion mute for aurora drift / pulses /
  shimmer / stagger; aria-label on every icon-only button;
  aria-describedby on Hint; combobox/listbox/aria-activedescendant on
  SearchPalette; modal dialog tabindex; 47 label-without-control
  warnings across 14 form pages cleaned up via for=/id= or label→div
- Dashboard derived state split into topology- vs status-bound layers
  so polling no longer re-runs the full provider/wires computation
- Mobile bottom nav derived from baseNavEntries by key lookup so
  adding a top-level nav entry keeps the two trees in sync
- Bug: template-configs page now respects the global provider filter
  for both the count meter and the type pill (was reading the
  unfiltered cache)
- Misc: portal EventChart tooltip and switch its swatches to Aurora
  tokens; CollapsibleSlot warning state uses warning-fg/-bg tokens
  instead of #d97706; Hint z-index 99999→9999; element refs across
  Modal/EntitySelect/MultiEntitySelect/SearchPalette/IconGridSelect/
  Hint/targets converted to \$state for reactivity; 4 dead
  .topbar-cta selectors removed
2026-04-25 14:41:12 +03:00
alexei.dolgolyov 9eb76c1407 feat(redesign): collapsible dashboard sections + glass mobile-more sheet
Generalize dashboard panel expansion: Stream / On Watch / Pulse / Wires
each get a chevron toggle persisted in localStorage under
dashboard_section_state (migrates legacy dashboard_chart_visible). Drop
the redundant inner border on EventChart so it doesn't double-frame the
panel. Mobile "more" sheet becomes full-height with translucent glass
(rgba(...,0.72) + 28px blur) instead of nearly-solid.
2026-04-25 12:37:56 +03:00
alexei.dolgolyov d356e5a3ac fix(redesign): portal overlays + solid popup surfaces for legibility
Portal EntitySelect/MultiEntitySelect/Modal/Snackbar/EventChart/Hint to
<body> so they escape backdrop-filter ancestors. Replace translucent
glass on popups (IconPicker, IconGridSelect, SearchPalette, Snackbar)
with solid backgrounds and theme-aware light-mode override.
2026-04-25 11:41:25 +03:00
alexei.dolgolyov 9643fe519e feat(redesign): stack PageHeader meter top-right, button bottom-right
The hero's right column now spreads vertically — count + label pinned
at the top, action button(s) flushed to the bottom-right corner of
the card via margin-top: auto. Row uses align-items: stretch so the
side column reaches the full hero height regardless of how tall the
left column gets (long descriptions, many pills, etc.).

Single CSS change in the shared component, so it applies to every
page that uses PageHeader (all 14 of them) — no per-page edits.
2026-04-25 02:58:58 +03:00
alexei.dolgolyov d662b50925 feat(redesign): roll subpage hero across all pages + Aurora Button + JinjaEditor + pulse fix
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.
2026-04-25 02:52:01 +03:00
alexei.dolgolyov 9733e5c122 feat(redesign): subpage hero header + iconpicker portal + tighter gaps
Three threads bundled:

- PageHeader.svelte upgraded to a glass-card subpage hero matching
  the dashboard hero language, scaled down. New optional props (all
  backward-compatible — old callers keep working): emphasis (italic
  gradient word appended to title), crumb (uppercase mono kicker),
  count + countLabel (right-side mono meter), pills (status chips
  with tones: mint / sky / orchid / coral / citrus / primary).
- Providers page wired up first as the test surface: pulls live
  online/offline/checking counts from the existing health probe and
  shows a type-count pill. Locale keys added (en + ru) for the new
  copy ('Service / providers' wordmark, longer description).
- IconPicker dropdown was suffering the same backdrop-filter
  containing-block bug as IconGridSelect — repositioned popups
  rendered inside any glass form panel got clipped or floated to
  the bottom of the page. Now portals to <body> via the shared
  $lib/portal action and uses Aurora glass styling end-to-end
  (solid surface, gradient-active cell, glass-strong search input).
- Layout gaps tightened to match the mockup:
    * sidebar→content horizontal gap is now 18px flat (was 50px:
      the 18px shell-gap PLUS another 32px wrapper padding on each
      child of main). Dropped px-4/md:px-8 from the topbar wrapper
      and the per-page content wrapper — main's children sit flush
      at the column boundary.
    * topbar→content vertical gap reduced to 12px (was 16px / pt-4).

Build clean, 0 errors.
2026-04-25 02:15:34 +03:00
alexei.dolgolyov 1895c5e2d4 fix(redesign): brand snap, event sentences, palette glass, full width
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.
2026-04-25 01:46:16 +03:00
alexei.dolgolyov 0105d9f0ec fix(redesign): portal IconGridSelect popup + snap navbar to mockup
Three issues addressed:

1. IconGridSelect popup was clipped/mispositioned because the .panel
   ancestor has backdrop-filter, which makes it the containing block
   for position:fixed descendants (CSS spec gotcha). Added a tiny
   portal action ($lib/portal) that re-parents the popup + backdrop
   to document.body, and refreshed the popup styling to be Aurora-
   native (solid surface, gradient active state, glass-strong search).

2. Always-visible top search bar: a sticky glass strip at the top of
   every page with the search trigger (⌘K), theme cycler, locale
   toggle, and a primary 'New tracker' gradient CTA — moved out of
   the sidebar to free up rail space and make search reachable from
   anywhere. The sidebar's inline search button is gone.

3. Navbar snapped to the mockup:
   - Sidebar footer redone as a glass user-card (avatar gradient +
     username + role + mint live chip + change-password / docs / logout
     row beneath a hairline).
   - Section labels above each nav group (Overview / Routing /
     Operators / System) with a hairline rule that extends to the
     edge — same rhythm as the dashboard mockup.
   - Active nav link keeps the gradient bar + glass-elev background
     + inset highlight + soft outer glow.

i18n: nav.section* keys (en + ru), dashboard.newTracker reused for
the topbar CTA. Build clean.
2026-04-25 01:36:14 +03:00
alexei.dolgolyov d3210fd5ea feat(redesign): project mockup richness onto live dashboard
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.
2026-04-25 01:27:08 +03:00
alexei.dolgolyov d9ef3c6cc3 feat(redesign): aurora foundation — tokens, glass sidebar, dashboard
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.
2026-04-25 01:11:56 +03:00
alexei.dolgolyov 2be608ba95 feat(cache): thumbhash-validated asset cache + settings UX overhaul
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.
2026-04-22 15:09:59 +03:00
alexei.dolgolyov a7a2b4efa4 feat: large polish pass — UX fixes, per-chat scope, restore/backup, action events
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.
2026-04-22 01:13:11 +03:00
alexei.dolgolyov f0739ca949 feat: security hardening — SSRF guard, template sandbox timeout, webhook log prune, auth & backup polish
- Add outbound URL validation (SSRF) for webhook/Discord/Slack/ntfy/Matrix dispatch
- Template renderer: input/output caps and thread-based render timeout
- Webhook log filter: strip Authorization/signature/token-like headers; atomic prune
- Auth/JWT/backup/config tightening; misc frontend UX fixes
2026-04-16 03:21:45 +03:00
alexei.dolgolyov 6e51164f8e refactor: comprehensive consistency review — UI/UX, code quality, functional parity
Fix 19 issues across 3 priority tiers found during full-codebase review:

CRITICAL:
- Fix undefined --color-secondary CSS variable causing invisible UI elements
- Fix Google Photos command templates using nonexistent asset.originalFileName
- Fix scheduler template variable docs (tracker_name → schedule_name)
- Add missing admin guard on notification template update endpoint

HIGH:
- Fix 5 hardcoded English strings missing i18n (MultiEntitySelect, actions,
  settings, TelegramBotTab, users)
- Replace 17 raw <button> elements with shared <Button> component
- Replace 5 raw error divs with shared <ErrorBanner> component
- Refactor webhook handler duplication into shared _dispatch_webhook_event()
- Add 30+ provider-specific fields to TrackingConfig TypeScript type
- Add default TrackingConfig seeds for immich and google_photos
- Add provider-specific command variable docs (Gitea, Planka, NUT, GP, Webhook)

MEDIUM:
- Replace hardcoded hex colors and Tailwind classes with CSS variable tokens
- Remove dead code (unused imports, orphaned check_notification_tracker)
- Fix Svelte 5 patterns ($state for _prevProviderId, remove unnecessary as any)
- Fix inconsistent POST response shape (targets now returns full response)
- Fix Google Photos template dead asset.year branches, clarify album_url docs
2026-03-31 23:27:35 +03:00
alexei.dolgolyov b803d004e1 refactor: comprehensive codebase review — security, performance, quality, UX
Security:
- Fix NUT protocol command injection (validate names against safe regex)
- Enable Jinja2 autoescape=True to prevent HTML injection via external data
- Add WebhookProviderConfig validation model

Performance:
- Shared aiohttp.ClientSession singleton (replaces 40+ per-request sessions)
- Fix 4 N+1 queries with batch IN loads (poller, scheduler, memory, broadcast)
- asyncio.gather for Gitea commands and notification dispatcher
- Add DB indexes on NotificationTrackerState.tracker_id, CommandTrackerListener
- LRU cache for compiled Jinja2 templates
- Daily EventLog cleanup job (90-day retention)
- 30s HTTP timeout on all external calls
- GROUP BY for target type counts (replaces 7 sequential queries)

Code quality:
- Extract get_owned_entity() helper (replaces 11 duplicate functions)
- Extract slot_helpers.py (load_slots, save_slots, render_template_preview)
- Extract command_utils.py (tracker lookup, last event, collection IDs)
- Extract http_session.py (shared session lifecycle)
- Provider connection validation dedup (3x → 1 helper)
- Command dispatch tables replacing if/elif chains
- Album+links fetch helper (fetch_albums_with_links)
- Provider dispatch polymorphism (list_provider_collections)
- Immutable _enrich_assets (no longer mutates in-place)
- Fix _format_assets return type + handler unpacking

Frontend:
- Fix 18+ hardcoded English strings → t() with new i18n keys (en + ru)
- Mobile "More" nav panel with provider filter and search
- Shared Button.svelte component (4 variants, 2 sizes)
- Shared ErrorBanner.svelte component (8 pages updated)
- SvelteKit goto() replacing window.location.href
- Dashboard grid fixed for 4 cards, paginator opacity consistency

Functionality:
- max_instances=1 on scheduler jobs (prevents duplicate events)
- Webhook provider in watcher (prevents error spam)
- Fix stale SQLModel reference in poller
- Gitea get_repo() direct API call
2026-03-28 13:22:26 +03:00
alexei.dolgolyov 307871cae5 feat: Google Photos provider backend + API hardening
- Add Google Photos provider: client, models, change detector, capabilities
- Add notification templates (en/ru) for all GP event slots
- Add command templates (en/ru) for GP bot commands
- Register GP in slot/command loaders, capabilities, and seeds
- Harden provider API: validate OAuth credentials on create/update
- Add internal URL rewriting for asset fetches (LAN optimization)
- Fix template renderer to handle missing variables gracefully
- Improve webhook command routing for multi-provider support
- Add provider health check endpoint and watcher improvements
2026-03-25 22:07:03 +03:00
alexei.dolgolyov b1ab5b884f feat: collapsible accordion slots for template editing UX
Template slot editors (notification + command) now use collapsible
accordion rows instead of showing all editors at once. Each slot
displays a compact header with status pill (empty/valid/warning/error).
Adds slot name filtering and a preview toggle button that swaps
between editor and rendered preview views.
2026-03-24 17:06:03 +03:00
alexei.dolgolyov 68ac13b452 feat: NUT (Network UPS Tools) service provider + provider-agnostic UI
Add full NUT support as a polling-based service provider:
- Async TCP client for upsd protocol (port 3493, configurable)
- 8 event types: online, on_battery, low_battery, battery_restored,
  comms_lost, comms_restored, replace_battery, overload
- 3 bot commands: /status, /devices, /battery
- 38 Jinja2 templates (EN+RU notification + command templates)
- Database: tracking config fields, migration, seeds
- Frontend: provider form with host/port/credentials, grid items, i18n

Provider-agnostic UI improvements:
- Remove hardcoded 'immich' defaults from all config forms
- Dynamic collection labels per provider type (Albums/Repos/Boards/UPS Devices)
- Capability-driven test types instead of provider type checks
- Template variable helpers for all providers (was Immich-only)
- Guard Immich-only shared link check to Immich providers
- Auto-clear stale global provider filter from localStorage
- EntitySelect search placeholder shows current selection
- Fix noneLabel in linked target config selectors

New CLAUDE.md rule #8: no provider-specific hardcoding
2026-03-23 23:23:58 +03:00
alexei.dolgolyov 37388c430c feat: locale-aware notification templates + UX improvements
- 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
2026-03-23 19:08:48 +03:00
alexei.dolgolyov 6a559bfcd2 feat: Actions system — scheduled mutations on external services
Full-stack implementation of provider-scoped Actions with extensible
executor architecture. First action type: Immich auto_organize (sort
assets into albums by person, CLIP search, date range, favorites).

Core:
- ActionTypeDefinition registry + ActionExecutor ABC with execute/validate/dry-run
- ImmichActionExecutor with multi-album support and client-side filtering
- ImmichClient write methods: add/remove assets, create album, paginated search

Server:
- Action, ActionRule, ActionExecution DB models
- Full CRUD API + manual execute + dry-run + execution history endpoints
- APScheduler integration (interval + cron) for automated execution
- Action type discovery API + provider people endpoint

Frontend:
- Actions page with CRUD, execute/dry-run buttons, inline rule editor
- RuleEditor: person/album MultiEntitySelect pickers, criteria config
- ExecutionHistory: expandable per-rule result details
- MultiEntitySelect reusable component (searchable multi-pick palette)
- Notification tracker album picker migrated to MultiEntitySelect
- Fixed MdiIcon race condition (icons missing after cache-clearing reload)
2026-03-23 16:59:20 +03:00
alexei.dolgolyov 39bac828fd feat: smart video size warnings + Jinja2 template autocomplete
Video size warnings:
- Add file_size field to ImmichAssetInfo from exifInfo.fileSizeInByte
- Expose per-target max_video_size (50 MB for Telegram, none for others)
- Compute has_oversized_videos and per-asset oversized flag in template context
- Default templates show warning only when videos actually exceed the limit
- Templates no longer hardcode Telegram-specific logic

Template autocomplete:
- New jinja-autocomplete.ts engine with contextual completions
- Top-level variables ({{ }}), asset/album fields (dot access in loops),
  Jinja2 filters (|), block tags ({% %}), and loop.* special vars
- JinjaEditor accepts optional variables prop via CodeMirror Compartment
- Wired into template-configs and command-template-configs pages

Also: fix template emoji (📷📎) and sync sample_context with new vars.
2026-03-23 15:03:35 +03:00
alexei.dolgolyov e0bae394ee feat: comprehensive code review fixes — security, performance, quality
Backend security:
- Reject Gitea webhooks when webhook_secret is empty (was silently skipping)
- Add slowapi rate limiting on login (5/min) and setup (3/min) endpoints
- Add CORS middleware with configurable origins
- Mask telegram_webhook_secret in settings API response
- Protect system-owned command template configs from regular user modification
- Increase minimum password length to 8 characters

Backend performance:
- Batch queries in _resolve_command_context (3 queries instead of 3N)
- Concurrent album fetching with asyncio.gather in immich commands
- Singleton Jinja2 SandboxedEnvironment (reuse instead of per-render creation)
- TTLCache for rate limits (bounded memory, auto-eviction)
- Optional aiohttp session reuse in send_reply/send_media_group

Backend code quality:
- Extract dispatch_helpers.py (shared link_data loading + event filtering)
- Extract database/seeds.py from main.py (490 lines → dedicated module)
- Split immich_handler.py (415 lines) into commands/immich/ subpackage
- Replace bare except blocks with logged warnings
- Add per-provider config validation (Pydantic models)
- Truncate command input to 512 chars
- Expose usage_* and desc_* slots in capabilities and variables API

Frontend security:
- CSS.escape() for user-controlled querySelector in highlight.ts
- Client-side password min 8 chars validation on setup and password change

Frontend code quality:
- Replace any types with proper interfaces across top files
- Decompose targets/+page.svelte into TargetForm + ReceiverSection
- Fix $derived.by usage, $state mutation patterns
- Add console.warn to empty catch blocks

Frontend UX:
- Auth redirect via goto() with "Redirecting..." state
- Platform-aware Ctrl/Cmd K keyboard hint
- Remove stat-card hover transform

Frontend accessibility:
- Modal: role=dialog, aria-modal, focus trap, restore focus
- EntitySelect/IconGridSelect: listbox/option roles, aria-selected/disabled
2026-03-23 01:59:51 +03:00
alexei.dolgolyov 31584c5d31 feat: consistent IconGridSelect sizing + descriptions + filter upgrades
- 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
2026-03-23 01:05:59 +03:00
alexei.dolgolyov bd254de7a9 fix: remove Card hover transform that breaks fixed-position overlays
The translateY(-2px) transform on Card hover created a new containing
block, trapping position:fixed EntitySelect overlays inside the Card
instead of rendering relative to the viewport.
2026-03-22 20:46:56 +03:00
alexei.dolgolyov 1167d138a3 feat: locale-aware command templates, debounced auto-sync, entity pickers
- 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.
2026-03-22 03:14:51 +03:00
alexei.dolgolyov 751097b347 feat: comprehensive code review fixes + receivers-only architecture
Security:
- Refuse startup with default secret_key in production (was just logging)
- Settings endpoint now requires admin role
- Password validation on initial setup
- DOM-based HTML sanitizer replaces regex in template previews
- Add *.log to .gitignore

Performance & reliability:
- Token refresh deduplication prevents race condition on concurrent 401s
- Theme media query listener registered once (no leak)
- IconPicker uses $derived instead of function call per render
- Snackbar uses single-batch state update instead of while loop
- Replace 11 inline hover handlers with CSS :hover in layout

Architecture - receivers-only:
- Delivery endpoints (chat_id, email, url, room_id, topic) now stored
  exclusively in TargetReceiver rows, never in target.config
- Migration extracts existing delivery fields to receiver rows
- Notifier and dispatcher remove all config fallbacks
- Frontend targets page shows receivers list per target with
  add/remove/toggle/test per receiver
- Single-receiver test endpoint: POST /targets/{id}/receivers/{id}/test

Code quality:
- Extract AuthLayout.svelte from login/setup (150 lines CSS dedup)
- Split telegram-bots page (754→51 lines + 3 tab components)
- Split notification-trackers page (547→432 lines + 4 components)
- Deduplicate _send_reply into shared handler.send_reply()
- Add locale column to template models, replace name-based detection
- Fix delete_notification_tracker dead protection check
- Fix check_telegram_bot query (filter by type, remove bogus OR)
- Add graceful scheduler shutdown in lifespan
- Consistent /bots?tab=telegram URLs across all nav links

i18n:
- Error page, chat actions, target types, provider types internationalized
- All new receiver UI strings in EN + RU
2026-03-22 02:19:31 +03:00
alexei.dolgolyov b525e3e7f4 refactor: rename /telegram-bots route to /bots
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).
2026-03-22 01:31:30 +03:00
alexei.dolgolyov 826be4c347 perf: lazy-load @mdi/js to reduce Vite dev server memory usage
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.
2026-03-22 01:26:08 +03:00
alexei.dolgolyov a7829c48a4 feat: add filter search to IconGridSelect when item count > 4 2026-03-22 01:19:30 +03:00
alexei.dolgolyov a9bb912c30 feat: replace all select dropdowns with IconGridSelect, fix EN template seed
Shared grid-items.ts with reusable item definitions for: sort by/order,
album mode, asset type, memory source, locale, response mode, event type
filter, chat action, preview target type, provider type.

Replaced selects on:
- Dashboard: event type, provider, sort (compact mode — auto-width)
- Tracking configs: sort by/order, album modes, asset types, memory source
- Command configs: locale, response mode, provider type
- Targets: chat action
- Template configs: preview target type, provider type
- Command template configs: provider type
- Providers: type selector (read-only during edit)

IconGridSelect: added compact prop for inline filter bars (auto-width,
smaller padding, shows icon + label text).

Backend: template seed now re-creates deleted system templates on startup
using raw SQL to handle legacy NOT NULL columns.

Added i18n: trackingConfig.providerType, trackingConfig.sortRandom
Added provider_type badge to tracking config cards.
2026-03-22 01:17:06 +03:00
alexei.dolgolyov a3a1fe3d75 feat: EntitySelect palette-style entity picker, replace select dropdowns
New EntitySelect component: modal palette with search, keyboard nav,
current-value indicator (left border), smooth overlay. Replaces native
<select> dropdowns for entity selection.

Replaced selects:
- Notification Trackers: provider selector
- Command Trackers: provider + command config selectors
- Command Configs: response template selector
- Targets: telegram bot, email bot, matrix bot selectors

Added reactive $effect watchers for loadCollections and loadBotChats
since EntitySelect uses bind:value instead of onchange.
2026-03-22 00:21:57 +03:00
alexei.dolgolyov 86115f5c75 fix: search palette triggers highlight, restore CSS keyframe blink
- SearchPalette now calls requestHighlight(id) before goto()
- Restore smooth CSS @keyframes cardHighlight (0%→none, 25%→glow,
  75%→glow, 100%→none) instead of JS interval pulse
- Inline style.animation overrides class-based stagger animation;
  cleanup sets animation:'none' (inline beats class, no stagger replay)
2026-03-22 00:13:53 +03:00
alexei.dolgolyov 88e21e41e2 fix: switch highlight to global store instead of URL params
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.
2026-03-22 00:11:32 +03:00
alexei.dolgolyov 637a4671cf feat: add search button to sidebar with Ctrl+K shortcut hint
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.
2026-03-22 00:03:02 +03:00
alexei.dolgolyov f0f49db21e feat: card highlight system for cross-entity navigation
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
2026-03-21 23:59:25 +03:00
alexei.dolgolyov 06b24638cb feat: IconGridSelect, CrossLink, SearchPalette components + entity crosslinks
New components:
- IconGridSelect: Visual grid selector replacing <select> dropdowns,
  with icon + label cells, fixed-position popup, smart placement
- CrossLink: Inline clickable badge for cross-entity navigation,
  hover highlights primary, used on entity cards
- SearchPalette: Ctrl+K global command palette, searches all entity
  types via cached data, grouped results, keyboard navigation

Integration:
- Targets: type selector uses IconGridSelect (4-column grid with icons)
- Targets: bot crosslink on telegram/email/matrix target cards
- Command Trackers: provider and config badges → CrossLinks
- Command Trackers: listener bot name → CrossLink
- Command Configs: template config shown as CrossLink on card
- Notification Trackers: provider CrossLink added to card
- Layout: SearchPalette mounted globally

Infrastructure:
- Added CommandConfig, CommandTemplateConfig, CommandTracker types
- Added notificationTrackersCache, commandTrackersCache to caches
- Added allCaches map and fetchAllCaches() for search palette
- Added searchPalette i18n keys (EN/RU)
2026-03-21 23:44:12 +03:00
alexei.dolgolyov 03ec9b3c86 feat: telegram commands, app settings, bot polling, webhook handling, UI improvements
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>
2026-03-20 23:11:42 +03:00
alexei.dolgolyov 91e5cd58e9 fix: comprehensive API/UI review — 26 bug fixes and improvements
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>
2026-03-20 14:26:20 +03:00