"use client"; import { Suspense, useEffect, useState } from "react"; import { useSearchParams } from "next/navigation"; import Link from "next/link"; import type { GeneratedDocs } from "@codeboard/shared"; import { MermaidDiagram } from "@/components/mermaid-diagram"; import { ArrowLeft, Clock, GitCommit, History, CheckSquare, Square, GitCompare, X, BookOpen, Layers, Folder, FileCode, } from "lucide-react"; interface Generation { id: string; repoUrl: string; repoName: string; commitHash: string; createdAt: string; duration: number | null; } function formatDate(dateStr: string): string { const date = new Date(dateStr); return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", hour: "2-digit", minute: "2-digit", }); } function formatDuration(seconds: number | null): string { if (!seconds) return "Unknown"; const mins = Math.floor(seconds / 60); const secs = seconds % 60; if (mins > 0) { return `${mins}m ${secs}s`; } return `${secs}s`; } function TechStackDiff({ leftStack, rightStack, }: { leftStack: string[]; rightStack: string[]; }) { const leftSet = new Set(leftStack.map((s) => s.toLowerCase())); const rightSet = new Set(rightStack.map((s) => s.toLowerCase())); const added = rightStack.filter((s) => !leftSet.has(s.toLowerCase())); const removed = leftStack.filter((s) => !rightSet.has(s.toLowerCase())); const unchanged = leftStack.filter((s) => rightSet.has(s.toLowerCase())); return (
{removed.map((tech) => ( {tech} ))} {unchanged.map((tech) => ( {tech} ))} {added.map((tech) => ( {tech} ))}
); } function ComparisonView({ left, right, leftGen, rightGen, onClose, }: { left: GeneratedDocs; right: GeneratedDocs; leftGen: Generation; rightGen: Generation; onClose: () => void; }) { const leftOverview = left.sections.overview; const rightOverview = right.sections.overview; const filesDiff = rightOverview.keyMetrics.files - leftOverview.keyMetrics.files; const modulesDiff = rightOverview.keyMetrics.modules - leftOverview.keyMetrics.modules; const languagesDiff = rightOverview.keyMetrics.languages.length - leftOverview.keyMetrics.languages.length; return (

Version Comparison

{/* Left Panel - Older */}
{leftGen.commitHash.slice(0, 7)} {formatDate(leftGen.createdAt)}

Older version

Overview

{leftOverview.description}

Tech Stack

{leftOverview.techStack.map((tech) => ( {tech} ))}

Architecture

{leftOverview.keyMetrics.files}
Files
{leftOverview.keyMetrics.modules}
Modules
{leftOverview.keyMetrics.languages.length}
Languages
{/* Right Panel - Newer */}
{rightGen.commitHash.slice(0, 7)} {formatDate(rightGen.createdAt)}

Newer version

Overview

{rightOverview.description}

Tech Stack Changes

Architecture

0 ? "text-green-400" : filesDiff < 0 ? "text-red-400" : "text-white" }`} > {rightOverview.keyMetrics.files}
Files
{filesDiff !== 0 && (
0 ? "text-green-400" : "text-red-400" }`} > {filesDiff > 0 ? "+" : ""} {filesDiff}
)}
0 ? "text-green-400" : modulesDiff < 0 ? "text-red-400" : "text-white" }`} > {rightOverview.keyMetrics.modules}
Modules
{modulesDiff !== 0 && (
0 ? "text-green-400" : "text-red-400" }`} > {modulesDiff > 0 ? "+" : ""} {modulesDiff}
)}
0 ? "text-green-400" : languagesDiff < 0 ? "text-red-400" : "text-white" }`} > {rightOverview.keyMetrics.languages.length}
Languages
{languagesDiff !== 0 && (
0 ? "text-green-400" : "text-red-400" }`} > {languagesDiff > 0 ? "+" : ""} {languagesDiff}
)}
{/* Module Comparison */}

Module Breakdown Comparison

