refactor(surface): remove the main-area surface concept entirely
activeSurface was permanently 'chat' after the legacy mobile layout
removal, so the whole concept is gone: the store field, surfaceGuard,
setActiveSurface/setSurfaceGuard, the per-runtime surface memory in
prepare/restoreForRuntimeSwitch, and WorkspaceSurface itself. All ~30
setActiveSurface('chat') call sites were no-ops and are deleted;
always-true 'is the chat active' checks in keyboard shortcuts, Header
and ChatContainer are unconditional now. FilesView's dirty-file guard
kept its file-switch and close protection but drops the surface-switch
branch nothing could trigger. TerminalView visibility comes only from
its callers. The router keeps parsing legacy ?tab= links (they open the
matching context-panel surface) via its own RouteTab type and no longer
serializes a tab or diff file into URLs — desktop URLs never carried
them anyway.
This commit is contained in:
@@ -1006,8 +1006,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const { activeSurface } = useUIStore.getState();
|
||||
if (activeSurface !== 'chat' || hasBlockingChatOverlay()) {
|
||||
if (hasBlockingChatOverlay()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -434,7 +434,6 @@ export const Header: React.FC = () => {
|
||||
const openContextOverview = useUIStore((state) => state.openContextOverview);
|
||||
const openContextPlan = useUIStore((state) => state.openContextPlan);
|
||||
const closeContextPanel = useUIStore((state) => state.closeContextPanel);
|
||||
const activeSurface = useUIStore((state) => state.activeSurface);
|
||||
const shortcutOverrides = useUIStore((state) => state.shortcutOverrides);
|
||||
|
||||
const getCurrentModel = useConfigStore((state) => state.getCurrentModel);
|
||||
@@ -612,7 +611,6 @@ export const Header: React.FC = () => {
|
||||
}, [setWorkStatusOverlayOpen, setWorkStatusPanelEnabled, workStatusOverlayOpen, workStatusPanelEnabled, workStatusPanelFits]);
|
||||
const showDesktopHeaderContextUsage = !isVSCode
|
||||
&& !workStatusPanelVisible
|
||||
&& activeSurface === 'chat'
|
||||
&& !!stableDesktopContextUsage
|
||||
&& stableDesktopContextUsage.totalTokens > 0;
|
||||
const desktopHeaderDisplayPercentage = stableDesktopContextUsage && stableDesktopContextUsage.contextLimit > 0
|
||||
@@ -1153,9 +1151,6 @@ export const Header: React.FC = () => {
|
||||
// Reset plan tab availability when session changes
|
||||
React.useEffect(() => {
|
||||
if (!planModeEnabled) {
|
||||
if (useUIStore.getState().activeSurface === 'plan') {
|
||||
useUIStore.getState().setActiveSurface('chat');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1165,11 +1160,6 @@ export const Header: React.FC = () => {
|
||||
if (lastPlanSessionKeyRef.current !== sessionKey) {
|
||||
lastPlanSessionKeyRef.current = sessionKey;
|
||||
}
|
||||
|
||||
// If plan is not available but user is on plan tab, switch them back to chat
|
||||
if (!planTabAvailable && useUIStore.getState().activeSurface === 'plan') {
|
||||
useUIStore.getState().setActiveSurface('chat');
|
||||
}
|
||||
}, [
|
||||
planModeEnabled,
|
||||
planTabAvailable,
|
||||
@@ -1759,7 +1749,7 @@ export const Header: React.FC = () => {
|
||||
className={cn(desktopHeaderIconButtonClass, 'mr-1')}
|
||||
Icon={'picture-in-picture-2'}
|
||||
/>
|
||||
{activeSurface === 'chat' && !isVSCode ? (
|
||||
{!isVSCode ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
|
||||
@@ -36,7 +36,6 @@ const SettingsWindow = lazyWithChunkRecovery(() => import('@/components/views/Se
|
||||
*/
|
||||
export const MainLayout: React.FC = () => {
|
||||
const isSidebarOpen = useUIStore((state) => state.isSidebarOpen);
|
||||
const activeSurface = useUIStore((state) => state.activeSurface);
|
||||
const setIsMobile = useUIStore((state) => state.setIsMobile);
|
||||
const isSettingsDialogOpen = useUIStore((state) => state.isSettingsDialogOpen);
|
||||
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
||||
@@ -71,12 +70,8 @@ export const MainLayout: React.FC = () => {
|
||||
const draftOpened = Boolean(state.newSessionDraft?.open) && state.newSessionDraft !== prev.newSessionDraft;
|
||||
if (sessionSelected || draftOpened) closeSurfacePages();
|
||||
});
|
||||
const unsubscribeTab = useUIStore.subscribe((state, prev) => {
|
||||
if (state.activeSurface !== prev.activeSurface) closeSurfacePages();
|
||||
});
|
||||
return () => {
|
||||
unsubscribeSession();
|
||||
unsubscribeTab();
|
||||
};
|
||||
}, []);
|
||||
const { isMobile } = useDeviceInfo();
|
||||
@@ -90,8 +85,6 @@ export const MainLayout: React.FC = () => {
|
||||
}
|
||||
}, [isMobile, setIsMobile]);
|
||||
|
||||
const isChatActive = activeSurface === 'chat';
|
||||
|
||||
return (
|
||||
<DiffWorkerProvider>
|
||||
<div
|
||||
@@ -127,8 +120,8 @@ export const MainLayout: React.FC = () => {
|
||||
which the context panel animates. */}
|
||||
<div className="relative flex flex-1 min-h-0 min-w-0 overflow-hidden" data-page-scroll-lock="true" data-chat-area="true">
|
||||
<main className="flex-1 overflow-hidden bg-background relative" data-page-scroll-lock="true">
|
||||
<div className={cn('absolute inset-0', (!isChatActive || isSurfacePageOpen) && 'invisible')}>
|
||||
<ErrorBoundary><ChatView active={isChatActive && !isSettingsDialogOpen && !isSurfacePageOpen} /></ErrorBoundary>
|
||||
<div className={cn('absolute inset-0', isSurfacePageOpen && 'invisible')}>
|
||||
<ErrorBoundary><ChatView active={!isSettingsDialogOpen && !isSurfacePageOpen} /></ErrorBoundary>
|
||||
</div>
|
||||
{isMultiRunLauncherOpen && (
|
||||
<div className="absolute inset-0 z-10 bg-background">
|
||||
|
||||
@@ -148,7 +148,6 @@ export const DirectoryExplorerDialog: React.FC<DirectoryExplorerDialogProps> = (
|
||||
const homeDirectory = useDirectoryStore((s) => s.homeDirectory);
|
||||
const projects = useProjectsStore((s) => s.projects);
|
||||
const addProject = useProjectsStore((s) => s.addProject);
|
||||
const setActiveSurface = useUIStore((s) => s.setActiveSurface);
|
||||
const setSessionSwitcherOpen = useUIStore((s) => s.setSessionSwitcherOpen);
|
||||
const openNewSessionDraft = useSessionUIStore((s) => s.openNewSessionDraft);
|
||||
const gitIdentityProfiles = useGitIdentitiesStore((s) => s.profiles);
|
||||
@@ -411,11 +410,10 @@ export const DirectoryExplorerDialog: React.FC<DirectoryExplorerDialogProps> = (
|
||||
}, [onOpenChange]);
|
||||
|
||||
const openProjectDraft = React.useCallback((projectId: string, projectPath: string) => {
|
||||
setActiveSurface('chat');
|
||||
if (isMobile) setSessionSwitcherOpen(false);
|
||||
openNewSessionDraft({ selectedProjectId: projectId, directoryOverride: projectPath });
|
||||
handleClose();
|
||||
}, [handleClose, isMobile, openNewSessionDraft, setActiveSurface, setSessionSwitcherOpen]);
|
||||
}, [handleClose, isMobile, openNewSessionDraft, setSessionSwitcherOpen]);
|
||||
|
||||
const handleQuickAdd = React.useCallback((event: React.MouseEvent, path: string) => {
|
||||
event.stopPropagation();
|
||||
|
||||
@@ -368,13 +368,9 @@ export function ScheduledTasksDialog() {
|
||||
return;
|
||||
}
|
||||
setOpen(false);
|
||||
if (isMobile) {
|
||||
useFilesViewTabsStore.getState().setSelectedPath(selectedProject.path, task.loopFile, { allowOutsideRoot: true });
|
||||
useUIStore.getState().setActiveSurface('files');
|
||||
return;
|
||||
}
|
||||
useFilesViewTabsStore.getState().setSelectedPath(selectedProject.path, task.loopFile, { allowOutsideRoot: true });
|
||||
useUIStore.getState().openContextFile(selectedProject.path, task.loopFile);
|
||||
}, [isMobile, selectedProject?.path, setOpen]);
|
||||
}, [selectedProject?.path, setOpen]);
|
||||
|
||||
const handleRunNow = React.useCallback(async (task: ScheduledTask) => {
|
||||
if (!selectedProjectID) {
|
||||
@@ -397,8 +393,7 @@ export function ScheduledTasksDialog() {
|
||||
// this surface (MainLayout closes surfaces on session selection).
|
||||
const project = projects.find((entry) => entry.id === selectedProjectID);
|
||||
useSessionUIStore.getState().setCurrentSession(sessionId, project?.path ?? null);
|
||||
useUIStore.getState().setActiveSurface('chat');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : t('sessions.scheduledTasks.dialog.toast.runFailed'));
|
||||
} finally {
|
||||
|
||||
@@ -391,7 +391,6 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
||||
const updateProjectMeta = useProjectsStore((state) => state.updateProjectMeta);
|
||||
const reorderProjects = useProjectsStore((state) => state.reorderProjects);
|
||||
|
||||
const setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
||||
const openContextPanelTab = useUIStore((state) => state.openContextPanelTab);
|
||||
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
||||
const toggleHelpDialog = useUIStore((state) => state.toggleHelpDialog);
|
||||
@@ -848,7 +847,6 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
||||
sessionSearchQuery,
|
||||
setSessionSearchQuery,
|
||||
setIsSessionSearchOpen,
|
||||
setActiveSurface,
|
||||
setSessionSwitcherOpen,
|
||||
setCurrentSession,
|
||||
updateSessionTitle,
|
||||
@@ -1698,7 +1696,6 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
||||
alwaysShowActions={alwaysShowSidebarActions}
|
||||
activeProjectId={activeProjectId}
|
||||
setActiveProjectIdOnly={setActiveProjectIdOnly}
|
||||
setActiveSurface={setActiveSurface}
|
||||
setSessionSwitcherOpen={setSessionSwitcherOpen}
|
||||
openNewSessionDraft={openNewSessionDraftFromTree}
|
||||
addSessionToFolder={addSessionToFolder}
|
||||
@@ -1741,8 +1738,7 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
||||
alwaysShowSidebarActions,
|
||||
activeProjectId,
|
||||
setActiveProjectIdOnly,
|
||||
setActiveSurface,
|
||||
setSessionSwitcherOpen,
|
||||
setSessionSwitcherOpen,
|
||||
openNewSessionDraftFromTree,
|
||||
addSessionToFolder,
|
||||
stableCreateFolderAndStartRename,
|
||||
@@ -1763,12 +1759,11 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
||||
|
||||
const handleOpenNewSessionDraftFromHeader = React.useCallback(() => {
|
||||
useUIStore.getState().closeMainSurfaces();
|
||||
setActiveSurface('chat');
|
||||
if (mobileVariant) {
|
||||
setSessionSwitcherOpen(false);
|
||||
}
|
||||
openNewSessionDraft();
|
||||
}, [mobileVariant, openNewSessionDraft, setActiveSurface, setSessionSwitcherOpen]);
|
||||
}, [mobileVariant, openNewSessionDraft, setSessionSwitcherOpen]);
|
||||
|
||||
const renderChatsSection = React.useCallback((items: ActivityItem[]) => {
|
||||
const chatsRoot = getChatsRootForHome(homeDirectory)
|
||||
@@ -1850,12 +1845,11 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
||||
setBulkDeleteConfirm,
|
||||
});
|
||||
const handleOpenMultiRunFromHeader = React.useCallback(() => {
|
||||
setActiveSurface('chat');
|
||||
if (mobileVariant) {
|
||||
setSessionSwitcherOpen(false);
|
||||
}
|
||||
openMultiRunLauncher();
|
||||
}, [mobileVariant, openMultiRunLauncher, setActiveSurface, setSessionSwitcherOpen]);
|
||||
}, [mobileVariant, openMultiRunLauncher, setSessionSwitcherOpen]);
|
||||
|
||||
return (
|
||||
// One shared tooltip provider for the whole sidebar: session tooltips open
|
||||
@@ -1889,7 +1883,6 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
||||
handleSessionSelect={stableHandleSessionSelect}
|
||||
mobileVariant={mobileVariant}
|
||||
openNewSessionDraft={openNewSessionDraft}
|
||||
setActiveSurface={setActiveSurface}
|
||||
setSessionSwitcherOpen={setSessionSwitcherOpen}
|
||||
sessionOwnerBySessionId={sessionOwnership.bySessionId}
|
||||
/>
|
||||
@@ -1959,7 +1952,6 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
||||
alwaysShowActions={alwaysShowSidebarActions}
|
||||
toggleProject={toggleProject}
|
||||
setActiveProjectIdOnly={setActiveProjectIdOnly}
|
||||
setActiveSurface={setActiveSurface}
|
||||
setSessionSwitcherOpen={setSessionSwitcherOpen}
|
||||
openNewSessionDraft={openNewSessionDraftFromTree}
|
||||
openNewWorktreeDialog={openNewWorktreeDialog}
|
||||
@@ -2033,8 +2025,7 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
||||
open={newWorktreeDialogOpen}
|
||||
onOpenChange={setNewWorktreeDialogOpen}
|
||||
onWorktreeCreated={(worktreePath, options) => {
|
||||
setActiveSurface('chat');
|
||||
if (mobileVariant) {
|
||||
if (mobileVariant) {
|
||||
setSessionSwitcherOpen(false);
|
||||
}
|
||||
if (options?.sessionId) {
|
||||
|
||||
@@ -71,14 +71,12 @@ type SwitcherContentProps = {
|
||||
function SwitcherContent({ onSelect, variant, scopeProjectId }: SwitcherContentProps): React.ReactElement {
|
||||
const items = useSwitcherItems(true, { scopeProjectId });
|
||||
const openNewSessionDraft = useSessionUIStore((state) => state.openNewSessionDraft);
|
||||
const setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
||||
const { t } = useI18n();
|
||||
|
||||
const handleNewSession = React.useCallback(() => {
|
||||
setActiveSurface('chat');
|
||||
onSelect();
|
||||
openNewSessionDraft();
|
||||
}, [onSelect, openNewSessionDraft, setActiveSurface]);
|
||||
}, [onSelect, openNewSessionDraft]);
|
||||
|
||||
const [expandedParents, setExpandedParents] = React.useState<Set<string>>(new Set());
|
||||
const toggleParent = React.useCallback((sessionId: string) => {
|
||||
|
||||
@@ -44,13 +44,11 @@ export const useProjectTodoSend = (options: {
|
||||
const sendMessage = useSessionUIStore((state) => state.sendMessage);
|
||||
const setCurrentSession = useSessionUIStore((state) => state.setCurrentSession);
|
||||
const setPendingInputText = useInputStore((state) => state.setPendingInputText);
|
||||
const setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
||||
const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen);
|
||||
|
||||
const routeToChat = React.useCallback(() => {
|
||||
setActiveSurface('chat');
|
||||
setSessionSwitcherOpen(false);
|
||||
}, [setActiveSurface, setSessionSwitcherOpen]);
|
||||
}, [setSessionSwitcherOpen]);
|
||||
|
||||
const sendToCurrentSession = React.useCallback(
|
||||
(todoText: string) => {
|
||||
|
||||
@@ -14,7 +14,6 @@ import { Button } from '@/components/ui/button';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { sessionEvents } from '@/lib/sessionEvents';
|
||||
import type { WorkspaceSurface } from '@/stores/useUIStore';
|
||||
import { SessionFolderItem } from '../SessionFolderItem';
|
||||
import type { SortableDragHandleProps } from './sortableItems';
|
||||
import { DroppableFolderWrapper, SessionFolderDndScope } from './sessionFolderDnd';
|
||||
@@ -84,7 +83,6 @@ type Props = {
|
||||
alwaysShowActions: boolean;
|
||||
activeProjectId: string | null;
|
||||
setActiveProjectIdOnly: (id: string) => void;
|
||||
setActiveSurface: (tab: WorkspaceSurface) => void;
|
||||
setSessionSwitcherOpen: (open: boolean) => void;
|
||||
openNewSessionDraft: (options?: { selectedProjectId?: string | null; directoryOverride?: string | null; targetFolderId?: string; target?: 'chat' | 'project' }) => void;
|
||||
addSessionToFolder: (scopeKey: string, folderId: string, sessionId: string) => void;
|
||||
@@ -265,7 +263,6 @@ const areGroupPropsEqual = (prev: Props, next: Props): boolean => {
|
||||
&& prev.alwaysShowActions === next.alwaysShowActions
|
||||
&& prev.activeProjectId === next.activeProjectId
|
||||
&& prev.setActiveProjectIdOnly === next.setActiveProjectIdOnly
|
||||
&& prev.setActiveSurface === next.setActiveSurface
|
||||
&& prev.setSessionSwitcherOpen === next.setSessionSwitcherOpen
|
||||
&& prev.openNewSessionDraft === next.openNewSessionDraft
|
||||
&& prev.addSessionToFolder === next.addSessionToFolder
|
||||
@@ -307,7 +304,6 @@ function SessionGroupSectionBase(props: Props): React.ReactNode {
|
||||
alwaysShowActions,
|
||||
activeProjectId,
|
||||
setActiveProjectIdOnly,
|
||||
setActiveSurface,
|
||||
setSessionSwitcherOpen,
|
||||
openNewSessionDraft,
|
||||
addSessionToFolder,
|
||||
@@ -880,7 +876,6 @@ function SessionGroupSectionBase(props: Props): React.ReactNode {
|
||||
depth={0}
|
||||
onNewSession={() => {
|
||||
if (projectId && projectId !== activeProjectId) setActiveProjectIdOnly(projectId);
|
||||
setActiveSurface('chat');
|
||||
if (mobileVariant) setSessionSwitcherOpen(false);
|
||||
openNewSessionDraft({
|
||||
selectedProjectId: projectId,
|
||||
@@ -1248,8 +1243,7 @@ function SessionGroupSectionBase(props: Props): React.ReactNode {
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
if (projectId && projectId !== activeProjectId) setActiveProjectIdOnly(projectId);
|
||||
setActiveSurface('chat');
|
||||
if (mobileVariant) setSessionSwitcherOpen(false);
|
||||
if (mobileVariant) setSessionSwitcherOpen(false);
|
||||
openNewSessionDraft({ selectedProjectId: projectId, directoryOverride: group.directory });
|
||||
}}
|
||||
className="inline-flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
|
||||
|
||||
@@ -16,7 +16,6 @@ import type { SortableDragHandleProps } from './sortableItems';
|
||||
import { ProjectHeaderIdentity, SortableGroupItem, SortableProjectItem } from './sortableItems';
|
||||
import { formatProjectLabel } from './utils';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import type { WorkspaceSurface } from '@/stores/useUIStore';
|
||||
import type { ProjectSortOrder } from '@/stores/useSessionDisplayStore';
|
||||
import { streamPerfCount } from '@/stores/utils/streamDebug';
|
||||
import { Icon } from '@/components/icon/Icon';
|
||||
@@ -91,7 +90,6 @@ type Props = {
|
||||
alwaysShowActions: boolean;
|
||||
toggleProject: (id: string) => void;
|
||||
setActiveProjectIdOnly: (id: string) => void;
|
||||
setActiveSurface: (tab: WorkspaceSurface) => void;
|
||||
setSessionSwitcherOpen: (open: boolean) => void;
|
||||
openNewSessionDraft: (options?: { selectedProjectId?: string | null; directoryOverride?: string | null }) => void;
|
||||
openNewWorktreeDialog: () => void;
|
||||
@@ -362,7 +360,6 @@ function SidebarProjectsListComponent(props: Props): React.ReactNode {
|
||||
}}
|
||||
onNewSession={() => {
|
||||
if (projectKey !== props.activeProjectId) props.setActiveProjectIdOnly(projectKey);
|
||||
props.setActiveSurface('chat');
|
||||
if (props.mobileVariant) props.setSessionSwitcherOpen(false);
|
||||
props.openNewSessionDraft({
|
||||
selectedProjectId: projectKey,
|
||||
@@ -371,7 +368,6 @@ function SidebarProjectsListComponent(props: Props): React.ReactNode {
|
||||
}}
|
||||
onNewWorktreeSession={() => {
|
||||
if (projectKey !== props.activeProjectId) props.setActiveProjectIdOnly(projectKey);
|
||||
props.setActiveSurface('chat');
|
||||
props.openNewWorktreeDialog();
|
||||
}}
|
||||
onManageWorktrees={() => props.openWorktreesPage(projectKey)}
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import type { Session } from '@opencode-ai/sdk/v2';
|
||||
import type { SessionGroup, SessionNode } from '../types';
|
||||
import { normalizePath } from '../utils';
|
||||
import type { WorkspaceSurface } from '@/stores/useUIStore';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
|
||||
@@ -22,7 +21,6 @@ type Args = {
|
||||
newSessionDraftOpen: boolean;
|
||||
mobileVariant: boolean;
|
||||
openNewSessionDraft: (options?: { selectedProjectId?: string | null; directoryOverride?: string | null }) => void;
|
||||
setActiveSurface: (tab: WorkspaceSurface) => void;
|
||||
setSessionSwitcherOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
@@ -101,7 +99,6 @@ export const useProjectSessionSelection = (args: Args): void => {
|
||||
newSessionDraftOpen,
|
||||
mobileVariant,
|
||||
openNewSessionDraft,
|
||||
setActiveSurface,
|
||||
setSessionSwitcherOpen,
|
||||
} = args;
|
||||
|
||||
@@ -205,7 +202,6 @@ export const useProjectSessionSelection = (args: Args): void => {
|
||||
previousActiveProjectRef.current = activeProjectId;
|
||||
|
||||
if (selection.kind === 'open-draft') {
|
||||
setActiveSurface('chat');
|
||||
if (mobileVariant) {
|
||||
setSessionSwitcherOpen(false);
|
||||
}
|
||||
@@ -232,7 +228,6 @@ export const useProjectSessionSelection = (args: Args): void => {
|
||||
openNewSessionDraft,
|
||||
projectSections,
|
||||
projectSessionMeta,
|
||||
setActiveSurface,
|
||||
setSessionSwitcherOpen,
|
||||
setActiveSessionByProject,
|
||||
]);
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Session } from '@opencode-ai/sdk/v2';
|
||||
import { toast } from '@/components/ui';
|
||||
import { copyTextToClipboard } from '@/lib/clipboard';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import type { WorkspaceSurface } from '@/stores/useUIStore';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { streamPerfMark } from '@/stores/utils/streamDebug';
|
||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
@@ -30,7 +29,6 @@ type Args = {
|
||||
sessionSearchQuery: string;
|
||||
setSessionSearchQuery: (value: string) => void;
|
||||
setIsSessionSearchOpen: (open: boolean) => void;
|
||||
setActiveSurface: (tab: WorkspaceSurface) => void;
|
||||
setSessionSwitcherOpen: (open: boolean) => void;
|
||||
setCurrentSession: (sessionId: string | null, directoryHint?: string | null) => void;
|
||||
updateSessionTitle: (id: string, title: string) => Promise<void>;
|
||||
@@ -79,7 +77,6 @@ export const useSessionActions = (args: Args) => {
|
||||
};
|
||||
|
||||
if (args.mobileVariant) {
|
||||
args.setActiveSurface('chat');
|
||||
args.setSessionSwitcherOpen(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,6 @@ export const CommandPalette: React.FC = () => {
|
||||
|
||||
const isCommandPaletteOpen = useUIStore((s) => s.isCommandPaletteOpen);
|
||||
const setCommandPaletteOpen = useUIStore((s) => s.setCommandPaletteOpen);
|
||||
const setActiveSurface = useUIStore((s) => s.setActiveSurface);
|
||||
const setSettingsDialogOpen = useUIStore((s) => s.setSettingsDialogOpen);
|
||||
const setSettingsPage = useUIStore((s) => s.setSettingsPage);
|
||||
const setSessionSwitcherOpen = useUIStore((s) => s.setSessionSwitcherOpen);
|
||||
@@ -170,7 +169,6 @@ export const CommandPalette: React.FC = () => {
|
||||
shortcutId: 'new_chat',
|
||||
searchText: t('commandPalette.item.newSession'),
|
||||
onSelect: run(() => {
|
||||
setActiveSurface('chat');
|
||||
setSessionSwitcherOpen(false);
|
||||
openNewSessionDraft();
|
||||
}),
|
||||
@@ -263,8 +261,7 @@ export const CommandPalette: React.FC = () => {
|
||||
t,
|
||||
run,
|
||||
isMobile,
|
||||
setActiveSurface,
|
||||
setSessionSwitcherOpen,
|
||||
setSessionSwitcherOpen,
|
||||
openNewSessionDraft,
|
||||
toggleSidebar,
|
||||
openContextSurface,
|
||||
|
||||
@@ -28,7 +28,6 @@ export function ArchiveView(): React.ReactNode {
|
||||
const { t } = useI18n();
|
||||
const open = useUIStore((state) => state.isArchivePageOpen);
|
||||
const setOpen = useUIStore((state) => state.setArchivePageOpen);
|
||||
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 +85,8 @@ export function ArchiveView(): React.ReactNode {
|
||||
const openSession = React.useCallback((session: Session) => {
|
||||
const directory = normalizePath(resolveGlobalSessionDirectory(session));
|
||||
setCurrentSession(session.id, directory ?? undefined);
|
||||
setActiveSurface('chat');
|
||||
setOpen(false);
|
||||
}, [setActiveSurface, setCurrentSession, setOpen]);
|
||||
}, [setCurrentSession, setOpen]);
|
||||
|
||||
const restoreSession = React.useCallback((session: Session) => {
|
||||
void unarchiveSession(session.id).then((success) => {
|
||||
|
||||
@@ -939,7 +939,6 @@ 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').WorkspaceSurface | null>(null);
|
||||
const pendingClosePathRef = React.useRef<string | null>(null);
|
||||
const skipDirtyOnceRef = React.useRef(false);
|
||||
const copiedContentTimeoutRef = React.useRef<number | null>(null);
|
||||
@@ -1029,7 +1028,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
const [isDragging, setIsDragging] = React.useState(false);
|
||||
|
||||
// Session/config for sending comments
|
||||
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 +1096,9 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
React.useEffect(() => {
|
||||
setLineSelection(null);
|
||||
reset();
|
||||
setSurfaceGuard(null);
|
||||
setDraftContent('');
|
||||
setIsSaving(false);
|
||||
}, [selectedFile?.path, reset, setSurfaceGuard]);
|
||||
}, [selectedFile?.path, reset]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setCommentSelection(lineSelection);
|
||||
@@ -1711,32 +1708,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
}
|
||||
}, [contentDetectedBinary, draftContent, fileContent, fileLoading, files, isDirty, loadedFileLineEnding, loadedFilePath, readFileStat, root, selectedFile, t]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isDirty) {
|
||||
setSurfaceGuard(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const guard = (_nextTab: import('@/stores/useUIStore').WorkspaceSurface) => {
|
||||
if (skipDirtyOnceRef.current) {
|
||||
skipDirtyOnceRef.current = false;
|
||||
return true;
|
||||
}
|
||||
setConfirmDiscardOpen(true);
|
||||
pendingTabRef.current = _nextTab;
|
||||
return false;
|
||||
};
|
||||
|
||||
setSurfaceGuard(guard);
|
||||
|
||||
return () => {
|
||||
const currentGuard = useUIStore.getState().surfaceGuard;
|
||||
if (currentGuard === guard) {
|
||||
setSurfaceGuard(null);
|
||||
}
|
||||
};
|
||||
}, [isDirty, setSurfaceGuard]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (autoSaveEnabled) {
|
||||
return;
|
||||
@@ -2136,11 +2107,9 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
|
||||
const discardAndContinue = React.useCallback(() => {
|
||||
const nextFile = pendingSelectFileRef.current;
|
||||
const nextTab = pendingTabRef.current;
|
||||
const closePath = pendingClosePathRef.current;
|
||||
|
||||
pendingSelectFileRef.current = null;
|
||||
pendingTabRef.current = null;
|
||||
pendingClosePathRef.current = null;
|
||||
|
||||
// Allow one guarded navigation (tab/file) without re-opening dialog.
|
||||
@@ -2179,15 +2148,10 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextTab) {
|
||||
setSurfaceGuard(null);
|
||||
useUIStore.getState().setActiveSurface(nextTab);
|
||||
}
|
||||
}, [displayedContent, handleSelectFile, isMobile, removeOpenPath, root, selectedFile?.path, setSurfaceGuard, setSelectedPath]);
|
||||
}, [displayedContent, handleSelectFile, isMobile, removeOpenPath, root, selectedFile?.path, setSelectedPath]);
|
||||
|
||||
const saveAndContinue = React.useCallback(async () => {
|
||||
const nextFile = pendingSelectFileRef.current;
|
||||
const nextTab = pendingTabRef.current;
|
||||
const closePath = pendingClosePathRef.current;
|
||||
|
||||
const saved = await saveDraft();
|
||||
@@ -2197,7 +2161,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
}
|
||||
|
||||
pendingSelectFileRef.current = null;
|
||||
pendingTabRef.current = null;
|
||||
pendingClosePathRef.current = null;
|
||||
|
||||
// We'll proceed after saving; suppress guard reopening.
|
||||
@@ -2233,11 +2196,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextTab) {
|
||||
setSurfaceGuard(null);
|
||||
useUIStore.getState().setActiveSurface(nextTab);
|
||||
}
|
||||
}, [handleSelectFile, isMobile, removeOpenPath, root, saveDraft, selectedFile?.path, setSurfaceGuard, setSelectedPath]);
|
||||
}, [handleSelectFile, isMobile, removeOpenPath, root, saveDraft, selectedFile?.path, setSelectedPath]);
|
||||
|
||||
const handleCloseFile = React.useCallback((path: string) => {
|
||||
const isActive = selectedFile?.path === path;
|
||||
|
||||
@@ -168,7 +168,6 @@ 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 setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
||||
const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen);
|
||||
const runtimeApis = useRuntimeAPIs();
|
||||
const { isMobile } = useDeviceInfo();
|
||||
@@ -579,10 +578,9 @@ export const PlanView: React.FC<PlanViewProps> = ({ targetPath = null, projectPl
|
||||
}, []);
|
||||
|
||||
const routeToChat = React.useCallback(() => {
|
||||
setActiveSurface('chat');
|
||||
setSessionSwitcherOpen(false);
|
||||
onNavigatedToChat?.();
|
||||
}, [onNavigatedToChat, setActiveSurface, setSessionSwitcherOpen]);
|
||||
}, [onNavigatedToChat, setSessionSwitcherOpen]);
|
||||
|
||||
const handleConfirmPlanSend = React.useCallback(
|
||||
async (execution: TodoSendExecution) => {
|
||||
|
||||
@@ -148,9 +148,7 @@ export const TerminalView: React.FC<TerminalViewProps> = ({ visible }) => {
|
||||
terminalControllerRef.current?.focus();
|
||||
}, [useTouchTerminalInput]);
|
||||
|
||||
const activeSurface = useUIStore((state) => state.activeSurface);
|
||||
const isTerminalActive = activeSurface === 'terminal';
|
||||
const isTerminalVisible = visible ?? isTerminalActive;
|
||||
const isTerminalVisible = visible ?? false;
|
||||
const [hasOpenedTerminalViewport, setHasOpenedTerminalViewport] = React.useState(isTerminalVisible);
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
@@ -10,7 +10,6 @@ import { Button } from '@/components/ui/button';
|
||||
|
||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
import { useInputStore } from '@/sync/input-store';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { toast } from '@/components/ui';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { getConflictDetails, type MergeConflictDetails } from '@/lib/gitApi';
|
||||
@@ -41,7 +40,6 @@ 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 setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
||||
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [conflictDetails, setConflictDetails] = React.useState<MergeConflictDetails | null>(null);
|
||||
@@ -137,7 +135,6 @@ export const ConflictDialog: React.FC<ConflictDialogProps> = ({
|
||||
{ text: context.payloadText, synthetic: true },
|
||||
]);
|
||||
|
||||
setActiveSurface('chat');
|
||||
onClearState?.();
|
||||
onOpenChange(false);
|
||||
};
|
||||
@@ -159,7 +156,6 @@ export const ConflictDialog: React.FC<ConflictDialogProps> = ({
|
||||
],
|
||||
});
|
||||
// Navigate to chat tab so user sees the new session
|
||||
setActiveSurface('chat');
|
||||
onClearState?.();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
@@ -18,7 +18,6 @@ import { toast } from '@/components/ui';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
import { useInputStore } from '@/sync/input-store';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { rankByQuery } from '@/lib/search/fuzzySearch';
|
||||
import { getGitCommitSummaries } from '@/lib/gitApi';
|
||||
import { renderMagicPrompt } from '@/lib/magicPrompts';
|
||||
@@ -65,7 +64,6 @@ export const IntegrateCommitsSection: React.FC<{
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
const currentSessionId = useSessionUIStore((s) => s.currentSessionId);
|
||||
const setActiveSurface = useUIStore((s) => s.setActiveSurface);
|
||||
const [branchDropdownOpen, setBranchDropdownOpen] = React.useState(false);
|
||||
const [branchSearch, setBranchSearch] = React.useState('');
|
||||
const searchInputRef = React.useRef<HTMLInputElement>(null);
|
||||
@@ -235,8 +233,6 @@ export const IntegrateCommitsSection: React.FC<{
|
||||
{ text: context.payloadText, synthetic: true },
|
||||
],
|
||||
});
|
||||
// Navigate to chat tab so user sees the new session
|
||||
setActiveSurface('chat');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -251,8 +247,7 @@ export const IntegrateCommitsSection: React.FC<{
|
||||
{ text: context.instructionsText, synthetic: true },
|
||||
{ text: context.payloadText, synthetic: true },
|
||||
]);
|
||||
setActiveSurface('chat');
|
||||
}, [currentSessionId, setActiveSurface, buildConflictContext, openNewSessionDraft, setPendingInputText, setPendingSyntheticParts, t]);
|
||||
}, [currentSessionId, buildConflictContext, openNewSessionDraft, setPendingInputText, setPendingSyntheticParts, t]);
|
||||
|
||||
const handleMove = React.useCallback(async () => {
|
||||
if (ui.kind !== 'ready') return;
|
||||
|
||||
@@ -327,7 +327,6 @@ export const PullRequestSection: React.FC<{
|
||||
const githubAuthChecked = useGitHubAuthStore((state) => state.hasChecked);
|
||||
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
||||
const setSettingsPage = useUIStore((state) => state.setSettingsPage);
|
||||
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 +985,13 @@ export const PullRequestSection: React.FC<{
|
||||
text: '',
|
||||
});
|
||||
}
|
||||
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, setActiveSurface, status?.repo, t]);
|
||||
}, [directory, ensurePrContext, github, pr, resolveDraftTarget, status?.repo, t]);
|
||||
|
||||
const sendCommentsToChat = React.useCallback(async () => {
|
||||
if (!github?.prContext) {
|
||||
@@ -1021,14 +1019,13 @@ export const PullRequestSection: React.FC<{
|
||||
for (const comment of timelineComments) {
|
||||
attachCommentDraft(target, comment);
|
||||
}
|
||||
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, setActiveSurface, status?.repo, t, timelineComments]);
|
||||
}, [attachCommentDraft, directory, ensurePrContext, github, pr, resolveDraftTarget, status?.repo, t, timelineComments]);
|
||||
|
||||
const sendSingleCommentToChat = React.useCallback(async (comment: TimelineCommentItem) => {
|
||||
const target = resolveDraftTarget();
|
||||
@@ -1037,8 +1034,7 @@ export const PullRequestSection: React.FC<{
|
||||
}
|
||||
|
||||
attachCommentDraft(target, comment);
|
||||
setActiveSurface('chat');
|
||||
}, [attachCommentDraft, resolveDraftTarget, setActiveSurface]);
|
||||
}, [attachCommentDraft, resolveDraftTarget]);
|
||||
|
||||
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