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
3.7 KiB
Frontend Architecture Notes
Stack
- SvelteKit 2 + Svelte 5 + Tailwind CSS v4 — Static adapter with SPA fallback. Dev proxy to :8420.
- Tailwind CSS v4: Uses
@themedirective inapp.cssfor CSS variables.
Svelte 5 Runes
$stateonly works in.svelteand.svelte.tsfiles. Regular.tsfiles cannot use runes — use plain variables instead.
i18n
- Uses
$staterune in.svelte.tsfile. Locale auto-detects from localStorage. t()is reactive via$state.setLocale()updates immediately without page reload.
Auth Flow
- After login/setup, use
window.location.href = '/'(hard redirect), NOTgoto('/').
Overlays
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.
Entity Cache System
Shared entities use a $state-based cache layer in frontend/src/lib/stores/:
entity-cache.svelte.ts— Generic cache factory with 30s TTL, request deduplication, and local mutation helpers (upsert,remove,set).caches.svelte.ts— Singleton caches for:providersCache,targetsCache,trackingConfigsCache,templateConfigsCache,telegramBotsCache,emailBotsCache,matrixBotsCache,commandConfigsCache,commandTemplateConfigsCache.
How pages use caches
- Cross-page references (e.g. providers on the Trackers page): Use
$derived(providersCache.items)and callprovidersCache.fetch()inload(). Cached data is returned instantly if fresh (<30s). - Owning pages (e.g. Providers page itself): Use
$derived(providersCache.items)and callprovidersCache.fetch(true)(force refresh) inload(). - After mutations (create/update/delete): The owning page calls
cache.invalidate()thenload()to force-refresh. Other pages pick up changes on next navigation via TTL expiry. - On logout:
clearAllCaches()is called to wipe all cached data.
Adding a new cached entity
- Add a type to
frontend/src/lib/types.ts - Add
export const fooCache = createEntityCache<Foo>('/foo');tocaches.svelte.ts - Add
fooCache.clear()toclearAllCaches() - In page components: replace
let foo = $state<Foo[]>([])withlet foo = $derived(fooCache.items)and replaceapi('/foo')withfooCache.fetch()
Provider-Aware UI
IMPORTANT: UI labels for collections, template variables, and icons MUST be dynamic per provider type — never hardcode Immich-specific terms like "Albums" or mdiImageMultiple where other providers will appear.
- TrackerForm (
TrackerForm.svelte): UsescollectionMetalookup byproviderTypefor collection label, icon, placeholder, and description. - Template variables (
/api/template-configs/variables): Must return variable definitions for ALL provider types (Immich, Gitea, Planka, NUT, Scheduler), not just Immich. When adding a new provider, add its slot variables to_<provider>_variables()intemplate_configs.py. - Grid items (
grid-items.ts): New provider types must be added to BOTHproviderTypeItemsANDproviderTypeFilterItems.
UI Conventions
Selector Placeholders
IMPORTANT: Selector/dropdown placeholder text must be plain and simple — no decorative dashes or special characters. Use Select provider... or Tracking Configs, NOT — Select provider — or -- Tracking Configs --. The i18n keys already follow this convention; never wrap them with '— ' + t('key') + ' —' in Svelte templates.