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
+66
View File
@@ -0,0 +1,66 @@
/**
* Reactive auth state using Svelte 5 runes.
*/
import { api, setTokens, clearTokens, isAuthenticated } from './api';
import { clearAllCaches } from './stores/caches.svelte';
interface User {
id: number;
username: string;
role: string;
}
let user = $state<User | null>(null);
let loading = $state(true);
export function getAuth() {
return {
get user() { return user; },
get loading() { return loading; },
get isAdmin() { return user?.role === 'admin'; },
};
}
export async function loadUser() {
if (!isAuthenticated()) {
user = null;
loading = false;
return;
}
try {
user = await api<User>('/auth/me');
} catch {
user = null;
clearTokens();
} finally {
loading = false;
}
}
export async function login(username: string, password: string) {
const data = await api<{ access_token: string; refresh_token: string }>('/auth/login', {
method: 'POST',
body: JSON.stringify({ username, password })
});
setTokens(data.access_token, data.refresh_token);
await loadUser();
}
export async function setup(username: string, password: string) {
const data = await api<{ access_token: string; refresh_token: string }>('/auth/setup', {
method: 'POST',
body: JSON.stringify({ username, password })
});
setTokens(data.access_token, data.refresh_token);
await loadUser();
}
export function logout() {
clearTokens();
clearAllCaches();
user = null;
if (typeof window !== 'undefined') {
window.location.href = '/login';
}
}