From 2430aeca7ccffd5f8bbb5227e58bf36948ea08d8 Mon Sep 17 00:00:00 2001 From: delibae Date: Sun, 22 Feb 2026 04:02:42 +0900 Subject: [PATCH] feat: implement history review feature with popover UI - Add Popover component for displaying history in a popover. - Integrate history review functionality in the LatexEditor component. - Create HistoryDiffView for displaying differences between snapshots. - Update HistoryPanel to manage linear history and snapshot restoration. - Refactor sidebar to remove direct history panel integration. - Enhance PDF preview with history button to access history panel. - Update history store to manage reviewing snapshots and related actions. --- apps/desktop/src/components/ui/popover.tsx | 40 ++ .../workspace/editor/latex-editor.tsx | 343 +++++++++++++- .../components/workspace/history-panel.tsx | 432 +++++------------- .../workspace/preview/pdf-preview.tsx | 39 +- .../src/components/workspace/sidebar.tsx | 15 - apps/desktop/src/stores/history-store.ts | 13 + 6 files changed, 537 insertions(+), 345 deletions(-) create mode 100644 apps/desktop/src/components/ui/popover.tsx diff --git a/apps/desktop/src/components/ui/popover.tsx b/apps/desktop/src/components/ui/popover.tsx new file mode 100644 index 0000000..9a829e0 --- /dev/null +++ b/apps/desktop/src/components/ui/popover.tsx @@ -0,0 +1,40 @@ +import * as React from "react"; +import { Popover as PopoverPrimitive } from "radix-ui"; + +import { cn } from "@/lib/utils"; + +function Popover({ + ...props +}: React.ComponentProps) { + return ; +} + +function PopoverTrigger({ + ...props +}: React.ComponentProps) { + return ; +} + +function PopoverContent({ + className, + align = "center", + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +export { Popover, PopoverTrigger, PopoverContent }; diff --git a/apps/desktop/src/components/workspace/editor/latex-editor.tsx b/apps/desktop/src/components/workspace/editor/latex-editor.tsx index e3f8f29..190101f 100644 --- a/apps/desktop/src/components/workspace/editor/latex-editor.tsx +++ b/apps/desktop/src/components/workspace/editor/latex-editor.tsx @@ -31,11 +31,26 @@ import { linter, lintGutter, forEachDiagnostic, type Diagnostic } from "@codemir import { useDocumentStore, type ProjectFile } from "@/stores/document-store"; import { useProposedChangesStore, type ProposedChange } from "@/stores/proposed-changes-store"; import { useClaudeChatStore } from "@/stores/claude-chat-store"; -import { useHistoryStore } from "@/stores/history-store"; +import { useHistoryStore, type FileDiff } from "@/stores/history-store"; import { compileLatex } from "@/lib/latex-compiler"; import { EditorToolbar } from "./editor-toolbar"; import { SelectionToolbar, type ToolbarAction } from "./selection-toolbar"; -import { SpellCheckIcon } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/components/ui/dialog"; +import { + SpellCheckIcon, + RotateCcwIcon, + TagIcon, + CopyIcon, + XIcon, +} from "lucide-react"; import { ClaudeChatDrawer } from "@/components/claude-chat/claude-chat-drawer"; import { ProposedChangesPanel } from "@/components/claude-chat/proposed-changes-panel"; import { ImagePreview } from "./image-preview"; @@ -72,6 +87,10 @@ export function LatexEditor() { const isTextFile = activeFile?.type === "tex" || activeFile?.type === "bib" || activeFile?.type === "style" || activeFile?.type === "other"; const activeFileContent = activeFile?.content; + // History review state + const reviewingSnapshot = useHistoryStore((s) => s.reviewingSnapshot); + const historyDiffResult = useHistoryStore((s) => s.diffResult); + const [imageScale, setImageScale] = useState(1.0); const [isSearchOpen, setIsSearchOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(""); @@ -599,6 +618,35 @@ export function LatexEditor() { setSelectionCoords(null); }, []); + // History review action handlers + const handleHistoryRestore = useCallback(async () => { + if (!reviewingSnapshot || !projectRoot) return; + useHistoryStore.getState().stopReview(); + await useHistoryStore.getState().restoreSnapshot(projectRoot, reviewingSnapshot.id); + await useDocumentStore.getState().openProject(projectRoot); + await useHistoryStore.getState().loadSnapshots(projectRoot); + }, [reviewingSnapshot, projectRoot]); + + const [historyLabelDialogOpen, setHistoryLabelDialogOpen] = useState(false); + const [historyLabelValue, setHistoryLabelValue] = useState(""); + + const handleHistoryAddLabel = useCallback(async () => { + const label = historyLabelValue.trim(); + if (!label || !reviewingSnapshot || !projectRoot) return; + await useHistoryStore.getState().addLabel(projectRoot, reviewingSnapshot.id, label); + setHistoryLabelDialogOpen(false); + setHistoryLabelValue(""); + }, [reviewingSnapshot, projectRoot, historyLabelValue]); + + const handleHistoryCopySha = useCallback(() => { + if (!reviewingSnapshot) return; + navigator.clipboard.writeText(reviewingSnapshot.id); + }, [reviewingSnapshot]); + + const handleHistoryClose = useCallback(() => { + useHistoryStore.getState().stopReview(); + }, []); + if (activeFile?.type === "pdf") { return ; } @@ -629,8 +677,42 @@ export function LatexEditor() { currentMatch={currentMatch} /> )} + {/* History review bar */} + {reviewingSnapshot && ( +
+
+ + Reviewing history + + {reviewingSnapshot.message.replace(/^\[.*?\]\s*/, "")} · {reviewingSnapshot.id.slice(0, 7)} + +
+
+ + + +
+ +
+
+ )}
-
+
+ {/* History diff overlay */} + {reviewingSnapshot && historyDiffResult && ( + + )} {/* Selection toolbar */} {toolbarPosition && selectionLabel && !isMergeActiveRef.current && ( @@ -720,6 +802,27 @@ export function LatexEditor() { onUndo={() => handleUndoAllRef.current()} /> )} + {/* History label dialog */} + + + + Add Label + +
+ setHistoryLabelValue(e.target.value)} + onKeyDown={(e) => { if (e.key === "Enter") handleHistoryAddLabel(); }} + autoFocus + /> +
+ + + + +
+
); } @@ -781,3 +884,237 @@ function InlinePdfViewer({
); } + +// ─── History Diff View (git-diff style combined view) ─── + +function HistoryDiffView({ diffs }: { diffs: FileDiff[] }) { + return ( +
+ {diffs.map((diff) => ( +
+ {/* File header */} +
+ + {diff.status === "added" ? "+" : diff.status === "deleted" ? "−" : "~"} + + {diff.file_path} + ({diff.status}) +
+ {/* Diff lines */} + +
+ ))} + {diffs.length === 0 && ( +
+ No changes in this snapshot +
+ )} +
+ ); +} + +function DiffLines({ diff }: { diff: FileDiff }) { + const oldLines = diff.old_content?.split("\n") ?? []; + const newLines = diff.new_content?.split("\n") ?? []; + + if (diff.status === "added") { + return ( +
+ {newLines.map((line, i) => ( +
+ {i + 1} + + + {line || " "} +
+ ))} +
+ ); + } + + if (diff.status === "deleted") { + return ( +
+ {oldLines.map((line, i) => ( +
+ {i + 1} + + {line || " "} +
+ ))} +
+ ); + } + + // Modified: compute unified diff with context + const hunks = computeUnifiedHunks(oldLines, newLines, 3); + + return ( +
+ {hunks.map((hunk, hi) => ( +
+ {/* Hunk header */} +
+ @@ -{hunk.oldStart},{hunk.oldCount} +{hunk.newStart},{hunk.newCount} @@ +
+ {hunk.lines.map((line, li) => ( +
+ + {line.type !== "add" ? line.oldNum : ""} + + + {line.type !== "del" ? line.newNum : ""} + + + {line.type === "del" ? "−" : line.type === "add" ? "+" : " "} + + + {line.text || " "} + +
+ ))} +
+ ))} +
+ ); +} + +interface DiffLine { + type: "ctx" | "del" | "add"; + text: string; + oldNum?: number; + newNum?: number; +} + +interface Hunk { + oldStart: number; + oldCount: number; + newStart: number; + newCount: number; + lines: DiffLine[]; +} + +function computeUnifiedHunks(oldLines: string[], newLines: string[], context: number): Hunk[] { + // Simple line-by-line diff to find changed regions + const ops: { type: "eq" | "del" | "add"; oldIdx?: number; newIdx?: number; text: string }[] = []; + let i = 0; + let j = 0; + + while (i < oldLines.length || j < newLines.length) { + if (i < oldLines.length && j < newLines.length && oldLines[i] === newLines[j]) { + ops.push({ type: "eq", oldIdx: i, newIdx: j, text: oldLines[i] }); + i++; + j++; + } else { + // Find the next matching line + let foundOld = -1; + let foundNew = -1; + const searchLimit = Math.min(50, Math.max(oldLines.length - i, newLines.length - j)); + for (let look = 1; look <= searchLimit; look++) { + if (i + look < oldLines.length && j < newLines.length && oldLines[i + look] === newLines[j]) { + foundOld = i + look; + break; + } + if (j + look < newLines.length && i < oldLines.length && newLines[j + look] === oldLines[i]) { + foundNew = j + look; + break; + } + } + + if (foundOld >= 0) { + // Delete lines from old until match + while (i < foundOld) { + ops.push({ type: "del", oldIdx: i, text: oldLines[i] }); + i++; + } + } else if (foundNew >= 0) { + // Add lines from new until match + while (j < foundNew) { + ops.push({ type: "add", newIdx: j, text: newLines[j] }); + j++; + } + } else { + // No match found nearby, emit del+add + if (i < oldLines.length) { + ops.push({ type: "del", oldIdx: i, text: oldLines[i] }); + i++; + } + if (j < newLines.length) { + ops.push({ type: "add", newIdx: j, text: newLines[j] }); + j++; + } + } + } + } + + // Group into hunks with context lines + const changedIndices = new Set(); + ops.forEach((op, idx) => { + if (op.type !== "eq") { + for (let c = Math.max(0, idx - context); c <= Math.min(ops.length - 1, idx + context); c++) { + changedIndices.add(c); + } + } + }); + + const hunks: Hunk[] = []; + let currentHunk: Hunk | null = null; + + for (let idx = 0; idx < ops.length; idx++) { + if (!changedIndices.has(idx)) { + if (currentHunk) { + hunks.push(currentHunk); + currentHunk = null; + } + continue; + } + + const op = ops[idx]; + if (!currentHunk) { + const oldStart = op.type !== "add" ? (op.oldIdx ?? 0) + 1 : (ops[idx + 1]?.oldIdx ?? 0) + 1; + const newStart = op.type !== "del" ? (op.newIdx ?? 0) + 1 : (ops[idx + 1]?.newIdx ?? 0) + 1; + currentHunk = { oldStart, oldCount: 0, newStart, newCount: 0, lines: [] }; + } + + if (op.type === "eq") { + currentHunk.lines.push({ type: "ctx", text: op.text, oldNum: (op.oldIdx ?? 0) + 1, newNum: (op.newIdx ?? 0) + 1 }); + currentHunk.oldCount++; + currentHunk.newCount++; + } else if (op.type === "del") { + currentHunk.lines.push({ type: "del", text: op.text, oldNum: (op.oldIdx ?? 0) + 1 }); + currentHunk.oldCount++; + } else { + currentHunk.lines.push({ type: "add", text: op.text, newNum: (op.newIdx ?? 0) + 1 }); + currentHunk.newCount++; + } + } + if (currentHunk) hunks.push(currentHunk); + + return hunks; +} diff --git a/apps/desktop/src/components/workspace/history-panel.tsx b/apps/desktop/src/components/workspace/history-panel.tsx index 0ab8ebc..9fd3827 100644 --- a/apps/desktop/src/components/workspace/history-panel.tsx +++ b/apps/desktop/src/components/workspace/history-panel.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState, useCallback } from "react"; +import { useEffect, useRef, useState, useCallback, useMemo } from "react"; import { HistoryIcon, LoaderIcon, @@ -7,11 +7,8 @@ import { CopyIcon, PlusIcon, XIcon, - FileTextIcon, - ChevronDownIcon, - ChevronRightIcon, } from "lucide-react"; -import { useHistoryStore, type SnapshotInfo, type FileDiff } from "@/stores/history-store"; +import { useHistoryStore, type SnapshotInfo } from "@/stores/history-store"; import { useDocumentStore } from "@/stores/document-store"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; @@ -62,61 +59,54 @@ function snapshotTypeBadgeColor(message: string): string { return "bg-muted text-muted-foreground"; } -function diffStatusColor(status: string): string { - if (status === "added") return "text-green-600 dark:text-green-400"; - if (status === "deleted") return "text-red-600 dark:text-red-400"; - return "text-blue-600 dark:text-blue-400"; -} - -function diffStatusPrefix(status: string): string { - if (status === "added") return "+"; - if (status === "deleted") return "−"; - return "~"; -} - -// ─── Header (rendered by Sidebar) ─── - -export function HistoryHeader() { - const isLoading = useHistoryStore((s) => s.isLoading); - const projectRoot = useDocumentStore((s) => s.projectRoot); - const loadSnapshots = useHistoryStore((s) => s.loadSnapshots); - - return ( -
-
- - History -
- -
- ); -} // ─── Panel ─── -export function HistoryPanel() { +export function HistoryPanel({ maxHeight }: { maxHeight?: string }) { const projectRoot = useDocumentStore((s) => s.projectRoot); const snapshots = useHistoryStore((s) => s.snapshots); const isLoading = useHistoryStore((s) => s.isLoading); const isRestoring = useHistoryStore((s) => s.isRestoring); - const diffResult = useHistoryStore((s) => s.diffResult); - const isDiffLoading = useHistoryStore((s) => s.isDiffLoading); + const reviewingSnapshot = useHistoryStore((s) => s.reviewingSnapshot); const init = useHistoryStore((s) => s.init); const loadSnapshots = useHistoryStore((s) => s.loadSnapshots); const loadMoreSnapshots = useHistoryStore((s) => s.loadMoreSnapshots); const loadDiff = useHistoryStore((s) => s.loadDiff); + const startReview = useHistoryStore((s) => s.startReview); const restoreSnapshot = useHistoryStore((s) => s.restoreSnapshot); const addLabel = useHistoryStore((s) => s.addLabel); const removeLabel = useHistoryStore((s) => s.removeLabel); const openProject = useDocumentStore((s) => s.openProject); - const [expandedId, setExpandedId] = useState(null); + // Compute linear history: when a [restore] snapshot appears, + // skip all snapshots between it and the restored target + const linearSnapshots = useMemo(() => { + const result: SnapshotInfo[] = []; + let skipUntilSha: string | null = null; + + for (const snap of snapshots) { + if (skipUntilSha) { + // Skip until we find the snapshot that was restored to + if (snap.id.startsWith(skipUntilSha)) { + skipUntilSha = null; + result.push(snap); + } + continue; + } + + result.push(snap); + + // If this is a restore snapshot, extract the target SHA and start skipping + if (snap.message.startsWith("[restore]")) { + const match = snap.message.match(/Restored to ([a-f0-9]+)/); + if (match) { + skipUntilSha = match[1]; + } + } + } + return result; + }, [snapshots]); + const [labelDialogOpen, setLabelDialogOpen] = useState(false); const [labelTargetId, setLabelTargetId] = useState(null); const [labelValue, setLabelValue] = useState(""); @@ -137,33 +127,37 @@ export function HistoryPanel() { } }, [projectRoot, isLoading, loadMoreSnapshots]); - // Double-click to expand and load diff + // Double-click to show diff in editor const handleDoubleClick = useCallback( async (snap: SnapshotInfo) => { if (!projectRoot) return; - if (expandedId === snap.id) { - setExpandedId(null); + // Toggle off if already reviewing this snapshot + if (reviewingSnapshot?.id === snap.id) { + useHistoryStore.getState().stopReview(); return; } - setExpandedId(snap.id); - // Find parent snapshot (the one right after in the list) - const idx = snapshots.findIndex((s) => s.id === snap.id); - const parent = snapshots[idx + 1]; + // Find parent snapshot (the one right after in the linear list) + const idx = linearSnapshots.findIndex((s) => s.id === snap.id); + const parent = linearSnapshots[idx + 1]; if (parent) { await loadDiff(projectRoot, parent.id, snap.id); + startReview(snap); } }, - [projectRoot, snapshots, expandedId, loadDiff], + [projectRoot, linearSnapshots, reviewingSnapshot, loadDiff, startReview], ); const handleRestore = useCallback( async (snapshotId: string) => { if (!projectRoot) return; + // Stop any active review + useHistoryStore.getState().stopReview(); await restoreSnapshot(projectRoot, snapshotId); - // Re-open project to fully reload all file contents into editor + // Re-open project and reload snapshot list await openProject(projectRoot); + await loadSnapshots(projectRoot); }, - [projectRoot, restoreSnapshot, openProject], + [projectRoot, restoreSnapshot, openProject, loadSnapshots], ); const handleAddLabel = useCallback(async () => { @@ -190,26 +184,38 @@ export function HistoryPanel() { } return ( -
+
+ {/* Header */} +
+
+ + History +
+ +
- {snapshots.length === 0 && !isLoading ? ( + {linearSnapshots.length === 0 && !isLoading ? (
No history yet
) : (
- {snapshots.map((snap) => ( + {linearSnapshots.map((snap) => ( handleDoubleClick(snap)} onRestore={() => handleRestore(snap.id)} onAddLabel={() => openLabelDialog(snap.id)} @@ -256,10 +262,8 @@ export function HistoryPanel() { function SnapshotRow({ snapshot, - isExpanded, + isSelected, isRestoring, - diffResult, - isDiffLoading, onDoubleClick, onRestore, onAddLabel, @@ -267,10 +271,8 @@ function SnapshotRow({ onCopySha, }: { snapshot: SnapshotInfo; - isExpanded: boolean; + isSelected: boolean; isRestoring: boolean; - diffResult: FileDiff[] | null; - isDiffLoading: boolean; onDoubleClick: () => void; onRestore: () => void; onAddLabel: () => void; @@ -282,93 +284,57 @@ function SnapshotRow({ return ( -
- - - ))} -
- )} - - {/* Changed files summary */} - {hasFiles && ( -
- {isExpanded ? ( - - ) : ( - - )} - - {snapshot.changed_files.map((f) => f.split("/").pop()).join(", ")} - -
- )} -
- - - {/* Expanded diff view */} - {isExpanded && hasFiles && ( -
- {isDiffLoading ? ( -
- - Loading diff... -
- ) : diffResult ? ( -
- {diffResult.map((diff) => ( - - ))} -
- ) : ( -
- {snapshot.changed_files.map((filePath) => ( -
- - {filePath} -
- ))} -
- )} -
+ + + ))} +
+ )} + + {/* Changed files summary */} + {hasFiles && ( +
+ {snapshot.changed_files.map((f) => f.split("/").pop()).join(", ")} +
+ )} +
+ @@ -389,165 +355,3 @@ function SnapshotRow({ ); } -// ─── Diff File Row ─── - -function DiffFileRow({ diff }: { diff: FileDiff }) { - const [expanded, setExpanded] = useState(false); - const fileName = diff.file_path.split("/").pop() || diff.file_path; - - return ( -
- - - {expanded && ( -
- {renderInlineDiff(diff)} -
- )} -
- ); -} - -// ─── Inline Diff Renderer ─── - -function renderInlineDiff(diff: FileDiff) { - const oldLines = diff.old_content?.split("\n") ?? []; - const newLines = diff.new_content?.split("\n") ?? []; - - if (diff.status === "added") { - return ( -
- {newLines.slice(0, 50).map((line, i) => ( -
- +{line} -
- ))} - {newLines.length > 50 && ( -
... {newLines.length - 50} more lines
- )} -
- ); - } - - if (diff.status === "deleted") { - return ( -
- {oldLines.slice(0, 50).map((line, i) => ( -
- {line} -
- ))} - {oldLines.length > 50 && ( -
... {oldLines.length - 50} more lines
- )} -
- ); - } - - // Modified: simple line-by-line comparison - const maxLen = Math.max(oldLines.length, newLines.length); - const diffLines: { type: "ctx" | "del" | "add"; text: string }[] = []; - let i = 0; - let j = 0; - - // Simple LCS-like comparison: show removed then added for changed regions - while (i < oldLines.length || j < newLines.length) { - if (i < oldLines.length && j < newLines.length && oldLines[i] === newLines[j]) { - diffLines.push({ type: "ctx", text: oldLines[i] }); - i++; - j++; - } else { - // Collect differing lines - const startI = i; - const startJ = j; - // Advance until we find a common line or exhaust both - while (i < oldLines.length && j < newLines.length && oldLines[i] !== newLines[j]) { - i++; - j++; - } - // If still not matching, try to find next match - if (i < oldLines.length && j < newLines.length) { - // Both advanced same amount, output as changes - } - for (let k = startI; k < i; k++) { - diffLines.push({ type: "del", text: oldLines[k] }); - } - for (let k = startJ; k < j; k++) { - diffLines.push({ type: "add", text: newLines[k] }); - } - if (i >= oldLines.length && j < newLines.length) { - while (j < newLines.length) { - diffLines.push({ type: "add", text: newLines[j] }); - j++; - } - } - if (j >= newLines.length && i < oldLines.length) { - while (i < oldLines.length) { - diffLines.push({ type: "del", text: oldLines[i] }); - i++; - } - } - } - if (diffLines.length > 100) break; - } - - // Trim to show only changed regions with context - const relevant: typeof diffLines = []; - const CONTEXT = 2; - const changedIndices = new Set(); - diffLines.forEach((line, idx) => { - if (line.type !== "ctx") { - for (let c = Math.max(0, idx - CONTEXT); c <= Math.min(diffLines.length - 1, idx + CONTEXT); c++) { - changedIndices.add(c); - } - } - }); - - let lastShown = -1; - for (let idx = 0; idx < diffLines.length; idx++) { - if (changedIndices.has(idx)) { - if (lastShown >= 0 && idx - lastShown > 1) { - relevant.push({ type: "ctx", text: "···" }); - } - relevant.push(diffLines[idx]); - lastShown = idx; - } - } - - if (relevant.length === 0) { - return
No visible changes
; - } - - return ( -
- {relevant.map((line, i) => ( -
- - {line.type === "del" ? "−" : line.type === "add" ? "+" : " "} - - {line.text} -
- ))} -
- ); -} diff --git a/apps/desktop/src/components/workspace/preview/pdf-preview.tsx b/apps/desktop/src/components/workspace/preview/pdf-preview.tsx index ec3cceb..b7b7eae 100644 --- a/apps/desktop/src/components/workspace/preview/pdf-preview.tsx +++ b/apps/desktop/src/components/workspace/preview/pdf-preview.tsx @@ -8,6 +8,7 @@ import { MinusIcon, PlusIcon, DownloadIcon, + HistoryIcon, MousePointerClickIcon, } from "lucide-react"; import { useDocumentStore } from "@/stores/document-store"; @@ -20,6 +21,8 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover"; +import { HistoryPanel } from "@/components/workspace/history-panel"; import { compileLatex, synctexEdit } from "@/lib/latex-compiler"; import { SelectionToolbar, type ToolbarAction } from "@/components/workspace/editor/selection-toolbar"; import type { PdfTextSelection } from "./pdf-viewer"; @@ -461,19 +464,29 @@ export function PdfPreview() { )}
- {pdfData && ( -
- {numPages} {numPages === 1 ? "page" : "pages"} - - - -
- -
- )} +
+ {pdfData && ( + <> + {numPages} {numPages === 1 ? "page" : "pages"} + + + +
+ + + )} + + + + + + + + +
{renderContent()} {/* PDF selection toolbar */} diff --git a/apps/desktop/src/components/workspace/sidebar.tsx b/apps/desktop/src/components/workspace/sidebar.tsx index e10a68b..d2a6ffd 100644 --- a/apps/desktop/src/components/workspace/sidebar.tsx +++ b/apps/desktop/src/components/workspace/sidebar.tsx @@ -38,7 +38,6 @@ import { useTheme } from "next-themes"; import { useDocumentStore, type ProjectFile } from "@/stores/document-store"; import { cn } from "@/lib/utils"; import { ZoteroPanel, ZoteroHeader } from "@/components/workspace/zotero-panel"; -import { HistoryPanel, HistoryHeader } from "@/components/workspace/history-panel"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -662,20 +661,6 @@ export function Sidebar() { - {/* History */} - -
-
- -
-
- -
-
-
- - - {/* Zotero */}
diff --git a/apps/desktop/src/stores/history-store.ts b/apps/desktop/src/stores/history-store.ts index ec93265..0dd3135 100644 --- a/apps/desktop/src/stores/history-store.ts +++ b/apps/desktop/src/stores/history-store.ts @@ -25,6 +25,7 @@ interface HistoryState { diffResult: FileDiff[] | null; isDiffLoading: boolean; isRestoring: boolean; + reviewingSnapshot: SnapshotInfo | null; init: (projectRoot: string) => Promise; createSnapshot: (projectRoot: string, message: string) => Promise; @@ -36,6 +37,8 @@ interface HistoryState { restoreSnapshot: (projectRoot: string, snapshotId: string) => Promise; addLabel: (projectRoot: string, snapshotId: string, label: string) => Promise; removeLabel: (projectRoot: string, label: string) => Promise; + startReview: (snapshot: SnapshotInfo) => void; + stopReview: () => void; reset: () => void; } @@ -48,6 +51,15 @@ export const useHistoryStore = create()((set, get) => ({ diffResult: null, isDiffLoading: false, isRestoring: false, + reviewingSnapshot: null, + + startReview: (snapshot) => { + set({ reviewingSnapshot: snapshot }); + }, + + stopReview: () => { + set({ reviewingSnapshot: null, diffResult: null }); + }, init: async (projectRoot) => { await invoke("history_init", { projectRoot }); @@ -165,5 +177,6 @@ export const useHistoryStore = create()((set, get) => ({ diffResult: null, isDiffLoading: false, isRestoring: false, + reviewingSnapshot: null, }), }));