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:
Bohdan Triapitsyn
2026-08-24 16:36:41 +03:00
parent c6e39f15fe
commit c82f188fc8
31 changed files with 49 additions and 276 deletions
@@ -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);
}