feat: entity cache system, nav UX improvements, split CLAUDE.md

- Add $state-based entity cache layer with 30s TTL, request deduplication,
  and local mutation helpers (entity-cache.svelte.ts + caches.svelte.ts)
- Wire all 10 page components to use shared caches for cross-page data
- Add slide animation for nav tree expand/collapse with rotating chevron
- Remove aggregate count badges from container nav nodes (keep on leaves)
- Convert Targets from flat leaf to group with per-type children
  (Telegram, Webhook, Email, Discord, Slack, ntfy, Matrix)
- Add URL-based type filtering on Targets page with per-type descriptions
- Add Bots group children for Email and Matrix alongside Telegram
- Tab-based routing for bots page (?tab=telegram/email/matrix)
- Add per-type target counts and email/matrix bot counts to /status/counts
- Split CLAUDE.md into focused context files under .claude/docs/
- Fix .gitignore: scope lib/ to root, allow .claude/docs/ tracking
- Clear all caches on logout
- Reset form state when switching target type tabs
This commit is contained in:
2026-03-21 23:35:50 +03:00
parent 2c740ff2d2
commit 563716fa76
25 changed files with 551 additions and 155 deletions
+44
View File
@@ -0,0 +1,44 @@
# 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()`