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>
This commit is contained in:
2026-03-20 14:26:20 +03:00
parent 9eec21a5b2
commit 91e5cd58e9
24 changed files with 3514 additions and 375 deletions
@@ -44,18 +44,6 @@ class TelegramBot(SQLModel, table=True):
icon: str = Field(default="")
bot_username: str = Field(default="")
bot_id: int = Field(default=0)
commands_config: dict[str, Any] = Field(
default_factory=lambda: {
"enabled": ["status", "albums", "events", "summary", "latest",
"memory", "random", "search", "find", "person",
"place", "favorites", "people", "help"],
"default_count": 5,
"response_mode": "media",
"rate_limits": {"search": 30, "find": 30, "default": 10},
"locale": "en",
},
sa_column=Column(JSON),
)
created_at: datetime = Field(default_factory=_utcnow)
@@ -134,7 +122,7 @@ class TemplateConfig(SQLModel, table=True):
__tablename__ = "template_config"
id: int | None = Field(default=None, primary_key=True)
user_id: int = Field(foreign_key="user.id")
user_id: int = Field(default=0) # 0 = system-owned (no FK to allow sentinel)
provider_type: str # Must match provider's type
name: str
description: str = Field(default="")
@@ -158,7 +146,7 @@ class TemplateConfig(SQLModel, table=True):
class NotificationTarget(SQLModel, table=True):
"""Where to send notifications. Owns the template config."""
"""Where to send notifications. Pure delivery endpoint."""
__tablename__ = "notification_target"
@@ -168,12 +156,11 @@ class NotificationTarget(SQLModel, table=True):
name: str
icon: str = Field(default="")
config: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
template_config_id: int | None = Field(default=None, foreign_key="template_config.id")
created_at: datetime = Field(default_factory=_utcnow)
class Tracker(SQLModel, table=True):
"""Watches a provider's collections for changes. Owns the tracking config."""
"""Watches a provider's collections for changes."""
__tablename__ = "tracker"
@@ -183,12 +170,32 @@ class Tracker(SQLModel, table=True):
name: str
icon: str = Field(default="")
collection_ids: list[str] = Field(default_factory=list, sa_column=Column(JSON))
target_ids: list[int] = Field(default_factory=list, sa_column=Column(JSON))
tracking_config_id: int | None = Field(default=None, foreign_key="tracking_config.id")
scan_interval: int = Field(default=60)
batch_duration: int = Field(default=0) # seconds to accumulate events before dispatch (0=immediate)
enabled: bool = Field(default=True)
created_at: datetime = Field(default_factory=_utcnow)
class TrackerTarget(SQLModel, table=True):
"""Junction between Tracker and NotificationTarget with per-link config."""
__tablename__ = "tracker_target"
id: int | None = Field(default=None, primary_key=True)
tracker_id: int = Field(foreign_key="tracker.id", index=True)
target_id: int = Field(foreign_key="notification_target.id", index=True)
tracking_config_id: int | None = Field(
default=None, foreign_key="tracking_config.id"
)
template_config_id: int | None = Field(
default=None, foreign_key="template_config.id"
)
enabled: bool = Field(default=True)
quiet_hours_start: str | None = None
quiet_hours_end: str | None = None
commands_config: dict[str, Any] | None = Field(
default=None, sa_column=Column(JSON)
)
created_at: datetime = Field(default_factory=_utcnow)
@@ -200,6 +207,8 @@ class TrackerState(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
tracker_id: int = Field(foreign_key="tracker.id")
collection_id: str
collection_name: str = Field(default="")
shared: bool = Field(default=False)
asset_ids: list[str] = Field(default_factory=list, sa_column=Column(JSON))
pending_asset_ids: list[str] = Field(default_factory=list, sa_column=Column(JSON))
last_updated: datetime = Field(default_factory=_utcnow)