Files
notify-bridge/.claude/docs/frontend-architecture.md
T
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

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 @theme directive in app.css for CSS variables.

Svelte 5 Runes

  • $state only works in .svelte and .svelte.ts files. Regular .ts files cannot use runes — use plain variables instead.

i18n

  • Uses $state rune in .svelte.ts file. 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), NOT goto('/').

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 call providersCache.fetch() in load(). Cached data is returned instantly if fresh (<30s).
  • Owning pages (e.g. Providers page itself): Use $derived(providersCache.items) and call providersCache.fetch(true) (force refresh) in load().
  • After mutations (create/update/delete): The owning page calls cache.invalidate() then load() 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

  1. Add a type to frontend/src/lib/types.ts
  2. Add export const fooCache = createEntityCache<Foo>('/foo'); to caches.svelte.ts
  3. Add fooCache.clear() to clearAllCaches()
  4. In page components: replace let foo = $state<Foo[]>([]) with let foo = $derived(fooCache.items) and replace api('/foo') with fooCache.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): Uses collectionMeta lookup by providerType for 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() in template_configs.py.
  • Grid items (grid-items.ts): New provider types must be added to BOTH providerTypeItems AND providerTypeFilterItems.

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.