export type WorkspaceItem = { id: string; label: string; path: string; kind: 'favorite' | 'view' | 'recent'; updatedAt: string; }; type WorkspaceState = { favorites: WorkspaceItem[]; views: WorkspaceItem[]; recent: WorkspaceItem[]; }; const STORAGE_KEY = 'pulse.operator.workspace.v1'; export const WORKSPACE_EVENT = 'pulse-workspace-changed'; const emptyState = (): WorkspaceState => ({ favorites: [], views: [], recent: [] }); function validItem(value: unknown): value is WorkspaceItem { if (!value || typeof value !== 'object') return false; const item = value as Partial; return typeof item.id === 'string' && typeof item.label === 'string' && typeof item.path === 'string' && ['favorite', 'view', 'recent'].includes(String(item.kind)) && typeof item.updatedAt === 'string'; } export function readWorkspaceState(): WorkspaceState { try { const raw = window.localStorage.getItem(STORAGE_KEY); if (!raw) return emptyState(); const parsed = JSON.parse(raw) as Partial; return { favorites: Array.isArray(parsed.favorites) ? parsed.favorites.filter(validItem).slice(0, 24) : [], views: Array.isArray(parsed.views) ? parsed.views.filter(validItem).slice(0, 24) : [], recent: Array.isArray(parsed.recent) ? parsed.recent.filter(validItem).slice(0, 12) : [], }; } catch { return emptyState(); } } function writeWorkspaceState(state: WorkspaceState): void { try { window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch { return; } window.dispatchEvent(new CustomEvent(WORKSPACE_EVENT)); } function timestamp(): string { return new Date().toISOString(); } function cleanPath(path: string): string { if (!path.startsWith('/')) return '/'; return path.slice(0, 2048); } function cleanLabel(label: string): string { return label.trim().slice(0, 160) || 'Pulse'; } export function isFavorite(path: string): boolean { const clean = cleanPath(path); return readWorkspaceState().favorites.some((item) => item.path === clean); } export function toggleFavorite(label: string, path: string): boolean { const state = readWorkspaceState(); const clean = cleanPath(path); const existing = state.favorites.some((item) => item.path === clean); const favorite: WorkspaceItem = { id: 'favorite:' + clean, label: cleanLabel(label), path: clean, kind: 'favorite', updatedAt: timestamp() }; state.favorites = existing ? state.favorites.filter((item) => item.path !== clean) : [favorite, ...state.favorites].slice(0, 24); writeWorkspaceState(state); return !existing; } export function saveCurrentView(label: string, path: string): void { const state = readWorkspaceState(); const clean = cleanPath(path); const item: WorkspaceItem = { id: 'view:' + clean, label: cleanLabel(label), path: clean, kind: 'view', updatedAt: timestamp() }; state.views = [item, ...state.views.filter((value) => value.path !== clean)].slice(0, 24); writeWorkspaceState(state); } export function removeSavedView(path: string): void { const state = readWorkspaceState(); const clean = cleanPath(path); state.views = state.views.filter((item) => item.path !== clean); writeWorkspaceState(state); } export function recordRecent(label: string, path: string): void { const state = readWorkspaceState(); const clean = cleanPath(path); if (clean === '/404') return; const item: WorkspaceItem = { id: 'recent:' + clean, label: cleanLabel(label), path: clean, kind: 'recent', updatedAt: timestamp() }; state.recent = [item, ...state.recent.filter((value) => value.path !== clean)].slice(0, 12); writeWorkspaceState(state); }