+ {headerLeft}
+ {headerLeft && shouldShowFileTree && (
+
+ )}
+ {shouldShowFileTree && (
+ <>
+
+
+ >
+ )}
+ {prMetadata ? (
+
+
+
+ {displayRepo}
+
+
+
+
+
+
+
+
+
+ ) : repoInfo ? (
+
+ {repoInfo.branch && (
+
+ {repoInfo.branch}
+
+ )}
+
+
+ {repoInfo.display}
+
+
+ ) : (
+
Review
+ )}
+
+
+
+ {/* Diff style toggle */}
+
+
+
+
+
+ {origin ? (
+ <>
+ {/* Destination dropdown (PR mode only) */}
+ {prMetadata && (
+
+
+ {showDestinationMenu && (
+ <>
+
setShowDestinationMenu(false)} />
+
+
+
+
+
+ {altKey}
+ {altKey}
+ to toggle
+
+
+
+ >
+ )}
+
+ )}
+
+ {/* GitHub error message */}
+ {platformActionError && (
+
+ {platformActionError}
+
+ )}
+
+ {/* Agent mode: Close/SendFeedback flip + Approve */}
+ {!platformMode ? (
+
totalAnnotationCount > 0 ? setShowApproveWarning(true) : handleApprove()}
+ onExit={() => totalAnnotationCount > 0 ? setShowExitWarning(true) : handleExit()}
+ />
+ ) : (
+ <>
+ {/* Platform mode: Close + Post Comments + Approve */}
+ totalAnnotationCount > 0 ? setShowExitWarning(true) : handleExit()}
+ disabled={isSendingFeedback || isApproving || isExiting || isPlatformActioning}
+ isLoading={isExiting}
+ />
+ openPlatformDialog('comment')}
+ disabled={isSendingFeedback || isApproving || isPlatformActioning}
+ isLoading={isSendingFeedback || isPlatformActioning}
+ label="Post Comments"
+ shortLabel="Post"
+ loadingLabel="Posting..."
+ shortLoadingLabel="Posting..."
+ title="Post review to platform"
+ />
+
+
{
+ if (platformUser && prMetadata?.author === platformUser) return;
+ openPlatformDialog('approve');
+ }}
+ disabled={
+ isSendingFeedback || isApproving || isPlatformActioning ||
+ (!!platformUser && prMetadata?.author === platformUser)
+ }
+ isLoading={isApproving}
+ muted={!!platformUser && prMetadata?.author === platformUser && !isSendingFeedback && !isApproving && !isPlatformActioning}
+ title={
+ platformUser && prMetadata?.author === platformUser
+ ? `You can't approve your own ${mrLabel}`
+ : "Approve - no changes needed"
+ }
+ />
+ {platformUser && prMetadata?.author === platformUser && (
+
+
+
+ You can't approve your own {mrLabel === 'MR' ? 'merge request' : 'pull request'} on {platformLabel}.
+
+ )}
+
+ >
+ )}
+ >
+ ) : (
+
+ )}
+
+
+
+ setOpenSettingsMenu(true)}
+ onOpenExport={() => setShowExportModal(true)}
+ onToggleFileTree={() => setIsFileTreeOpen(prev => !prev)}
+ onToggleSidebar={() => reviewSidebar.isOpen ? reviewSidebar.close() : reviewSidebar.open()}
+ isFileTreeOpen={isFileTreeOpen}
+ isSidebarOpen={reviewSidebar.isOpen}
+ appVersion={appVersion}
+ />
+
+
+
+ {/* Sidebar tab toggles */}
+
+ {aiAvailable && (
+
+ )}
+ {agentJobs.capabilities?.available && (
+
+ )}
+
+
+
+ {/* Main content */}
+
+ {shouldShowFileTree && isFileTreeOpen && (
+ <>
+
f.path === allFilesVisibleFile) : undefined}
+ onSelectFile={handleFilePreview}
+ onDoubleClickFile={handleFilePinned}
+ annotations={allAnnotations}
+ viewedFiles={viewedFiles}
+ onToggleViewed={handleToggleViewed}
+ hideViewedFiles={hideViewedFiles}
+ onToggleHideViewed={() => setHideViewedFiles(prev => !prev)}
+ enableKeyboardNav={!showExportModal && hasSearchableFiles}
+ diffOptions={gitContext?.diffOptions}
+ activeDiffType={activeDiffBase}
+ onSelectDiff={handleDiffSwitch}
+ isLoadingDiff={isLoadingDiff}
+ width={fileTreeResize.width}
+ worktrees={gitContext?.worktrees}
+ activeWorktreePath={activeWorktreePath}
+ onSelectWorktree={handleWorktreeSwitch}
+ currentBranch={gitContext?.currentBranch}
+ availableBranches={prMetadata ? undefined : gitContext?.availableBranches}
+ selectedBase={prMetadata ? undefined : selectedBase ?? undefined}
+ detectedBase={prMetadata ? undefined : gitContext?.defaultBranch || gitContext?.compareTarget?.fallback}
+ onSelectBase={prMetadata ? undefined : handleBaseSelect}
+ compareTarget={gitContext?.compareTarget}
+ recentCommits={prMetadata ? undefined : gitContext?.recentCommits}
+ jjEvologs={prMetadata ? undefined : gitContext?.jjEvologs}
+ detectedEvoBase={prMetadata ? undefined : gitContext?.jjEvologs?.[1]?.commitId}
+ stagedFiles={stagedFiles}
+ onCopyRawDiff={handleCopyDiff}
+ canCopyRawDiff={!!diffData?.rawPatch}
+ copyRawDiffStatus={copyRawDiffStatus}
+ searchQuery={hasSearchableFiles ? searchQuery : ''}
+ isSearchOpen={hasSearchableFiles ? isSearchOpen : false}
+ isSearchPending={isSearchPending}
+ searchInputRef={hasSearchableFiles ? searchInputRef : undefined}
+ onOpenSearch={hasSearchableFiles ? openSearch : undefined}
+ onSearchChange={hasSearchableFiles ? handleSearchInputChange : undefined}
+ onSearchClear={hasSearchableFiles ? clearSearch : undefined}
+ onSearchClose={hasSearchableFiles ? closeSearch : undefined}
+ searchGroups={hasSearchableFiles ? searchGroups : []}
+ searchMatches={hasSearchableFiles ? searchMatches : []}
+ activeSearchMatchId={hasSearchableFiles ? activeSearchMatchId : null}
+ onSelectSearchMatch={hasSearchableFiles ? handleSelectSearchMatch : undefined}
+ onStepSearchMatch={hasSearchableFiles ? stepSearchMatch : undefined}
+ repoRoot={prMetadata ? null : (activeWorktreePath ?? agentCwd ?? gitContext?.cwd ?? null)}
+ />
+
+ >
+ )}
+
+ {/* Center dock area */}
+
+ {files.length > 0 ? (
+
+ ) : (
+
+
+
+ {diffError ? (
+
+ ) : (
+
+ )}
+
+
+ {diffError ? (
+ <>
+
Failed to load diff
+
{diffError}
+ >
+ ) : (
+ <>
+
No changes
+
+ {activeDiffBase === 'uncommitted' && `No uncommitted changes${activeWorktreePath ? ' in this worktree' : ' to review'}.`}
+ {activeDiffBase === 'staged' && "No staged changes. Stage some files with git add."}
+ {activeDiffBase === 'unstaged' && "No unstaged changes. All changes are staged."}
+ {activeDiffBase === 'last-commit' && `No changes in the last commit${activeWorktreePath ? ' in this worktree' : ''}.`}
+ {activeDiffBase === 'jj-current' && "No changes in the current jj change."}
+ {activeDiffBase === 'jj-last' && "No changes in the last jj change."}
+ {activeDiffBase === 'jj-line' && `No changes in your line of work vs ${selectedBase || gitContext?.defaultBranch || '@-'}.`}
+ {activeDiffBase === 'jj-evolog' && `No changes since evolution ${selectedBase ? selectedBase.slice(0, 8) : 'previous'} — the change looks the same as before.`}
+ {activeDiffBase === 'jj-all' && "No files at the current jj change."}
+ {activeDiffBase === 'branch' && `No changes vs ${selectedBase || gitContext?.defaultBranch || 'main'}${activeWorktreePath ? ' in this worktree' : ''}.`}
+ {activeDiffBase === 'merge-base' && `No changes vs ${selectedBase || gitContext?.defaultBranch || 'main'}${activeWorktreePath ? ' in this worktree' : ''}.`}
+ {activeDiffBase === 'all' && `No tracked files${activeWorktreePath ? ' in this worktree' : ' in this repository'}.`}
+
+ >
+ )}
+
+ {gitContext?.diffOptions && gitContext.diffOptions.length > 1 && (
+
+ Try selecting a different view from the dropdown.
+
+ )}
+
+
+ )}
+
+
+ {/* Resize Handle + Sidebar */}
+ {reviewSidebar.isOpen && (
+ <>
+
+
+ >
+ )}
+
+
+ {/* Export Modal */}
+ {showExportModal && (
+
+
+
+
Export Review Feedback
+
+
+
+
+ {allAnnotations.length} annotation{allAnnotations.length !== 1 ? 's' : ''}
+
+
+ {feedbackMarkdown}
+
+
+
+
+
+
+
+ )}
+
+
+ {}}
+ onIdentityChange={handleIdentityChange}
+ origin={origin}
+ mode="review"
+ aiProviders={aiProviders}
+ gitUser={gitUser}
+ externalOpen={openSettingsMenu}
+ onExternalClose={() => setOpenSettingsMenu(false)}
+ />
+
+
+ {/* Worktree info dialog */}
+ {(gitContext?.cwd || agentCwd) && prMetadata && (
+
setShowWorktreeDialog(false)}
+ title="Local Worktree"
+ wide
+ message={
+
+
This PR is checked out locally so review agents have full file access.
+
+ Path
+
+
+
Automatically removed when this review session ends.
+
+ }
+ variant="info"
+ />
+ )}
+
+ {/* No annotations dialog */}
+ setShowNoAnnotationsDialog(false)}
+ title="No Annotations"
+ message="You haven't made any annotations yet. There's nothing to copy."
+ variant="info"
+ />
+
+ {/* Approve with annotations warning */}
+ setShowApproveWarning(false)}
+ onConfirm={() => {
+ setShowApproveWarning(false);
+ handleApprove();
+ }}
+ title="Annotations Won't Be Sent"
+ message={<>You have {totalAnnotationCount} annotation{totalAnnotationCount !== 1 ? 's' : ''} that will be lost if you approve.>}
+ subMessage="To send your feedback, use Send Feedback instead."
+ confirmText="Approve Anyway"
+ cancelText="Cancel"
+ variant="warning"
+ showCancel
+ />
+
+ setShowExitWarning(false)}
+ onConfirm={() => {
+ setShowExitWarning(false);
+ handleExit();
+ }}
+ title="Annotations Won't Be Sent"
+ message={<>You have {totalAnnotationCount} annotation{totalAnnotationCount !== 1 ? 's' : ''} that will be lost if you close.>}
+ subMessage="To send your feedback, use Send Feedback instead."
+ confirmText="Close Anyway"
+ cancelText="Cancel"
+ variant="warning"
+ showCancel
+ />
+
+ {/* AI setup dialog — first-run only */}
+ {
+ setShowAISetup(false);
+ handleAIConfigChange({ providerId });
+ }}
+ />
+
+ {/* Diff type setup dialog — first-run only */}
+ {showDiffTypeSetup && (
+ {
+ setShowDiffTypeSetup(false);
+ if (selected !== diffType) handleDiffSwitch(selected);
+ }}
+ />
+ )}
+
+ {/* Completion overlay - shown after approve/feedback/exit */}
+
+
+ {/* Update notification */}
+
+
+ {/* GitHub general comment dialog */}
+ {
+ setPlatformOpenPR(checked);
+ storage.setItem('plannotator-platform-open-pr', String(checked));
+ }}
+ onConfirm={() => {
+ if (!platformCommentDialog) return;
+ handlePlatformAction(platformCommentDialog.action, platformCommentDialog.plan, platformGeneralComment);
+ }}
+ onCancel={() => setPlatformCommentDialog(null)}
+ isSubmitting={isPlatformActioning}
+ mrLabel={mrLabel}
+ platformLabel={platformLabel}
+ />
+
+
+ {/* Tour dialog overlay */}
+