Older Version ({left.sections.modules.length} modules)

{left.sections.modules.map((module) => (
{module.name}
{module.path}
))}

Newer Version ({right.sections.modules.length} modules)

{right.sections.modules.map((module) => { const existedInLeft = left.sections.modules.some( (m) => m.name === module.name ); return (
{module.name} {!existedInLeft && ( (new) )}
{module.path}
); })}
); } function HistoryContent() { const searchParams = useSearchParams(); const repo = searchParams.get("repo"); const [generations, setGenerations] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selectedIds, setSelectedIds] = useState>(new Set()); const [comparing, setComparing] = useState(false); const [leftDoc, setLeftDoc] = useState(null); const [rightDoc, setRightDoc] = useState(null); const [leftGen, setLeftGen] = useState(null); const [rightGen, setRightGen] = useState(null); useEffect(() => { if (!repo) { setLoading(false); setError("No repository URL provided"); return; } fetch(`/api/history?repo=${encodeURIComponent(repo)}`) .then((res) => { if (!res.ok) throw new Error("Failed to fetch history"); return res.json(); }) .then((data: Generation[]) => { setGenerations(data); setLoading(false); }) .catch((err) => { setError(err.message); setLoading(false); }); }, [repo]); const toggleSelection = (id: string) => { const newSelected = new Set(selectedIds); if (newSelected.has(id)) { newSelected.delete(id); } else if (newSelected.size < 2) { newSelected.add(id); } setSelectedIds(newSelected); }; const handleCompare = async () => { if (selectedIds.size !== 2) return; const ids = Array.from(selectedIds); const gen1 = generations.find((g) => g.id === ids[0])!; const gen2 = generations.find((g) => g.id === ids[1])!; // Sort by date - older first const [olderGen, newerGen] = new Date(gen1.createdAt) < new Date(gen2.createdAt) ? [gen1, gen2] : [gen2, gen1]; setComparing(true); try { const [olderDocRes, newerDocRes] = await Promise.all([ fetch(`/api/docs/${olderGen.id}`), fetch(`/api/docs/${newerGen.id}`), ]); if (!olderDocRes.ok || !newerDocRes.ok) { throw new Error("Failed to fetch documentation"); } const [olderDoc, newerDoc] = await Promise.all([ olderDocRes.json(), newerDocRes.json(), ]); setLeftDoc(olderDoc); setRightDoc(newerDoc); setLeftGen(olderGen); setRightGen(newerGen); } catch (err) { setError(err instanceof Error ? err.message : "Failed to compare"); setComparing(false); } }; const closeComparison = () => { setLeftDoc(null); setRightDoc(null); setLeftGen(null); setRightGen(null); setComparing(false); }; if (loading) { return (
Loading history...
); } if (error) { return (
Error

{error}

Back to Home
); } if (!repo) { return (

No Repository Specified

Please provide a repository URL to view its history.

Back to Home
); } if (generations.length === 0) { return (

No History Found

No documentation has been generated for this repository yet.

Back to Home
); } if (generations.length === 1) { return (
Back to Home

Version History

{generations[0].repoName}

Only One Version Exists

Generate docs again after code changes to compare versions and track how your architecture evolves over time.

{generations[0].commitHash.slice(0, 7)}
{formatDate(generations[0].createdAt)}
Generated in {formatDuration(generations[0].duration)}
View Documentation
); } return (
Back to Home {selectedIds.size} of 2 selected

Version History

{generations[0]?.repoName}

Select any 2 versions to compare side-by-side

{generations.map((gen) => { const isSelected = selectedIds.has(gen.id); const canSelect = selectedIds.size < 2 || isSelected; return (
{gen.commitHash.slice(0, 7)} {formatDate(gen.createdAt)} {formatDuration(gen.duration)}
View
); })}
{selectedIds.size === 2 && (
)}
{leftDoc && rightDoc && leftGen && rightGen && ( )}
); } export default function HistoryPage() { return (
Loading...
} >
); }