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:
@@ -626,7 +626,6 @@ function App({ apis }: AppProps) {
|
|||||||
const directory = typeof detail?.directory === 'string' && detail.directory.trim().length > 0
|
const directory = typeof detail?.directory === 'string' && detail.directory.trim().length > 0
|
||||||
? detail.directory.trim()
|
? detail.directory.trim()
|
||||||
: null;
|
: null;
|
||||||
useUIStore.getState().setActiveSurface('chat');
|
|
||||||
void useSessionUIStore.getState().setCurrentSession(sessionId, directory);
|
void useSessionUIStore.getState().setCurrentSession(sessionId, directory);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -675,7 +674,6 @@ function App({ apis }: AppProps) {
|
|||||||
? detail.projectId.trim()
|
? detail.projectId.trim()
|
||||||
: null;
|
: null;
|
||||||
const hasProjectTarget = Boolean(directory || projectId);
|
const hasProjectTarget = Boolean(directory || projectId);
|
||||||
useUIStore.getState().setActiveSurface('chat');
|
|
||||||
useUIStore.getState().setSessionSwitcherOpen(false);
|
useUIStore.getState().setSessionSwitcherOpen(false);
|
||||||
useSessionUIStore.getState().openNewSessionDraft({
|
useSessionUIStore.getState().openNewSessionDraft({
|
||||||
target: hasProjectTarget ? 'project' : 'chat',
|
target: hasProjectTarget ? 'project' : 'chat',
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { useProjectsStore } from '@/stores/useProjectsStore';
|
|||||||
import { useProjectContextStore } from '@/stores/useProjectContextStore';
|
import { useProjectContextStore } from '@/stores/useProjectContextStore';
|
||||||
import { useGlobalSessionsStore } from '@/stores/useGlobalSessionsStore';
|
import { useGlobalSessionsStore } from '@/stores/useGlobalSessionsStore';
|
||||||
import { useAutoReviewStore } from '@/stores/useAutoReviewStore';
|
import { useAutoReviewStore } from '@/stores/useAutoReviewStore';
|
||||||
import { useUIStore } from '@/stores/useUIStore';
|
|
||||||
import { usePermissionStore } from '@/stores/permissionStore';
|
import { usePermissionStore } from '@/stores/permissionStore';
|
||||||
import { useFileSearchStore } from '@/stores/useFileSearchStore';
|
import { useFileSearchStore } from '@/stores/useFileSearchStore';
|
||||||
import { useGitStore } from '@/stores/useGitStore';
|
import { useGitStore } from '@/stores/useGitStore';
|
||||||
@@ -37,7 +36,6 @@ export const reconnectAppForTransportSwitch = (): void => {
|
|||||||
|
|
||||||
export const resetAppForRuntimeEndpointChange = (detail: RuntimeEndpointChangedDetail): void => {
|
export const resetAppForRuntimeEndpointChange = (detail: RuntimeEndpointChangedDetail): void => {
|
||||||
useSessionUIStore.getState().prepareForRuntimeSwitch(detail.previousRuntimeKey);
|
useSessionUIStore.getState().prepareForRuntimeSwitch(detail.previousRuntimeKey);
|
||||||
useUIStore.getState().prepareForRuntimeSwitch(detail.previousRuntimeKey);
|
|
||||||
if (detail.previousRuntimeKey) {
|
if (detail.previousRuntimeKey) {
|
||||||
useAutoReviewStore.getState().stopRunningRunsForRuntime(detail.previousRuntimeKey);
|
useAutoReviewStore.getState().stopRunningRunsForRuntime(detail.previousRuntimeKey);
|
||||||
}
|
}
|
||||||
@@ -71,7 +69,6 @@ export const resetAppForRuntimeEndpointChange = (detail: RuntimeEndpointChangedD
|
|||||||
useSessionFoldersStore.getState().resetForRuntimeSwitch(detail.runtimeKey);
|
useSessionFoldersStore.getState().resetForRuntimeSwitch(detail.runtimeKey);
|
||||||
useFilesViewTabsStore.getState().resetForRuntimeSwitch(detail.runtimeKey);
|
useFilesViewTabsStore.getState().resetForRuntimeSwitch(detail.runtimeKey);
|
||||||
useSessionUIStore.getState().restoreForRuntimeSwitch(detail.runtimeKey);
|
useSessionUIStore.getState().restoreForRuntimeSwitch(detail.runtimeKey);
|
||||||
useUIStore.getState().restoreForRuntimeSwitch(detail.runtimeKey);
|
|
||||||
resetStreamingState();
|
resetStreamingState();
|
||||||
queueMicrotask(() => void syncDesktopSettings());
|
queueMicrotask(() => void syncDesktopSettings());
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1006,8 +1006,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { activeSurface } = useUIStore.getState();
|
if (hasBlockingChatOverlay()) {
|
||||||
if (activeSurface !== 'chat' || hasBlockingChatOverlay()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -434,7 +434,6 @@ export const Header: React.FC = () => {
|
|||||||
const openContextOverview = useUIStore((state) => state.openContextOverview);
|
const openContextOverview = useUIStore((state) => state.openContextOverview);
|
||||||
const openContextPlan = useUIStore((state) => state.openContextPlan);
|
const openContextPlan = useUIStore((state) => state.openContextPlan);
|
||||||
const closeContextPanel = useUIStore((state) => state.closeContextPanel);
|
const closeContextPanel = useUIStore((state) => state.closeContextPanel);
|
||||||
const activeSurface = useUIStore((state) => state.activeSurface);
|
|
||||||
const shortcutOverrides = useUIStore((state) => state.shortcutOverrides);
|
const shortcutOverrides = useUIStore((state) => state.shortcutOverrides);
|
||||||
|
|
||||||
const getCurrentModel = useConfigStore((state) => state.getCurrentModel);
|
const getCurrentModel = useConfigStore((state) => state.getCurrentModel);
|
||||||
@@ -612,7 +611,6 @@ export const Header: React.FC = () => {
|
|||||||
}, [setWorkStatusOverlayOpen, setWorkStatusPanelEnabled, workStatusOverlayOpen, workStatusPanelEnabled, workStatusPanelFits]);
|
}, [setWorkStatusOverlayOpen, setWorkStatusPanelEnabled, workStatusOverlayOpen, workStatusPanelEnabled, workStatusPanelFits]);
|
||||||
const showDesktopHeaderContextUsage = !isVSCode
|
const showDesktopHeaderContextUsage = !isVSCode
|
||||||
&& !workStatusPanelVisible
|
&& !workStatusPanelVisible
|
||||||
&& activeSurface === 'chat'
|
|
||||||
&& !!stableDesktopContextUsage
|
&& !!stableDesktopContextUsage
|
||||||
&& stableDesktopContextUsage.totalTokens > 0;
|
&& stableDesktopContextUsage.totalTokens > 0;
|
||||||
const desktopHeaderDisplayPercentage = stableDesktopContextUsage && stableDesktopContextUsage.contextLimit > 0
|
const desktopHeaderDisplayPercentage = stableDesktopContextUsage && stableDesktopContextUsage.contextLimit > 0
|
||||||
@@ -1153,9 +1151,6 @@ export const Header: React.FC = () => {
|
|||||||
// Reset plan tab availability when session changes
|
// Reset plan tab availability when session changes
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!planModeEnabled) {
|
if (!planModeEnabled) {
|
||||||
if (useUIStore.getState().activeSurface === 'plan') {
|
|
||||||
useUIStore.getState().setActiveSurface('chat');
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1165,11 +1160,6 @@ export const Header: React.FC = () => {
|
|||||||
if (lastPlanSessionKeyRef.current !== sessionKey) {
|
if (lastPlanSessionKeyRef.current !== sessionKey) {
|
||||||
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,
|
planModeEnabled,
|
||||||
planTabAvailable,
|
planTabAvailable,
|
||||||
@@ -1759,7 +1749,7 @@ export const Header: React.FC = () => {
|
|||||||
className={cn(desktopHeaderIconButtonClass, 'mr-1')}
|
className={cn(desktopHeaderIconButtonClass, 'mr-1')}
|
||||||
Icon={'picture-in-picture-2'}
|
Icon={'picture-in-picture-2'}
|
||||||
/>
|
/>
|
||||||
{activeSurface === 'chat' && !isVSCode ? (
|
{!isVSCode ? (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ const SettingsWindow = lazyWithChunkRecovery(() => import('@/components/views/Se
|
|||||||
*/
|
*/
|
||||||
export const MainLayout: React.FC = () => {
|
export const MainLayout: React.FC = () => {
|
||||||
const isSidebarOpen = useUIStore((state) => state.isSidebarOpen);
|
const isSidebarOpen = useUIStore((state) => state.isSidebarOpen);
|
||||||
const activeSurface = useUIStore((state) => state.activeSurface);
|
|
||||||
const setIsMobile = useUIStore((state) => state.setIsMobile);
|
const setIsMobile = useUIStore((state) => state.setIsMobile);
|
||||||
const isSettingsDialogOpen = useUIStore((state) => state.isSettingsDialogOpen);
|
const isSettingsDialogOpen = useUIStore((state) => state.isSettingsDialogOpen);
|
||||||
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
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;
|
const draftOpened = Boolean(state.newSessionDraft?.open) && state.newSessionDraft !== prev.newSessionDraft;
|
||||||
if (sessionSelected || draftOpened) closeSurfacePages();
|
if (sessionSelected || draftOpened) closeSurfacePages();
|
||||||
});
|
});
|
||||||
const unsubscribeTab = useUIStore.subscribe((state, prev) => {
|
|
||||||
if (state.activeSurface !== prev.activeSurface) closeSurfacePages();
|
|
||||||
});
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribeSession();
|
unsubscribeSession();
|
||||||
unsubscribeTab();
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
const { isMobile } = useDeviceInfo();
|
const { isMobile } = useDeviceInfo();
|
||||||
@@ -90,8 +85,6 @@ export const MainLayout: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [isMobile, setIsMobile]);
|
}, [isMobile, setIsMobile]);
|
||||||
|
|
||||||
const isChatActive = activeSurface === 'chat';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DiffWorkerProvider>
|
<DiffWorkerProvider>
|
||||||
<div
|
<div
|
||||||
@@ -127,8 +120,8 @@ export const MainLayout: React.FC = () => {
|
|||||||
which the context panel animates. */}
|
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">
|
<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">
|
<main className="flex-1 overflow-hidden bg-background relative" data-page-scroll-lock="true">
|
||||||
<div className={cn('absolute inset-0', (!isChatActive || isSurfacePageOpen) && 'invisible')}>
|
<div className={cn('absolute inset-0', isSurfacePageOpen && 'invisible')}>
|
||||||
<ErrorBoundary><ChatView active={isChatActive && !isSettingsDialogOpen && !isSurfacePageOpen} /></ErrorBoundary>
|
<ErrorBoundary><ChatView active={!isSettingsDialogOpen && !isSurfacePageOpen} /></ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
{isMultiRunLauncherOpen && (
|
{isMultiRunLauncherOpen && (
|
||||||
<div className="absolute inset-0 z-10 bg-background">
|
<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 homeDirectory = useDirectoryStore((s) => s.homeDirectory);
|
||||||
const projects = useProjectsStore((s) => s.projects);
|
const projects = useProjectsStore((s) => s.projects);
|
||||||
const addProject = useProjectsStore((s) => s.addProject);
|
const addProject = useProjectsStore((s) => s.addProject);
|
||||||
const setActiveSurface = useUIStore((s) => s.setActiveSurface);
|
|
||||||
const setSessionSwitcherOpen = useUIStore((s) => s.setSessionSwitcherOpen);
|
const setSessionSwitcherOpen = useUIStore((s) => s.setSessionSwitcherOpen);
|
||||||
const openNewSessionDraft = useSessionUIStore((s) => s.openNewSessionDraft);
|
const openNewSessionDraft = useSessionUIStore((s) => s.openNewSessionDraft);
|
||||||
const gitIdentityProfiles = useGitIdentitiesStore((s) => s.profiles);
|
const gitIdentityProfiles = useGitIdentitiesStore((s) => s.profiles);
|
||||||
@@ -411,11 +410,10 @@ export const DirectoryExplorerDialog: React.FC<DirectoryExplorerDialogProps> = (
|
|||||||
}, [onOpenChange]);
|
}, [onOpenChange]);
|
||||||
|
|
||||||
const openProjectDraft = React.useCallback((projectId: string, projectPath: string) => {
|
const openProjectDraft = React.useCallback((projectId: string, projectPath: string) => {
|
||||||
setActiveSurface('chat');
|
|
||||||
if (isMobile) setSessionSwitcherOpen(false);
|
if (isMobile) setSessionSwitcherOpen(false);
|
||||||
openNewSessionDraft({ selectedProjectId: projectId, directoryOverride: projectPath });
|
openNewSessionDraft({ selectedProjectId: projectId, directoryOverride: projectPath });
|
||||||
handleClose();
|
handleClose();
|
||||||
}, [handleClose, isMobile, openNewSessionDraft, setActiveSurface, setSessionSwitcherOpen]);
|
}, [handleClose, isMobile, openNewSessionDraft, setSessionSwitcherOpen]);
|
||||||
|
|
||||||
const handleQuickAdd = React.useCallback((event: React.MouseEvent, path: string) => {
|
const handleQuickAdd = React.useCallback((event: React.MouseEvent, path: string) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|||||||
@@ -368,13 +368,9 @@ export function ScheduledTasksDialog() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
if (isMobile) {
|
useFilesViewTabsStore.getState().setSelectedPath(selectedProject.path, task.loopFile, { allowOutsideRoot: true });
|
||||||
useFilesViewTabsStore.getState().setSelectedPath(selectedProject.path, task.loopFile, { allowOutsideRoot: true });
|
|
||||||
useUIStore.getState().setActiveSurface('files');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
useUIStore.getState().openContextFile(selectedProject.path, task.loopFile);
|
useUIStore.getState().openContextFile(selectedProject.path, task.loopFile);
|
||||||
}, [isMobile, selectedProject?.path, setOpen]);
|
}, [selectedProject?.path, setOpen]);
|
||||||
|
|
||||||
const handleRunNow = React.useCallback(async (task: ScheduledTask) => {
|
const handleRunNow = React.useCallback(async (task: ScheduledTask) => {
|
||||||
if (!selectedProjectID) {
|
if (!selectedProjectID) {
|
||||||
@@ -397,8 +393,7 @@ export function ScheduledTasksDialog() {
|
|||||||
// this surface (MainLayout closes surfaces on session selection).
|
// this surface (MainLayout closes surfaces on session selection).
|
||||||
const project = projects.find((entry) => entry.id === selectedProjectID);
|
const project = projects.find((entry) => entry.id === selectedProjectID);
|
||||||
useSessionUIStore.getState().setCurrentSession(sessionId, project?.path ?? null);
|
useSessionUIStore.getState().setCurrentSession(sessionId, project?.path ?? null);
|
||||||
useUIStore.getState().setActiveSurface('chat');
|
}
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(error instanceof Error ? error.message : t('sessions.scheduledTasks.dialog.toast.runFailed'));
|
toast.error(error instanceof Error ? error.message : t('sessions.scheduledTasks.dialog.toast.runFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -391,7 +391,6 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
|||||||
const updateProjectMeta = useProjectsStore((state) => state.updateProjectMeta);
|
const updateProjectMeta = useProjectsStore((state) => state.updateProjectMeta);
|
||||||
const reorderProjects = useProjectsStore((state) => state.reorderProjects);
|
const reorderProjects = useProjectsStore((state) => state.reorderProjects);
|
||||||
|
|
||||||
const setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
|
||||||
const openContextPanelTab = useUIStore((state) => state.openContextPanelTab);
|
const openContextPanelTab = useUIStore((state) => state.openContextPanelTab);
|
||||||
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
||||||
const toggleHelpDialog = useUIStore((state) => state.toggleHelpDialog);
|
const toggleHelpDialog = useUIStore((state) => state.toggleHelpDialog);
|
||||||
@@ -848,7 +847,6 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
|||||||
sessionSearchQuery,
|
sessionSearchQuery,
|
||||||
setSessionSearchQuery,
|
setSessionSearchQuery,
|
||||||
setIsSessionSearchOpen,
|
setIsSessionSearchOpen,
|
||||||
setActiveSurface,
|
|
||||||
setSessionSwitcherOpen,
|
setSessionSwitcherOpen,
|
||||||
setCurrentSession,
|
setCurrentSession,
|
||||||
updateSessionTitle,
|
updateSessionTitle,
|
||||||
@@ -1698,7 +1696,6 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
|||||||
alwaysShowActions={alwaysShowSidebarActions}
|
alwaysShowActions={alwaysShowSidebarActions}
|
||||||
activeProjectId={activeProjectId}
|
activeProjectId={activeProjectId}
|
||||||
setActiveProjectIdOnly={setActiveProjectIdOnly}
|
setActiveProjectIdOnly={setActiveProjectIdOnly}
|
||||||
setActiveSurface={setActiveSurface}
|
|
||||||
setSessionSwitcherOpen={setSessionSwitcherOpen}
|
setSessionSwitcherOpen={setSessionSwitcherOpen}
|
||||||
openNewSessionDraft={openNewSessionDraftFromTree}
|
openNewSessionDraft={openNewSessionDraftFromTree}
|
||||||
addSessionToFolder={addSessionToFolder}
|
addSessionToFolder={addSessionToFolder}
|
||||||
@@ -1741,8 +1738,7 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
|||||||
alwaysShowSidebarActions,
|
alwaysShowSidebarActions,
|
||||||
activeProjectId,
|
activeProjectId,
|
||||||
setActiveProjectIdOnly,
|
setActiveProjectIdOnly,
|
||||||
setActiveSurface,
|
setSessionSwitcherOpen,
|
||||||
setSessionSwitcherOpen,
|
|
||||||
openNewSessionDraftFromTree,
|
openNewSessionDraftFromTree,
|
||||||
addSessionToFolder,
|
addSessionToFolder,
|
||||||
stableCreateFolderAndStartRename,
|
stableCreateFolderAndStartRename,
|
||||||
@@ -1763,12 +1759,11 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
|||||||
|
|
||||||
const handleOpenNewSessionDraftFromHeader = React.useCallback(() => {
|
const handleOpenNewSessionDraftFromHeader = React.useCallback(() => {
|
||||||
useUIStore.getState().closeMainSurfaces();
|
useUIStore.getState().closeMainSurfaces();
|
||||||
setActiveSurface('chat');
|
|
||||||
if (mobileVariant) {
|
if (mobileVariant) {
|
||||||
setSessionSwitcherOpen(false);
|
setSessionSwitcherOpen(false);
|
||||||
}
|
}
|
||||||
openNewSessionDraft();
|
openNewSessionDraft();
|
||||||
}, [mobileVariant, openNewSessionDraft, setActiveSurface, setSessionSwitcherOpen]);
|
}, [mobileVariant, openNewSessionDraft, setSessionSwitcherOpen]);
|
||||||
|
|
||||||
const renderChatsSection = React.useCallback((items: ActivityItem[]) => {
|
const renderChatsSection = React.useCallback((items: ActivityItem[]) => {
|
||||||
const chatsRoot = getChatsRootForHome(homeDirectory)
|
const chatsRoot = getChatsRootForHome(homeDirectory)
|
||||||
@@ -1850,12 +1845,11 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
|||||||
setBulkDeleteConfirm,
|
setBulkDeleteConfirm,
|
||||||
});
|
});
|
||||||
const handleOpenMultiRunFromHeader = React.useCallback(() => {
|
const handleOpenMultiRunFromHeader = React.useCallback(() => {
|
||||||
setActiveSurface('chat');
|
|
||||||
if (mobileVariant) {
|
if (mobileVariant) {
|
||||||
setSessionSwitcherOpen(false);
|
setSessionSwitcherOpen(false);
|
||||||
}
|
}
|
||||||
openMultiRunLauncher();
|
openMultiRunLauncher();
|
||||||
}, [mobileVariant, openMultiRunLauncher, setActiveSurface, setSessionSwitcherOpen]);
|
}, [mobileVariant, openMultiRunLauncher, setSessionSwitcherOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// One shared tooltip provider for the whole sidebar: session tooltips open
|
// One shared tooltip provider for the whole sidebar: session tooltips open
|
||||||
@@ -1889,7 +1883,6 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
|||||||
handleSessionSelect={stableHandleSessionSelect}
|
handleSessionSelect={stableHandleSessionSelect}
|
||||||
mobileVariant={mobileVariant}
|
mobileVariant={mobileVariant}
|
||||||
openNewSessionDraft={openNewSessionDraft}
|
openNewSessionDraft={openNewSessionDraft}
|
||||||
setActiveSurface={setActiveSurface}
|
|
||||||
setSessionSwitcherOpen={setSessionSwitcherOpen}
|
setSessionSwitcherOpen={setSessionSwitcherOpen}
|
||||||
sessionOwnerBySessionId={sessionOwnership.bySessionId}
|
sessionOwnerBySessionId={sessionOwnership.bySessionId}
|
||||||
/>
|
/>
|
||||||
@@ -1959,7 +1952,6 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
|||||||
alwaysShowActions={alwaysShowSidebarActions}
|
alwaysShowActions={alwaysShowSidebarActions}
|
||||||
toggleProject={toggleProject}
|
toggleProject={toggleProject}
|
||||||
setActiveProjectIdOnly={setActiveProjectIdOnly}
|
setActiveProjectIdOnly={setActiveProjectIdOnly}
|
||||||
setActiveSurface={setActiveSurface}
|
|
||||||
setSessionSwitcherOpen={setSessionSwitcherOpen}
|
setSessionSwitcherOpen={setSessionSwitcherOpen}
|
||||||
openNewSessionDraft={openNewSessionDraftFromTree}
|
openNewSessionDraft={openNewSessionDraftFromTree}
|
||||||
openNewWorktreeDialog={openNewWorktreeDialog}
|
openNewWorktreeDialog={openNewWorktreeDialog}
|
||||||
@@ -2033,8 +2025,7 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
|
|||||||
open={newWorktreeDialogOpen}
|
open={newWorktreeDialogOpen}
|
||||||
onOpenChange={setNewWorktreeDialogOpen}
|
onOpenChange={setNewWorktreeDialogOpen}
|
||||||
onWorktreeCreated={(worktreePath, options) => {
|
onWorktreeCreated={(worktreePath, options) => {
|
||||||
setActiveSurface('chat');
|
if (mobileVariant) {
|
||||||
if (mobileVariant) {
|
|
||||||
setSessionSwitcherOpen(false);
|
setSessionSwitcherOpen(false);
|
||||||
}
|
}
|
||||||
if (options?.sessionId) {
|
if (options?.sessionId) {
|
||||||
|
|||||||
@@ -71,14 +71,12 @@ type SwitcherContentProps = {
|
|||||||
function SwitcherContent({ onSelect, variant, scopeProjectId }: SwitcherContentProps): React.ReactElement {
|
function SwitcherContent({ onSelect, variant, scopeProjectId }: SwitcherContentProps): React.ReactElement {
|
||||||
const items = useSwitcherItems(true, { scopeProjectId });
|
const items = useSwitcherItems(true, { scopeProjectId });
|
||||||
const openNewSessionDraft = useSessionUIStore((state) => state.openNewSessionDraft);
|
const openNewSessionDraft = useSessionUIStore((state) => state.openNewSessionDraft);
|
||||||
const setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const handleNewSession = React.useCallback(() => {
|
const handleNewSession = React.useCallback(() => {
|
||||||
setActiveSurface('chat');
|
|
||||||
onSelect();
|
onSelect();
|
||||||
openNewSessionDraft();
|
openNewSessionDraft();
|
||||||
}, [onSelect, openNewSessionDraft, setActiveSurface]);
|
}, [onSelect, openNewSessionDraft]);
|
||||||
|
|
||||||
const [expandedParents, setExpandedParents] = React.useState<Set<string>>(new Set());
|
const [expandedParents, setExpandedParents] = React.useState<Set<string>>(new Set());
|
||||||
const toggleParent = React.useCallback((sessionId: string) => {
|
const toggleParent = React.useCallback((sessionId: string) => {
|
||||||
|
|||||||
@@ -44,13 +44,11 @@ export const useProjectTodoSend = (options: {
|
|||||||
const sendMessage = useSessionUIStore((state) => state.sendMessage);
|
const sendMessage = useSessionUIStore((state) => state.sendMessage);
|
||||||
const setCurrentSession = useSessionUIStore((state) => state.setCurrentSession);
|
const setCurrentSession = useSessionUIStore((state) => state.setCurrentSession);
|
||||||
const setPendingInputText = useInputStore((state) => state.setPendingInputText);
|
const setPendingInputText = useInputStore((state) => state.setPendingInputText);
|
||||||
const setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
|
||||||
const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen);
|
const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen);
|
||||||
|
|
||||||
const routeToChat = React.useCallback(() => {
|
const routeToChat = React.useCallback(() => {
|
||||||
setActiveSurface('chat');
|
|
||||||
setSessionSwitcherOpen(false);
|
setSessionSwitcherOpen(false);
|
||||||
}, [setActiveSurface, setSessionSwitcherOpen]);
|
}, [setSessionSwitcherOpen]);
|
||||||
|
|
||||||
const sendToCurrentSession = React.useCallback(
|
const sendToCurrentSession = React.useCallback(
|
||||||
(todoText: string) => {
|
(todoText: string) => {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Icon } from "@/components/icon/Icon";
|
import { Icon } from "@/components/icon/Icon";
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { sessionEvents } from '@/lib/sessionEvents';
|
import { sessionEvents } from '@/lib/sessionEvents';
|
||||||
import type { WorkspaceSurface } from '@/stores/useUIStore';
|
|
||||||
import { SessionFolderItem } from '../SessionFolderItem';
|
import { SessionFolderItem } from '../SessionFolderItem';
|
||||||
import type { SortableDragHandleProps } from './sortableItems';
|
import type { SortableDragHandleProps } from './sortableItems';
|
||||||
import { DroppableFolderWrapper, SessionFolderDndScope } from './sessionFolderDnd';
|
import { DroppableFolderWrapper, SessionFolderDndScope } from './sessionFolderDnd';
|
||||||
@@ -84,7 +83,6 @@ type Props = {
|
|||||||
alwaysShowActions: boolean;
|
alwaysShowActions: boolean;
|
||||||
activeProjectId: string | null;
|
activeProjectId: string | null;
|
||||||
setActiveProjectIdOnly: (id: string) => void;
|
setActiveProjectIdOnly: (id: string) => void;
|
||||||
setActiveSurface: (tab: WorkspaceSurface) => void;
|
|
||||||
setSessionSwitcherOpen: (open: boolean) => void;
|
setSessionSwitcherOpen: (open: boolean) => void;
|
||||||
openNewSessionDraft: (options?: { selectedProjectId?: string | null; directoryOverride?: string | null; targetFolderId?: string; target?: 'chat' | 'project' }) => void;
|
openNewSessionDraft: (options?: { selectedProjectId?: string | null; directoryOverride?: string | null; targetFolderId?: string; target?: 'chat' | 'project' }) => void;
|
||||||
addSessionToFolder: (scopeKey: string, folderId: string, sessionId: string) => 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.alwaysShowActions === next.alwaysShowActions
|
||||||
&& prev.activeProjectId === next.activeProjectId
|
&& prev.activeProjectId === next.activeProjectId
|
||||||
&& prev.setActiveProjectIdOnly === next.setActiveProjectIdOnly
|
&& prev.setActiveProjectIdOnly === next.setActiveProjectIdOnly
|
||||||
&& prev.setActiveSurface === next.setActiveSurface
|
|
||||||
&& prev.setSessionSwitcherOpen === next.setSessionSwitcherOpen
|
&& prev.setSessionSwitcherOpen === next.setSessionSwitcherOpen
|
||||||
&& prev.openNewSessionDraft === next.openNewSessionDraft
|
&& prev.openNewSessionDraft === next.openNewSessionDraft
|
||||||
&& prev.addSessionToFolder === next.addSessionToFolder
|
&& prev.addSessionToFolder === next.addSessionToFolder
|
||||||
@@ -307,7 +304,6 @@ function SessionGroupSectionBase(props: Props): React.ReactNode {
|
|||||||
alwaysShowActions,
|
alwaysShowActions,
|
||||||
activeProjectId,
|
activeProjectId,
|
||||||
setActiveProjectIdOnly,
|
setActiveProjectIdOnly,
|
||||||
setActiveSurface,
|
|
||||||
setSessionSwitcherOpen,
|
setSessionSwitcherOpen,
|
||||||
openNewSessionDraft,
|
openNewSessionDraft,
|
||||||
addSessionToFolder,
|
addSessionToFolder,
|
||||||
@@ -880,7 +876,6 @@ function SessionGroupSectionBase(props: Props): React.ReactNode {
|
|||||||
depth={0}
|
depth={0}
|
||||||
onNewSession={() => {
|
onNewSession={() => {
|
||||||
if (projectId && projectId !== activeProjectId) setActiveProjectIdOnly(projectId);
|
if (projectId && projectId !== activeProjectId) setActiveProjectIdOnly(projectId);
|
||||||
setActiveSurface('chat');
|
|
||||||
if (mobileVariant) setSessionSwitcherOpen(false);
|
if (mobileVariant) setSessionSwitcherOpen(false);
|
||||||
openNewSessionDraft({
|
openNewSessionDraft({
|
||||||
selectedProjectId: projectId,
|
selectedProjectId: projectId,
|
||||||
@@ -1248,8 +1243,7 @@ function SessionGroupSectionBase(props: Props): React.ReactNode {
|
|||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (projectId && projectId !== activeProjectId) setActiveProjectIdOnly(projectId);
|
if (projectId && projectId !== activeProjectId) setActiveProjectIdOnly(projectId);
|
||||||
setActiveSurface('chat');
|
if (mobileVariant) setSessionSwitcherOpen(false);
|
||||||
if (mobileVariant) setSessionSwitcherOpen(false);
|
|
||||||
openNewSessionDraft({ selectedProjectId: projectId, directoryOverride: group.directory });
|
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"
|
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 { ProjectHeaderIdentity, SortableGroupItem, SortableProjectItem } from './sortableItems';
|
||||||
import { formatProjectLabel } from './utils';
|
import { formatProjectLabel } from './utils';
|
||||||
import { useI18n } from '@/lib/i18n';
|
import { useI18n } from '@/lib/i18n';
|
||||||
import type { WorkspaceSurface } from '@/stores/useUIStore';
|
|
||||||
import type { ProjectSortOrder } from '@/stores/useSessionDisplayStore';
|
import type { ProjectSortOrder } from '@/stores/useSessionDisplayStore';
|
||||||
import { streamPerfCount } from '@/stores/utils/streamDebug';
|
import { streamPerfCount } from '@/stores/utils/streamDebug';
|
||||||
import { Icon } from '@/components/icon/Icon';
|
import { Icon } from '@/components/icon/Icon';
|
||||||
@@ -91,7 +90,6 @@ type Props = {
|
|||||||
alwaysShowActions: boolean;
|
alwaysShowActions: boolean;
|
||||||
toggleProject: (id: string) => void;
|
toggleProject: (id: string) => void;
|
||||||
setActiveProjectIdOnly: (id: string) => void;
|
setActiveProjectIdOnly: (id: string) => void;
|
||||||
setActiveSurface: (tab: WorkspaceSurface) => void;
|
|
||||||
setSessionSwitcherOpen: (open: boolean) => void;
|
setSessionSwitcherOpen: (open: boolean) => void;
|
||||||
openNewSessionDraft: (options?: { selectedProjectId?: string | null; directoryOverride?: string | null }) => void;
|
openNewSessionDraft: (options?: { selectedProjectId?: string | null; directoryOverride?: string | null }) => void;
|
||||||
openNewWorktreeDialog: () => void;
|
openNewWorktreeDialog: () => void;
|
||||||
@@ -362,7 +360,6 @@ function SidebarProjectsListComponent(props: Props): React.ReactNode {
|
|||||||
}}
|
}}
|
||||||
onNewSession={() => {
|
onNewSession={() => {
|
||||||
if (projectKey !== props.activeProjectId) props.setActiveProjectIdOnly(projectKey);
|
if (projectKey !== props.activeProjectId) props.setActiveProjectIdOnly(projectKey);
|
||||||
props.setActiveSurface('chat');
|
|
||||||
if (props.mobileVariant) props.setSessionSwitcherOpen(false);
|
if (props.mobileVariant) props.setSessionSwitcherOpen(false);
|
||||||
props.openNewSessionDraft({
|
props.openNewSessionDraft({
|
||||||
selectedProjectId: projectKey,
|
selectedProjectId: projectKey,
|
||||||
@@ -371,7 +368,6 @@ function SidebarProjectsListComponent(props: Props): React.ReactNode {
|
|||||||
}}
|
}}
|
||||||
onNewWorktreeSession={() => {
|
onNewWorktreeSession={() => {
|
||||||
if (projectKey !== props.activeProjectId) props.setActiveProjectIdOnly(projectKey);
|
if (projectKey !== props.activeProjectId) props.setActiveProjectIdOnly(projectKey);
|
||||||
props.setActiveSurface('chat');
|
|
||||||
props.openNewWorktreeDialog();
|
props.openNewWorktreeDialog();
|
||||||
}}
|
}}
|
||||||
onManageWorktrees={() => props.openWorktreesPage(projectKey)}
|
onManageWorktrees={() => props.openWorktreesPage(projectKey)}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React from 'react';
|
|||||||
import type { Session } from '@opencode-ai/sdk/v2';
|
import type { Session } from '@opencode-ai/sdk/v2';
|
||||||
import type { SessionGroup, SessionNode } from '../types';
|
import type { SessionGroup, SessionNode } from '../types';
|
||||||
import { normalizePath } from '../utils';
|
import { normalizePath } from '../utils';
|
||||||
import type { WorkspaceSurface } from '@/stores/useUIStore';
|
|
||||||
import { useUIStore } from '@/stores/useUIStore';
|
import { useUIStore } from '@/stores/useUIStore';
|
||||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||||
|
|
||||||
@@ -22,7 +21,6 @@ type Args = {
|
|||||||
newSessionDraftOpen: boolean;
|
newSessionDraftOpen: boolean;
|
||||||
mobileVariant: boolean;
|
mobileVariant: boolean;
|
||||||
openNewSessionDraft: (options?: { selectedProjectId?: string | null; directoryOverride?: string | null }) => void;
|
openNewSessionDraft: (options?: { selectedProjectId?: string | null; directoryOverride?: string | null }) => void;
|
||||||
setActiveSurface: (tab: WorkspaceSurface) => void;
|
|
||||||
setSessionSwitcherOpen: (open: boolean) => void;
|
setSessionSwitcherOpen: (open: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -101,7 +99,6 @@ export const useProjectSessionSelection = (args: Args): void => {
|
|||||||
newSessionDraftOpen,
|
newSessionDraftOpen,
|
||||||
mobileVariant,
|
mobileVariant,
|
||||||
openNewSessionDraft,
|
openNewSessionDraft,
|
||||||
setActiveSurface,
|
|
||||||
setSessionSwitcherOpen,
|
setSessionSwitcherOpen,
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
@@ -205,7 +202,6 @@ export const useProjectSessionSelection = (args: Args): void => {
|
|||||||
previousActiveProjectRef.current = activeProjectId;
|
previousActiveProjectRef.current = activeProjectId;
|
||||||
|
|
||||||
if (selection.kind === 'open-draft') {
|
if (selection.kind === 'open-draft') {
|
||||||
setActiveSurface('chat');
|
|
||||||
if (mobileVariant) {
|
if (mobileVariant) {
|
||||||
setSessionSwitcherOpen(false);
|
setSessionSwitcherOpen(false);
|
||||||
}
|
}
|
||||||
@@ -232,7 +228,6 @@ export const useProjectSessionSelection = (args: Args): void => {
|
|||||||
openNewSessionDraft,
|
openNewSessionDraft,
|
||||||
projectSections,
|
projectSections,
|
||||||
projectSessionMeta,
|
projectSessionMeta,
|
||||||
setActiveSurface,
|
|
||||||
setSessionSwitcherOpen,
|
setSessionSwitcherOpen,
|
||||||
setActiveSessionByProject,
|
setActiveSessionByProject,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import type { Session } from '@opencode-ai/sdk/v2';
|
|||||||
import { toast } from '@/components/ui';
|
import { toast } from '@/components/ui';
|
||||||
import { copyTextToClipboard } from '@/lib/clipboard';
|
import { copyTextToClipboard } from '@/lib/clipboard';
|
||||||
import { useI18n } from '@/lib/i18n';
|
import { useI18n } from '@/lib/i18n';
|
||||||
import type { WorkspaceSurface } from '@/stores/useUIStore';
|
|
||||||
import { useUIStore } from '@/stores/useUIStore';
|
import { useUIStore } from '@/stores/useUIStore';
|
||||||
import { streamPerfMark } from '@/stores/utils/streamDebug';
|
import { streamPerfMark } from '@/stores/utils/streamDebug';
|
||||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||||
@@ -30,7 +29,6 @@ type Args = {
|
|||||||
sessionSearchQuery: string;
|
sessionSearchQuery: string;
|
||||||
setSessionSearchQuery: (value: string) => void;
|
setSessionSearchQuery: (value: string) => void;
|
||||||
setIsSessionSearchOpen: (open: boolean) => void;
|
setIsSessionSearchOpen: (open: boolean) => void;
|
||||||
setActiveSurface: (tab: WorkspaceSurface) => void;
|
|
||||||
setSessionSwitcherOpen: (open: boolean) => void;
|
setSessionSwitcherOpen: (open: boolean) => void;
|
||||||
setCurrentSession: (sessionId: string | null, directoryHint?: string | null) => void;
|
setCurrentSession: (sessionId: string | null, directoryHint?: string | null) => void;
|
||||||
updateSessionTitle: (id: string, title: string) => Promise<void>;
|
updateSessionTitle: (id: string, title: string) => Promise<void>;
|
||||||
@@ -79,7 +77,6 @@ export const useSessionActions = (args: Args) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (args.mobileVariant) {
|
if (args.mobileVariant) {
|
||||||
args.setActiveSurface('chat');
|
|
||||||
args.setSessionSwitcherOpen(false);
|
args.setSessionSwitcherOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ export const CommandPalette: React.FC = () => {
|
|||||||
|
|
||||||
const isCommandPaletteOpen = useUIStore((s) => s.isCommandPaletteOpen);
|
const isCommandPaletteOpen = useUIStore((s) => s.isCommandPaletteOpen);
|
||||||
const setCommandPaletteOpen = useUIStore((s) => s.setCommandPaletteOpen);
|
const setCommandPaletteOpen = useUIStore((s) => s.setCommandPaletteOpen);
|
||||||
const setActiveSurface = useUIStore((s) => s.setActiveSurface);
|
|
||||||
const setSettingsDialogOpen = useUIStore((s) => s.setSettingsDialogOpen);
|
const setSettingsDialogOpen = useUIStore((s) => s.setSettingsDialogOpen);
|
||||||
const setSettingsPage = useUIStore((s) => s.setSettingsPage);
|
const setSettingsPage = useUIStore((s) => s.setSettingsPage);
|
||||||
const setSessionSwitcherOpen = useUIStore((s) => s.setSessionSwitcherOpen);
|
const setSessionSwitcherOpen = useUIStore((s) => s.setSessionSwitcherOpen);
|
||||||
@@ -170,7 +169,6 @@ export const CommandPalette: React.FC = () => {
|
|||||||
shortcutId: 'new_chat',
|
shortcutId: 'new_chat',
|
||||||
searchText: t('commandPalette.item.newSession'),
|
searchText: t('commandPalette.item.newSession'),
|
||||||
onSelect: run(() => {
|
onSelect: run(() => {
|
||||||
setActiveSurface('chat');
|
|
||||||
setSessionSwitcherOpen(false);
|
setSessionSwitcherOpen(false);
|
||||||
openNewSessionDraft();
|
openNewSessionDraft();
|
||||||
}),
|
}),
|
||||||
@@ -263,8 +261,7 @@ export const CommandPalette: React.FC = () => {
|
|||||||
t,
|
t,
|
||||||
run,
|
run,
|
||||||
isMobile,
|
isMobile,
|
||||||
setActiveSurface,
|
setSessionSwitcherOpen,
|
||||||
setSessionSwitcherOpen,
|
|
||||||
openNewSessionDraft,
|
openNewSessionDraft,
|
||||||
toggleSidebar,
|
toggleSidebar,
|
||||||
openContextSurface,
|
openContextSurface,
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export function ArchiveView(): React.ReactNode {
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const open = useUIStore((state) => state.isArchivePageOpen);
|
const open = useUIStore((state) => state.isArchivePageOpen);
|
||||||
const setOpen = useUIStore((state) => state.setArchivePageOpen);
|
const setOpen = useUIStore((state) => state.setArchivePageOpen);
|
||||||
const setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
|
||||||
const setCurrentSession = useSessionUIStore((state) => state.setCurrentSession);
|
const setCurrentSession = useSessionUIStore((state) => state.setCurrentSession);
|
||||||
const unarchiveSession = useSessionUIStore((state) => state.unarchiveSession);
|
const unarchiveSession = useSessionUIStore((state) => state.unarchiveSession);
|
||||||
const homeDirectory = useDirectoryStore((state) => state.homeDirectory);
|
const homeDirectory = useDirectoryStore((state) => state.homeDirectory);
|
||||||
@@ -86,9 +85,8 @@ export function ArchiveView(): React.ReactNode {
|
|||||||
const openSession = React.useCallback((session: Session) => {
|
const openSession = React.useCallback((session: Session) => {
|
||||||
const directory = normalizePath(resolveGlobalSessionDirectory(session));
|
const directory = normalizePath(resolveGlobalSessionDirectory(session));
|
||||||
setCurrentSession(session.id, directory ?? undefined);
|
setCurrentSession(session.id, directory ?? undefined);
|
||||||
setActiveSurface('chat');
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}, [setActiveSurface, setCurrentSession, setOpen]);
|
}, [setCurrentSession, setOpen]);
|
||||||
|
|
||||||
const restoreSession = React.useCallback((session: Session) => {
|
const restoreSession = React.useCallback((session: Session) => {
|
||||||
void unarchiveSession(session.id).then((success) => {
|
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 [confirmDiscardOpen, setConfirmDiscardOpen] = React.useState(false);
|
||||||
const pendingSelectFileRef = React.useRef<FileNode | null>(null);
|
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 pendingClosePathRef = React.useRef<string | null>(null);
|
||||||
const skipDirtyOnceRef = React.useRef(false);
|
const skipDirtyOnceRef = React.useRef(false);
|
||||||
const copiedContentTimeoutRef = React.useRef<number | null>(null);
|
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);
|
const [isDragging, setIsDragging] = React.useState(false);
|
||||||
|
|
||||||
// Session/config for sending comments
|
// Session/config for sending comments
|
||||||
const setSurfaceGuard = useUIStore((state) => state.setSurfaceGuard);
|
|
||||||
const pendingFileNavigation = useUIStore((state) => state.pendingFileNavigation);
|
const pendingFileNavigation = useUIStore((state) => state.pendingFileNavigation);
|
||||||
const setPendingFileNavigation = useUIStore((state) => state.setPendingFileNavigation);
|
const setPendingFileNavigation = useUIStore((state) => state.setPendingFileNavigation);
|
||||||
const pendingFileFocusPath = useUIStore((state) => state.pendingFileFocusPath);
|
const pendingFileFocusPath = useUIStore((state) => state.pendingFileFocusPath);
|
||||||
@@ -1098,10 +1096,9 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setLineSelection(null);
|
setLineSelection(null);
|
||||||
reset();
|
reset();
|
||||||
setSurfaceGuard(null);
|
|
||||||
setDraftContent('');
|
setDraftContent('');
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
}, [selectedFile?.path, reset, setSurfaceGuard]);
|
}, [selectedFile?.path, reset]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setCommentSelection(lineSelection);
|
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]);
|
}, [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(() => {
|
React.useEffect(() => {
|
||||||
if (autoSaveEnabled) {
|
if (autoSaveEnabled) {
|
||||||
return;
|
return;
|
||||||
@@ -2136,11 +2107,9 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
|||||||
|
|
||||||
const discardAndContinue = React.useCallback(() => {
|
const discardAndContinue = React.useCallback(() => {
|
||||||
const nextFile = pendingSelectFileRef.current;
|
const nextFile = pendingSelectFileRef.current;
|
||||||
const nextTab = pendingTabRef.current;
|
|
||||||
const closePath = pendingClosePathRef.current;
|
const closePath = pendingClosePathRef.current;
|
||||||
|
|
||||||
pendingSelectFileRef.current = null;
|
pendingSelectFileRef.current = null;
|
||||||
pendingTabRef.current = null;
|
|
||||||
pendingClosePathRef.current = null;
|
pendingClosePathRef.current = null;
|
||||||
|
|
||||||
// Allow one guarded navigation (tab/file) without re-opening dialog.
|
// Allow one guarded navigation (tab/file) without re-opening dialog.
|
||||||
@@ -2179,15 +2148,10 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextTab) {
|
}, [displayedContent, handleSelectFile, isMobile, removeOpenPath, root, selectedFile?.path, setSelectedPath]);
|
||||||
setSurfaceGuard(null);
|
|
||||||
useUIStore.getState().setActiveSurface(nextTab);
|
|
||||||
}
|
|
||||||
}, [displayedContent, handleSelectFile, isMobile, removeOpenPath, root, selectedFile?.path, setSurfaceGuard, setSelectedPath]);
|
|
||||||
|
|
||||||
const saveAndContinue = React.useCallback(async () => {
|
const saveAndContinue = React.useCallback(async () => {
|
||||||
const nextFile = pendingSelectFileRef.current;
|
const nextFile = pendingSelectFileRef.current;
|
||||||
const nextTab = pendingTabRef.current;
|
|
||||||
const closePath = pendingClosePathRef.current;
|
const closePath = pendingClosePathRef.current;
|
||||||
|
|
||||||
const saved = await saveDraft();
|
const saved = await saveDraft();
|
||||||
@@ -2197,7 +2161,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pendingSelectFileRef.current = null;
|
pendingSelectFileRef.current = null;
|
||||||
pendingTabRef.current = null;
|
|
||||||
pendingClosePathRef.current = null;
|
pendingClosePathRef.current = null;
|
||||||
|
|
||||||
// We'll proceed after saving; suppress guard reopening.
|
// We'll proceed after saving; suppress guard reopening.
|
||||||
@@ -2233,11 +2196,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextTab) {
|
}, [handleSelectFile, isMobile, removeOpenPath, root, saveDraft, selectedFile?.path, setSelectedPath]);
|
||||||
setSurfaceGuard(null);
|
|
||||||
useUIStore.getState().setActiveSurface(nextTab);
|
|
||||||
}
|
|
||||||
}, [handleSelectFile, isMobile, removeOpenPath, root, saveDraft, selectedFile?.path, setSurfaceGuard, setSelectedPath]);
|
|
||||||
|
|
||||||
const handleCloseFile = React.useCallback((path: string) => {
|
const handleCloseFile = React.useCallback((path: string) => {
|
||||||
const isActive = selectedFile?.path === path;
|
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 activeProjectId = useProjectsStore((state) => state.activeProjectId);
|
||||||
const gitDirectories = useGitStore((state) => state.directories);
|
const gitDirectories = useGitStore((state) => state.directories);
|
||||||
const effectiveDirectory = useEffectiveDirectory() ?? '';
|
const effectiveDirectory = useEffectiveDirectory() ?? '';
|
||||||
const setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
|
||||||
const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen);
|
const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen);
|
||||||
const runtimeApis = useRuntimeAPIs();
|
const runtimeApis = useRuntimeAPIs();
|
||||||
const { isMobile } = useDeviceInfo();
|
const { isMobile } = useDeviceInfo();
|
||||||
@@ -579,10 +578,9 @@ export const PlanView: React.FC<PlanViewProps> = ({ targetPath = null, projectPl
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const routeToChat = React.useCallback(() => {
|
const routeToChat = React.useCallback(() => {
|
||||||
setActiveSurface('chat');
|
|
||||||
setSessionSwitcherOpen(false);
|
setSessionSwitcherOpen(false);
|
||||||
onNavigatedToChat?.();
|
onNavigatedToChat?.();
|
||||||
}, [onNavigatedToChat, setActiveSurface, setSessionSwitcherOpen]);
|
}, [onNavigatedToChat, setSessionSwitcherOpen]);
|
||||||
|
|
||||||
const handleConfirmPlanSend = React.useCallback(
|
const handleConfirmPlanSend = React.useCallback(
|
||||||
async (execution: TodoSendExecution) => {
|
async (execution: TodoSendExecution) => {
|
||||||
|
|||||||
@@ -148,9 +148,7 @@ export const TerminalView: React.FC<TerminalViewProps> = ({ visible }) => {
|
|||||||
terminalControllerRef.current?.focus();
|
terminalControllerRef.current?.focus();
|
||||||
}, [useTouchTerminalInput]);
|
}, [useTouchTerminalInput]);
|
||||||
|
|
||||||
const activeSurface = useUIStore((state) => state.activeSurface);
|
const isTerminalVisible = visible ?? false;
|
||||||
const isTerminalActive = activeSurface === 'terminal';
|
|
||||||
const isTerminalVisible = visible ?? isTerminalActive;
|
|
||||||
const [hasOpenedTerminalViewport, setHasOpenedTerminalViewport] = React.useState(isTerminalVisible);
|
const [hasOpenedTerminalViewport, setHasOpenedTerminalViewport] = React.useState(isTerminalVisible);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { Button } from '@/components/ui/button';
|
|||||||
|
|
||||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||||
import { useInputStore } from '@/sync/input-store';
|
import { useInputStore } from '@/sync/input-store';
|
||||||
import { useUIStore } from '@/stores/useUIStore';
|
|
||||||
import { toast } from '@/components/ui';
|
import { toast } from '@/components/ui';
|
||||||
import { Icon } from "@/components/icon/Icon";
|
import { Icon } from "@/components/icon/Icon";
|
||||||
import { getConflictDetails, type MergeConflictDetails } from '@/lib/gitApi';
|
import { getConflictDetails, type MergeConflictDetails } from '@/lib/gitApi';
|
||||||
@@ -41,7 +40,6 @@ export const ConflictDialog: React.FC<ConflictDialogProps> = ({
|
|||||||
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
|
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
|
||||||
const setPendingInputText = useInputStore((state) => state.setPendingInputText);
|
const setPendingInputText = useInputStore((state) => state.setPendingInputText);
|
||||||
const setPendingSyntheticParts = useInputStore((state) => state.setPendingSyntheticParts);
|
const setPendingSyntheticParts = useInputStore((state) => state.setPendingSyntheticParts);
|
||||||
const setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = React.useState(false);
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
const [conflictDetails, setConflictDetails] = React.useState<MergeConflictDetails | null>(null);
|
const [conflictDetails, setConflictDetails] = React.useState<MergeConflictDetails | null>(null);
|
||||||
@@ -137,7 +135,6 @@ export const ConflictDialog: React.FC<ConflictDialogProps> = ({
|
|||||||
{ text: context.payloadText, synthetic: true },
|
{ text: context.payloadText, synthetic: true },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setActiveSurface('chat');
|
|
||||||
onClearState?.();
|
onClearState?.();
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
};
|
};
|
||||||
@@ -159,7 +156,6 @@ export const ConflictDialog: React.FC<ConflictDialogProps> = ({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
// Navigate to chat tab so user sees the new session
|
// Navigate to chat tab so user sees the new session
|
||||||
setActiveSurface('chat');
|
|
||||||
onClearState?.();
|
onClearState?.();
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import { toast } from '@/components/ui';
|
|||||||
import { Icon } from "@/components/icon/Icon";
|
import { Icon } from "@/components/icon/Icon";
|
||||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||||
import { useInputStore } from '@/sync/input-store';
|
import { useInputStore } from '@/sync/input-store';
|
||||||
import { useUIStore } from '@/stores/useUIStore';
|
|
||||||
import { rankByQuery } from '@/lib/search/fuzzySearch';
|
import { rankByQuery } from '@/lib/search/fuzzySearch';
|
||||||
import { getGitCommitSummaries } from '@/lib/gitApi';
|
import { getGitCommitSummaries } from '@/lib/gitApi';
|
||||||
import { renderMagicPrompt } from '@/lib/magicPrompts';
|
import { renderMagicPrompt } from '@/lib/magicPrompts';
|
||||||
@@ -65,7 +64,6 @@ export const IntegrateCommitsSection: React.FC<{
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const currentSessionId = useSessionUIStore((s) => s.currentSessionId);
|
const currentSessionId = useSessionUIStore((s) => s.currentSessionId);
|
||||||
const setActiveSurface = useUIStore((s) => s.setActiveSurface);
|
|
||||||
const [branchDropdownOpen, setBranchDropdownOpen] = React.useState(false);
|
const [branchDropdownOpen, setBranchDropdownOpen] = React.useState(false);
|
||||||
const [branchSearch, setBranchSearch] = React.useState('');
|
const [branchSearch, setBranchSearch] = React.useState('');
|
||||||
const searchInputRef = React.useRef<HTMLInputElement>(null);
|
const searchInputRef = React.useRef<HTMLInputElement>(null);
|
||||||
@@ -235,8 +233,6 @@ export const IntegrateCommitsSection: React.FC<{
|
|||||||
{ text: context.payloadText, synthetic: true },
|
{ text: context.payloadText, synthetic: true },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
// Navigate to chat tab so user sees the new session
|
|
||||||
setActiveSurface('chat');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,8 +247,7 @@ export const IntegrateCommitsSection: React.FC<{
|
|||||||
{ text: context.instructionsText, synthetic: true },
|
{ text: context.instructionsText, synthetic: true },
|
||||||
{ text: context.payloadText, synthetic: true },
|
{ text: context.payloadText, synthetic: true },
|
||||||
]);
|
]);
|
||||||
setActiveSurface('chat');
|
}, [currentSessionId, buildConflictContext, openNewSessionDraft, setPendingInputText, setPendingSyntheticParts, t]);
|
||||||
}, [currentSessionId, setActiveSurface, buildConflictContext, openNewSessionDraft, setPendingInputText, setPendingSyntheticParts, t]);
|
|
||||||
|
|
||||||
const handleMove = React.useCallback(async () => {
|
const handleMove = React.useCallback(async () => {
|
||||||
if (ui.kind !== 'ready') return;
|
if (ui.kind !== 'ready') return;
|
||||||
|
|||||||
@@ -327,7 +327,6 @@ export const PullRequestSection: React.FC<{
|
|||||||
const githubAuthChecked = useGitHubAuthStore((state) => state.hasChecked);
|
const githubAuthChecked = useGitHubAuthStore((state) => state.hasChecked);
|
||||||
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
||||||
const setSettingsPage = useUIStore((state) => state.setSettingsPage);
|
const setSettingsPage = useUIStore((state) => state.setSettingsPage);
|
||||||
const setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
|
||||||
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
|
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
|
||||||
const newSessionDraftOpen = useSessionUIStore((state) => Boolean(state.newSessionDraft?.open));
|
const newSessionDraftOpen = useSessionUIStore((state) => Boolean(state.newSessionDraft?.open));
|
||||||
const { isMobile, hasTouchInput, screenWidth } = useDeviceInfo();
|
const { isMobile, hasTouchInput, screenWidth } = useDeviceInfo();
|
||||||
@@ -986,14 +985,13 @@ export const PullRequestSection: React.FC<{
|
|||||||
text: '',
|
text: '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setActiveSurface('chat');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const message = e instanceof Error ? e.message : String(e);
|
const message = e instanceof Error ? e.message : String(e);
|
||||||
toast.error(t('gitView.pr.toast.loadChecksFailed'), { description: message });
|
toast.error(t('gitView.pr.toast.loadChecksFailed'), { description: message });
|
||||||
} finally {
|
} finally {
|
||||||
setIsAttachingChecks(false);
|
setIsAttachingChecks(false);
|
||||||
}
|
}
|
||||||
}, [directory, ensurePrContext, github, pr, resolveDraftTarget, setActiveSurface, status?.repo, t]);
|
}, [directory, ensurePrContext, github, pr, resolveDraftTarget, status?.repo, t]);
|
||||||
|
|
||||||
const sendCommentsToChat = React.useCallback(async () => {
|
const sendCommentsToChat = React.useCallback(async () => {
|
||||||
if (!github?.prContext) {
|
if (!github?.prContext) {
|
||||||
@@ -1021,14 +1019,13 @@ export const PullRequestSection: React.FC<{
|
|||||||
for (const comment of timelineComments) {
|
for (const comment of timelineComments) {
|
||||||
attachCommentDraft(target, comment);
|
attachCommentDraft(target, comment);
|
||||||
}
|
}
|
||||||
setActiveSurface('chat');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const message = e instanceof Error ? e.message : String(e);
|
const message = e instanceof Error ? e.message : String(e);
|
||||||
toast.error(t('gitView.pr.toast.loadPrCommentsFailed'), { description: message });
|
toast.error(t('gitView.pr.toast.loadPrCommentsFailed'), { description: message });
|
||||||
} finally {
|
} finally {
|
||||||
setIsAttachingComments(false);
|
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 sendSingleCommentToChat = React.useCallback(async (comment: TimelineCommentItem) => {
|
||||||
const target = resolveDraftTarget();
|
const target = resolveDraftTarget();
|
||||||
@@ -1037,8 +1034,7 @@ export const PullRequestSection: React.FC<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
attachCommentDraft(target, comment);
|
attachCommentDraft(target, comment);
|
||||||
setActiveSurface('chat');
|
}, [attachCommentDraft, resolveDraftTarget]);
|
||||||
}, [attachCommentDraft, resolveDraftTarget, setActiveSurface]);
|
|
||||||
|
|
||||||
const refresh = React.useCallback(async (options?: { force?: boolean; onlyExistingPr?: boolean; silent?: boolean; markInitialResolved?: boolean }) => {
|
const refresh = React.useCallback(async (options?: { force?: boolean; onlyExistingPr?: boolean; silent?: boolean; markInitialResolved?: boolean }) => {
|
||||||
await refreshPrStatus(prStatusKey, options);
|
await refreshPrStatus(prStatusKey, options);
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ export const useKeyboardShortcuts = () => {
|
|||||||
}, [currentShortcutDirectory]);
|
}, [currentShortcutDirectory]);
|
||||||
const isMobile = useUIStore((s) => s.isMobile);
|
const isMobile = useUIStore((s) => s.isMobile);
|
||||||
const setSessionSwitcherOpen = useUIStore((s) => s.setSessionSwitcherOpen);
|
const setSessionSwitcherOpen = useUIStore((s) => s.setSessionSwitcherOpen);
|
||||||
const setActiveSurface = useUIStore((s) => s.setActiveSurface);
|
|
||||||
const setSettingsDialogOpen = useUIStore((s) => s.setSettingsDialogOpen);
|
const setSettingsDialogOpen = useUIStore((s) => s.setSettingsDialogOpen);
|
||||||
const setModelSelectorOpen = useUIStore((s) => s.setModelSelectorOpen);
|
const setModelSelectorOpen = useUIStore((s) => s.setModelSelectorOpen);
|
||||||
const setTimelineDialogOpen = useUIStore((s) => s.setTimelineDialogOpen);
|
const setTimelineDialogOpen = useUIStore((s) => s.setTimelineDialogOpen);
|
||||||
@@ -154,7 +153,6 @@ export const useKeyboardShortcuts = () => {
|
|||||||
isAboutDialogOpen,
|
isAboutDialogOpen,
|
||||||
isMultiRunLauncherOpen,
|
isMultiRunLauncherOpen,
|
||||||
isImagePreviewOpen,
|
isImagePreviewOpen,
|
||||||
activeSurface,
|
|
||||||
isPromptNavigatorPanelOpen,
|
isPromptNavigatorPanelOpen,
|
||||||
} = useUIStore.getState();
|
} = useUIStore.getState();
|
||||||
|
|
||||||
@@ -183,7 +181,7 @@ export const useKeyboardShortcuts = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen || isMultiRunLauncherOpen || isImagePreviewOpen;
|
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen || isMultiRunLauncherOpen || isImagePreviewOpen;
|
||||||
const isChatActive = activeSurface === 'chat';
|
const isChatActive = true;
|
||||||
|
|
||||||
if (hasOverlay || !isChatActive) {
|
if (hasOverlay || !isChatActive) {
|
||||||
resetAbortPriming();
|
resetAbortPriming();
|
||||||
@@ -245,7 +243,6 @@ export const useKeyboardShortcuts = () => {
|
|||||||
|
|
||||||
if (eventMatchesShortcut(e, combo('toggle_prompt_navigator'))) {
|
if (eventMatchesShortcut(e, combo('toggle_prompt_navigator'))) {
|
||||||
const {
|
const {
|
||||||
activeSurface,
|
|
||||||
promptNavigatorEnabled,
|
promptNavigatorEnabled,
|
||||||
isSettingsDialogOpen,
|
isSettingsDialogOpen,
|
||||||
isCommandPaletteOpen,
|
isCommandPaletteOpen,
|
||||||
@@ -257,7 +254,7 @@ export const useKeyboardShortcuts = () => {
|
|||||||
isImagePreviewOpen,
|
isImagePreviewOpen,
|
||||||
} = useUIStore.getState();
|
} = useUIStore.getState();
|
||||||
|
|
||||||
if (!promptNavigatorEnabled || isMobile || isVSCodeRuntime() || activeSurface !== 'chat') {
|
if (!promptNavigatorEnabled || isMobile || isVSCodeRuntime()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +305,6 @@ export const useKeyboardShortcuts = () => {
|
|||||||
if (matchedNewSessionShortcut || matchedWorktreeShortcut) {
|
if (matchedNewSessionShortcut || matchedWorktreeShortcut) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
setActiveSurface('chat');
|
|
||||||
setSessionSwitcherOpen(false);
|
setSessionSwitcherOpen(false);
|
||||||
|
|
||||||
if (!isVSCodeRuntime() && matchedWorktreeShortcut) {
|
if (!isVSCodeRuntime() && matchedWorktreeShortcut) {
|
||||||
@@ -394,11 +390,10 @@ export const useKeyboardShortcuts = () => {
|
|||||||
isHelpDialogOpen,
|
isHelpDialogOpen,
|
||||||
isSessionSwitcherOpen,
|
isSessionSwitcherOpen,
|
||||||
isAboutDialogOpen,
|
isAboutDialogOpen,
|
||||||
activeSurface,
|
|
||||||
} = useUIStore.getState();
|
} = useUIStore.getState();
|
||||||
|
|
||||||
const hasOverlay = isSettingsDialogOpen || isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
|
const hasOverlay = isSettingsDialogOpen || isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
|
||||||
if (hasOverlay || activeSurface !== 'chat' || !isChatInputTarget(e.target)) {
|
if (hasOverlay || !isChatInputTarget(e.target)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,7 +520,6 @@ export const useKeyboardShortcuts = () => {
|
|||||||
isHelpDialogOpen,
|
isHelpDialogOpen,
|
||||||
isSessionSwitcherOpen,
|
isSessionSwitcherOpen,
|
||||||
isAboutDialogOpen,
|
isAboutDialogOpen,
|
||||||
activeSurface,
|
|
||||||
isModelSelectorOpen,
|
isModelSelectorOpen,
|
||||||
} = useUIStore.getState();
|
} = useUIStore.getState();
|
||||||
|
|
||||||
@@ -536,7 +530,7 @@ export const useKeyboardShortcuts = () => {
|
|||||||
|
|
||||||
// Skip if any overlay open or not on chat tab
|
// Skip if any overlay open or not on chat tab
|
||||||
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
|
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
|
||||||
const isChatActive = activeSurface === 'chat';
|
const isChatActive = true;
|
||||||
|
|
||||||
if (hasOverlay || !isChatActive) {
|
if (hasOverlay || !isChatActive) {
|
||||||
return;
|
return;
|
||||||
@@ -555,7 +549,6 @@ export const useKeyboardShortcuts = () => {
|
|||||||
isHelpDialogOpen,
|
isHelpDialogOpen,
|
||||||
isSessionSwitcherOpen,
|
isSessionSwitcherOpen,
|
||||||
isAboutDialogOpen,
|
isAboutDialogOpen,
|
||||||
activeSurface,
|
|
||||||
} = useUIStore.getState();
|
} = useUIStore.getState();
|
||||||
|
|
||||||
if (isSettingsDialogOpen) {
|
if (isSettingsDialogOpen) {
|
||||||
@@ -563,7 +556,7 @@ export const useKeyboardShortcuts = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
|
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
|
||||||
const isChatActive = activeSurface === 'chat';
|
const isChatActive = true;
|
||||||
|
|
||||||
if (hasOverlay || !isChatActive) {
|
if (hasOverlay || !isChatActive) {
|
||||||
return;
|
return;
|
||||||
@@ -602,7 +595,6 @@ export const useKeyboardShortcuts = () => {
|
|||||||
isHelpDialogOpen,
|
isHelpDialogOpen,
|
||||||
isSessionSwitcherOpen,
|
isSessionSwitcherOpen,
|
||||||
isAboutDialogOpen,
|
isAboutDialogOpen,
|
||||||
activeSurface,
|
|
||||||
favoriteModels,
|
favoriteModels,
|
||||||
addRecentModel,
|
addRecentModel,
|
||||||
} = useUIStore.getState();
|
} = useUIStore.getState();
|
||||||
@@ -612,7 +604,7 @@ export const useKeyboardShortcuts = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
|
const hasOverlay = isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isAboutDialogOpen;
|
||||||
const isChatActive = activeSurface === 'chat';
|
const isChatActive = true;
|
||||||
|
|
||||||
if (hasOverlay || !isChatActive || favoriteModels.length === 0) {
|
if (hasOverlay || !isChatActive || favoriteModels.length === 0) {
|
||||||
return;
|
return;
|
||||||
@@ -644,8 +636,8 @@ export const useKeyboardShortcuts = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (eventMatchesShortcut(e, combo('toggle_dictation'))) {
|
if (eventMatchesShortcut(e, combo('toggle_dictation'))) {
|
||||||
const { activeSurface, isCommandPaletteOpen, isHelpDialogOpen, isSessionSwitcherOpen, isSettingsDialogOpen } = useUIStore.getState();
|
const { isCommandPaletteOpen, isHelpDialogOpen, isSessionSwitcherOpen, isSettingsDialogOpen } = useUIStore.getState();
|
||||||
if (activeSurface !== 'chat' || isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isSettingsDialogOpen) {
|
if (isCommandPaletteOpen || isHelpDialogOpen || isSessionSwitcherOpen || isSettingsDialogOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -695,7 +687,6 @@ export const useKeyboardShortcuts = () => {
|
|||||||
toggleTerminalSurfaceExpanded,
|
toggleTerminalSurfaceExpanded,
|
||||||
isMobile,
|
isMobile,
|
||||||
setSessionSwitcherOpen,
|
setSessionSwitcherOpen,
|
||||||
setActiveSurface,
|
|
||||||
setSettingsDialogOpen,
|
setSettingsDialogOpen,
|
||||||
setModelSelectorOpen,
|
setModelSelectorOpen,
|
||||||
setTimelineDialogOpen,
|
setTimelineDialogOpen,
|
||||||
|
|||||||
@@ -102,7 +102,6 @@ export const useMenuActions = (
|
|||||||
const toggleHelpDialog = useUIStore((s) => s.toggleHelpDialog);
|
const toggleHelpDialog = useUIStore((s) => s.toggleHelpDialog);
|
||||||
const toggleSidebar = useUIStore((s) => s.toggleSidebar);
|
const toggleSidebar = useUIStore((s) => s.toggleSidebar);
|
||||||
const setSessionSwitcherOpen = useUIStore((s) => s.setSessionSwitcherOpen);
|
const setSessionSwitcherOpen = useUIStore((s) => s.setSessionSwitcherOpen);
|
||||||
const setActiveSurface = useUIStore((s) => s.setActiveSurface);
|
|
||||||
const setSettingsDialogOpen = useUIStore((s) => s.setSettingsDialogOpen);
|
const setSettingsDialogOpen = useUIStore((s) => s.setSettingsDialogOpen);
|
||||||
const setAboutDialogOpen = useUIStore((s) => s.setAboutDialogOpen);
|
const setAboutDialogOpen = useUIStore((s) => s.setAboutDialogOpen);
|
||||||
const checkForUpdates = useUpdateStore((state) => state.checkForUpdates);
|
const checkForUpdates = useUpdateStore((state) => state.checkForUpdates);
|
||||||
@@ -151,10 +150,9 @@ export const useMenuActions = (
|
|||||||
const nextSession = sessions[nextIndex];
|
const nextSession = sessions[nextIndex];
|
||||||
if (!nextSession) return;
|
if (!nextSession) return;
|
||||||
|
|
||||||
setActiveSurface('chat');
|
|
||||||
setSessionSwitcherOpen(false);
|
setSessionSwitcherOpen(false);
|
||||||
useSessionUIStore.getState().setCurrentSession(nextSession.id);
|
useSessionUIStore.getState().setCurrentSession(nextSession.id);
|
||||||
}, [setActiveSurface, setSessionSwitcherOpen]);
|
}, [setSessionSwitcherOpen]);
|
||||||
|
|
||||||
const navigateProject = React.useCallback((direction: -1 | 1) => {
|
const navigateProject = React.useCallback((direction: -1 | 1) => {
|
||||||
const { activeProjectId, projects, setActiveProject } = useProjectsStore.getState();
|
const { activeProjectId, projects, setActiveProject } = useProjectsStore.getState();
|
||||||
@@ -191,8 +189,7 @@ export const useMenuActions = (
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'new-session':
|
case 'new-session':
|
||||||
setActiveSurface('chat');
|
setSessionSwitcherOpen(false);
|
||||||
setSessionSwitcherOpen(false);
|
|
||||||
{
|
{
|
||||||
const sessionState = useSessionUIStore.getState();
|
const sessionState = useSessionUIStore.getState();
|
||||||
const directory = useDirectoryStore.getState().currentDirectory;
|
const directory = useDirectoryStore.getState().currentDirectory;
|
||||||
@@ -203,8 +200,7 @@ export const useMenuActions = (
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'new-worktree-session':
|
case 'new-worktree-session':
|
||||||
setActiveSurface('chat');
|
setSessionSwitcherOpen(false);
|
||||||
setSessionSwitcherOpen(false);
|
|
||||||
createWorktreeSession();
|
createWorktreeSession();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -341,7 +337,6 @@ export const useMenuActions = (
|
|||||||
onToggleMemoryDebug,
|
onToggleMemoryDebug,
|
||||||
openNewSessionDraft,
|
openNewSessionDraft,
|
||||||
setAboutDialogOpen,
|
setAboutDialogOpen,
|
||||||
setActiveSurface,
|
|
||||||
setSessionSwitcherOpen,
|
setSessionSwitcherOpen,
|
||||||
setCommandPaletteOpen,
|
setCommandPaletteOpen,
|
||||||
setSettingsDialogOpen,
|
setSettingsDialogOpen,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { useSessionUIStore } from '@/sync/session-ui-store';
|
|||||||
import { useUIStore, type ContextPanelMode } from '@/stores/useUIStore';
|
import { useUIStore, type ContextPanelMode } from '@/stores/useUIStore';
|
||||||
import { parseRoute, updateBrowserURL, hasRouteParams } from '@/lib/router';
|
import { parseRoute, updateBrowserURL, hasRouteParams } from '@/lib/router';
|
||||||
import type { RouteState, AppRouteState } from '@/lib/router';
|
import type { RouteState, AppRouteState } from '@/lib/router';
|
||||||
import type { WorkspaceSurface } from '@/stores/useUIStore';
|
|
||||||
import { resolveSettingsSlug } from '@/lib/settings/metadata';
|
import { resolveSettingsSlug } from '@/lib/settings/metadata';
|
||||||
import { isEmbeddedSessionChat } from '@/components/layout/contextPanelEmbeddedChat';
|
import { isEmbeddedSessionChat } from '@/components/layout/contextPanelEmbeddedChat';
|
||||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||||
@@ -50,7 +49,6 @@ export function useRouter(): void {
|
|||||||
|
|
||||||
// Get store actions (stable references)
|
// Get store actions (stable references)
|
||||||
const setCurrentSession = useSessionUIStore((state) => state.setCurrentSession);
|
const setCurrentSession = useSessionUIStore((state) => state.setCurrentSession);
|
||||||
const setActiveSurface = useUIStore((state) => state.setActiveSurface);
|
|
||||||
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
||||||
const setSettingsPage = useUIStore((state) => state.setSettingsPage);
|
const setSettingsPage = useUIStore((state) => state.setSettingsPage);
|
||||||
const navigateToDiff = useUIStore((state) => state.navigateToDiff);
|
const navigateToDiff = useUIStore((state) => state.navigateToDiff);
|
||||||
@@ -109,7 +107,7 @@ export function useRouter(): void {
|
|||||||
isApplyingRouteRef.current = false;
|
isApplyingRouteRef.current = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[setCurrentSession, setActiveSurface, setSettingsDialogOpen, setSettingsPage, navigateToDiff]
|
[setCurrentSession, setSettingsDialogOpen, setSettingsPage, navigateToDiff]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,10 +119,8 @@ export function useRouter(): void {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
sessionId: sessionState.currentSessionId,
|
sessionId: sessionState.currentSessionId,
|
||||||
tab: uiState.activeSurface,
|
|
||||||
isSettingsOpen: uiState.isSettingsDialogOpen,
|
isSettingsOpen: uiState.isSettingsDialogOpen,
|
||||||
settingsPath: uiState.settingsPage,
|
settingsPath: uiState.settingsPage,
|
||||||
diffFile: uiState.pendingDiffFile,
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -170,9 +166,7 @@ export function useRouter(): void {
|
|||||||
updateBrowserURL({
|
updateBrowserURL({
|
||||||
...getCurrentAppState(),
|
...getCurrentAppState(),
|
||||||
sessionId: route.sessionId ?? useSessionUIStore.getState().currentSessionId,
|
sessionId: route.sessionId ?? useSessionUIStore.getState().currentSessionId,
|
||||||
tab: route.tab ?? useUIStore.getState().activeSurface,
|
|
||||||
settingsPath: route.settingsPath ?? useUIStore.getState().settingsPage,
|
settingsPath: route.settingsPath ?? useUIStore.getState().settingsPage,
|
||||||
diffFile: route.diffFile ?? useUIStore.getState().pendingDiffFile,
|
|
||||||
}, { replace: true, force: true });
|
}, { replace: true, force: true });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -209,10 +203,8 @@ export function useRouter(): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let prevSurface: WorkspaceSurface = useUIStore.getState().activeSurface;
|
|
||||||
let prevSettingsOpen: boolean = useUIStore.getState().isSettingsDialogOpen;
|
let prevSettingsOpen: boolean = useUIStore.getState().isSettingsDialogOpen;
|
||||||
let prevSettingsPath: string = useUIStore.getState().settingsPage;
|
let prevSettingsPath: string = useUIStore.getState().settingsPage;
|
||||||
let prevDiffFile: string | null = useUIStore.getState().pendingDiffFile;
|
|
||||||
|
|
||||||
const unsubscribe = useUIStore.subscribe((state) => {
|
const unsubscribe = useUIStore.subscribe((state) => {
|
||||||
// Skip if we're currently applying a route
|
// Skip if we're currently applying a route
|
||||||
@@ -220,19 +212,13 @@ export function useRouter(): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const surfaceChanged = state.activeSurface !== prevSurface;
|
|
||||||
const settingsOpenChanged = state.isSettingsDialogOpen !== prevSettingsOpen;
|
const settingsOpenChanged = state.isSettingsDialogOpen !== prevSettingsOpen;
|
||||||
const settingsPathChanged = state.settingsPage !== prevSettingsPath;
|
const settingsPathChanged = state.settingsPage !== prevSettingsPath;
|
||||||
const diffFileChanged = state.pendingDiffFile !== prevDiffFile && state.activeSurface === 'diff';
|
|
||||||
|
|
||||||
// Update tracking vars
|
|
||||||
prevSurface = state.activeSurface;
|
|
||||||
prevSettingsOpen = state.isSettingsDialogOpen;
|
prevSettingsOpen = state.isSettingsDialogOpen;
|
||||||
prevSettingsPath = state.settingsPage;
|
prevSettingsPath = state.settingsPage;
|
||||||
prevDiffFile = state.pendingDiffFile;
|
|
||||||
|
|
||||||
// Only sync if something relevant changed
|
if (settingsOpenChanged || settingsPathChanged) {
|
||||||
if (surfaceChanged || settingsOpenChanged || settingsPathChanged || diffFileChanged) {
|
|
||||||
syncURLFromState();
|
syncURLFromState();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -260,10 +246,6 @@ export function useRouter(): void {
|
|||||||
if (uiState.isSettingsDialogOpen) {
|
if (uiState.isSettingsDialogOpen) {
|
||||||
setSettingsDialogOpen(false);
|
setSettingsDialogOpen(false);
|
||||||
}
|
}
|
||||||
// Reset to chat when no route view is specified.
|
|
||||||
if (uiState.activeSurface !== 'chat') {
|
|
||||||
setActiveSurface('chat');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -272,5 +254,5 @@ export function useRouter(): void {
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('popstate', handlePopState);
|
window.removeEventListener('popstate', handlePopState);
|
||||||
};
|
};
|
||||||
}, [applyRoute, isVSCode, isEmbeddedChat, setActiveSurface, setSettingsDialogOpen]);
|
}, [applyRoute, isVSCode, isEmbeddedChat, setSettingsDialogOpen]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,7 +151,6 @@ export const captureSelectionMarkdownForChat = (): string | null => {
|
|||||||
export const addSelectionToChat = (): boolean => {
|
export const addSelectionToChat = (): boolean => {
|
||||||
const markdown = captureSelectionMarkdownForChat();
|
const markdown = captureSelectionMarkdownForChat();
|
||||||
|
|
||||||
useUIStore.getState().setActiveSurface('chat');
|
|
||||||
useUIStore.getState().setSessionSwitcherOpen(false);
|
useUIStore.getState().setSessionSwitcherOpen(false);
|
||||||
|
|
||||||
if (markdown) {
|
if (markdown) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { WorkspaceSurface } from '@/stores/useUIStore';
|
|
||||||
import {
|
import {
|
||||||
type RouteState,
|
type RouteState,
|
||||||
|
type RouteTab,
|
||||||
VALID_TABS,
|
VALID_TABS,
|
||||||
VALID_SETTINGS_SECTIONS,
|
VALID_SETTINGS_SECTIONS,
|
||||||
ROUTE_PARAMS,
|
ROUTE_PARAMS,
|
||||||
@@ -52,13 +52,13 @@ function parseSessionId(params: URLSearchParams): string | null {
|
|||||||
* Parse main tab from URL parameters.
|
* Parse main tab from URL parameters.
|
||||||
* Returns null if missing or invalid.
|
* Returns null if missing or invalid.
|
||||||
*/
|
*/
|
||||||
function parseTab(params: URLSearchParams): WorkspaceSurface | null {
|
function parseTab(params: URLSearchParams): RouteTab | null {
|
||||||
const value = params.get(ROUTE_PARAMS.TAB);
|
const value = params.get(ROUTE_PARAMS.TAB);
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalized = value.toLowerCase().trim() as WorkspaceSurface;
|
const normalized = value.toLowerCase().trim() as RouteTab;
|
||||||
if (VALID_TABS.includes(normalized)) {
|
if (VALID_TABS.includes(normalized)) {
|
||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,10 +65,8 @@ afterAll(() => {
|
|||||||
|
|
||||||
const sessionState = (sessionId: string): AppRouteState => ({
|
const sessionState = (sessionId: string): AppRouteState => ({
|
||||||
sessionId,
|
sessionId,
|
||||||
tab: 'chat',
|
|
||||||
isSettingsOpen: false,
|
isSettingsOpen: false,
|
||||||
settingsPath: '',
|
settingsPath: '',
|
||||||
diffFile: null,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('updateBrowserURL embedded-session-chat guard', () => {
|
describe('updateBrowserURL embedded-session-chat guard', () => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { WorkspaceSurface } from '@/stores/useUIStore';
|
|
||||||
import { isEmbeddedSessionChat } from '@/components/layout/contextPanelEmbeddedChat';
|
import { isEmbeddedSessionChat } from '@/components/layout/contextPanelEmbeddedChat';
|
||||||
import { ROUTE_PARAMS } from './types';
|
import { ROUTE_PARAMS } from './types';
|
||||||
|
|
||||||
@@ -7,17 +6,10 @@ import { ROUTE_PARAMS } from './types';
|
|||||||
*/
|
*/
|
||||||
export interface AppRouteState {
|
export interface AppRouteState {
|
||||||
sessionId: string | null;
|
sessionId: string | null;
|
||||||
tab: WorkspaceSurface;
|
|
||||||
isSettingsOpen: boolean;
|
isSettingsOpen: boolean;
|
||||||
settingsPath: string;
|
settingsPath: string;
|
||||||
diffFile: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Default tab when none is specified.
|
|
||||||
*/
|
|
||||||
const DEFAULT_TAB: WorkspaceSurface = 'chat';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize application state to URL search parameters.
|
* Serialize application state to URL search parameters.
|
||||||
* Only includes parameters that differ from defaults to keep URLs clean.
|
* Only includes parameters that differ from defaults to keep URLs clean.
|
||||||
@@ -38,15 +30,6 @@ function serializeRoute(state: AppRouteState): URLSearchParams {
|
|||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tab - only include if not the default
|
|
||||||
if (state.tab !== DEFAULT_TAB) {
|
|
||||||
params.set(ROUTE_PARAMS.TAB, state.tab);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diff file - only include when on diff tab
|
|
||||||
if (state.tab === 'diff' && state.diffFile && state.diffFile.trim().length > 0) {
|
|
||||||
params.set(ROUTE_PARAMS.FILE, state.diffFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { SidebarSection } from '@/constants/sidebar';
|
import type { SidebarSection } from '@/constants/sidebar';
|
||||||
import type { WorkspaceSurface } from '@/stores/useUIStore';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the current route state derived from URL parameters.
|
* Represents the current route state derived from URL parameters.
|
||||||
@@ -9,7 +8,7 @@ export interface RouteState {
|
|||||||
/** Session ID to navigate to */
|
/** Session ID to navigate to */
|
||||||
sessionId: string | null;
|
sessionId: string | null;
|
||||||
/** View selected through the legacy `tab` URL parameter. */
|
/** View selected through the legacy `tab` URL parameter. */
|
||||||
tab: WorkspaceSurface | null;
|
tab: RouteTab | null;
|
||||||
/** Settings section - when non-null, settings dialog should be open */
|
/** Settings section - when non-null, settings dialog should be open */
|
||||||
settingsPath: string | null;
|
settingsPath: string | null;
|
||||||
/** File path for diff view */
|
/** File path for diff view */
|
||||||
@@ -17,9 +16,11 @@ export interface RouteState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid values for the legacy `tab` URL parameter.
|
* Valid values for the legacy `tab` URL parameter. Non-chat tabs open the
|
||||||
|
* matching context-panel surface; the chat always owns the main area.
|
||||||
*/
|
*/
|
||||||
export const VALID_TABS: readonly WorkspaceSurface[] = ['chat', 'git', 'diff', 'terminal', 'files'] as const;
|
export type RouteTab = 'chat' | 'git' | 'diff' | 'terminal' | 'files';
|
||||||
|
export const VALID_TABS: readonly RouteTab[] = ['chat', 'git', 'diff', 'terminal', 'files'] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid settings section values for URL routing.
|
* Valid settings section values for URL routing.
|
||||||
|
|||||||
@@ -7,17 +7,11 @@ import type { ShortcutCombo } from '@/lib/shortcuts';
|
|||||||
import type { DraftStarterRef } from '@/lib/draftStarters';
|
import type { DraftStarterRef } from '@/lib/draftStarters';
|
||||||
import { DEFAULT_MONO_FONT, DEFAULT_UI_FONT, type MonoFontOption, type UiFontOption } from '@/lib/fontOptions';
|
import { DEFAULT_MONO_FONT, DEFAULT_UI_FONT, type MonoFontOption, type UiFontOption } from '@/lib/fontOptions';
|
||||||
import { getStoredMobileKeyboardMode, type MobileKeyboardMode } from '@/lib/mobileKeyboardMode';
|
import { getStoredMobileKeyboardMode, type MobileKeyboardMode } from '@/lib/mobileKeyboardMode';
|
||||||
import { getRuntimeKey } from '@/lib/runtime-switch';
|
|
||||||
import type { TerminalShell } from '@/lib/api/types';
|
import type { TerminalShell } from '@/lib/api/types';
|
||||||
import { useFilesViewTabsStore } from './useFilesViewTabsStore';
|
import { useFilesViewTabsStore } from './useFilesViewTabsStore';
|
||||||
import { isWindowsArm64 } from '@/lib/platform';
|
import { isWindowsArm64 } from '@/lib/platform';
|
||||||
import { isVSCodeRuntime } from '@/lib/desktop';
|
import { isVSCodeRuntime } from '@/lib/desktop';
|
||||||
|
|
||||||
/**
|
|
||||||
* The primary view on mobile and the desktop's promoted full-screen view.
|
|
||||||
* Desktop context-panel content is not represented here.
|
|
||||||
*/
|
|
||||||
export type WorkspaceSurface = 'chat' | 'plan' | 'git' | 'diff' | 'terminal' | 'files' | 'context';
|
|
||||||
export type PendingDiffScope = 'working' | 'staged' | 'turn' | 'branch';
|
export type PendingDiffScope = 'working' | 'staged' | 'turn' | 'branch';
|
||||||
export type ContextPanelMode = 'diff' | 'walkthrough' | 'file' | 'context' | 'plan' | 'chat' | 'browser' | 'git' | 'pr' | 'notes' | 'terminal';
|
export type ContextPanelMode = 'diff' | 'walkthrough' | 'file' | 'context' | 'plan' | 'chat' | 'browser' | 'git' | 'pr' | 'notes' | 'terminal';
|
||||||
export type MermaidRenderingMode = 'svg' | 'ascii';
|
export type MermaidRenderingMode = 'svg' | 'ascii';
|
||||||
@@ -81,7 +75,6 @@ type PendingFileNavigation = {
|
|||||||
column: number;
|
column: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkspaceSurfaceGuard = (nextSurface: WorkspaceSurface) => boolean;
|
|
||||||
export type EventStreamStatus =
|
export type EventStreamStatus =
|
||||||
| 'idle'
|
| 'idle'
|
||||||
| 'connecting'
|
| 'connecting'
|
||||||
@@ -133,15 +126,9 @@ const CONTEXT_PANEL_MAX_WIDTH = 1400;
|
|||||||
const CONTEXT_PANEL_MAX_TABS = 12;
|
const CONTEXT_PANEL_MAX_TABS = 12;
|
||||||
const CONTEXT_PANEL_MAX_LABEL_LENGTH = 120;
|
const CONTEXT_PANEL_MAX_LABEL_LENGTH = 120;
|
||||||
const LEFT_SIDEBAR_MIN_WIDTH = 280;
|
const LEFT_SIDEBAR_MIN_WIDTH = 280;
|
||||||
const activeSurfaceByRuntime = new Map<string, WorkspaceSurface>();
|
|
||||||
/** Separates browser tabs opened in the same millisecond. */
|
/** Separates browser tabs opened in the same millisecond. */
|
||||||
let browserTabSequence = 0;
|
let browserTabSequence = 0;
|
||||||
|
|
||||||
const runtimeMemoryKey = (value?: string | null): string => {
|
|
||||||
const key = (value ?? getRuntimeKey()).trim();
|
|
||||||
return key || 'default';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Shared with rail/panel consumers so contextPanelByDirectory lookups agree on keys.
|
// Shared with rail/panel consumers so contextPanelByDirectory lookups agree on keys.
|
||||||
export const normalizeContextPanelDirectoryKey = (value: string): string => normalizeDirectoryPath(value);
|
export const normalizeContextPanelDirectoryKey = (value: string): string => normalizeDirectoryPath(value);
|
||||||
|
|
||||||
@@ -652,9 +639,6 @@ interface UIStore {
|
|||||||
workStatusHiddenSections: string[];
|
workStatusHiddenSections: string[];
|
||||||
isSessionSwitcherOpen: boolean;
|
isSessionSwitcherOpen: boolean;
|
||||||
isSessionDropdownOpen: boolean;
|
isSessionDropdownOpen: boolean;
|
||||||
activeSurface: WorkspaceSurface;
|
|
||||||
surfaceGuard: WorkspaceSurfaceGuard | null;
|
|
||||||
sidebarOpenBeforeFullscreenTab: boolean | null;
|
|
||||||
pendingDiffFile: string | null;
|
pendingDiffFile: string | null;
|
||||||
pendingDiffStaged: boolean;
|
pendingDiffStaged: boolean;
|
||||||
pendingDiffScope: PendingDiffScope | null;
|
pendingDiffScope: PendingDiffScope | null;
|
||||||
@@ -844,10 +828,6 @@ interface UIStore {
|
|||||||
setWorkStatusHiddenSections: (sectionIds: string[]) => void;
|
setWorkStatusHiddenSections: (sectionIds: string[]) => void;
|
||||||
setSessionSwitcherOpen: (open: boolean) => void;
|
setSessionSwitcherOpen: (open: boolean) => void;
|
||||||
setSessionDropdownOpen: (open: boolean) => void;
|
setSessionDropdownOpen: (open: boolean) => void;
|
||||||
setActiveSurface: (surface: WorkspaceSurface) => void;
|
|
||||||
prepareForRuntimeSwitch: (runtimeKey?: string | null) => void;
|
|
||||||
restoreForRuntimeSwitch: (runtimeKey?: string | null) => void;
|
|
||||||
setSurfaceGuard: (guard: WorkspaceSurfaceGuard | null) => void;
|
|
||||||
setPendingDiffFile: (filePath: string | null, staged?: boolean, scope?: PendingDiffScope | null) => void;
|
setPendingDiffFile: (filePath: string | null, staged?: boolean, scope?: PendingDiffScope | null) => void;
|
||||||
setPendingFileNavigation: (navigation: PendingFileNavigation | null) => void;
|
setPendingFileNavigation: (navigation: PendingFileNavigation | null) => void;
|
||||||
setPendingFileFocusPath: (path: string | null) => void;
|
setPendingFileFocusPath: (path: string | null) => void;
|
||||||
@@ -1019,9 +999,6 @@ export const useUIStore = create<UIStore>()(
|
|||||||
workStatusHiddenSections: [],
|
workStatusHiddenSections: [],
|
||||||
isSessionSwitcherOpen: false,
|
isSessionSwitcherOpen: false,
|
||||||
isSessionDropdownOpen: false,
|
isSessionDropdownOpen: false,
|
||||||
activeSurface: 'chat',
|
|
||||||
surfaceGuard: null,
|
|
||||||
sidebarOpenBeforeFullscreenTab: null,
|
|
||||||
pendingDiffFile: null,
|
pendingDiffFile: null,
|
||||||
pendingDiffStaged: false,
|
pendingDiffStaged: false,
|
||||||
pendingDiffScope: null,
|
pendingDiffScope: null,
|
||||||
@@ -1633,31 +1610,6 @@ export const useUIStore = create<UIStore>()(
|
|||||||
set({ isSessionDropdownOpen: open });
|
set({ isSessionDropdownOpen: open });
|
||||||
},
|
},
|
||||||
|
|
||||||
setSurfaceGuard: (guard) => {
|
|
||||||
if (get().surfaceGuard === guard) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
set({ surfaceGuard: guard });
|
|
||||||
},
|
|
||||||
|
|
||||||
setActiveSurface: (surface) => {
|
|
||||||
const guard = get().surfaceGuard;
|
|
||||||
if (guard && !guard(surface)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
activeSurfaceByRuntime.set(runtimeMemoryKey(), surface);
|
|
||||||
set({ activeSurface: surface });
|
|
||||||
},
|
|
||||||
|
|
||||||
prepareForRuntimeSwitch: (runtimeKey?: string | null) => {
|
|
||||||
activeSurfaceByRuntime.set(runtimeMemoryKey(runtimeKey), get().activeSurface);
|
|
||||||
},
|
|
||||||
|
|
||||||
restoreForRuntimeSwitch: (runtimeKey?: string | null) => {
|
|
||||||
const restored = activeSurfaceByRuntime.get(runtimeMemoryKey(runtimeKey)) ?? 'chat';
|
|
||||||
set({ activeSurface: restored });
|
|
||||||
},
|
|
||||||
|
|
||||||
setPendingDiffFile: (filePath, staged = false, scope = null) => {
|
setPendingDiffFile: (filePath, staged = false, scope = null) => {
|
||||||
set({
|
set({
|
||||||
pendingDiffFile: filePath,
|
pendingDiffFile: filePath,
|
||||||
@@ -1675,11 +1627,7 @@ export const useUIStore = create<UIStore>()(
|
|||||||
},
|
},
|
||||||
|
|
||||||
navigateToDiff: (filePath, staged = false, scope = null) => {
|
navigateToDiff: (filePath, staged = false, scope = null) => {
|
||||||
const guard = get().surfaceGuard;
|
set({ pendingDiffFile: filePath, pendingDiffStaged: staged, pendingDiffScope: scope });
|
||||||
if (guard && !guard('diff')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
set({ pendingDiffFile: filePath, pendingDiffStaged: staged, pendingDiffScope: scope, activeSurface: 'diff' });
|
|
||||||
},
|
},
|
||||||
|
|
||||||
consumePendingDiffFile: () => {
|
consumePendingDiffFile: () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user