From bcd7eb12d82b3e90c8eb5b12a24152cabbbfda39 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Mon, 20 Apr 2026 19:06:19 -0700 Subject: [PATCH 01/10] Add dismiss button and reduce error toast duration --- apps/code/src/renderer/utils/toast.tsx | 50 +++++++++++++++++--------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/apps/code/src/renderer/utils/toast.tsx b/apps/code/src/renderer/utils/toast.tsx index ea0e21411..f64bbab69 100644 --- a/apps/code/src/renderer/utils/toast.tsx +++ b/apps/code/src/renderer/utils/toast.tsx @@ -1,5 +1,11 @@ -import { CheckIcon, InfoIcon, WarningIcon, XIcon } from "@phosphor-icons/react"; -import { Card, Flex, Spinner, Text } from "@radix-ui/themes"; +import { + CheckIcon, + InfoIcon, + WarningIcon, + XCircleIcon, + XIcon, +} from "@phosphor-icons/react"; +import { Card, Flex, IconButton, Spinner, Text } from "@radix-ui/themes"; import type { ReactNode } from "react"; import { toast as sonnerToast } from "sonner"; @@ -26,7 +32,7 @@ function ToastComponent(props: ToastProps) { case "success": return ; case "error": - return ; + return ; case "info": return ; case "warning": @@ -50,20 +56,30 @@ function ToastComponent(props: ToastProps) { {title} - {action && ( - + {action && ( + { + action.onClick(); + sonnerToast.dismiss(id); + }} + > + {action.label} + + )} + { - action.onClick(); - sonnerToast.dismiss(id); - }} + variant="ghost" + color="gray" + onClick={() => sonnerToast.dismiss(id)} > - {action.label} - - )} + + + {description && ( @@ -112,7 +128,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 +139,7 @@ export const toast = { description={options?.description} /> ), - { id: options?.id }, + { id: options?.id, duration: options?.duration ?? 5000 }, ); }, From 98c7638b41a89278b2958305f539a20df6e62dbd Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Mon, 20 Apr 2026 19:10:30 -0700 Subject: [PATCH 02/10] Shrink task filter dropdown to fit content --- .../src/renderer/features/sidebar/components/TaskListView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx b/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx index 820fe3be6..dda89ed77 100644 --- a/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx +++ b/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx @@ -145,7 +145,7 @@ function TaskFilterMenu() { align="start" side="bottom" sideOffset={6} - className="min-w-xs" + className="min-w-fit" > Organize Date: Mon, 20 Apr 2026 19:11:13 -0700 Subject: [PATCH 03/10] Add environment badge to command center panels --- .../components/CommandCenterPanel.tsx | 32 ++++++++++++++++++- .../hooks/useCommandCenterData.ts | 20 ++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) 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); From d6f41604f3de0e3083d61b4de5db13a79980b266 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Mon, 20 Apr 2026 19:19:00 -0700 Subject: [PATCH 04/10] Use canonical logout in settings sign-out --- .../features/settings/components/SettingsDialog.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx b/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx index af1a3969c..69e224373 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( () => @@ -232,7 +233,10 @@ export function SettingsDialog() { + {!isOnTaskInput && ( + navigateToTaskInput()} + whileHover={{ scale: 1.05, backgroundColor: "var(--gray-4)" }} + whileTap={{ scale: 0.97 }} + > + Start building + + )} ) : organizeMode === "by-project" ? ( Date: Mon, 20 Apr 2026 19:39:34 -0700 Subject: [PATCH 07/10] Fix auth state flicker on project select step --- .../src/renderer/features/auth/hooks/authQueries.ts | 6 ++++++ .../onboarding/components/ProjectSelectStep.tsx | 6 ++++-- .../renderer/features/projects/hooks/useProjects.tsx | 11 +++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) 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/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..20a0d3906 100644 --- a/apps/code/src/renderer/features/projects/hooks/useProjects.tsx +++ b/apps/code/src/renderer/features/projects/hooks/useProjects.tsx @@ -5,7 +5,7 @@ import { useCurrentUser, } from "@features/auth/hooks/authQueries"; import { logger } from "@utils/logger"; -import { useEffect, useMemo } from "react"; +import { useEffect, useMemo, useRef } from "react"; const log = logger.scope("useProjects"); @@ -45,7 +45,14 @@ 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 hasLoadedOnce = useRef(false); + if (currentUser) hasLoadedOnce.current = true; + const isLoading = isQueryLoading && !hasLoadedOnce.current; const projects = useMemo(() => { if (!currentUser?.organization) return []; From ff740e252bc95e177249e89fccb035a5616b257a Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Mon, 20 Apr 2026 20:56:29 -0700 Subject: [PATCH 08/10] Stop overriding CMD+0 so native zoom reset works --- .../renderer/components/GlobalEventHandlers.tsx | 14 +++++--------- .../src/renderer/constants/keyboard-shortcuts.ts | 6 +++--- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/apps/code/src/renderer/components/GlobalEventHandlers.tsx b/apps/code/src/renderer/components/GlobalEventHandlers.tsx index 8fb59afc2..297c3fdf7 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(() => { 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", }, { From 0cce297326fd7513a2f49bfa7741cd1078a56a8b Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Tue, 21 Apr 2026 12:24:27 -0700 Subject: [PATCH 09/10] Fix review nits and add defensive guards --- apps/code/src/renderer/components/GlobalEventHandlers.tsx | 2 +- .../command-center/components/CommandCenterPanel.tsx | 5 ++++- .../src/renderer/features/projects/hooks/useProjects.tsx | 6 ++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/code/src/renderer/components/GlobalEventHandlers.tsx b/apps/code/src/renderer/components/GlobalEventHandlers.tsx index 297c3fdf7..e1453521d 100644 --- a/apps/code/src/renderer/components/GlobalEventHandlers.tsx +++ b/apps/code/src/renderer/components/GlobalEventHandlers.tsx @@ -191,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/features/command-center/components/CommandCenterPanel.tsx b/apps/code/src/renderer/features/command-center/components/CommandCenterPanel.tsx index 07a8a28ab..b09a43ba2 100644 --- a/apps/code/src/renderer/features/command-center/components/CommandCenterPanel.tsx +++ b/apps/code/src/renderer/features/command-center/components/CommandCenterPanel.tsx @@ -38,7 +38,10 @@ const environmentConfig: Record< function EnvironmentBadge({ mode }: { mode: WorkspaceMode | null }) { if (!mode) return null; - const config = environmentConfig[mode]; + const config = environmentConfig[mode] as + | (typeof environmentConfig)[WorkspaceMode] + | undefined; + if (!config) return null; const Icon = config.icon; return ( diff --git a/apps/code/src/renderer/features/projects/hooks/useProjects.tsx b/apps/code/src/renderer/features/projects/hooks/useProjects.tsx index 20a0d3906..098fe0b93 100644 --- a/apps/code/src/renderer/features/projects/hooks/useProjects.tsx +++ b/apps/code/src/renderer/features/projects/hooks/useProjects.tsx @@ -5,7 +5,7 @@ import { useCurrentUser, } from "@features/auth/hooks/authQueries"; import { logger } from "@utils/logger"; -import { useEffect, useMemo, useRef } from "react"; +import { useEffect, useMemo } from "react"; const log = logger.scope("useProjects"); @@ -50,9 +50,7 @@ export function useProjects() { isLoading: isQueryLoading, error, } = useCurrentUser({ client }); - const hasLoadedOnce = useRef(false); - if (currentUser) hasLoadedOnce.current = true; - const isLoading = isQueryLoading && !hasLoadedOnce.current; + const isLoading = isQueryLoading && !currentUser; const projects = useMemo(() => { if (!currentUser?.organization) return []; From 58b436ee52414899fd177f5343464f7175197f0f Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Tue, 21 Apr 2026 12:27:17 -0700 Subject: [PATCH 10/10] Fix review feedback across toast, settings and command center --- .../components/CommandCenterPanel.tsx | 5 +---- .../features/projects/hooks/useProjects.tsx | 4 ++-- .../settings/components/SettingsDialog.tsx | 3 ++- .../sidebar/components/TaskListView.tsx | 2 +- apps/code/src/renderer/utils/toast.tsx | 18 ++++++++++-------- 5 files changed, 16 insertions(+), 16 deletions(-) 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 b09a43ba2..07a8a28ab 100644 --- a/apps/code/src/renderer/features/command-center/components/CommandCenterPanel.tsx +++ b/apps/code/src/renderer/features/command-center/components/CommandCenterPanel.tsx @@ -38,10 +38,7 @@ const environmentConfig: Record< function EnvironmentBadge({ mode }: { mode: WorkspaceMode | null }) { if (!mode) return null; - const config = environmentConfig[mode] as - | (typeof environmentConfig)[WorkspaceMode] - | undefined; - if (!config) return null; + const config = environmentConfig[mode]; const Icon = config.icon; return ( diff --git a/apps/code/src/renderer/features/projects/hooks/useProjects.tsx b/apps/code/src/renderer/features/projects/hooks/useProjects.tsx index 098fe0b93..063539db6 100644 --- a/apps/code/src/renderer/features/projects/hooks/useProjects.tsx +++ b/apps/code/src/renderer/features/projects/hooks/useProjects.tsx @@ -50,7 +50,7 @@ export function useProjects() { isLoading: isQueryLoading, error, } = useCurrentUser({ client }); - const isLoading = isQueryLoading && !currentUser; + const isInitialLoading = isQueryLoading && !currentUser; const projects = useMemo(() => { if (!currentUser?.organization) return []; @@ -124,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 69e224373..10ea83948 100644 --- a/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx +++ b/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx @@ -232,7 +232,8 @@ export function SettingsDialog() { {isAuthenticated && (