- 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
6.2 KiB
Project Guidelines
Development Servers
MANDATORY: You MUST restart the backend server IMMEDIATELY after ANY backend code change (files in packages/server/ or packages/core/). Do NOT wait for the user to ask — restart automatically every time. Failure to restart means the user will test against stale code and encounter bugs that don't exist. Use this one-liner:
PID=$(netstat -ano 2>/dev/null | grep ':8420.*LISTENING' | awk '{print $5}' | head -1) && [ -n "$PID" ] && taskkill //F //PID $PID 2>/dev/null; sleep 1 && cd packages/server && pip install -e . 2>&1 | tail -1 && cd ../.. && NOTIFY_BRIDGE_DATA_DIR=./test-data NOTIFY_BRIDGE_SECRET_KEY=test-secret-key-minimum-32chars nohup python -m uvicorn notify_bridge_server.main:app --host 0.0.0.0 --port 8420 > /dev/null 2>&1 & sleep 3 && curl -s http://localhost:8420/api/health
IMPORTANT: Overlays (modals, dropdowns, pickers) MUST use position: fixed with inline styles and z-index: 9999. Tailwind CSS v4 fixed/absolute classes do NOT work reliably inside flex/overflow containers in this project. Always calculate position from getBoundingClientRect() for dropdowns, or use top:0;left:0;right:0;bottom:0 for full-screen backdrops.
IMPORTANT: When the user requests it, restart the frontend dev server using this one-liner:
PID=$(netstat -ano 2>/dev/null | grep ':5173.*LISTENING' | awk '{print $5}' | head -1) && [ -n "$PID" ] && taskkill //F //PID $PID 2>/dev/null; sleep 1 && cd frontend && npx vite dev --port 5173 --host > /dev/null 2>&1 & sleep 4 && curl -s -o /dev/null -w "Frontend: %{http_code}" http://localhost:5173/
Test Credentials
Default test account: username admin, password admin1.
Frontend Architecture Notes
- i18n: Uses
$staterune in.svelte.tsfile. Locale auto-detects from localStorage.t()is reactive via$state.setLocale()updates immediately without page reload. - Svelte 5 runes:
$stateonly works in.svelteand.svelte.tsfiles. Regular.tsfiles cannot use runes -- use plain variables instead. - Static adapter: Frontend uses
@sveltejs/adapter-staticwith SPA fallback. API calls proxied via Vite dev server config. - Auth flow: After login/setup, use
window.location.href = '/'(hard redirect), NOTgoto('/'). - Tailwind CSS v4: Uses
@themedirective inapp.cssfor CSS variables.
Backend Architecture Notes
- SQLAlchemy async + aiohttp: Cannot nest
async with aiohttp.ClientSession()inside a route that has an active SQLAlchemy async session -- greenlet context breaks. Eagerly load all DB data before entering aiohttp context. - Jinja2 SandboxedEnvironment: All template rendering MUST use
from jinja2.sandbox import SandboxedEnvironment. - System-owned entities:
user_id=0means system-owned (e.g. default templates). - FastAPI route ordering: Static path routes MUST be registered BEFORE parameterized routes.
__pycache__: Add to.gitignore. Never commit.
Project Structure (Phase 1)
- packages/core (
notify_bridge_core): Shared library — providers, models, notifications, templates. No DB dependency. - packages/server (
notify_bridge_server): FastAPI REST API + SQLite. Depends on core. - frontend: SvelteKit 2 + Svelte 5 + Tailwind CSS v4. Static adapter with SPA fallback. Dev proxy to :8420.
- Environment vars:
NOTIFY_BRIDGE_DATA_DIR,NOTIFY_BRIDGE_SECRET_KEY,NOTIFY_BRIDGE_DATABASE_URL - Core package includes
jinja2dependency (template rendering lives in core, not server).
Entity Relationships
ServiceProvider → type: "immich" (inferred capabilities: notifications, commands)
NotificationTracker → provider_id, collection_ids, scan_interval, batch_duration, enabled
NotificationTrackerTarget → notification_tracker_id, target_id, tracking_config_id, template_config_id, quiet_hours, enabled
TrackingConfig → provider_type, event flags, scheduling rules
TemplateConfig → provider_type, Jinja2 template slots per event type
NotificationTarget → type: "telegram"/"webhook", config JSON, chat_action (telegram only)
CommandConfig → provider_type, enabled_commands, locale, response_mode, default_count, rate_limits
CommandTracker → provider_id, command_config_id, enabled
CommandTrackerListener → command_tracker_id, listener_type ("telegram_bot"), listener_id
TelegramBot → token, update_mode, bot_username (used as notification target backend + commands listener)
- NotificationTrackerTarget links a tracker to a target with per-link tracking/template config and quiet hours
- CommandTrackerListener links a command tracker to a listener (e.g. TelegramBot) for slash-command handling
user_id=0on TemplateConfig = system default (EN/RU seeded on first startup)- DB: SQLite + async SQLAlchemy via sqlmodel, auto-created on startup with migrations
- API: All CRUD routes under
/api/, auth via JWT Bearer,NOTIFY_BRIDGE_env prefix
Template System Sync Rules
IMPORTANT: When adding or changing template context variables, you MUST update ALL of these in sync:
packages/core/.../templates/context.py—build_template_context()where variables are computedpackages/server/.../api/template_configs.py—_SAMPLE_CONTEXTdict (for preview rendering)packages/server/.../api/template_configs.py—get_template_variables()endpoint (event_vars,asset_fields,album_fields,scheduled_vars, per-slot variable dicts)packages/core/.../templates/defaults/{en,ru}/*.jinja2— default template files using the new variablespackages/core/.../providers/immich/provider.py—IMMICH_VARIABLESlist (provider-specific variable definitions)packages/server/.../api/command_template_configs.py—get_command_variables()endpoint (for command response templates)
IMPORTANT: Variable reference endpoints MUST document child/nested properties, not only top-level variables. When a variable is a list of dicts (e.g. assets, albums, events, commands), the endpoint MUST include a corresponding *_fields dict describing the child properties (e.g. asset_fields: {"id": "...", "filename": "..."}) so the frontend can show them (e.g. {{ asset.id }}, {{ album.name }}). Never list only "assets": "List of asset dicts" — always specify what fields each dict contains.