import { useEffect, useMemo, useState } from "react"; // Cross-project asset view — the global media-use cache (~/.media), fetched from // /api/assets/global. Self-contained (owns its fetch + state) so AssetsTab stays // focused on the local view. export interface GlobalAssetRecord { id?: string; type?: string; description?: string; entity?: string; sha?: string; } export interface GlobalAssetRow { id: string; type: string; label: string; } /** * Normalize global records into display rows, filtered by an optional query * (id / type / description / entity). Pure — unit-tested. */ export function globalAssetRows(records: GlobalAssetRecord[], query = ""): GlobalAssetRow[] { const q = query.trim().toLowerCase(); return records .filter((r) => !q ? true : [r.id, r.type, r.description, r.entity].some( (f) => f && String(f).toLowerCase().includes(q), ), ) .map((r) => ({ id: r.id ?? r.sha ?? "asset", type: r.type ?? "asset", label: r.description || r.entity || r.id || r.sha || "asset", })); } export function GlobalAssetsView({ searchQuery }: { searchQuery: string }) { const [records, setRecords] = useState(null); useEffect(() => { let cancelled = false; fetch("/api/assets/global") .then((r) => (r.ok ? r.json() : { assets: [] })) .then((d) => { if (!cancelled) setRecords(Array.isArray(d.assets) ? d.assets : []); }) .catch(() => { if (!cancelled) setRecords([]); }); return () => { cancelled = true; }; }, []); const rows = useMemo(() => globalAssetRows(records ?? [], searchQuery), [records, searchQuery]); if (records === null) { return

Loading global assets…

; } if (rows.length === 0) { return (

No assets in the global cache yet. Resolved media is promoted to ~/.media and becomes reusable across projects.

); } return (
{rows.length} reusable across all projects
{rows.map((row) => (
{row.type} {row.label}
))}
); }