// Library — saved artifacts. Today: conversation snapshots. // Tomorrow: snippets, prompts, generated documents. import { useEffect, useSyncExternalStore } from "react" export type LibraryItem = { id: string kind: "conversation" | "snippet" title: string // Free-form body. For "conversation": markdown transcript. For "snippet": text. content: string tags: string[] // Optional metadata. agentName?: string agentRole?: string threadId?: string messageCount?: number createdAt: number } const STORAGE_KEY = "crema.library" const CHANGE_EVENT = "crema:library-change" const MAX_BYTES = 1_500_000 export function newLibraryId(): string { return `lib-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}` } function isLibraryItem(v: unknown): v is LibraryItem { if (!v || typeof v !== "object") return false const x = v as LibraryItem return ( typeof x.id === "string" && (x.kind === "conversation" || x.kind === "snippet") && typeof x.title === "string" && typeof x.content === "string" && Array.isArray(x.tags) && typeof x.createdAt === "number" ) } function readFromStorage(): LibraryItem[] { if (typeof window === "undefined") return [] try { const raw = localStorage.getItem(STORAGE_KEY) if (!raw) return [] const parsed = JSON.parse(raw) if (!Array.isArray(parsed)) return [] return parsed.filter(isLibraryItem) } catch { return [] } } function writeToStorage(items: LibraryItem[]) { if (typeof window === "undefined") return let trimmed = items let serialized = JSON.stringify(trimmed) while (serialized.length > MAX_BYTES && trimmed.length > 1) { trimmed = trimmed.slice(0, -1) serialized = JSON.stringify(trimmed) } try { localStorage.setItem(STORAGE_KEY, serialized) window.dispatchEvent(new CustomEvent(CHANGE_EVENT)) } catch { /* quota — bail */ } } export function loadLibrary(): LibraryItem[] { return readFromStorage() } export function addLibraryItem(item: Omit): LibraryItem { const next: LibraryItem = { ...item, id: newLibraryId(), createdAt: Date.now(), } const items = readFromStorage() writeToStorage([next, ...items]) return next } export function deleteLibraryItem(id: string) { const items = readFromStorage().filter((x) => x.id !== id) writeToStorage(items) } export function updateLibraryItem(id: string, patch: Partial) { const items = readFromStorage().map((x) => x.id === id ? { ...x, ...patch } : x, ) writeToStorage(items) } let cached: LibraryItem[] | null = null function subscribe(cb: () => void): () => void { const onChange = () => { cached = null cb() } window.addEventListener(CHANGE_EVENT, onChange) window.addEventListener("storage", (e) => { if (e.key === STORAGE_KEY) onChange() }) return () => window.removeEventListener(CHANGE_EVENT, onChange) } function getSnapshot(): LibraryItem[] { if (!cached) cached = readFromStorage() return cached } function getServerSnapshot(): LibraryItem[] { return [] } export function useLibrary(): LibraryItem[] { const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) useEffect(() => { cached = null }, []) return value }