refactor(surface): drop the deprecated main-tab aliases and dead diagram surface
MainTab/activeMainTab/setActiveMainTab/setMainTabGuard were deprecated mirrors of the surface names — every call site now uses activeSurface/setActiveSurface/setSurfaceGuard directly and the aliases are gone, including the persisted mirror field. The 'diagram' surface had no way to open it (navigateToDiagram had no callers except a .drawio attachment click that navigated to a surface nothing rendered); the surface, DiagramView, and its store plumbing are removed, and a .drawio attachment now opens in the file panel. ?tab= deep links map to the matching context-panel surface instead of setting a main-area surface nothing renders, and a persisted non-chat surface can no longer rehydrate into a blank main area.
This commit is contained in:
@@ -28,7 +28,7 @@ export function ArchiveView(): React.ReactNode {
|
||||
const { t } = useI18n();
|
||||
const open = useUIStore((state) => state.isArchivePageOpen);
|
||||
const setOpen = useUIStore((state) => state.setArchivePageOpen);
|
||||
const setActiveMainTab = useUIStore((state) => state.setActiveMainTab);
|
||||
const setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
||||
const setCurrentSession = useSessionUIStore((state) => state.setCurrentSession);
|
||||
const unarchiveSession = useSessionUIStore((state) => state.unarchiveSession);
|
||||
const homeDirectory = useDirectoryStore((state) => state.homeDirectory);
|
||||
@@ -86,9 +86,9 @@ export function ArchiveView(): React.ReactNode {
|
||||
const openSession = React.useCallback((session: Session) => {
|
||||
const directory = normalizePath(resolveGlobalSessionDirectory(session));
|
||||
setCurrentSession(session.id, directory ?? undefined);
|
||||
setActiveMainTab('chat');
|
||||
setActiveSurface('chat');
|
||||
setOpen(false);
|
||||
}, [setActiveMainTab, setCurrentSession, setOpen]);
|
||||
}, [setActiveSurface, setCurrentSession, setOpen]);
|
||||
|
||||
const restoreSession = React.useCallback((session: Session) => {
|
||||
void unarchiveSession(session.id).then((success) => {
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { DiagramEditor, type DiagramEditorHandle } from '@/components/diagram';
|
||||
import { Icon } from '@/components/icon/Icon';
|
||||
|
||||
export function DiagramView() {
|
||||
const { t } = useI18n();
|
||||
const { files } = useRuntimeAPIs();
|
||||
|
||||
const [filePath, setFilePath] = React.useState<string | null>(null);
|
||||
const [xml, setXml] = React.useState('');
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const editorRef = React.useRef<DiagramEditorHandle>(null);
|
||||
const pendingDiagramFile = useUIStore((state) => state.pendingDiagramFile);
|
||||
|
||||
const loadFile = React.useCallback(async (path: string) => {
|
||||
setLoading(true);
|
||||
setFilePath(path);
|
||||
try {
|
||||
const result = await files?.readFile?.(path);
|
||||
if (result) {
|
||||
setXml(result.content);
|
||||
}
|
||||
} catch {
|
||||
setXml('');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [files]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!pendingDiagramFile) {
|
||||
return;
|
||||
}
|
||||
const pending = useUIStore.getState().consumePendingDiagramFile();
|
||||
if (pending) {
|
||||
void loadFile(pending);
|
||||
}
|
||||
}, [loadFile, pendingDiagramFile]);
|
||||
|
||||
const saveDiagram = React.useCallback(async () => {
|
||||
const newXml = editorRef.current?.getXml();
|
||||
if (filePath && files?.writeFile && newXml && newXml !== xml) {
|
||||
await files.writeFile(filePath, newXml);
|
||||
setXml(newXml);
|
||||
}
|
||||
}, [filePath, files, xml]);
|
||||
|
||||
const fileName = filePath ? filePath.split('/').pop() || filePath : '';
|
||||
|
||||
if (!filePath) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center p-3">
|
||||
<div className="typography-ui text-muted-foreground">
|
||||
{t('filesView.editor.pickFileFromTree')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center p-3">
|
||||
<Icon name="loader-4" className="size-4 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex items-center gap-2 border-b border-border/30 px-3 py-1.5">
|
||||
<Icon name="file" className="size-4 shrink-0 text-muted-foreground" />
|
||||
<span className="typography-ui text-muted-foreground truncate flex-1">{fileName}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void saveDiagram()}
|
||||
className="size-6 flex items-center justify-center rounded-md text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
title={t('filesView.diagram.saveDiagram')}
|
||||
>
|
||||
<Icon name="save-3" className="size-4" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => useUIStore.getState().setActiveMainTab('chat')}
|
||||
className="size-6 flex items-center justify-center rounded-md text-muted-foreground hover:bg-interactive-hover/50 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
||||
title={t('filesView.diagram.closeDiagramView')}
|
||||
>
|
||||
<Icon name="close" className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 min-h-0">
|
||||
<DiagramEditor
|
||||
ref={editorRef}
|
||||
xml={xml}
|
||||
className="h-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -939,7 +939,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
|
||||
const [confirmDiscardOpen, setConfirmDiscardOpen] = React.useState(false);
|
||||
const pendingSelectFileRef = React.useRef<FileNode | null>(null);
|
||||
const pendingTabRef = React.useRef<import('@/stores/useUIStore').MainTab | null>(null);
|
||||
const pendingTabRef = React.useRef<import('@/stores/useUIStore').WorkspaceSurface | null>(null);
|
||||
const pendingClosePathRef = React.useRef<string | null>(null);
|
||||
const skipDirtyOnceRef = React.useRef(false);
|
||||
const copiedContentTimeoutRef = React.useRef<number | null>(null);
|
||||
@@ -1029,7 +1029,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
const [isDragging, setIsDragging] = React.useState(false);
|
||||
|
||||
// Session/config for sending comments
|
||||
const setMainTabGuard = useUIStore((state) => state.setMainTabGuard);
|
||||
const setSurfaceGuard = useUIStore((state) => state.setSurfaceGuard);
|
||||
const pendingFileNavigation = useUIStore((state) => state.pendingFileNavigation);
|
||||
const setPendingFileNavigation = useUIStore((state) => state.setPendingFileNavigation);
|
||||
const pendingFileFocusPath = useUIStore((state) => state.pendingFileFocusPath);
|
||||
@@ -1098,10 +1098,10 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
React.useEffect(() => {
|
||||
setLineSelection(null);
|
||||
reset();
|
||||
setMainTabGuard(null);
|
||||
setSurfaceGuard(null);
|
||||
setDraftContent('');
|
||||
setIsSaving(false);
|
||||
}, [selectedFile?.path, reset, setMainTabGuard]);
|
||||
}, [selectedFile?.path, reset, setSurfaceGuard]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setCommentSelection(lineSelection);
|
||||
@@ -1713,11 +1713,11 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isDirty) {
|
||||
setMainTabGuard(null);
|
||||
setSurfaceGuard(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const guard = (_nextTab: import('@/stores/useUIStore').MainTab) => {
|
||||
const guard = (_nextTab: import('@/stores/useUIStore').WorkspaceSurface) => {
|
||||
if (skipDirtyOnceRef.current) {
|
||||
skipDirtyOnceRef.current = false;
|
||||
return true;
|
||||
@@ -1727,15 +1727,15 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
setMainTabGuard(guard);
|
||||
setSurfaceGuard(guard);
|
||||
|
||||
return () => {
|
||||
const currentGuard = useUIStore.getState().mainTabGuard;
|
||||
const currentGuard = useUIStore.getState().surfaceGuard;
|
||||
if (currentGuard === guard) {
|
||||
setMainTabGuard(null);
|
||||
setSurfaceGuard(null);
|
||||
}
|
||||
};
|
||||
}, [isDirty, setMainTabGuard]);
|
||||
}, [isDirty, setSurfaceGuard]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (autoSaveEnabled) {
|
||||
@@ -2180,10 +2180,10 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
}
|
||||
|
||||
if (nextTab) {
|
||||
setMainTabGuard(null);
|
||||
useUIStore.getState().setActiveMainTab(nextTab);
|
||||
setSurfaceGuard(null);
|
||||
useUIStore.getState().setActiveSurface(nextTab);
|
||||
}
|
||||
}, [displayedContent, handleSelectFile, isMobile, removeOpenPath, root, selectedFile?.path, setMainTabGuard, setSelectedPath]);
|
||||
}, [displayedContent, handleSelectFile, isMobile, removeOpenPath, root, selectedFile?.path, setSurfaceGuard, setSelectedPath]);
|
||||
|
||||
const saveAndContinue = React.useCallback(async () => {
|
||||
const nextFile = pendingSelectFileRef.current;
|
||||
@@ -2234,10 +2234,10 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
}
|
||||
|
||||
if (nextTab) {
|
||||
setMainTabGuard(null);
|
||||
useUIStore.getState().setActiveMainTab(nextTab);
|
||||
setSurfaceGuard(null);
|
||||
useUIStore.getState().setActiveSurface(nextTab);
|
||||
}
|
||||
}, [handleSelectFile, isMobile, removeOpenPath, root, saveDraft, selectedFile?.path, setMainTabGuard, setSelectedPath]);
|
||||
}, [handleSelectFile, isMobile, removeOpenPath, root, saveDraft, selectedFile?.path, setSurfaceGuard, setSelectedPath]);
|
||||
|
||||
const handleCloseFile = React.useCallback((path: string) => {
|
||||
const isActive = selectedFile?.path === path;
|
||||
|
||||
@@ -168,7 +168,7 @@ export const PlanView: React.FC<PlanViewProps> = ({ targetPath = null, projectPl
|
||||
const activeProjectId = useProjectsStore((state) => state.activeProjectId);
|
||||
const gitDirectories = useGitStore((state) => state.directories);
|
||||
const effectiveDirectory = useEffectiveDirectory() ?? '';
|
||||
const setActiveMainTab = useUIStore((state) => state.setActiveMainTab);
|
||||
const setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
||||
const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen);
|
||||
const runtimeApis = useRuntimeAPIs();
|
||||
const { isMobile } = useDeviceInfo();
|
||||
@@ -579,10 +579,10 @@ export const PlanView: React.FC<PlanViewProps> = ({ targetPath = null, projectPl
|
||||
}, []);
|
||||
|
||||
const routeToChat = React.useCallback(() => {
|
||||
setActiveMainTab('chat');
|
||||
setActiveSurface('chat');
|
||||
setSessionSwitcherOpen(false);
|
||||
onNavigatedToChat?.();
|
||||
}, [onNavigatedToChat, setActiveMainTab, setSessionSwitcherOpen]);
|
||||
}, [onNavigatedToChat, setActiveSurface, setSessionSwitcherOpen]);
|
||||
|
||||
const handleConfirmPlanSend = React.useCallback(
|
||||
async (execution: TodoSendExecution) => {
|
||||
|
||||
@@ -41,7 +41,7 @@ export const ConflictDialog: React.FC<ConflictDialogProps> = ({
|
||||
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
|
||||
const setPendingInputText = useInputStore((state) => state.setPendingInputText);
|
||||
const setPendingSyntheticParts = useInputStore((state) => state.setPendingSyntheticParts);
|
||||
const setActiveMainTab = useUIStore((state) => state.setActiveMainTab);
|
||||
const setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
||||
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [conflictDetails, setConflictDetails] = React.useState<MergeConflictDetails | null>(null);
|
||||
@@ -137,7 +137,7 @@ export const ConflictDialog: React.FC<ConflictDialogProps> = ({
|
||||
{ text: context.payloadText, synthetic: true },
|
||||
]);
|
||||
|
||||
setActiveMainTab('chat');
|
||||
setActiveSurface('chat');
|
||||
onClearState?.();
|
||||
onOpenChange(false);
|
||||
};
|
||||
@@ -159,7 +159,7 @@ export const ConflictDialog: React.FC<ConflictDialogProps> = ({
|
||||
],
|
||||
});
|
||||
// Navigate to chat tab so user sees the new session
|
||||
setActiveMainTab('chat');
|
||||
setActiveSurface('chat');
|
||||
onClearState?.();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
@@ -65,7 +65,7 @@ export const IntegrateCommitsSection: React.FC<{
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
const currentSessionId = useSessionUIStore((s) => s.currentSessionId);
|
||||
const setActiveMainTab = useUIStore((s) => s.setActiveMainTab);
|
||||
const setActiveSurface = useUIStore((s) => s.setActiveSurface);
|
||||
const [branchDropdownOpen, setBranchDropdownOpen] = React.useState(false);
|
||||
const [branchSearch, setBranchSearch] = React.useState('');
|
||||
const searchInputRef = React.useRef<HTMLInputElement>(null);
|
||||
@@ -236,7 +236,7 @@ export const IntegrateCommitsSection: React.FC<{
|
||||
],
|
||||
});
|
||||
// Navigate to chat tab so user sees the new session
|
||||
setActiveMainTab('chat');
|
||||
setActiveSurface('chat');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -251,8 +251,8 @@ export const IntegrateCommitsSection: React.FC<{
|
||||
{ text: context.instructionsText, synthetic: true },
|
||||
{ text: context.payloadText, synthetic: true },
|
||||
]);
|
||||
setActiveMainTab('chat');
|
||||
}, [currentSessionId, setActiveMainTab, buildConflictContext, openNewSessionDraft, setPendingInputText, setPendingSyntheticParts, t]);
|
||||
setActiveSurface('chat');
|
||||
}, [currentSessionId, setActiveSurface, buildConflictContext, openNewSessionDraft, setPendingInputText, setPendingSyntheticParts, t]);
|
||||
|
||||
const handleMove = React.useCallback(async () => {
|
||||
if (ui.kind !== 'ready') return;
|
||||
|
||||
@@ -327,7 +327,7 @@ export const PullRequestSection: React.FC<{
|
||||
const githubAuthChecked = useGitHubAuthStore((state) => state.hasChecked);
|
||||
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
||||
const setSettingsPage = useUIStore((state) => state.setSettingsPage);
|
||||
const setActiveMainTab = useUIStore((state) => state.setActiveMainTab);
|
||||
const setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
||||
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
|
||||
const newSessionDraftOpen = useSessionUIStore((state) => Boolean(state.newSessionDraft?.open));
|
||||
const { isMobile, hasTouchInput, screenWidth } = useDeviceInfo();
|
||||
@@ -986,14 +986,14 @@ export const PullRequestSection: React.FC<{
|
||||
text: '',
|
||||
});
|
||||
}
|
||||
setActiveMainTab('chat');
|
||||
setActiveSurface('chat');
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
toast.error(t('gitView.pr.toast.loadChecksFailed'), { description: message });
|
||||
} finally {
|
||||
setIsAttachingChecks(false);
|
||||
}
|
||||
}, [directory, ensurePrContext, github, pr, resolveDraftTarget, setActiveMainTab, status?.repo, t]);
|
||||
}, [directory, ensurePrContext, github, pr, resolveDraftTarget, setActiveSurface, status?.repo, t]);
|
||||
|
||||
const sendCommentsToChat = React.useCallback(async () => {
|
||||
if (!github?.prContext) {
|
||||
@@ -1021,14 +1021,14 @@ export const PullRequestSection: React.FC<{
|
||||
for (const comment of timelineComments) {
|
||||
attachCommentDraft(target, comment);
|
||||
}
|
||||
setActiveMainTab('chat');
|
||||
setActiveSurface('chat');
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
toast.error(t('gitView.pr.toast.loadPrCommentsFailed'), { description: message });
|
||||
} finally {
|
||||
setIsAttachingComments(false);
|
||||
}
|
||||
}, [attachCommentDraft, directory, ensurePrContext, github, pr, resolveDraftTarget, setActiveMainTab, status?.repo, t, timelineComments]);
|
||||
}, [attachCommentDraft, directory, ensurePrContext, github, pr, resolveDraftTarget, setActiveSurface, status?.repo, t, timelineComments]);
|
||||
|
||||
const sendSingleCommentToChat = React.useCallback(async (comment: TimelineCommentItem) => {
|
||||
const target = resolveDraftTarget();
|
||||
@@ -1037,8 +1037,8 @@ export const PullRequestSection: React.FC<{
|
||||
}
|
||||
|
||||
attachCommentDraft(target, comment);
|
||||
setActiveMainTab('chat');
|
||||
}, [attachCommentDraft, resolveDraftTarget, setActiveMainTab]);
|
||||
setActiveSurface('chat');
|
||||
}, [attachCommentDraft, resolveDraftTarget, setActiveSurface]);
|
||||
|
||||
const refresh = React.useCallback(async (options?: { force?: boolean; onlyExistingPr?: boolean; silent?: boolean; markInitialResolved?: boolean }) => {
|
||||
await refreshPrStatus(prStatusKey, options);
|
||||
|
||||
Reference in New Issue
Block a user