diff --git a/src/browser/components/ChatInput/CreationControls.tsx b/src/browser/components/ChatInput/CreationControls.tsx index 3ad806fe42..dd01b02022 100644 --- a/src/browser/components/ChatInput/CreationControls.tsx +++ b/src/browser/components/ChatInput/CreationControls.tsx @@ -1,15 +1,21 @@ import React from "react"; import { RUNTIME_MODE, type RuntimeMode } from "@/common/types/runtime"; -import { TooltipWrapper, Tooltip } from "../Tooltip"; import { Select } from "../Select"; +import { RuntimeIconSelector } from "../RuntimeIconSelector"; interface CreationControlsProps { branches: string[]; trunkBranch: string; onTrunkBranchChange: (branch: string) => void; runtimeMode: RuntimeMode; + defaultRuntimeMode: RuntimeMode; sshHost: string; - onRuntimeChange: (mode: RuntimeMode, host: string) => void; + /** Called when user clicks a runtime icon to select it (does not persist) */ + onRuntimeModeChange: (mode: RuntimeMode) => void; + /** Called when user checks "Default for project" checkbox (persists) */ + onSetDefaultRuntime: (mode: RuntimeMode) => void; + /** Called when user changes SSH host */ + onSshHostChange: (host: string) => void; disabled: boolean; } @@ -25,40 +31,14 @@ export function CreationControls(props: CreationControlsProps) { return (
- {/* Runtime Selector - first */} -
- - props.onRuntimeChange(RUNTIME_MODE.SSH, e.target.value)} + onChange={(e) => props.onSshHostChange(e.target.value)} placeholder="user@host" disabled={props.disabled} className="bg-separator text-foreground border-border-medium focus:border-accent w-32 rounded border px-1 py-0.5 text-xs focus:outline-none disabled:opacity-50" diff --git a/src/browser/components/ChatInput/index.tsx b/src/browser/components/ChatInput/index.tsx index 7588eb53f9..994b83214c 100644 --- a/src/browser/components/ChatInput/index.tsx +++ b/src/browser/components/ChatInput/index.tsx @@ -1382,8 +1382,11 @@ export const ChatInput: React.FC = (props) => { trunkBranch={creationState.trunkBranch} onTrunkBranchChange={creationState.setTrunkBranch} runtimeMode={creationState.runtimeMode} + defaultRuntimeMode={creationState.defaultRuntimeMode} sshHost={creationState.sshHost} - onRuntimeChange={creationState.setRuntimeOptions} + onRuntimeModeChange={creationState.setRuntimeMode} + onSetDefaultRuntime={creationState.setDefaultRuntimeMode} + onSshHostChange={creationState.setSshHost} disabled={creationState.isSending || isSending} /> )} diff --git a/src/browser/components/ChatInput/useCreationWorkspace.test.tsx b/src/browser/components/ChatInput/useCreationWorkspace.test.tsx index 53ce12c51d..172f45f5fc 100644 --- a/src/browser/components/ChatInput/useCreationWorkspace.test.tsx +++ b/src/browser/components/ChatInput/useCreationWorkspace.test.tsx @@ -473,41 +473,58 @@ function createDraftSettingsHarness( sshHost: string; trunkBranch: string; runtimeString?: string | undefined; + defaultRuntimeMode?: RuntimeMode; }> ) { const state = { runtimeMode: initial?.runtimeMode ?? ("local" as RuntimeMode), + defaultRuntimeMode: initial?.defaultRuntimeMode ?? ("worktree" as RuntimeMode), sshHost: initial?.sshHost ?? "", trunkBranch: initial?.trunkBranch ?? "main", runtimeString: initial?.runtimeString, } satisfies { runtimeMode: RuntimeMode; + defaultRuntimeMode: RuntimeMode; sshHost: string; trunkBranch: string; runtimeString: string | undefined; }; - const setRuntimeOptions = mock((mode: RuntimeMode, host: string) => { + const setTrunkBranch = mock((branch: string) => { + state.trunkBranch = branch; + }); + + const getRuntimeString = mock(() => state.runtimeString); + + const setRuntimeMode = mock((mode: RuntimeMode) => { state.runtimeMode = mode; - state.sshHost = host; - const trimmedHost = host.trim(); + const trimmedHost = state.sshHost.trim(); state.runtimeString = mode === "ssh" ? (trimmedHost ? `ssh ${trimmedHost}` : "ssh") : undefined; }); - const setTrunkBranch = mock((branch: string) => { - state.trunkBranch = branch; + const setDefaultRuntimeMode = mock((mode: RuntimeMode) => { + state.defaultRuntimeMode = mode; + state.runtimeMode = mode; + const trimmedHost = state.sshHost.trim(); + state.runtimeString = mode === "ssh" ? (trimmedHost ? `ssh ${trimmedHost}` : "ssh") : undefined; }); - const getRuntimeString = mock(() => state.runtimeString); + const setSshHost = mock((host: string) => { + state.sshHost = host; + }); return { state, - setRuntimeOptions, + setRuntimeMode, + setDefaultRuntimeMode, + setSshHost, setTrunkBranch, getRuntimeString, snapshot(): { settings: DraftWorkspaceSettings; - setRuntimeOptions: typeof setRuntimeOptions; + setRuntimeMode: typeof setRuntimeMode; + setDefaultRuntimeMode: typeof setDefaultRuntimeMode; + setSshHost: typeof setSshHost; setTrunkBranch: typeof setTrunkBranch; getRuntimeString: typeof getRuntimeString; } { @@ -516,12 +533,15 @@ function createDraftSettingsHarness( thinkingLevel: "medium", mode: "exec", runtimeMode: state.runtimeMode, + defaultRuntimeMode: state.defaultRuntimeMode, sshHost: state.sshHost, trunkBranch: state.trunkBranch, }; return { settings, - setRuntimeOptions, + setRuntimeMode, + setDefaultRuntimeMode, + setSshHost, setTrunkBranch, getRuntimeString, }; diff --git a/src/browser/components/ChatInput/useCreationWorkspace.ts b/src/browser/components/ChatInput/useCreationWorkspace.ts index 98eea859bb..fcbf4a96b7 100644 --- a/src/browser/components/ChatInput/useCreationWorkspace.ts +++ b/src/browser/components/ChatInput/useCreationWorkspace.ts @@ -46,8 +46,14 @@ interface UseCreationWorkspaceReturn { trunkBranch: string; setTrunkBranch: (branch: string) => void; runtimeMode: RuntimeMode; + defaultRuntimeMode: RuntimeMode; sshHost: string; - setRuntimeOptions: (mode: RuntimeMode, host: string) => void; + /** Set the currently selected runtime mode (does not persist) */ + setRuntimeMode: (mode: RuntimeMode) => void; + /** Set the default runtime mode for this project (persists via checkbox) */ + setDefaultRuntimeMode: (mode: RuntimeMode) => void; + /** Set the SSH host (persisted separately from runtime mode) */ + setSshHost: (host: string) => void; toast: Toast | null; setToast: (toast: Toast | null) => void; isSending: boolean; @@ -72,8 +78,14 @@ export function useCreationWorkspace({ const [isSending, setIsSending] = useState(false); // Centralized draft workspace settings with automatic persistence - const { settings, setRuntimeOptions, setTrunkBranch, getRuntimeString } = - useDraftWorkspaceSettings(projectPath, branches, recommendedTrunk); + const { + settings, + setRuntimeMode, + setDefaultRuntimeMode, + setSshHost, + setTrunkBranch, + getRuntimeString, + } = useDraftWorkspaceSettings(projectPath, branches, recommendedTrunk); // Get send options from shared hook (uses project-scoped storage key) const sendMessageOptions = useSendMessageOptions(getProjectScopeId(projectPath)); @@ -180,8 +192,11 @@ export function useCreationWorkspace({ trunkBranch: settings.trunkBranch, setTrunkBranch, runtimeMode: settings.runtimeMode, + defaultRuntimeMode: settings.defaultRuntimeMode, sshHost: settings.sshHost, - setRuntimeOptions, + setRuntimeMode, + setDefaultRuntimeMode, + setSshHost, toast, setToast, isSending, diff --git a/src/browser/components/RuntimeBadge.tsx b/src/browser/components/RuntimeBadge.tsx index 5d895eb76e..e70bdf3bde 100644 --- a/src/browser/components/RuntimeBadge.tsx +++ b/src/browser/components/RuntimeBadge.tsx @@ -4,6 +4,7 @@ import type { RuntimeConfig } from "@/common/types/runtime"; import { isSSHRuntime, isWorktreeRuntime, isLocalProjectRuntime } from "@/common/types/runtime"; import { extractSshHostname } from "@/browser/utils/ui/runtimeBadge"; import { TooltipWrapper, Tooltip } from "./Tooltip"; +import { SSHIcon, WorktreeIcon, LocalIcon } from "./icons/RuntimeIcons"; interface RuntimeBadgeProps { runtimeConfig?: RuntimeConfig; @@ -12,72 +13,6 @@ interface RuntimeBadgeProps { isWorking?: boolean; } -/** Server rack icon for SSH runtime */ -function SSHIcon() { - return ( - - - - - - - ); -} - -/** Git branch icon for worktree runtime */ -function WorktreeIcon() { - return ( - - {/* Simplified git branch: vertical line with branch off */} - - - - - - - ); -} - -/** Folder icon for local project-dir runtime */ -function LocalIcon() { - return ( - - {/* Folder icon */} - - - ); -} - // Runtime-specific color schemes - each type has consistent colors in idle/working states // Idle: subtle with visible colored border for discrimination // Working: brighter colors with pulse animation diff --git a/src/browser/components/RuntimeIconSelector.tsx b/src/browser/components/RuntimeIconSelector.tsx new file mode 100644 index 0000000000..e95da78d86 --- /dev/null +++ b/src/browser/components/RuntimeIconSelector.tsx @@ -0,0 +1,133 @@ +import React from "react"; +import { cn } from "@/common/lib/utils"; +import { RUNTIME_MODE, type RuntimeMode } from "@/common/types/runtime"; +import { SSHIcon, WorktreeIcon, LocalIcon } from "./icons/RuntimeIcons"; +import { TooltipWrapper, Tooltip } from "./Tooltip"; + +interface RuntimeIconSelectorProps { + value: RuntimeMode; + onChange: (mode: RuntimeMode) => void; + /** The persisted default runtime for this project */ + defaultMode: RuntimeMode; + /** Called when user checks "Default for project" in tooltip */ + onSetDefault: (mode: RuntimeMode) => void; + disabled?: boolean; + className?: string; +} + +// Runtime-specific color schemes matching RuntimeBadge +// Selected (active) uses the "working" styling, unselected uses "idle" +const RUNTIME_STYLES = { + ssh: { + idle: "bg-transparent text-muted border-blue-500/30 hover:border-blue-500/50", + active: "bg-blue-500/20 text-blue-400 border-blue-500/60", + }, + worktree: { + idle: "bg-transparent text-muted border-purple-500/30 hover:border-purple-500/50", + active: "bg-purple-500/20 text-purple-400 border-purple-500/60", + }, + local: { + idle: "bg-transparent text-muted border-muted/30 hover:border-muted/50", + active: "bg-muted/30 text-foreground border-muted/60", + }, +} as const; + +const RUNTIME_INFO: Record = { + local: { + label: "Local", + description: "Work directly in project directory (no isolation)", + }, + worktree: { + label: "Worktree", + description: "Git worktree in ~/.mux/src (isolated)", + }, + ssh: { + label: "SSH", + description: "Remote clone on SSH host", + }, +}; + +interface RuntimeIconButtonProps { + mode: RuntimeMode; + isSelected: boolean; + isDefault: boolean; + onClick: () => void; + onSetDefault: () => void; + disabled?: boolean; +} + +function RuntimeIconButton(props: RuntimeIconButtonProps) { + const info = RUNTIME_INFO[props.mode]; + const styles = RUNTIME_STYLES[props.mode]; + const stateStyle = props.isSelected ? styles.active : styles.idle; + + const Icon = + props.mode === RUNTIME_MODE.SSH + ? SSHIcon + : props.mode === RUNTIME_MODE.WORKTREE + ? WorktreeIcon + : LocalIcon; + + return ( + + + + {info.label} +

{info.description}

+ +
+
+ ); +} + +/** + * Runtime selector using icons with tooltips. + * Shows Local, Worktree, and SSH options as clickable icons. + * Selected runtime uses "active" styling (brighter colors). + * Each tooltip has a "Default for project" checkbox to persist the preference. + */ +export function RuntimeIconSelector(props: RuntimeIconSelectorProps) { + const modes: RuntimeMode[] = [RUNTIME_MODE.LOCAL, RUNTIME_MODE.WORKTREE, RUNTIME_MODE.SSH]; + + return ( +
+ {modes.map((mode) => ( + props.onChange(mode)} + onSetDefault={() => props.onSetDefault(mode)} + disabled={props.disabled} + /> + ))} +
+ ); +} diff --git a/src/browser/components/icons/RuntimeIcons.tsx b/src/browser/components/icons/RuntimeIcons.tsx new file mode 100644 index 0000000000..51ae7b7c72 --- /dev/null +++ b/src/browser/components/icons/RuntimeIcons.tsx @@ -0,0 +1,75 @@ +import React from "react"; + +interface IconProps { + size?: number; + className?: string; +} + +/** Server rack icon for SSH runtime */ +export function SSHIcon({ size = 10, className }: IconProps) { + return ( + + + + + + + ); +} + +/** Git branch icon for worktree runtime */ +export function WorktreeIcon({ size = 10, className }: IconProps) { + return ( + + {/* Simplified git branch: vertical line with branch off */} + + + + + + + ); +} + +/** Folder icon for local project-dir runtime */ +export function LocalIcon({ size = 10, className }: IconProps) { + return ( + + {/* Folder icon */} + + + ); +} diff --git a/src/browser/hooks/useDraftWorkspaceSettings.ts b/src/browser/hooks/useDraftWorkspaceSettings.ts index fffb6304fc..b1aab7dd75 100644 --- a/src/browser/hooks/useDraftWorkspaceSettings.ts +++ b/src/browser/hooks/useDraftWorkspaceSettings.ts @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useState, useEffect } from "react"; import { usePersistedState } from "./usePersistedState"; import { useThinkingLevel } from "./useThinkingLevel"; import { useMode } from "@/browser/contexts/ModeContext"; @@ -29,7 +29,10 @@ export interface DraftWorkspaceSettings { mode: UIMode; // Workspace creation settings (project-specific) + /** Currently selected runtime for this workspace creation (may differ from default) */ runtimeMode: RuntimeMode; + /** Persisted default runtime for this project (used to initialize selection) */ + defaultRuntimeMode: RuntimeMode; sshHost: string; trunkBranch: string; } @@ -49,7 +52,11 @@ export function useDraftWorkspaceSettings( recommendedTrunk: string | null ): { settings: DraftWorkspaceSettings; - setRuntimeOptions: (mode: RuntimeMode, host: string) => void; + /** Set the currently selected runtime mode (does not persist) */ + setRuntimeMode: (mode: RuntimeMode) => void; + /** Set the default runtime mode for this project (persists via checkbox) */ + setDefaultRuntimeMode: (mode: RuntimeMode) => void; + setSshHost: (host: string) => void; setTrunkBranch: (branch: string) => void; getRuntimeString: () => string | undefined; } { @@ -65,13 +72,25 @@ export function useDraftWorkspaceSettings( { listener: true } ); - // Project-scoped runtime preference (persisted per project) - const [runtimeString, setRuntimeString] = usePersistedState( + // Project-scoped default runtime (worktree by default, only changed via checkbox) + const [defaultRuntimeString, setDefaultRuntimeString] = usePersistedState( getRuntimeKey(projectPath), - undefined, + undefined, // undefined means worktree (the app default) { listener: true } ); + // Parse default runtime string into mode (worktree when undefined) + const { mode: defaultRuntimeMode } = parseRuntimeModeAndHost(defaultRuntimeString); + + // Currently selected runtime mode for this session (initialized from default) + // This allows user to select a different runtime without changing the default + const [selectedRuntimeMode, setSelectedRuntimeMode] = useState(defaultRuntimeMode); + + // Sync selected mode when default changes (e.g., from checkbox or project switch) + useEffect(() => { + setSelectedRuntimeMode(defaultRuntimeMode); + }, [defaultRuntimeMode]); + // Project-scoped trunk branch preference (persisted per project) const [trunkBranch, setTrunkBranch] = usePersistedState( getTrunkBranchKey(projectPath), @@ -87,9 +106,6 @@ export function useDraftWorkspaceSettings( { listener: true } ); - // Parse runtime string into mode (host from runtime string is ignored in favor of lastSshHost) - const { mode: runtimeMode } = parseRuntimeModeAndHost(runtimeString); - // Initialize trunk branch from backend recommendation or first branch useEffect(() => { if (!trunkBranch && branches.length > 0) { @@ -98,19 +114,27 @@ export function useDraftWorkspaceSettings( } }, [branches, recommendedTrunk, trunkBranch, setTrunkBranch]); - // Setter for runtime options (updates persisted runtime mode and SSH host separately) - const setRuntimeOptions = (newMode: RuntimeMode, newHost: string) => { - const newRuntimeString = buildRuntimeString(newMode, newHost); - setRuntimeString(newRuntimeString); - // Always persist the SSH host separately so it's remembered across mode switches - if (newHost) { - setLastSshHost(newHost); - } + // Setter for selected runtime mode (changes current selection, does not persist) + const setRuntimeMode = (newMode: RuntimeMode) => { + setSelectedRuntimeMode(newMode); + }; + + // Setter for default runtime mode (persists via checkbox in tooltip) + const setDefaultRuntimeMode = (newMode: RuntimeMode) => { + const newRuntimeString = buildRuntimeString(newMode, lastSshHost); + setDefaultRuntimeString(newRuntimeString); + // Also update selection to match new default + setSelectedRuntimeMode(newMode); + }; + + // Setter for SSH host (persisted separately so it's remembered across mode switches) + const setSshHost = (newHost: string) => { + setLastSshHost(newHost); }; - // Helper to get runtime string for IPC calls + // Helper to get runtime string for IPC calls (uses selected mode, not default) const getRuntimeString = (): string | undefined => { - return buildRuntimeString(runtimeMode, lastSshHost); + return buildRuntimeString(selectedRuntimeMode, lastSshHost); }; return { @@ -118,11 +142,14 @@ export function useDraftWorkspaceSettings( model, thinkingLevel, mode, - runtimeMode, + runtimeMode: selectedRuntimeMode, + defaultRuntimeMode, sshHost: lastSshHost, trunkBranch, }, - setRuntimeOptions, + setRuntimeMode, + setDefaultRuntimeMode, + setSshHost, setTrunkBranch, getRuntimeString, }; diff --git a/src/browser/hooks/useStartWorkspaceCreation.test.ts b/src/browser/hooks/useStartWorkspaceCreation.test.ts index d7bab6c673..ae1330a6db 100644 --- a/src/browser/hooks/useStartWorkspaceCreation.test.ts +++ b/src/browser/hooks/useStartWorkspaceCreation.test.ts @@ -1,7 +1,6 @@ import { describe, expect, test } from "bun:test"; import { getFirstProjectPath, - normalizeRuntimePreference, persistWorkspaceCreationPrefill, type StartWorkspaceCreationDetail, } from "./useStartWorkspaceCreation"; @@ -10,7 +9,6 @@ import { getModelKey, getPendingScopeId, getProjectScopeId, - getRuntimeKey, getTrunkBranchKey, } from "@/common/constants/storage"; import type { ProjectConfig } from "@/node/config"; @@ -20,25 +18,6 @@ import type { updatePersistedState } from "@/browser/hooks/usePersistedState"; type PersistFn = typeof updatePersistedState; type PersistCall = [string, unknown, unknown?]; -describe("normalizeRuntimePreference", () => { - test("returns undefined for local or empty runtime", () => { - expect(normalizeRuntimePreference(undefined)).toBeUndefined(); - expect(normalizeRuntimePreference(" ")).toBeUndefined(); - expect(normalizeRuntimePreference("local")).toBeUndefined(); - expect(normalizeRuntimePreference("LOCAL")).toBeUndefined(); - }); - - test("normalizes ssh runtimes", () => { - expect(normalizeRuntimePreference("ssh")).toBe("ssh"); - expect(normalizeRuntimePreference("ssh host")).toBe("ssh host"); - expect(normalizeRuntimePreference("SSH user@host")).toBe("ssh user@host"); - }); - - test("returns trimmed custom runtime", () => { - expect(normalizeRuntimePreference(" custom-runtime ")).toBe("custom-runtime"); - }); -}); - describe("persistWorkspaceCreationPrefill", () => { const projectPath = "/tmp/project"; @@ -57,7 +36,7 @@ describe("persistWorkspaceCreationPrefill", () => { startMessage: "Ship it", model: "provider/model", trunkBranch: " main ", - runtime: " ssh dev ", + runtime: " ssh dev ", // runtime is NOT persisted - it's a one-time override }; const { persist, calls } = createPersistSpy(); @@ -71,14 +50,14 @@ describe("persistWorkspaceCreationPrefill", () => { expect(callMap.get(getInputKey(getPendingScopeId(projectPath)))).toBe("Ship it"); expect(callMap.get(getModelKey(getProjectScopeId(projectPath)))).toBe("provider/model"); expect(callMap.get(getTrunkBranchKey(projectPath))).toBe("main"); - expect(callMap.get(getRuntimeKey(projectPath))).toBe("ssh dev"); + // runtime is intentionally not persisted - default can only be changed via icon selector + expect(calls.length).toBe(3); }); test("clears persisted values when empty strings are provided", () => { const detail: StartWorkspaceCreationDetail = { projectPath, trunkBranch: " ", - runtime: " ", }; const { persist, calls } = createPersistSpy(); @@ -90,7 +69,6 @@ describe("persistWorkspaceCreationPrefill", () => { } expect(callMap.get(getTrunkBranchKey(projectPath))).toBeUndefined(); - expect(callMap.get(getRuntimeKey(projectPath))).toBeUndefined(); }); test("no-op when detail is undefined", () => { diff --git a/src/browser/hooks/useStartWorkspaceCreation.ts b/src/browser/hooks/useStartWorkspaceCreation.ts index 10d55de5dd..e5b7bda373 100644 --- a/src/browser/hooks/useStartWorkspaceCreation.ts +++ b/src/browser/hooks/useStartWorkspaceCreation.ts @@ -8,41 +8,12 @@ import { getModelKey, getPendingScopeId, getProjectScopeId, - getRuntimeKey, getTrunkBranchKey, } from "@/common/constants/storage"; -import { RUNTIME_MODE, SSH_RUNTIME_PREFIX } from "@/common/types/runtime"; export type StartWorkspaceCreationDetail = CustomEventPayloads[typeof CUSTOM_EVENTS.START_WORKSPACE_CREATION]; -export function normalizeRuntimePreference(runtime: string | undefined): string | undefined { - if (!runtime) { - return undefined; - } - - const trimmed = runtime.trim(); - if (!trimmed) { - return undefined; - } - - const lower = trimmed.toLowerCase(); - if (lower === RUNTIME_MODE.LOCAL) { - return undefined; - } - - if (lower === RUNTIME_MODE.SSH) { - return RUNTIME_MODE.SSH; - } - - if (lower.startsWith(SSH_RUNTIME_PREFIX)) { - const host = trimmed.slice(SSH_RUNTIME_PREFIX.length).trim(); - return host ? `${RUNTIME_MODE.SSH} ${host}` : RUNTIME_MODE.SSH; - } - - return trimmed; -} - export function getFirstProjectPath(projects: Map): string | null { const iterator = projects.keys().next(); return iterator.done ? null : iterator.value; @@ -75,10 +46,8 @@ export function persistWorkspaceCreationPrefill( ); } - if (detail.runtime !== undefined) { - const normalizedRuntime = normalizeRuntimePreference(detail.runtime); - persist(getRuntimeKey(projectPath), normalizedRuntime); - } + // Note: runtime is intentionally NOT persisted here - it's a one-time override. + // The default runtime can only be changed via the icon selector. } interface UseStartWorkspaceCreationOptions { diff --git a/src/browser/utils/chatCommands.ts b/src/browser/utils/chatCommands.ts index 89643d5dcb..f7fbdaab40 100644 --- a/src/browser/utils/chatCommands.ts +++ b/src/browser/utils/chatCommands.ts @@ -499,7 +499,7 @@ export async function createNewWorkspace( effectiveTrunk = recommendedTrunk ?? "main"; } - // Use saved runtime preference if not explicitly provided + // Use saved default runtime preference if not explicitly provided let effectiveRuntime = options.runtime; if (effectiveRuntime === undefined) { const runtimeKey = getRuntimeKey(options.projectPath); diff --git a/src/common/constants/storage.ts b/src/common/constants/storage.ts index 09b4724162..5987a6be42 100644 --- a/src/common/constants/storage.ts +++ b/src/common/constants/storage.ts @@ -110,7 +110,7 @@ export function getModeKey(workspaceId: string): string { /** * Get the localStorage key for the default runtime for a project - * Stores the last successfully used runtime config when creating a workspace + * Defaults to worktree if not set; can only be changed via the "Default for project" checkbox. * Format: "runtime:{projectPath}" */ export function getRuntimeKey(projectPath: string): string {