diff --git a/apps/desktop/src/lib/trpc/routers/settings/index.ts b/apps/desktop/src/lib/trpc/routers/settings/index.ts
index ba0dcfa465e..4676a7d9ed9 100644
--- a/apps/desktop/src/lib/trpc/routers/settings/index.ts
+++ b/apps/desktop/src/lib/trpc/routers/settings/index.ts
@@ -29,6 +29,18 @@ function getSettings() {
return row;
}
+/** Get presets tagged with a given auto-apply field, falling back to the isDefault preset */
+export function getPresetsForTrigger(
+ field: "applyOnWorkspaceCreated" | "applyOnNewTab",
+) {
+ const row = getSettings();
+ const presets = row.terminalPresets ?? [];
+ const tagged = presets.filter((p) => p[field]);
+ if (tagged.length > 0) return tagged;
+ const defaultPreset = presets.find((p) => p.isDefault);
+ return defaultPreset ? [defaultPreset] : [];
+}
+
export const createSettingsRouter = () => {
return router({
getLastUsedApp: publicProcedure.query(() => {
@@ -159,6 +171,54 @@ export const createSettingsRouter = () => {
return { success: true };
}),
+ setPresetAutoApply: publicProcedure
+ .input(
+ z.object({
+ id: z.string(),
+ field: z.enum(["applyOnWorkspaceCreated", "applyOnNewTab"]),
+ enabled: z.boolean(),
+ }),
+ )
+ .mutation(({ input }) => {
+ const row = getSettings();
+ const presets = row.terminalPresets ?? [];
+
+ const updatedPresets = presets.map((p) => {
+ if (p.id !== input.id) return p;
+
+ // Migrate legacy isDefault preset to explicit fields on first toggle
+ const needsMigration =
+ p.isDefault &&
+ p.applyOnWorkspaceCreated === undefined &&
+ p.applyOnNewTab === undefined;
+
+ const base = needsMigration
+ ? {
+ ...p,
+ isDefault: undefined,
+ applyOnWorkspaceCreated: true as const,
+ applyOnNewTab: true as const,
+ }
+ : p;
+
+ return {
+ ...base,
+ [input.field]: input.enabled ? true : undefined,
+ };
+ });
+
+ localDb
+ .insert(settings)
+ .values({ id: 1, terminalPresets: updatedPresets })
+ .onConflictDoUpdate({
+ target: settings.id,
+ set: { terminalPresets: updatedPresets },
+ })
+ .run();
+
+ return { success: true };
+ }),
+
reorderTerminalPresets: publicProcedure
.input(
z.object({
@@ -206,6 +266,14 @@ export const createSettingsRouter = () => {
return presets.find((p) => p.isDefault) ?? null;
}),
+ getWorkspaceCreationPresets: publicProcedure.query(() =>
+ getPresetsForTrigger("applyOnWorkspaceCreated"),
+ ),
+
+ getNewTabPresets: publicProcedure.query(() =>
+ getPresetsForTrigger("applyOnNewTab"),
+ ),
+
getSelectedRingtoneId: publicProcedure.query(() => {
const row = getSettings();
const storedId = row.selectedRingtoneId;
diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/procedures/init.ts b/apps/desktop/src/lib/trpc/routers/workspaces/procedures/init.ts
index 0e380dc3e57..ce8780614fe 100644
--- a/apps/desktop/src/lib/trpc/routers/workspaces/procedures/init.ts
+++ b/apps/desktop/src/lib/trpc/routers/workspaces/procedures/init.ts
@@ -1,21 +1,13 @@
-import { settings } from "@superset/local-db";
import { observable } from "@trpc/server/observable";
-import { localDb } from "main/lib/local-db";
import { workspaceInitManager } from "main/lib/workspace-init-manager";
import type { WorkspaceInitProgress } from "shared/types/workspace-init";
import { z } from "zod";
import { publicProcedure, router } from "../../..";
+import { getPresetsForTrigger } from "../../settings";
import { getProject, getWorkspaceWithRelations } from "../utils/db-helpers";
import { loadSetupConfig } from "../utils/setup";
import { initializeWorkspaceWorktree } from "../utils/workspace-init";
-function getDefaultPreset() {
- const row = localDb.select().from(settings).get();
- if (!row) return null;
- const presets = row.terminalPresets ?? [];
- return presets.find((p) => p.isDefault) ?? null;
-}
-
export const createInitProcedures = () => {
return router({
onInitProgress: publicProcedure
@@ -120,12 +112,12 @@ export const createInitProcedures = () => {
worktreePath: relations.worktree?.path,
projectName: project.name,
});
- const defaultPreset = getDefaultPreset();
+ const defaultPresets = getPresetsForTrigger("applyOnWorkspaceCreated");
return {
projectId: project.id,
initialCommands: setupConfig?.setup ?? null,
- defaultPreset,
+ defaultPresets,
};
}),
});
diff --git a/apps/desktop/src/renderer/react-query/presets/index.ts b/apps/desktop/src/renderer/react-query/presets/index.ts
index a2c2b888985..b4c340034e2 100644
--- a/apps/desktop/src/renderer/react-query/presets/index.ts
+++ b/apps/desktop/src/renderer/react-query/presets/index.ts
@@ -48,18 +48,19 @@ function useDeleteTerminalPreset(
});
}
-function useSetDefaultPreset(
+function useSetPresetAutoApply(
options?: Parameters<
- typeof electronTrpc.settings.setDefaultPreset.useMutation
+ typeof electronTrpc.settings.setPresetAutoApply.useMutation
>[0],
) {
const utils = electronTrpc.useUtils();
- return electronTrpc.settings.setDefaultPreset.useMutation({
+ return electronTrpc.settings.setPresetAutoApply.useMutation({
...options,
onSuccess: async (...args) => {
await utils.settings.getTerminalPresets.invalidate();
- await utils.settings.getDefaultPreset.invalidate();
+ await utils.settings.getWorkspaceCreationPresets.invalidate();
+ await utils.settings.getNewTabPresets.invalidate();
await options?.onSuccess?.(...args);
},
});
@@ -81,31 +82,23 @@ function useReorderTerminalPresets(
});
}
-/**
- * Combined hook for accessing terminal presets with all CRUD operations
- * Provides easy access to presets data and mutations from anywhere in the app
- */
export function usePresets() {
const { data: presets = [], isLoading } =
electronTrpc.settings.getTerminalPresets.useQuery();
- const { data: defaultPreset } =
- electronTrpc.settings.getDefaultPreset.useQuery();
-
const createPreset = useCreateTerminalPreset();
const updatePreset = useUpdateTerminalPreset();
const deletePreset = useDeleteTerminalPreset();
- const setDefaultPreset = useSetDefaultPreset();
+ const setPresetAutoApply = useSetPresetAutoApply();
const reorderPresets = useReorderTerminalPresets();
return {
presets,
- defaultPreset,
isLoading,
createPreset,
updatePreset,
deletePreset,
- setDefaultPreset,
+ setPresetAutoApply,
reorderPresets,
};
}
diff --git a/apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts b/apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
index 468761474a1..699bf40dbab 100644
--- a/apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
+++ b/apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
@@ -38,7 +38,7 @@ export function useCreateBranchWorkspace(
workspaceId: data.workspace.id,
projectId: data.projectId,
initialCommands: setupData?.initialCommands ?? null,
- defaultPreset: setupData?.defaultPreset ?? null,
+ defaultPresets: setupData?.defaultPresets ?? [],
});
// Branch workspaces skip git init, so mark ready immediately to trigger terminal setup
diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/start-claude-session.ts b/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/start-claude-session.ts
index 50ca43bf59e..6c7bb002ab9 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/start-claude-session.ts
+++ b/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/start-claude-session.ts
@@ -33,8 +33,7 @@ async function execute(
workspaceId: workspace.id,
projectId: pending?.projectId ?? workspace.projectId,
initialCommands: [...(pending?.initialCommands ?? []), params.command],
- // Preserve undefined (signals "fetch from backend") vs null (no preset needed)
- defaultPreset: pending ? pending.defaultPreset : null,
+ defaultPresets: pending?.defaultPresets,
});
return {
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/presets/types.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/presets/types.ts
index df42000bffd..11586832422 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/settings/presets/types.ts
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/presets/types.ts
@@ -9,6 +9,7 @@ export interface PresetColumnConfig {
label: string;
placeholder: string;
mono?: boolean;
+ tooltip?: string;
}
export const PRESET_COLUMNS: PresetColumnConfig[] = [
@@ -20,9 +21,11 @@ export const PRESET_COLUMNS: PresetColumnConfig[] = [
},
{
key: "cwd",
- label: "CWD",
+ label: "Directory",
placeholder: "e.g. ./src (optional)",
mono: true,
+ tooltip:
+ "Working directory for the terminal session (relative to workspace root)",
},
{
key: "commands",
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/TerminalSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/TerminalSettings.tsx
index 8800cdab342..136ad182d72 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/TerminalSettings.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/TerminalSettings.tsx
@@ -1,116 +1,39 @@
-import type {
- ExecutionMode,
- TerminalLinkBehavior,
- TerminalPreset,
-} from "@superset/local-db";
-import {
- AlertDialog,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
-} from "@superset/ui/alert-dialog";
-import { Button } from "@superset/ui/button";
-import { Label } from "@superset/ui/label";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@superset/ui/select";
-import { toast } from "@superset/ui/sonner";
-import { Switch } from "@superset/ui/switch";
-import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
-import { useCallback, useEffect, useMemo, useRef, useState } from "react";
-import {
- HiOutlineCheck,
- HiOutlinePlus,
- HiOutlineQuestionMarkCircle,
-} from "react-icons/hi2";
-import {
- getPresetIcon,
- useIsDarkTheme,
-} from "renderer/assets/app-icons/preset-icons";
-import { electronTrpc } from "renderer/lib/electron-trpc";
-import { usePresets } from "renderer/react-query/presets";
-import {
- PRESET_COLUMNS,
- type PresetColumnKey,
-} from "renderer/routes/_authenticated/settings/presets/types";
-import { DEFAULT_AUTO_APPLY_DEFAULT_PRESET } from "shared/constants";
+import type { ReactNode } from "react";
import {
isItemVisible,
SETTING_ITEM_ID,
type SettingItemId,
} from "../../../utils/settings-search";
-import { PresetRow } from "./components/PresetRow";
-
-interface PresetTemplate {
- name: string;
- preset: {
- name: string;
- description: string;
- cwd: string;
- commands: string[];
- };
-}
-
-const PRESET_TEMPLATES: PresetTemplate[] = [
- {
- name: "codex",
- preset: {
- name: "codex",
- description: "Danger mode: All permissions auto-approved",
- cwd: "",
- commands: [
- 'codex -c model_reasoning_effort="high" --ask-for-approval never --sandbox danger-full-access -c model_reasoning_summary="detailed" -c model_supports_reasoning_summaries=true',
- ],
- },
- },
- {
- name: "claude",
- preset: {
- name: "claude",
- description: "Danger mode: All permissions auto-approved",
- cwd: "",
- commands: ["claude --dangerously-skip-permissions"],
- },
- },
- {
- name: "gemini",
- preset: {
- name: "gemini",
- description: "Danger mode: All permissions auto-approved",
- cwd: "",
- commands: ["gemini --yolo"],
- },
- },
- {
- name: "cursor-agent",
- preset: {
- name: "cursor-agent",
- description: "Cursor AI agent for terminal-based coding assistance",
- cwd: "",
- commands: ["cursor-agent"],
- },
- },
- {
- name: "opencode",
- preset: {
- name: "opencode",
- description: "OpenCode: Open-source AI coding agent",
- cwd: "",
- commands: ["opencode"],
- },
- },
-];
+import { AutoApplyPresetSetting } from "./components/AutoApplyPresetSetting";
+import { LinkBehaviorSetting } from "./components/LinkBehaviorSetting";
+import { PresetsSection } from "./components/PresetsSection";
+import { SessionsSection } from "./components/SessionsSection";
interface TerminalSettingsProps {
visibleItems?: SettingItemId[] | null;
}
+/**
+ * Renders a list of visible sections with automatic border separators.
+ * Each section is its own component that owns its data-fetching,
+ * so query resolutions in one section don't re-render others.
+ */
+function SectionList({ children }: { children: ReactNode[] }) {
+ const visibleChildren = children.filter(Boolean);
+ return (
+
+ {visibleChildren.map((child, i) => (
+
0 ? "pt-6 border-t mt-6" : ""}
+ >
+ {child}
+
+ ))}
+
+ );
+}
+
export function TerminalSettings({ visibleItems }: TerminalSettingsProps) {
const showPresets = isItemVisible(
SETTING_ITEM_ID.TERMINAL_PRESETS,
@@ -124,385 +47,14 @@ export function TerminalSettings({ visibleItems }: TerminalSettingsProps) {
SETTING_ITEM_ID.TERMINAL_AUTO_APPLY_PRESET,
visibleItems,
);
- const showSessions = isItemVisible(
- SETTING_ITEM_ID.TERMINAL_SESSIONS,
- visibleItems,
- );
const showLinkBehavior = isItemVisible(
SETTING_ITEM_ID.TERMINAL_LINK_BEHAVIOR,
visibleItems,
);
-
- const utils = electronTrpc.useUtils();
- const isDark = useIsDarkTheme();
-
- // Presets
- const {
- presets: serverPresets,
- isLoading: isLoadingPresets,
- createPreset,
- updatePreset,
- deletePreset,
- setDefaultPreset,
- reorderPresets,
- } = usePresets();
- const [localPresets, setLocalPresets] =
- useState(serverPresets);
- const presetsContainerRef = useRef(null);
- const prevPresetsCountRef = useRef(serverPresets.length);
- const serverPresetsRef = useRef(serverPresets);
-
- useEffect(() => {
- serverPresetsRef.current = serverPresets;
- }, [serverPresets]);
-
- useEffect(() => {
- setLocalPresets(serverPresets);
-
- if (serverPresets.length > prevPresetsCountRef.current) {
- requestAnimationFrame(() => {
- presetsContainerRef.current?.scrollTo({
- top: presetsContainerRef.current.scrollHeight,
- behavior: "smooth",
- });
- });
- }
- prevPresetsCountRef.current = serverPresets.length;
- }, [serverPresets]);
-
- const existingPresetNames = useMemo(
- () => new Set(serverPresets.map((p) => p.name)),
- [serverPresets],
- );
-
- const isTemplateAdded = (template: PresetTemplate) =>
- existingPresetNames.has(template.preset.name);
-
- const handleCellChange = useCallback(
- (rowIndex: number, column: PresetColumnKey, value: string) => {
- setLocalPresets((prev) =>
- prev.map((p, i) => (i === rowIndex ? { ...p, [column]: value } : p)),
- );
- },
- [],
- );
-
- const handleCellBlur = useCallback(
- (rowIndex: number, column: PresetColumnKey) => {
- setLocalPresets((currentLocal) => {
- const preset = currentLocal[rowIndex];
- if (!preset) return currentLocal;
- const serverPreset = serverPresetsRef.current.find(
- (p) => p.id === preset.id,
- );
- if (!serverPreset) return currentLocal;
- if (preset[column] === serverPreset[column]) return currentLocal;
-
- updatePreset.mutate({
- id: preset.id,
- patch: { [column]: preset[column] },
- });
- return currentLocal;
- });
- },
- [updatePreset],
- );
-
- const handleCommandsChange = useCallback(
- (rowIndex: number, commands: string[]) => {
- setLocalPresets((prev) => {
- const preset = prev[rowIndex];
- const isDelete = preset && commands.length < preset.commands.length;
- const newPresets = prev.map((p, i) =>
- i === rowIndex ? { ...p, commands } : p,
- );
-
- // Save immediately on delete since onBlur won't have the updated state yet
- if (isDelete && preset) {
- updatePreset.mutate({
- id: preset.id,
- patch: { commands },
- });
- }
- return newPresets;
- });
- },
- [updatePreset],
- );
-
- const handleCommandsBlur = useCallback(
- (rowIndex: number) => {
- setLocalPresets((currentLocal) => {
- const preset = currentLocal[rowIndex];
- if (!preset) return currentLocal;
- const serverPreset = serverPresetsRef.current.find(
- (p) => p.id === preset.id,
- );
- if (!serverPreset) return currentLocal;
- if (
- JSON.stringify(preset.commands) ===
- JSON.stringify(serverPreset.commands)
- )
- return currentLocal;
-
- updatePreset.mutate({
- id: preset.id,
- patch: { commands: preset.commands },
- });
- return currentLocal;
- });
- },
- [updatePreset],
- );
-
- const handleExecutionModeChange = useCallback(
- (rowIndex: number, mode: ExecutionMode) => {
- setLocalPresets((currentLocal) => {
- const preset = currentLocal[rowIndex];
- if (!preset) return currentLocal;
-
- const newPresets = currentLocal.map((p, i) =>
- i === rowIndex ? { ...p, executionMode: mode } : p,
- );
-
- updatePreset.mutate({
- id: preset.id,
- patch: { executionMode: mode },
- });
-
- return newPresets;
- });
- },
- [updatePreset],
- );
-
- const handleAddRow = useCallback(() => {
- createPreset.mutate({
- name: "",
- cwd: "",
- commands: [""],
- });
- }, [createPreset]);
-
- const handleAddTemplate = useCallback(
- (template: PresetTemplate) => {
- if (existingPresetNames.has(template.preset.name)) return;
- createPreset.mutate(template.preset);
- },
- [createPreset, existingPresetNames],
- );
-
- const handleDeleteRow = useCallback(
- (rowIndex: number) => {
- setLocalPresets((currentLocal) => {
- const preset = currentLocal[rowIndex];
- if (preset) {
- deletePreset.mutate({ id: preset.id });
- }
- return currentLocal;
- });
- },
- [deletePreset],
- );
-
- const handleSetDefault = useCallback(
- (presetId: string | null) => {
- setDefaultPreset.mutate({ id: presetId });
- },
- [setDefaultPreset],
- );
-
- const handleLocalReorder = useCallback(
- (fromIndex: number, toIndex: number) => {
- setLocalPresets((prev) => {
- const newPresets = [...prev];
- const [removed] = newPresets.splice(fromIndex, 1);
- newPresets.splice(toIndex, 0, removed);
- return newPresets;
- });
- },
- [],
- );
-
- const handlePersistReorder = useCallback(
- (presetId: string, targetIndex: number) => {
- reorderPresets.mutate({ presetId, targetIndex });
- },
- [reorderPresets],
- );
-
- const { data: daemonSessions } =
- electronTrpc.terminal.listDaemonSessions.useQuery();
- const sessions = daemonSessions?.sessions ?? [];
- const aliveSessions = useMemo(
- () => sessions.filter((session) => session.isAlive),
- [sessions],
+ const showSessions = isItemVisible(
+ SETTING_ITEM_ID.TERMINAL_SESSIONS,
+ visibleItems,
);
- const sessionsSorted = useMemo(() => {
- return [...aliveSessions].sort((a, b) => {
- // Attached sessions first, then newest attach time.
- if (a.attachedClients !== b.attachedClients) {
- return b.attachedClients - a.attachedClients;
- }
- const aTime = a.lastAttachedAt ? Date.parse(a.lastAttachedAt) : 0;
- const bTime = b.lastAttachedAt ? Date.parse(b.lastAttachedAt) : 0;
- return bTime - aTime;
- });
- }, [aliveSessions]);
-
- const [confirmKillAllOpen, setConfirmKillAllOpen] = useState(false);
- const [confirmClearHistoryOpen, setConfirmClearHistoryOpen] = useState(false);
- const [confirmRestartDaemonOpen, setConfirmRestartDaemonOpen] =
- useState(false);
- const [showSessionList, setShowSessionList] = useState(false);
- const [pendingKillSession, setPendingKillSession] = useState<{
- sessionId: string;
- workspaceId: string;
- } | null>(null);
-
- // Terminal link behavior setting
- const { data: terminalLinkBehavior, isLoading: isLoadingLinkBehavior } =
- electronTrpc.settings.getTerminalLinkBehavior.useQuery();
-
- const setTerminalLinkBehavior =
- electronTrpc.settings.setTerminalLinkBehavior.useMutation({
- onMutate: async ({ behavior }) => {
- await utils.settings.getTerminalLinkBehavior.cancel();
- const previous = utils.settings.getTerminalLinkBehavior.getData();
- utils.settings.getTerminalLinkBehavior.setData(undefined, behavior);
- return { previous };
- },
- onError: (_err, _vars, context) => {
- if (context?.previous !== undefined) {
- utils.settings.getTerminalLinkBehavior.setData(
- undefined,
- context.previous,
- );
- }
- },
- onSettled: () => {
- utils.settings.getTerminalLinkBehavior.invalidate();
- },
- });
-
- const handleLinkBehaviorChange = (value: string) => {
- setTerminalLinkBehavior.mutate({
- behavior: value as TerminalLinkBehavior,
- });
- };
-
- // Auto-apply default preset setting
- const { data: autoApplyDefaultPreset, isLoading: isLoadingAutoApply } =
- electronTrpc.settings.getAutoApplyDefaultPreset.useQuery();
-
- const setAutoApplyDefaultPreset =
- electronTrpc.settings.setAutoApplyDefaultPreset.useMutation({
- onMutate: async ({ enabled }) => {
- await utils.settings.getAutoApplyDefaultPreset.cancel();
- const previous = utils.settings.getAutoApplyDefaultPreset.getData();
- utils.settings.getAutoApplyDefaultPreset.setData(undefined, enabled);
- return { previous };
- },
- onError: (_err, _vars, context) => {
- if (context?.previous !== undefined) {
- utils.settings.getAutoApplyDefaultPreset.setData(
- undefined,
- context.previous,
- );
- }
- },
- onSettled: () => {
- utils.settings.getAutoApplyDefaultPreset.invalidate();
- },
- });
-
- const handleAutoApplyToggle = (enabled: boolean) => {
- setAutoApplyDefaultPreset.mutate({ enabled });
- };
-
- const killAllDaemonSessions =
- electronTrpc.terminal.killAllDaemonSessions.useMutation({
- onMutate: async () => {
- await utils.terminal.listDaemonSessions.cancel();
- const previous = utils.terminal.listDaemonSessions.getData();
- utils.terminal.listDaemonSessions.setData(undefined, {
- sessions: [],
- });
- return { previous };
- },
- onSuccess: (result) => {
- if (result.remainingCount > 0) {
- toast.warning("Some sessions could not be killed", {
- description: `${result.killedCount} terminated, ${result.remainingCount} remaining`,
- });
- } else {
- toast.success("Killed all terminal sessions", {
- description: `${result.killedCount} sessions terminated`,
- });
- }
- },
- onError: (error, _vars, context) => {
- if (context?.previous) {
- utils.terminal.listDaemonSessions.setData(
- undefined,
- context.previous,
- );
- }
- toast.error("Failed to kill sessions", {
- description: error.message,
- });
- },
- onSettled: () => {
- setTimeout(() => {
- utils.terminal.listDaemonSessions.invalidate();
- }, 300);
- },
- });
-
- const clearTerminalHistory =
- electronTrpc.terminal.clearTerminalHistory.useMutation({
- onSuccess: () => {
- toast.success("Cleared terminal history");
- utils.terminal.listDaemonSessions.invalidate();
- },
- onError: (error) => {
- toast.error("Failed to clear terminal history", {
- description: error.message,
- });
- },
- });
-
- const killDaemonSession = electronTrpc.terminal.kill.useMutation({
- onSuccess: () => {
- toast.success("Killed terminal session");
- utils.terminal.listDaemonSessions.invalidate();
- },
- onError: (error) => {
- toast.error("Failed to kill session", {
- description: error.message,
- });
- },
- });
-
- const restartDaemon = electronTrpc.terminal.restartDaemon.useMutation({
- onSuccess: () => {
- toast.success("Daemon restarted", {
- description:
- "Terminal daemon has been restarted. Open a terminal to spawn a fresh daemon.",
- });
- utils.terminal.listDaemonSessions.invalidate();
- },
- onError: (error) => {
- toast.error("Failed to restart daemon", {
- description: error.message,
- });
- },
- });
-
- const formatTimestamp = (value?: string) => {
- if (!value) return "—";
- return value.replace("T", " ").replace(/\.\d+Z$/, "Z");
- };
return (
@@ -513,536 +65,18 @@ export function TerminalSettings({ visibleItems }: TerminalSettingsProps) {
-
- {/* Presets Section */}
+
{(showPresets || showQuickAdd) && (
-
-
-
-
Terminal Presets
-
- Presets let you quickly launch terminals with pre-configured
- commands.
-
-
- {showPresets && (
-
-
- Add Preset
-
- )}
-
-
- {showQuickAdd && (
-
-
- Quick add:
-
- {PRESET_TEMPLATES.map((template) => {
- const alreadyAdded = isTemplateAdded(template);
- const presetIcon = getPresetIcon(template.name, isDark);
- return (
-
-
- handleAddTemplate(template)}
- disabled={alreadyAdded || createPreset.isPending}
- >
- {alreadyAdded ? (
-
- ) : presetIcon ? (
-
- ) : null}
- {template.name}
-
-
-
- {alreadyAdded
- ? "Already added"
- : template.preset.description}
-
-
- );
- })}
-
- )}
-
- {showPresets && (
-
-
-
- {PRESET_COLUMNS.map((column) => (
-
- {column.label}
-
- ))}
-
-
-
- Mode
-
-
-
-
- Execution Mode
-
- Sequential: Commands run one after
- another in a single terminal (joined with &&)
-
-
- Parallel: Each command runs in its own
- split pane within a single tab
-
-
-
-
- Actions
-
-
-
-
- {isLoadingPresets ? (
-
- Loading presets...
-
- ) : localPresets.length > 0 ? (
- localPresets.map((preset, index) => (
-
- ))
- ) : (
-
- No presets yet. Click "Add Preset" to create your first
- preset.
-
- )}
-
-
- )}
-
+
)}
-
- {showAutoApplyPreset && (
-
-
-
- Auto-apply default preset
-
-
- Automatically apply your default preset when creating new
- workspaces
-
-
-
-
- )}
-
- {showLinkBehavior && (
-
-
-
- Terminal file links
-
-
- Choose how to open file paths when Cmd+clicking in the terminal
-
-
-
-
-
-
-
- External editor
- File viewer
-
-
-
- )}
-
- {showSessions && (
-
-
-
- Manage sessions
- utils.terminal.listDaemonSessions.invalidate()}
- >
- Refresh
-
-
-
- Daemon sessions running: {aliveSessions.length}
-
- {aliveSessions.length >= 20 && (
-
- Large numbers of persistent terminals can increase CPU/memory
- usage. Consider killing old sessions if you notice slowdowns.
-
- )}
-
-
-
- setConfirmKillAllOpen(true)}
- >
- Kill all sessions
-
- setConfirmClearHistoryOpen(true)}
- >
- Clear terminal history
-
- setConfirmRestartDaemonOpen(true)}
- >
- Restart daemon
-
- setShowSessionList((v) => !v)}
- >
- {showSessionList ? "Hide sessions" : "Show sessions"}
-
-
-
- {showSessionList && aliveSessions.length > 0 && (
-
-
-
-
-
-
- Workspace
-
-
- Session
-
-
- Clients
-
-
- PID
-
-
- Last attached
-
-
- Action
-
-
-
-
- {sessionsSorted.map((session) => (
-
-
- {session.workspaceId}
-
-
- {session.sessionId}
-
-
- {session.attachedClients}
-
-
- {session.pid ?? "—"}
-
-
- {formatTimestamp(session.lastAttachedAt)}
-
-
-
- setPendingKillSession({
- sessionId: session.sessionId,
- workspaceId: session.workspaceId,
- })
- }
- >
- Kill
-
-
-
- ))}
-
-
-
-
- )}
-
- )}
-
-
-
-
-
-
- Kill all terminal sessions?
-
-
-
-
- This will terminate all persistent terminal processes (builds,
- tests, agents, etc.).
-
-
- You can't undo this action. Terminal panes will show "Process
- exited" and can be restarted.
-
-
-
-
-
- setConfirmKillAllOpen(false)}
- >
- Cancel
-
- {
- setConfirmKillAllOpen(false);
- killAllDaemonSessions.mutate();
- }}
- >
- Kill all
-
-
-
-
-
-
-
-
-
- Clear terminal history?
-
-
-
-
- This deletes the saved scrollback used for reboot/crash
- recovery.
-
-
- Running terminal processes continue, but older output may no
- longer be available after restarting the app.
-
-
-
-
-
- setConfirmClearHistoryOpen(false)}
- >
- Cancel
-
- {
- setConfirmClearHistoryOpen(false);
- clearTerminalHistory.mutate();
- }}
- >
- Clear history
-
-
-
-
-
- {
- if (!open) setPendingKillSession(null);
- }}
- >
-
-
-
- Kill terminal session?
-
-
-
-
- This will terminate the session and its underlying process.
-
- {pendingKillSession && (
-
- {pendingKillSession.workspaceId} /{" "}
- {pendingKillSession.sessionId}
-
- )}
-
-
-
-
- setPendingKillSession(null)}
- >
- Cancel
-
- {
- const sessionId = pendingKillSession?.sessionId;
- setPendingKillSession(null);
- if (!sessionId) return;
- killDaemonSession.mutate({ paneId: sessionId });
- }}
- >
- Kill
-
-
-
-
-
-
-
-
-
- Restart terminal daemon?
-
-
-
-
- This will shut down the terminal daemon process and kill all
- running sessions. Use this to fix terminals that are stuck or
- unresponsive.
-
-
- A fresh daemon will start automatically when you open a new
- terminal.
-
-
-
-
-
- setConfirmRestartDaemonOpen(false)}
- >
- Cancel
-
- {
- setConfirmRestartDaemonOpen(false);
- restartDaemon.mutate(undefined, {});
- }}
- >
- Restart daemon
-
-
-
-
+ {showAutoApplyPreset && }
+ {showLinkBehavior && }
+ {showSessions && }
+
);
}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/AutoApplyPresetSetting.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/AutoApplyPresetSetting.tsx
new file mode 100644
index 00000000000..4f69fe7142d
--- /dev/null
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/AutoApplyPresetSetting.tsx
@@ -0,0 +1,54 @@
+import { Label } from "@superset/ui/label";
+import { Switch } from "@superset/ui/switch";
+import { electronTrpc } from "renderer/lib/electron-trpc";
+import { DEFAULT_AUTO_APPLY_DEFAULT_PRESET } from "shared/constants";
+
+export function AutoApplyPresetSetting() {
+ const utils = electronTrpc.useUtils();
+
+ const { data: autoApplyDefaultPreset, isLoading } =
+ electronTrpc.settings.getAutoApplyDefaultPreset.useQuery();
+
+ const setAutoApplyDefaultPreset =
+ electronTrpc.settings.setAutoApplyDefaultPreset.useMutation({
+ onMutate: async ({ enabled }) => {
+ await utils.settings.getAutoApplyDefaultPreset.cancel();
+ const previous = utils.settings.getAutoApplyDefaultPreset.getData();
+ utils.settings.getAutoApplyDefaultPreset.setData(undefined, enabled);
+ return { previous };
+ },
+ onError: (_err, _vars, context) => {
+ if (context?.previous !== undefined) {
+ utils.settings.getAutoApplyDefaultPreset.setData(
+ undefined,
+ context.previous,
+ );
+ }
+ },
+ onSettled: () => {
+ utils.settings.getAutoApplyDefaultPreset.invalidate();
+ },
+ });
+
+ return (
+
+
+
+ Auto-apply default preset
+
+
+ Automatically apply the workspace creation preset when creating new
+ workspaces
+
+
+
+ setAutoApplyDefaultPreset.mutate({ enabled })
+ }
+ disabled={isLoading || setAutoApplyDefaultPreset.isPending}
+ />
+
+ );
+}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/LinkBehaviorSetting.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/LinkBehaviorSetting.tsx
new file mode 100644
index 00000000000..963a2157f40
--- /dev/null
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/LinkBehaviorSetting.tsx
@@ -0,0 +1,68 @@
+import type { TerminalLinkBehavior } from "@superset/local-db";
+import { Label } from "@superset/ui/label";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@superset/ui/select";
+import { electronTrpc } from "renderer/lib/electron-trpc";
+
+export function LinkBehaviorSetting() {
+ const utils = electronTrpc.useUtils();
+
+ const { data: terminalLinkBehavior, isLoading } =
+ electronTrpc.settings.getTerminalLinkBehavior.useQuery();
+
+ const setTerminalLinkBehavior =
+ electronTrpc.settings.setTerminalLinkBehavior.useMutation({
+ onMutate: async ({ behavior }) => {
+ await utils.settings.getTerminalLinkBehavior.cancel();
+ const previous = utils.settings.getTerminalLinkBehavior.getData();
+ utils.settings.getTerminalLinkBehavior.setData(undefined, behavior);
+ return { previous };
+ },
+ onError: (_err, _vars, context) => {
+ if (context?.previous !== undefined) {
+ utils.settings.getTerminalLinkBehavior.setData(
+ undefined,
+ context.previous,
+ );
+ }
+ },
+ onSettled: () => {
+ utils.settings.getTerminalLinkBehavior.invalidate();
+ },
+ });
+
+ return (
+
+
+
+ Terminal file links
+
+
+ Choose how to open file paths when Cmd+clicking in the terminal
+
+
+
+ setTerminalLinkBehavior.mutate({
+ behavior: value as TerminalLinkBehavior,
+ })
+ }
+ disabled={isLoading || setTerminalLinkBehavior.isPending}
+ >
+
+
+
+
+ External editor
+ File viewer
+
+
+
+ );
+}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetRow/PresetRow.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetRow/PresetRow.tsx
index 785d5e298db..d959a374295 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetRow/PresetRow.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetRow/PresetRow.tsx
@@ -1,5 +1,5 @@
import { EXECUTION_MODES, type ExecutionMode } from "@superset/local-db";
-import { Button } from "@superset/ui/button";
+import { Checkbox } from "@superset/ui/checkbox";
import { Input } from "@superset/ui/input";
import {
Select,
@@ -8,10 +8,8 @@ import {
SelectTrigger,
SelectValue,
} from "@superset/ui/select";
-import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
-import { useRef } from "react";
+import { useEffect, useRef } from "react";
import { useDrag, useDrop } from "react-dnd";
-import { HiOutlineStar, HiStar } from "react-icons/hi2";
import { LuGripVertical, LuTrash } from "react-icons/lu";
import {
PRESET_COLUMNS,
@@ -67,6 +65,8 @@ function PresetCell({
);
}
+type AutoApplyField = "applyOnWorkspaceCreated" | "applyOnNewTab";
+
interface PresetRowProps {
preset: TerminalPreset;
rowIndex: number;
@@ -77,7 +77,11 @@ interface PresetRowProps {
onCommandsBlur: (rowIndex: number) => void;
onExecutionModeChange: (rowIndex: number, mode: ExecutionMode) => void;
onDelete: (rowIndex: number) => void;
- onSetDefault: (presetId: string | null) => void;
+ onToggleAutoApply: (
+ presetId: string,
+ field: AutoApplyField,
+ enabled: boolean,
+ ) => void;
onLocalReorder: (fromIndex: number, toIndex: number) => void;
onPersistReorder: (presetId: string, targetIndex: number) => void;
}
@@ -92,7 +96,7 @@ export function PresetRow({
onCommandsBlur,
onExecutionModeChange,
onDelete,
- onSetDefault,
+ onToggleAutoApply,
onLocalReorder,
onPersistReorder,
}: PresetRowProps) {
@@ -125,12 +129,17 @@ export function PresetRow({
},
});
- preview(drop(rowRef));
- drag(dragHandleRef);
+ useEffect(() => {
+ preview(drop(rowRef));
+ drag(dragHandleRef);
+ }, [preview, drop, drag]);
- const handleToggleDefault = () => {
- onSetDefault(preset.isDefault ? null : preset.id);
- };
+ const isWorkspaceCreation =
+ preset.applyOnWorkspaceCreated ||
+ (!preset.applyOnNewTab && preset.isDefault);
+ const isNewTab =
+ preset.applyOnNewTab ||
+ (!preset.applyOnWorkspaceCreated && preset.isDefault);
return (
-
-
-
-
- {preset.isDefault ? (
-
- ) : (
-
- )}
-
-
-
- {preset.isDefault
- ? "Remove as default"
- : "Set as default for new terminals"}
-
-
-
+
+ onToggleAutoApply(
+ preset.id,
+ "applyOnWorkspaceCreated",
+ checked === true,
+ )
+ }
+ />
+
+
+
+ onToggleAutoApply(preset.id, "applyOnNewTab", checked === true)
+ }
+ />
+
+
+ onDelete(rowIndex)}
- className="h-8 w-8 p-0 hover:bg-destructive/10 hover:text-destructive"
+ className="h-7 w-7 flex items-center justify-center rounded-md text-muted-foreground hover:bg-destructive/10 hover:text-destructive transition-colors"
aria-label="Delete row"
>
-
-
+
+
);
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection.tsx
new file mode 100644
index 00000000000..76d939614e9
--- /dev/null
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection.tsx
@@ -0,0 +1,458 @@
+import type { ExecutionMode, TerminalPreset } from "@superset/local-db";
+import { Button } from "@superset/ui/button";
+import { Label } from "@superset/ui/label";
+import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import {
+ HiOutlineCheck,
+ HiOutlinePlus,
+ HiOutlineQuestionMarkCircle,
+} from "react-icons/hi2";
+import {
+ getPresetIcon,
+ useIsDarkTheme,
+} from "renderer/assets/app-icons/preset-icons";
+import { usePresets } from "renderer/react-query/presets";
+import {
+ PRESET_COLUMNS,
+ type PresetColumnKey,
+} from "renderer/routes/_authenticated/settings/presets/types";
+import { PresetRow } from "./PresetRow";
+
+interface PresetTemplate {
+ name: string;
+ preset: {
+ name: string;
+ description: string;
+ cwd: string;
+ commands: string[];
+ };
+}
+
+const PRESET_TEMPLATES: PresetTemplate[] = [
+ {
+ name: "codex",
+ preset: {
+ name: "codex",
+ description: "Danger mode: All permissions auto-approved",
+ cwd: "",
+ commands: [
+ 'codex -c model_reasoning_effort="high" --ask-for-approval never --sandbox danger-full-access -c model_reasoning_summary="detailed" -c model_supports_reasoning_summaries=true',
+ ],
+ },
+ },
+ {
+ name: "claude",
+ preset: {
+ name: "claude",
+ description: "Danger mode: All permissions auto-approved",
+ cwd: "",
+ commands: ["claude --dangerously-skip-permissions"],
+ },
+ },
+ {
+ name: "gemini",
+ preset: {
+ name: "gemini",
+ description: "Danger mode: All permissions auto-approved",
+ cwd: "",
+ commands: ["gemini --yolo"],
+ },
+ },
+ {
+ name: "cursor-agent",
+ preset: {
+ name: "cursor-agent",
+ description: "Cursor AI agent for terminal-based coding assistance",
+ cwd: "",
+ commands: ["cursor-agent"],
+ },
+ },
+ {
+ name: "opencode",
+ preset: {
+ name: "opencode",
+ description: "OpenCode: Open-source AI coding agent",
+ cwd: "",
+ commands: ["opencode"],
+ },
+ },
+];
+
+interface PresetsSectionProps {
+ showPresets: boolean;
+ showQuickAdd: boolean;
+}
+
+export function PresetsSection({
+ showPresets,
+ showQuickAdd,
+}: PresetsSectionProps) {
+ const isDark = useIsDarkTheme();
+ const {
+ presets: serverPresets,
+ isLoading: isLoadingPresets,
+ createPreset,
+ updatePreset,
+ deletePreset,
+ setPresetAutoApply,
+ reorderPresets,
+ } = usePresets();
+
+ const [localPresets, setLocalPresets] =
+ useState(serverPresets);
+ const presetsContainerRef = useRef(null);
+ const prevPresetsCountRef = useRef(serverPresets.length);
+ const serverPresetsRef = useRef(serverPresets);
+
+ useEffect(() => {
+ serverPresetsRef.current = serverPresets;
+ }, [serverPresets]);
+
+ useEffect(() => {
+ setLocalPresets(serverPresets);
+
+ if (serverPresets.length > prevPresetsCountRef.current) {
+ requestAnimationFrame(() => {
+ presetsContainerRef.current?.scrollTo({
+ top: presetsContainerRef.current.scrollHeight,
+ behavior: "smooth",
+ });
+ });
+ }
+ prevPresetsCountRef.current = serverPresets.length;
+ }, [serverPresets]);
+
+ const existingPresetNames = useMemo(
+ () => new Set(serverPresets.map((p) => p.name)),
+ [serverPresets],
+ );
+
+ const isTemplateAdded = (template: PresetTemplate) =>
+ existingPresetNames.has(template.preset.name);
+
+ const handleCellChange = useCallback(
+ (rowIndex: number, column: PresetColumnKey, value: string) => {
+ setLocalPresets((prev) =>
+ prev.map((p, i) => (i === rowIndex ? { ...p, [column]: value } : p)),
+ );
+ },
+ [],
+ );
+
+ const handleCellBlur = useCallback(
+ (rowIndex: number, column: PresetColumnKey) => {
+ setLocalPresets((currentLocal) => {
+ const preset = currentLocal[rowIndex];
+ if (!preset) return currentLocal;
+ const serverPreset = serverPresetsRef.current.find(
+ (p) => p.id === preset.id,
+ );
+ if (!serverPreset) return currentLocal;
+ if (preset[column] === serverPreset[column]) return currentLocal;
+
+ updatePreset.mutate({
+ id: preset.id,
+ patch: { [column]: preset[column] },
+ });
+ return currentLocal;
+ });
+ },
+ [updatePreset],
+ );
+
+ const handleCommandsChange = useCallback(
+ (rowIndex: number, commands: string[]) => {
+ setLocalPresets((prev) => {
+ const preset = prev[rowIndex];
+ const isDelete = preset && commands.length < preset.commands.length;
+ const newPresets = prev.map((p, i) =>
+ i === rowIndex ? { ...p, commands } : p,
+ );
+
+ if (isDelete && preset) {
+ updatePreset.mutate({
+ id: preset.id,
+ patch: { commands },
+ });
+ }
+ return newPresets;
+ });
+ },
+ [updatePreset],
+ );
+
+ const handleCommandsBlur = useCallback(
+ (rowIndex: number) => {
+ setLocalPresets((currentLocal) => {
+ const preset = currentLocal[rowIndex];
+ if (!preset) return currentLocal;
+ const serverPreset = serverPresetsRef.current.find(
+ (p) => p.id === preset.id,
+ );
+ if (!serverPreset) return currentLocal;
+ if (
+ JSON.stringify(preset.commands) ===
+ JSON.stringify(serverPreset.commands)
+ )
+ return currentLocal;
+
+ updatePreset.mutate({
+ id: preset.id,
+ patch: { commands: preset.commands },
+ });
+ return currentLocal;
+ });
+ },
+ [updatePreset],
+ );
+
+ const handleExecutionModeChange = useCallback(
+ (rowIndex: number, mode: ExecutionMode) => {
+ setLocalPresets((currentLocal) => {
+ const preset = currentLocal[rowIndex];
+ if (!preset) return currentLocal;
+
+ const newPresets = currentLocal.map((p, i) =>
+ i === rowIndex ? { ...p, executionMode: mode } : p,
+ );
+
+ updatePreset.mutate({
+ id: preset.id,
+ patch: { executionMode: mode },
+ });
+
+ return newPresets;
+ });
+ },
+ [updatePreset],
+ );
+
+ const handleAddRow = useCallback(() => {
+ createPreset.mutate({
+ name: "",
+ cwd: "",
+ commands: [""],
+ });
+ }, [createPreset]);
+
+ const handleAddTemplate = useCallback(
+ (template: PresetTemplate) => {
+ if (existingPresetNames.has(template.preset.name)) return;
+ createPreset.mutate(template.preset);
+ },
+ [createPreset, existingPresetNames],
+ );
+
+ const handleDeleteRow = useCallback(
+ (rowIndex: number) => {
+ setLocalPresets((currentLocal) => {
+ const preset = currentLocal[rowIndex];
+ if (preset) {
+ deletePreset.mutate({ id: preset.id });
+ }
+ return currentLocal;
+ });
+ },
+ [deletePreset],
+ );
+
+ const handleToggleAutoApply = useCallback(
+ (
+ presetId: string,
+ field: "applyOnWorkspaceCreated" | "applyOnNewTab",
+ enabled: boolean,
+ ) => {
+ setPresetAutoApply.mutate({ id: presetId, field, enabled });
+ },
+ [setPresetAutoApply],
+ );
+
+ const handleLocalReorder = useCallback(
+ (fromIndex: number, toIndex: number) => {
+ setLocalPresets((prev) => {
+ const newPresets = [...prev];
+ const [removed] = newPresets.splice(fromIndex, 1);
+ newPresets.splice(toIndex, 0, removed);
+ return newPresets;
+ });
+ },
+ [],
+ );
+
+ const handlePersistReorder = useCallback(
+ (presetId: string, targetIndex: number) => {
+ reorderPresets.mutate({ presetId, targetIndex });
+ },
+ [reorderPresets],
+ );
+
+ return (
+
+
+
+
Terminal Presets
+
+ Presets let you quickly launch terminals with pre-configured
+ commands.
+
+
+ {showPresets && (
+
+
+ Add Preset
+
+ )}
+
+
+ {showQuickAdd && (
+
+
+ Quick add:
+
+ {PRESET_TEMPLATES.map((template) => {
+ const alreadyAdded = isTemplateAdded(template);
+ const presetIcon = getPresetIcon(template.name, isDark);
+ return (
+
+
+ handleAddTemplate(template)}
+ disabled={alreadyAdded || createPreset.isPending}
+ >
+ {alreadyAdded ? (
+
+ ) : presetIcon ? (
+
+ ) : null}
+ {template.name}
+
+
+
+ {alreadyAdded ? "Already added" : template.preset.description}
+
+
+ );
+ })}
+
+ )}
+
+ {showPresets && (
+
+
+
+ {PRESET_COLUMNS.map((column) =>
+ column.tooltip ? (
+
+
+
+ {column.label}
+
+
+
+
+ {column.tooltip}
+
+
+ ) : (
+
+ {column.label}
+
+ ),
+ )}
+
+
+
+ Mode
+
+
+
+
+ Execution Mode
+
+ Sequential: Commands run one after another in
+ a single terminal (joined with &&)
+
+
+ Parallel: Each command runs in its own split
+ pane within a single tab
+
+
+
+
+
+
+ Workspace
+
+
+
+
+ Auto-run this preset when creating a new workspace
+
+
+
+
+
+ Tab
+
+
+
+
+ Auto-run this preset when opening a new tab
+
+
+
+
+
+
+ {isLoadingPresets ? (
+
+ Loading presets...
+
+ ) : localPresets.length > 0 ? (
+ localPresets.map((preset, index) => (
+
+ ))
+ ) : (
+
+ No presets yet. Click "Add Preset" to create your first preset.
+
+ )}
+
+
+ )}
+
+ );
+}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/SessionsSection.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/SessionsSection.tsx
new file mode 100644
index 00000000000..36dda370f0f
--- /dev/null
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/SessionsSection.tsx
@@ -0,0 +1,442 @@
+import {
+ AlertDialog,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from "@superset/ui/alert-dialog";
+import { Button } from "@superset/ui/button";
+import { Label } from "@superset/ui/label";
+import { toast } from "@superset/ui/sonner";
+import { useMemo, useState } from "react";
+import { electronTrpc } from "renderer/lib/electron-trpc";
+
+export function SessionsSection() {
+ const utils = electronTrpc.useUtils();
+
+ const { data: daemonSessions } =
+ electronTrpc.terminal.listDaemonSessions.useQuery();
+ const sessions = daemonSessions?.sessions ?? [];
+ const aliveSessions = useMemo(
+ () => sessions.filter((session) => session.isAlive),
+ [sessions],
+ );
+ const sessionsSorted = useMemo(() => {
+ return [...aliveSessions].sort((a, b) => {
+ if (a.attachedClients !== b.attachedClients) {
+ return b.attachedClients - a.attachedClients;
+ }
+ const aTime = a.lastAttachedAt ? Date.parse(a.lastAttachedAt) : 0;
+ const bTime = b.lastAttachedAt ? Date.parse(b.lastAttachedAt) : 0;
+ return bTime - aTime;
+ });
+ }, [aliveSessions]);
+
+ const [confirmKillAllOpen, setConfirmKillAllOpen] = useState(false);
+ const [confirmClearHistoryOpen, setConfirmClearHistoryOpen] = useState(false);
+ const [confirmRestartDaemonOpen, setConfirmRestartDaemonOpen] =
+ useState(false);
+ const [showSessionList, setShowSessionList] = useState(false);
+ const [pendingKillSession, setPendingKillSession] = useState<{
+ sessionId: string;
+ workspaceId: string;
+ } | null>(null);
+
+ const killAllDaemonSessions =
+ electronTrpc.terminal.killAllDaemonSessions.useMutation({
+ onMutate: async () => {
+ await utils.terminal.listDaemonSessions.cancel();
+ const previous = utils.terminal.listDaemonSessions.getData();
+ utils.terminal.listDaemonSessions.setData(undefined, {
+ sessions: [],
+ });
+ return { previous };
+ },
+ onSuccess: (result) => {
+ if (result.remainingCount > 0) {
+ toast.warning("Some sessions could not be killed", {
+ description: `${result.killedCount} terminated, ${result.remainingCount} remaining`,
+ });
+ } else {
+ toast.success("Killed all terminal sessions", {
+ description: `${result.killedCount} sessions terminated`,
+ });
+ }
+ },
+ onError: (error, _vars, context) => {
+ if (context?.previous) {
+ utils.terminal.listDaemonSessions.setData(
+ undefined,
+ context.previous,
+ );
+ }
+ toast.error("Failed to kill sessions", {
+ description: error.message,
+ });
+ },
+ onSettled: () => {
+ setTimeout(() => {
+ utils.terminal.listDaemonSessions.invalidate();
+ }, 300);
+ },
+ });
+
+ const clearTerminalHistory =
+ electronTrpc.terminal.clearTerminalHistory.useMutation({
+ onSuccess: () => {
+ toast.success("Cleared terminal history");
+ utils.terminal.listDaemonSessions.invalidate();
+ },
+ onError: (error) => {
+ toast.error("Failed to clear terminal history", {
+ description: error.message,
+ });
+ },
+ });
+
+ const killDaemonSession = electronTrpc.terminal.kill.useMutation({
+ onSuccess: () => {
+ toast.success("Killed terminal session");
+ utils.terminal.listDaemonSessions.invalidate();
+ },
+ onError: (error) => {
+ toast.error("Failed to kill session", {
+ description: error.message,
+ });
+ },
+ });
+
+ const restartDaemon = electronTrpc.terminal.restartDaemon.useMutation({
+ onSuccess: () => {
+ toast.success("Daemon restarted", {
+ description:
+ "Terminal daemon has been restarted. Open a terminal to spawn a fresh daemon.",
+ });
+ utils.terminal.listDaemonSessions.invalidate();
+ },
+ onError: (error) => {
+ toast.error("Failed to restart daemon", {
+ description: error.message,
+ });
+ },
+ });
+
+ const formatTimestamp = (value?: string) => {
+ if (!value) return "—";
+ return value.replace("T", " ").replace(/\.\d+Z$/, "Z");
+ };
+
+ return (
+ <>
+
+
+
+ Manage sessions
+ utils.terminal.listDaemonSessions.invalidate()}
+ >
+ Refresh
+
+
+
+ Daemon sessions running: {aliveSessions.length}
+
+ {aliveSessions.length >= 20 && (
+
+ Large numbers of persistent terminals can increase CPU/memory
+ usage. Consider killing old sessions if you notice slowdowns.
+
+ )}
+
+
+
+ setConfirmKillAllOpen(true)}
+ >
+ Kill all sessions
+
+ setConfirmClearHistoryOpen(true)}
+ >
+ Clear terminal history
+
+ setConfirmRestartDaemonOpen(true)}
+ >
+ Restart daemon
+
+ setShowSessionList((v) => !v)}
+ >
+ {showSessionList ? "Hide sessions" : "Show sessions"}
+
+
+
+ {showSessionList && aliveSessions.length > 0 && (
+
+
+
+
+
+
+ Workspace
+
+ Session
+
+ Clients
+
+ PID
+
+ Last attached
+
+ Action
+
+
+
+ {sessionsSorted.map((session) => (
+
+
+ {session.workspaceId}
+
+
+ {session.sessionId}
+
+
+ {session.attachedClients}
+
+
+ {session.pid ?? "—"}
+
+
+ {formatTimestamp(session.lastAttachedAt)}
+
+
+
+ setPendingKillSession({
+ sessionId: session.sessionId,
+ workspaceId: session.workspaceId,
+ })
+ }
+ >
+ Kill
+
+
+
+ ))}
+
+
+
+
+ )}
+
+
+
+
+
+
+ Kill all terminal sessions?
+
+
+
+
+ This will terminate all persistent terminal processes (builds,
+ tests, agents, etc.).
+
+
+ You can't undo this action. Terminal panes will show "Process
+ exited" and can be restarted.
+
+
+
+
+
+ setConfirmKillAllOpen(false)}
+ >
+ Cancel
+
+ {
+ setConfirmKillAllOpen(false);
+ killAllDaemonSessions.mutate();
+ }}
+ >
+ Kill all
+
+
+
+
+
+
+
+
+
+ Clear terminal history?
+
+
+
+
+ This deletes the saved scrollback used for reboot/crash
+ recovery.
+
+
+ Running terminal processes continue, but older output may no
+ longer be available after restarting the app.
+
+
+
+
+
+ setConfirmClearHistoryOpen(false)}
+ >
+ Cancel
+
+ {
+ setConfirmClearHistoryOpen(false);
+ clearTerminalHistory.mutate();
+ }}
+ >
+ Clear history
+
+
+
+
+
+ {
+ if (!open) setPendingKillSession(null);
+ }}
+ >
+
+
+
+ Kill terminal session?
+
+
+
+
+ This will terminate the session and its underlying process.
+
+ {pendingKillSession && (
+
+ {pendingKillSession.workspaceId} /{" "}
+ {pendingKillSession.sessionId}
+
+ )}
+
+
+
+
+ setPendingKillSession(null)}
+ >
+ Cancel
+
+ {
+ const sessionId = pendingKillSession?.sessionId;
+ setPendingKillSession(null);
+ if (!sessionId) return;
+ killDaemonSession.mutate({ paneId: sessionId });
+ }}
+ >
+ Kill
+
+
+
+
+
+
+
+
+
+ Restart terminal daemon?
+
+
+
+
+ This will shut down the terminal daemon process and kill all
+ running sessions. Use this to fix terminals that are stuck or
+ unresponsive.
+
+
+ A fresh daemon will start automatically when you open a new
+ terminal.
+
+
+
+
+
+ setConfirmRestartDaemonOpen(false)}
+ >
+ Cancel
+
+ {
+ setConfirmRestartDaemonOpen(false);
+ restartDaemon.mutate(undefined, {});
+ }}
+ >
+ Restart daemon
+
+
+
+
+ >
+ );
+}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts
index c9632ae229a..cb611ce0951 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts
@@ -405,8 +405,9 @@ export const SETTINGS_ITEMS: SettingsItem[] = [
{
id: SETTING_ITEM_ID.TERMINAL_AUTO_APPLY_PRESET,
section: "terminal",
- title: "Auto-apply Default Preset",
- description: "Automatically apply default preset when creating workspaces",
+ title: "Auto-Apply Default Preset",
+ description:
+ "Automatically apply the workspace creation preset when creating new workspaces",
keywords: [
"terminal",
"preset",
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceInitEffects.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceInitEffects.tsx
index ff37c84f56d..2cf37b499d2 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceInitEffects.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceInitEffects.tsx
@@ -2,17 +2,14 @@ import { toast } from "@superset/ui/sonner";
import { useCallback, useEffect, useRef } from "react";
import { electronTrpc } from "renderer/lib/electron-trpc";
import { useTabsStore } from "renderer/stores/tabs/store";
-import type { AddTabWithMultiplePanesOptions } from "renderer/stores/tabs/types";
+import { useTabsWithPresets } from "renderer/stores/tabs/useTabsWithPresets";
import {
type PendingTerminalSetup,
useWorkspaceInitStore,
} from "renderer/stores/workspace-init";
import { DEFAULT_AUTO_APPLY_DEFAULT_PRESET } from "shared/constants";
-/**
- * Handles terminal setup when workspaces become ready.
- * Mounted at app root to survive dialog unmounts.
- */
+/** Mounted at app root to survive dialog unmounts. */
export function WorkspaceInitEffects() {
const initProgress = useWorkspaceInitStore((s) => s.initProgress);
const pendingTerminalSetups = useWorkspaceInitStore(
@@ -31,78 +28,29 @@ export function WorkspaceInitEffects() {
const processingRef = useRef>(new Set());
const addTab = useTabsStore((state) => state.addTab);
- const addPane = useTabsStore((state) => state.addPane);
- const addPanesToTab = useTabsStore((state) => state.addPanesToTab);
- const addTabWithMultiplePanes = useTabsStore(
- (state) => state.addTabWithMultiplePanes,
- );
const setTabAutoTitle = useTabsStore((state) => state.setTabAutoTitle);
- const renameTab = useTabsStore((state) => state.renameTab);
+ const { openPreset } = useTabsWithPresets();
const createOrAttach = electronTrpc.terminal.createOrAttach.useMutation();
const utils = electronTrpc.useUtils();
- const createPresetTerminal = useCallback(
- (
- workspaceId: string,
- preset: NonNullable,
- existingTabId?: string,
- ) => {
- const isParallel =
- preset.executionMode === "parallel" && preset.commands.length > 1;
-
- if (existingTabId) {
- if (isParallel) {
- addPanesToTab(existingTabId, {
- commands: preset.commands,
- initialCwd: preset.cwd || undefined,
- });
- } else {
- addPane(existingTabId, {
- initialCommands: preset.commands,
- initialCwd: preset.cwd || undefined,
- });
- }
- return;
- }
-
- if (isParallel) {
- const options: AddTabWithMultiplePanesOptions = {
- commands: preset.commands,
- initialCwd: preset.cwd || undefined,
- };
- const { tabId } = addTabWithMultiplePanes(workspaceId, options);
- renameTab(tabId, preset.name);
- } else {
- const { tabId } = addTab(workspaceId, {
- initialCommands: preset.commands,
- initialCwd: preset.cwd || undefined,
- });
- renameTab(tabId, preset.name);
- }
- },
- [addTab, addPane, addPanesToTab, addTabWithMultiplePanes, renameTab],
- );
-
const handleTerminalSetup = useCallback(
(setup: PendingTerminalSetup, onComplete: () => void) => {
const hasSetupScript =
Array.isArray(setup.initialCommands) &&
setup.initialCommands.length > 0;
- const hasDefaultPreset =
- shouldApplyPreset &&
- setup.defaultPreset != null &&
- setup.defaultPreset.commands.length > 0;
+ const presets = (setup.defaultPresets ?? []).filter(
+ (p) => p.commands.length > 0,
+ );
+ const hasPresets = shouldApplyPreset && presets.length > 0;
- if (hasSetupScript && hasDefaultPreset && setup.defaultPreset) {
+ if (hasSetupScript && hasPresets) {
const { tabId: setupTabId, paneId: setupPaneId } = addTab(
setup.workspaceId,
);
setTabAutoTitle(setupTabId, "Workspace Setup");
- createPresetTerminal(
- setup.workspaceId,
- setup.defaultPreset,
- setupTabId,
- );
+ for (const preset of presets) {
+ openPreset(setup.workspaceId, preset);
+ }
createOrAttach.mutate(
{
@@ -171,26 +119,17 @@ export function WorkspaceInitEffects() {
return;
}
- if (
- shouldApplyPreset &&
- setup.defaultPreset &&
- setup.defaultPreset.commands.length > 0
- ) {
- createPresetTerminal(setup.workspaceId, setup.defaultPreset);
+ if (hasPresets) {
+ for (const preset of presets) {
+ openPreset(setup.workspaceId, preset);
+ }
onComplete();
return;
}
- // No setup script or default preset — sidebar card handles the prompt
onComplete();
},
- [
- addTab,
- setTabAutoTitle,
- createOrAttach,
- createPresetTerminal,
- shouldApplyPreset,
- ],
+ [addTab, setTabAutoTitle, createOrAttach, openPreset, shouldApplyPreset],
);
useEffect(() => {
@@ -201,7 +140,6 @@ export function WorkspaceInitEffects() {
continue;
}
- // No initProgress means workspace is already initialized — process immediately
if (!progress) {
processingRef.current.add(workspaceId);
handleTerminalSetup(setup, () => {
@@ -216,13 +154,13 @@ export function WorkspaceInitEffects() {
// Always fetch from backend to ensure we have the latest preset
// (client-side preset query may not have resolved when pending setup was created)
- if (setup.defaultPreset === undefined) {
+ if (setup.defaultPresets === undefined) {
utils.workspaces.getSetupCommands
.fetch({ workspaceId })
.then((setupData) => {
const completeSetup: PendingTerminalSetup = {
...setup,
- defaultPreset: setupData?.defaultPreset ?? null,
+ defaultPresets: setupData?.defaultPresets ?? [],
};
handleTerminalSetup(completeSetup, () => {
removePendingTerminalSetup(workspaceId);
@@ -282,7 +220,7 @@ export function WorkspaceInitEffects() {
workspaceId,
projectId: setupData.projectId,
initialCommands: setupData.initialCommands,
- defaultPreset: setupData.defaultPreset,
+ defaultPresets: setupData.defaultPresets ?? [],
};
handleTerminalSetup(fetchedSetup, () => {
diff --git a/apps/desktop/src/renderer/stores/tabs/useTabsWithPresets.ts b/apps/desktop/src/renderer/stores/tabs/useTabsWithPresets.ts
index 2d082087402..88b82ed8239 100644
--- a/apps/desktop/src/renderer/stores/tabs/useTabsWithPresets.ts
+++ b/apps/desktop/src/renderer/stores/tabs/useTabsWithPresets.ts
@@ -1,17 +1,13 @@
import type { TerminalPreset } from "@superset/local-db";
import { useCallback, useMemo } from "react";
import type { MosaicBranch } from "react-mosaic-component";
-import { usePresets } from "renderer/react-query/presets";
+import { electronTrpc } from "renderer/lib/electron-trpc";
import { useTabsStore } from "./store";
import type { AddTabOptions } from "./types";
-/**
- * Hook that wraps tab store actions with default preset support.
- * When a default preset is configured, new terminals will automatically
- * use that preset's commands and cwd.
- */
export function useTabsWithPresets() {
- const { defaultPreset } = usePresets();
+ const { data: newTabPresets = [] } =
+ electronTrpc.settings.getNewTabPresets.useQuery();
const storeAddTab = useTabsStore((s) => s.addTab);
const storeAddTabWithMultiplePanes = useTabsStore(
@@ -23,20 +19,48 @@ export function useTabsWithPresets() {
const storeSplitPaneAuto = useTabsStore((s) => s.splitPaneAuto);
const renameTab = useTabsStore((s) => s.renameTab);
- const defaultPresetOptions: AddTabOptions | undefined = useMemo(() => {
- if (!defaultPreset) return undefined;
+ const firstPreset = newTabPresets[0] ?? null;
+
+ const firstPresetOptions: AddTabOptions | undefined = useMemo(() => {
+ if (!firstPreset) return undefined;
return {
- initialCommands: defaultPreset.commands,
- initialCwd: defaultPreset.cwd || undefined,
+ initialCommands: firstPreset.commands,
+ initialCwd: firstPreset.cwd || undefined,
};
- }, [defaultPreset]);
+ }, [firstPreset]);
+
+ const openPresetAsTab = useCallback(
+ (workspaceId: string, preset: TerminalPreset) => {
+ const isParallel =
+ preset.executionMode === "parallel" && preset.commands.length > 1;
- const shouldUseParallelMode = useMemo(() => {
- return (
- defaultPreset?.executionMode === "parallel" &&
- defaultPreset.commands.length > 1
- );
- }, [defaultPreset]);
+ let tabId: string;
+ let paneId: string;
+
+ if (isParallel) {
+ const result = storeAddTabWithMultiplePanes(workspaceId, {
+ commands: preset.commands,
+ initialCwd: preset.cwd || undefined,
+ });
+ tabId = result.tabId;
+ paneId = result.paneIds[0];
+ } else {
+ const result = storeAddTab(workspaceId, {
+ initialCommands: preset.commands,
+ initialCwd: preset.cwd || undefined,
+ });
+ tabId = result.tabId;
+ paneId = result.paneId;
+ }
+
+ if (preset.name) {
+ renameTab(tabId, preset.name);
+ }
+
+ return { tabId, paneId };
+ },
+ [storeAddTab, storeAddTabWithMultiplePanes, renameTab],
+ );
const addTab = useCallback(
(workspaceId: string, options?: AddTabOptions) => {
@@ -44,43 +68,26 @@ export function useTabsWithPresets() {
return storeAddTab(workspaceId, options);
}
- if (shouldUseParallelMode && defaultPreset) {
- const { tabId, paneIds } = storeAddTabWithMultiplePanes(workspaceId, {
- commands: defaultPreset.commands,
- initialCwd: defaultPreset.cwd || undefined,
- });
-
- if (defaultPreset.name) {
- renameTab(tabId, defaultPreset.name);
- }
-
- return { tabId, paneId: paneIds[0] };
+ if (newTabPresets.length === 0) {
+ return storeAddTab(workspaceId);
}
- const result = storeAddTab(workspaceId, defaultPresetOptions);
-
- if (defaultPreset?.name) {
- renameTab(result.tabId, defaultPreset.name);
+ const firstResult = openPresetAsTab(workspaceId, newTabPresets[0]);
+ for (let i = 1; i < newTabPresets.length; i++) {
+ openPresetAsTab(workspaceId, newTabPresets[i]);
}
- return result;
+ return { tabId: firstResult.tabId, paneId: firstResult.paneId };
},
- [
- storeAddTab,
- storeAddTabWithMultiplePanes,
- defaultPresetOptions,
- defaultPreset,
- shouldUseParallelMode,
- renameTab,
- ],
+ [storeAddTab, newTabPresets, openPresetAsTab],
);
const addPane = useCallback(
(tabId: string, options?: AddTabOptions) => {
- const effectiveOptions = options ?? defaultPresetOptions;
+ const effectiveOptions = options ?? firstPresetOptions;
return storeAddPane(tabId, effectiveOptions);
},
- [storeAddPane, defaultPresetOptions],
+ [storeAddPane, firstPresetOptions],
);
const splitPaneVertical = useCallback(
@@ -90,7 +97,7 @@ export function useTabsWithPresets() {
path?: MosaicBranch[],
options?: AddTabOptions,
) => {
- const effectiveOptions = options ?? defaultPresetOptions;
+ const effectiveOptions = options ?? firstPresetOptions;
return storeSplitPaneVertical(
tabId,
sourcePaneId,
@@ -98,7 +105,7 @@ export function useTabsWithPresets() {
effectiveOptions,
);
},
- [storeSplitPaneVertical, defaultPresetOptions],
+ [storeSplitPaneVertical, firstPresetOptions],
);
const splitPaneHorizontal = useCallback(
@@ -108,7 +115,7 @@ export function useTabsWithPresets() {
path?: MosaicBranch[],
options?: AddTabOptions,
) => {
- const effectiveOptions = options ?? defaultPresetOptions;
+ const effectiveOptions = options ?? firstPresetOptions;
return storeSplitPaneHorizontal(
tabId,
sourcePaneId,
@@ -116,7 +123,7 @@ export function useTabsWithPresets() {
effectiveOptions,
);
},
- [storeSplitPaneHorizontal, defaultPresetOptions],
+ [storeSplitPaneHorizontal, firstPresetOptions],
);
const splitPaneAuto = useCallback(
@@ -127,7 +134,7 @@ export function useTabsWithPresets() {
path?: MosaicBranch[],
options?: AddTabOptions,
) => {
- const effectiveOptions = options ?? defaultPresetOptions;
+ const effectiveOptions = options ?? firstPresetOptions;
return storeSplitPaneAuto(
tabId,
sourcePaneId,
@@ -136,31 +143,7 @@ export function useTabsWithPresets() {
effectiveOptions,
);
},
- [storeSplitPaneAuto, defaultPresetOptions],
- );
-
- const openPreset = useCallback(
- (workspaceId: string, preset: TerminalPreset) => {
- const isParallel =
- preset.executionMode === "parallel" && preset.commands.length > 1;
-
- const { tabId } = isParallel
- ? storeAddTabWithMultiplePanes(workspaceId, {
- commands: preset.commands,
- initialCwd: preset.cwd || undefined,
- })
- : storeAddTab(workspaceId, {
- initialCommands: preset.commands,
- initialCwd: preset.cwd || undefined,
- });
-
- if (preset.name) {
- renameTab(tabId, preset.name);
- }
-
- return { tabId };
- },
- [storeAddTab, storeAddTabWithMultiplePanes, renameTab],
+ [storeSplitPaneAuto, firstPresetOptions],
);
return {
@@ -169,7 +152,6 @@ export function useTabsWithPresets() {
splitPaneVertical,
splitPaneHorizontal,
splitPaneAuto,
- openPreset,
- defaultPreset,
+ openPreset: openPresetAsTab,
};
}
diff --git a/apps/desktop/src/renderer/stores/workspace-init.ts b/apps/desktop/src/renderer/stores/workspace-init.ts
index 25fb88337e4..26aad9b4654 100644
--- a/apps/desktop/src/renderer/stores/workspace-init.ts
+++ b/apps/desktop/src/renderer/stores/workspace-init.ts
@@ -7,7 +7,8 @@ export interface PendingTerminalSetup {
workspaceId: string;
projectId: string;
initialCommands: string[] | null;
- defaultPreset?: TerminalPreset | null;
+ /** When undefined, signals that presets haven't been fetched yet and should be loaded from the backend */
+ defaultPresets?: TerminalPreset[];
}
interface WorkspaceInitState {
diff --git a/packages/local-db/src/schema/zod.ts b/packages/local-db/src/schema/zod.ts
index bd91026d78e..be05a5d5e74 100644
--- a/packages/local-db/src/schema/zod.ts
+++ b/packages/local-db/src/schema/zod.ts
@@ -61,6 +61,8 @@ export const terminalPresetSchema = z.object({
cwd: z.string(),
commands: z.array(z.string()),
isDefault: z.boolean().optional(),
+ applyOnWorkspaceCreated: z.boolean().optional(),
+ applyOnNewTab: z.boolean().optional(),
executionMode: z.enum(EXECUTION_MODES).optional(),
});