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
@@ -3,6 +3,7 @@
import { slide } from 'svelte/transition';
import { api } from '$lib/api';
import { t, getLocale } from '$lib/i18n';
import { providersCache, targetsCache, trackingConfigsCache, templateConfigsCache } from '$lib/stores/caches.svelte';
import PageHeader from '$lib/components/PageHeader.svelte';
import Card from '$lib/components/Card.svelte';
import Loading from '$lib/components/Loading.svelte';
@@ -19,10 +20,10 @@
let loaded = $state(false);
let loadError = $state('');
let notificationTrackers = $state<Tracker[]>([]);
let providers = $state<ServiceProvider[]>([]);
let targets = $state<NotificationTarget[]>([]);
let trackingConfigs = $state<TrackingConfig[]>([]);
let templateConfigs = $state<TemplateConfig[]>([]);
let providers = $derived(providersCache.items);
let targets = $derived(targetsCache.items);
let trackingConfigs = $derived(trackingConfigsCache.items);
let templateConfigs = $derived(templateConfigsCache.items);
let collections = $state<any[]>([]);
let showForm = $state(false);
let editing = $state<number | null>(null);
@@ -59,9 +60,10 @@
async function load() {
loadError = '';
try {
[notificationTrackers, providers, targets, trackingConfigs, templateConfigs] = await Promise.all([
api('/notification-trackers'), api('/providers'), api('/targets'),
api('/tracking-configs'), api('/template-configs'),
[notificationTrackers] = await Promise.all([
api('/notification-trackers'),
providersCache.fetch(), targetsCache.fetch(),
trackingConfigsCache.fetch(), templateConfigsCache.fetch(),
]);
} catch (err: any) {
loadError = err.message || 'Failed to load data';