import type { SidebarInitialData } from "@/components/sidebar/types"; import type { SidebarTreeNode } from "@/lib/kernel-sidebar"; import type { MediaAsset } from "@/types/media"; import type { KernelFileTreeProjection } from "@/lib/kernel-file-tree"; type SidebarTreeSnapshot = { id: string; title: string | null; parentId: string | null; sortOrder: number | null; updatedAt: string | null; children: SidebarTreeSnapshot[]; }; function toSidebarTreeSnapshot(nodes: SidebarTreeNode[]): SidebarTreeSnapshot[] { return nodes.map((node) => ({ id: node.id, title: node.title ?? null, parentId: node.parent_id ?? null, sortOrder: node.sort_order ?? null, updatedAt: node.updated_at ?? null, children: toSidebarTreeSnapshot(node.children ?? []), })); } function toMediaAssetSnapshot(assets: MediaAsset[]) { return assets.map((asset) => ({ id: asset.id, assetType: asset.asset_type, documentId: asset.document_id, fileName: asset.file_name ?? null, updatedAt: asset.updated_at ?? null, storagePath: asset.storage_path ?? null, fileUrl: asset.file_url ?? null, })); } function toMillis(value: string | null | undefined): number { if (!value) { return 0; } const parsed = Date.parse(value); return Number.isFinite(parsed) ? parsed : 0; } function normalizeKernelFileTreeProjection( projection: SidebarInitialData["kernelFileTreeProjection"] | undefined, ): KernelFileTreeProjection { return ( projection ?? { projectionId: "kernel_projection:file_tree:missing", projection: "file_tree", rootNodeId: null, items: [], edges: [], } ); } export function buildSidebarTreeSyncKey(nodes: SidebarTreeNode[]): string { return JSON.stringify(toSidebarTreeSnapshot(nodes)); } export function buildMediaAssetListSyncKey(assets: MediaAsset[]): string { return JSON.stringify(toMediaAssetSnapshot(assets)); } export function buildSidebarDataSyncKey(data: SidebarInitialData): string { const fileTreeProjection = normalizeKernelFileTreeProjection(data.kernelFileTreeProjection); return JSON.stringify({ activeWorkspaceId: data.activeWorkspaceId, tree: toSidebarTreeSnapshot(data.kernelSidebarTree), fileTree: { projectionId: fileTreeProjection.projectionId, rootNodeId: fileTreeProjection.rootNodeId, items: fileTreeProjection.items.map((item) => ({ rowId: item.rowId, rowKind: item.rowKind, nodeId: item.nodeId, parentNodeId: item.parentNodeId, title: item.title, depth: item.depth, position: item.position, childCount: item.childCount, expandable: item.expandable, expandedByDefault: item.expandedByDefault, iconHint: item.iconHint ?? null, resourceKind: item.resourceMeta.resourceKind, documentId: item.resourceMeta.documentId ?? null, assetId: item.resourceMeta.assetId ?? null, assetKind: item.resourceMeta.assetKind ?? null, })), }, mediaAssets: toMediaAssetSnapshot(data.mediaAssets ?? []), mindmapAssets: toMediaAssetSnapshot(data.mindmapAssets ?? []), tableAssets: toMediaAssetSnapshot(data.tableAssets ?? []), trashedDocuments: (data.trashedDocuments ?? []).map((item) => ({ id: item.id, title: item.title ?? null, parentId: item.parent_id ?? null, deletedAt: item.deleted_at, accessScope: item.access_scope, })), trashedMediaAssets: toMediaAssetSnapshot(data.trashedMediaAssets ?? []), trashedMindmapAssets: toMediaAssetSnapshot(data.trashedMindmapAssets ?? []), trashedTableAssets: toMediaAssetSnapshot(data.trashedTableAssets ?? []), }); } export function getSidebarDataFreshness(data: SidebarInitialData): number { const documentTimes = data.documents.map((item) => toMillis(item.updated_at ?? item.created_at)); const treeTimes = data.kernelSidebarTree.map((item) => toMillis(item.updated_at ?? item.created_at)); const fileTreeProjection = normalizeKernelFileTreeProjection(data.kernelFileTreeProjection); const fileTreeTimes = fileTreeProjection.items.map((item) => item.position ?? 0); const assetTimes = [ ...(data.mediaAssets ?? []), ...(data.mindmapAssets ?? []), ...(data.tableAssets ?? []), ...(data.trashedMediaAssets ?? []), ...(data.trashedMindmapAssets ?? []), ...(data.trashedTableAssets ?? []), ].map((item) => toMillis(item.updated_at ?? item.created_at)); const trashedDocumentTimes = (data.trashedDocuments ?? []).map((item) => toMillis(item.deleted_at)); return Math.max( 0, ...documentTimes, ...treeTimes, ...fileTreeTimes, ...assetTimes, ...trashedDocumentTimes, ); }