({
workspaceId,
workspacePath,
projectPath,
onReviewNote,
focusTrigger,
isCreating = false,
onStatsChange,
isTouchImmersive = false,
onTouchImmersiveChange,
})
| 780 | }; |
| 781 | |
| 782 | export const ReviewPanel: React.FC<ReviewPanelProps> = ({ |
| 783 | workspaceId, |
| 784 | workspacePath, |
| 785 | projectPath, |
| 786 | onReviewNote, |
| 787 | focusTrigger, |
| 788 | isCreating = false, |
| 789 | onStatsChange, |
| 790 | isTouchImmersive = false, |
| 791 | onTouchImmersiveChange, |
| 792 | }) => { |
| 793 | const originFetchRef = useRef<OriginFetchState | null>(null); |
| 794 | const { api } = useAPI(); |
| 795 | const { theme } = useTheme(); |
| 796 | const { workspaceMetadata } = useWorkspaceMetadata(); |
| 797 | const panelRef = useRef<HTMLDivElement>(null); |
| 798 | const scrollContainerRef = useRef<HTMLDivElement>(null); |
| 799 | const searchInputRef = useRef<HTMLInputElement>(null); |
| 800 | |
| 801 | useEffect(() => { |
| 802 | // Review is a code-heavy surface; warm the worker/highlighter during diff loading |
| 803 | // so immersive mode does not reveal plain text before Shiki is ready. |
| 804 | preloadHighlightedDiff({ |
| 805 | content: "+const muxReviewSyntaxWarmup = true;", |
| 806 | filePath: "review-warmup.ts", |
| 807 | themeMode: theme, |
| 808 | }).catch(() => undefined); |
| 809 | }, [theme]); |
| 810 | |
| 811 | // Unified diff state - discriminated union makes invalid states unrepresentable |
| 812 | // Note: Parent renders with key={workspaceId}, so component remounts on workspace change. |
| 813 | const [diffState, setDiffState] = useState<DiffState>({ status: "loading" }); |
| 814 | |
| 815 | const selectedHunkStorageKey = getReviewSelectedHunkKey(workspaceId); |
| 816 | // Keep hunk selection local during navigation; persisting every J/K step writes |
| 817 | // localStorage synchronously and dominates large immersive-review iteration. |
| 818 | const [selectedHunkId, setSelectedHunkId] = useState<string | null>(() => |
| 819 | readPersistedState(selectedHunkStorageKey, null) |
| 820 | ); |
| 821 | const [isLoadingTree, setIsLoadingTree] = useState(true); |
| 822 | const [diagnosticInfo, setDiagnosticInfo] = useState<DiagnosticInfo | null>(null); |
| 823 | const [isPanelFocused, setIsPanelFocused] = useState(false); |
| 824 | const [refreshTrigger, setRefreshTrigger] = useState(0); |
| 825 | const [fileTree, setFileTree] = useState<FileTreeNode | null>(null); |
| 826 | |
| 827 | // Map of hunkId -> toggle function for expand/collapse |
| 828 | const toggleExpandFnsRef = useRef<Map<string, () => void>>(new Map()); |
| 829 | |
| 830 | // Ref to hold current filteredHunks for use in navigation callbacks. |
| 831 | // Avoids needing filteredHunks as a dependency (which changes frequently). |
| 832 | const filteredHunksRef = useRef<DiffHunk[]>([]); |
| 833 | |
| 834 | // Track refresh trigger changes so we can distinguish initial mount vs manual refresh. |
| 835 | // Each effect gets its own ref to avoid cross-effect interference. |
| 836 | const lastDiffRefreshTriggerRef = useRef<number | null>(null); |
| 837 | const lastFileTreeRefreshTriggerRef = useRef<number | null>(null); |
| 838 | |
| 839 | // Check if tools completed while we were unmounted - skip cache on initial mount if so. |
nothing calls this directly
no test coverage detected