diff --git a/apps/code/src/renderer/components/GlobalEventHandlers.tsx b/apps/code/src/renderer/components/GlobalEventHandlers.tsx index 8fb59afc2..e1453521d 100644 --- a/apps/code/src/renderer/components/GlobalEventHandlers.tsx +++ b/apps/code/src/renderer/components/GlobalEventHandlers.tsx @@ -73,17 +73,13 @@ export function GlobalEventHandlers({ const handleSwitchTask = useCallback( (index: number) => { - if (index === 0) { - navigateToTaskInput(); - } else { - const taskData = visualTaskOrder[index - 1]; - const task = taskData ? taskById.get(taskData.id) : undefined; - if (task) { - navigateToTask(task); - } + const taskData = visualTaskOrder[index - 1]; + const task = taskData ? taskById.get(taskData.id) : undefined; + if (task) { + navigateToTask(task); } }, - [visualTaskOrder, taskById, navigateToTask, navigateToTaskInput], + [visualTaskOrder, taskById, navigateToTask], ); const handlePrevTask = useCallback(() => { @@ -195,7 +191,7 @@ export function GlobalEventHandlers({ [handleToggleFocus], ); - // Task switching with mod+0-9 + // Task switching with mod+1-9 useHotkeys( SHORTCUTS.SWITCH_TASK, (event, handler) => { diff --git a/apps/code/src/renderer/components/ui/combobox/Combobox.css b/apps/code/src/renderer/components/ui/combobox/Combobox.css index 06d2ce115..e4f19acec 100644 --- a/apps/code/src/renderer/components/ui/combobox/Combobox.css +++ b/apps/code/src/renderer/components/ui/combobox/Combobox.css @@ -299,6 +299,7 @@ box-sizing: border-box; display: flex; align-items: center; + gap: 6px; width: 100%; height: var(--combobox-item-height); padding-left: var(--combobox-content-padding); diff --git a/apps/code/src/renderer/constants/keyboard-shortcuts.ts b/apps/code/src/renderer/constants/keyboard-shortcuts.ts index a4329c3e4..204ea94ee 100644 --- a/apps/code/src/renderer/constants/keyboard-shortcuts.ts +++ b/apps/code/src/renderer/constants/keyboard-shortcuts.ts @@ -13,7 +13,7 @@ export const SHORTCUTS = { NEXT_TASK: "mod+shift+],ctrl+tab", CLOSE_TAB: "mod+w", SWITCH_TAB: "ctrl+1,ctrl+2,ctrl+3,ctrl+4,ctrl+5,ctrl+6,ctrl+7,ctrl+8,ctrl+9", - SWITCH_TASK: "mod+0,mod+1,mod+2,mod+3,mod+4,mod+5,mod+6,mod+7,mod+8,mod+9", + SWITCH_TASK: "mod+1,mod+2,mod+3,mod+4,mod+5,mod+6,mod+7,mod+8,mod+9", OPEN_IN_EDITOR: "mod+o", COPY_PATH: "mod+shift+c", TOGGLE_FOCUS: "mod+r", @@ -68,8 +68,8 @@ export const KEYBOARD_SHORTCUTS: KeyboardShortcut[] = [ }, { id: "switch-task", - keys: "mod+0-9", - description: "Switch to task 1-9 (0 = home)", + keys: "mod+1-9", + description: "Switch to task 1-9", category: "navigation", }, { diff --git a/apps/code/src/renderer/features/auth/hooks/authQueries.ts b/apps/code/src/renderer/features/auth/hooks/authQueries.ts index de88c4d34..83f3c9cbe 100644 --- a/apps/code/src/renderer/features/auth/hooks/authQueries.ts +++ b/apps/code/src/renderer/features/auth/hooks/authQueries.ts @@ -58,9 +58,15 @@ export function useAuthState() { return useQuery({ ...getAuthStateQueryOptions(), placeholderData: ANONYMOUS_AUTH_STATE, + refetchOnMount: true, }); } +export function useAuthStateFetched(): boolean { + const { isFetched } = useAuthState(); + return isFetched; +} + export function useAuthStateValue(selector: (state: AuthState) => T): T { const { data } = useAuthState(); return selector(data ?? ANONYMOUS_AUTH_STATE); diff --git a/apps/code/src/renderer/features/command-center/components/CommandCenterPanel.tsx b/apps/code/src/renderer/features/command-center/components/CommandCenterPanel.tsx index 2e24a8862..07a8a28ab 100644 --- a/apps/code/src/renderer/features/command-center/components/CommandCenterPanel.tsx +++ b/apps/code/src/renderer/features/command-center/components/CommandCenterPanel.tsx @@ -1,6 +1,14 @@ import { useDraftStore } from "@features/message-editor/stores/draftStore"; import { TaskInput } from "@features/task-detail/components/TaskInput"; -import { ArrowsOut, Plus, X } from "@phosphor-icons/react"; +import type { WorkspaceMode } from "@main/services/workspace/schemas"; +import { + ArrowsOut, + Cloud, + Desktop, + GitFork, + Plus, + X, +} from "@phosphor-icons/react"; import { Flex, Text } from "@radix-ui/themes"; import type { Task } from "@shared/types"; import { useNavigationStore } from "@stores/navigationStore"; @@ -19,6 +27,27 @@ interface CommandCenterPanelProps { isActiveSession: boolean; } +const environmentConfig: Record< + WorkspaceMode, + { label: string; icon: typeof Desktop } +> = { + local: { label: "Local", icon: Desktop }, + worktree: { label: "Worktree", icon: GitFork }, + cloud: { label: "Cloud", icon: Cloud }, +}; + +function EnvironmentBadge({ mode }: { mode: WorkspaceMode | null }) { + if (!mode) return null; + const config = environmentConfig[mode]; + const Icon = config.icon; + return ( + + + {config.label} + + ); +} + function EmptyCell({ cellIndex }: { cellIndex: number }) { const [selectorOpen, setSelectorOpen] = useState(false); const isCreating = useCommandCenterStore((s) => @@ -148,6 +177,7 @@ function PopulatedCell({ + {cell.repoName && ( {cell.repoName} diff --git a/apps/code/src/renderer/features/command-center/hooks/useCommandCenterData.ts b/apps/code/src/renderer/features/command-center/hooks/useCommandCenterData.ts index a89d79d51..85560c306 100644 --- a/apps/code/src/renderer/features/command-center/hooks/useCommandCenterData.ts +++ b/apps/code/src/renderer/features/command-center/hooks/useCommandCenterData.ts @@ -1,6 +1,8 @@ import { useSessions } from "@features/sessions/hooks/useSession"; import type { AgentSession } from "@features/sessions/stores/sessionStore"; import { useTasks } from "@features/tasks/hooks/useTasks"; +import { useWorkspaces } from "@features/workspace/hooks/useWorkspace"; +import type { WorkspaceMode } from "@main/services/workspace/schemas"; import type { Task } from "@shared/types"; import { getTaskRepository, parseRepository } from "@utils/repository"; import { useMemo } from "react"; @@ -15,6 +17,7 @@ export interface CommandCenterCellData { session: AgentSession | undefined; status: CellStatus; repoName: string | null; + workspaceMode: WorkspaceMode | null; } export interface StatusSummary { @@ -56,6 +59,7 @@ export function useCommandCenterData(): { const storeCells = useCommandCenterStore((s) => s.cells); const { data: tasks = [] } = useTasks(); const sessions = useSessions(); + const { data: workspaces } = useWorkspaces(); const taskMap = useMemo(() => { const map = new Map(); @@ -81,10 +85,20 @@ export function useCommandCenterData(): { const session = taskId ? sessionByTaskId.get(taskId) : undefined; const status = taskId ? deriveStatus(session) : "idle"; const repoName = task ? getRepoName(task) : null; - - return { cellIndex, taskId, task, session, status, repoName }; + const workspaceMode = + (taskId ? workspaces?.[taskId]?.mode : null) ?? null; + + return { + cellIndex, + taskId, + task, + session, + status, + repoName, + workspaceMode, + }; }); - }, [storeCells, taskMap, sessionByTaskId]); + }, [storeCells, taskMap, sessionByTaskId, workspaces]); const summary = useMemo(() => { const populated = cells.filter((c) => c.taskId && c.task); diff --git a/apps/code/src/renderer/features/onboarding/components/ProjectSelectStep.tsx b/apps/code/src/renderer/features/onboarding/components/ProjectSelectStep.tsx index f79e0eb62..45ac0cb33 100644 --- a/apps/code/src/renderer/features/onboarding/components/ProjectSelectStep.tsx +++ b/apps/code/src/renderer/features/onboarding/components/ProjectSelectStep.tsx @@ -3,6 +3,7 @@ import { useOptionalAuthenticatedClient } from "@features/auth/hooks/authClient" import { useSelectProjectMutation } from "@features/auth/hooks/authMutations"; import { authKeys, + useAuthStateFetched, useAuthStateValue, useCurrentUser, } from "@features/auth/hooks/authQueries"; @@ -34,6 +35,7 @@ interface ProjectSelectStepProps { } export function ProjectSelectStep({ onNext, onBack }: ProjectSelectStepProps) { + const authFetched = useAuthStateFetched(); const isAuthenticated = useAuthStateValue((state) => state.status) === "authenticated"; const selectProjectMutation = useSelectProjectMutation(); @@ -134,7 +136,7 @@ export function ProjectSelectStep({ onNext, onBack }: ProjectSelectStepProps) { - ) : ( + ) : authFetched ? ( - )} + ) : null} diff --git a/apps/code/src/renderer/features/projects/hooks/useProjects.tsx b/apps/code/src/renderer/features/projects/hooks/useProjects.tsx index d9bf2bd1b..063539db6 100644 --- a/apps/code/src/renderer/features/projects/hooks/useProjects.tsx +++ b/apps/code/src/renderer/features/projects/hooks/useProjects.tsx @@ -45,7 +45,12 @@ export function useProjects() { ); const currentProjectId = useAuthStateValue((state) => state.projectId); const client = useOptionalAuthenticatedClient(); - const { data: currentUser, isLoading, error } = useCurrentUser({ client }); + const { + data: currentUser, + isLoading: isQueryLoading, + error, + } = useCurrentUser({ client }); + const isInitialLoading = isQueryLoading && !currentUser; const projects = useMemo(() => { if (!currentUser?.organization) return []; @@ -119,7 +124,7 @@ export function useProjects() { currentProject, currentProjectId, currentUser: currentUser ?? null, - isLoading, + isLoading: isInitialLoading, error, }; } diff --git a/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx b/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx index af1a3969c..10ea83948 100644 --- a/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx +++ b/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx @@ -1,9 +1,9 @@ import { useOptionalAuthenticatedClient } from "@features/auth/hooks/authClient"; +import { useLogoutMutation } from "@features/auth/hooks/authMutations"; import { useAuthStateValue, useCurrentUser, } from "@features/auth/hooks/authQueries"; -import { useAuthStore } from "@features/auth/stores/authStore"; import { type SettingsCategory, useSettingsDialogStore, @@ -129,6 +129,7 @@ export function SettingsDialog() { const { data: user } = useCurrentUser({ client }); const { seat, planLabel } = useSeat(); const billingEnabled = useFeatureFlag("posthog-code-billing"); + const logoutMutation = useLogoutMutation(); const sidebarItems = useMemo( () => @@ -231,8 +232,12 @@ export function SettingsDialog() { {isAuthenticated && ( + {!isOnTaskInput && ( + navigateToTaskInput()} + whileHover={{ scale: 1.05, backgroundColor: "var(--gray-4)" }} + whileTap={{ scale: 0.97 }} + > + Start building + + )} ) : organizeMode === "by-project" ? ( {title} - {action && ( - { - action.onClick(); - sonnerToast.dismiss(id); - }} - > - {action.label} - - )} + + {action && ( + { + action.onClick(); + sonnerToast.dismiss(id); + }} + > + {action.label} + + )} + {type !== "loading" && ( + sonnerToast.dismiss(id)} + > + + + )} + {description && ( @@ -112,7 +124,7 @@ export const toast = { error: ( title: ReactNode, - options?: { description?: string; id?: string | number }, + options?: { description?: string; id?: string | number; duration?: number }, ) => { return sonnerToast.custom( (id) => ( @@ -123,7 +135,7 @@ export const toast = { description={options?.description} /> ), - { id: options?.id }, + { id: options?.id, duration: options?.duration ?? 5000 }, ); },