From 150afb0777f177bcb70be70878beec5c5e527ce9 Mon Sep 17 00:00:00 2001 From: Ivan Borinschi Date: Sat, 31 Jan 2026 13:05:53 +0200 Subject: [PATCH 1/3] Add multi launch scripts with icons --- src-tauri/src/types.rs | 11 + src-tauri/src/workspaces/tests.rs | 1 + src/App.tsx | 19 + .../app/components/LaunchScriptButton.tsx | 86 +++- .../components/LaunchScriptEntryButton.tsx | 134 +++++++ .../app/components/LaunchScriptIconPicker.tsx | 34 ++ src/features/app/components/MainHeader.tsx | 62 ++- .../hooks/useWorkspaceLaunchScripts.test.tsx | 127 ++++++ .../app/hooks/useWorkspaceLaunchScripts.ts | 366 ++++++++++++++++++ src/features/app/utils/launchScriptIcons.ts | 89 +++++ src/features/layout/hooks/useLayoutNodes.tsx | 3 + src/styles/main.css | 55 +++ src/types.ts | 8 + 13 files changed, 982 insertions(+), 13 deletions(-) create mode 100644 src/features/app/components/LaunchScriptEntryButton.tsx create mode 100644 src/features/app/components/LaunchScriptIconPicker.tsx create mode 100644 src/features/app/hooks/useWorkspaceLaunchScripts.test.tsx create mode 100644 src/features/app/hooks/useWorkspaceLaunchScripts.ts create mode 100644 src/features/app/utils/launchScriptIcons.ts diff --git a/src-tauri/src/types.rs b/src-tauri/src/types.rs index 5e3f421f..a9163904 100644 --- a/src-tauri/src/types.rs +++ b/src-tauri/src/types.rs @@ -268,10 +268,21 @@ pub(crate) struct WorkspaceSettings { pub(crate) codex_args: Option, #[serde(default, rename = "launchScript")] pub(crate) launch_script: Option, + #[serde(default, rename = "launchScripts")] + pub(crate) launch_scripts: Option>, #[serde(default, rename = "worktreeSetupScript")] pub(crate) worktree_setup_script: Option, } +#[derive(Debug, Serialize, Deserialize, Clone)] +pub(crate) struct LaunchScriptEntry { + pub(crate) id: String, + pub(crate) script: String, + pub(crate) icon: String, + #[serde(default)] + pub(crate) label: Option, +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub(crate) struct WorktreeSetupStatus { #[serde(rename = "shouldRun")] diff --git a/src-tauri/src/workspaces/tests.rs b/src-tauri/src/workspaces/tests.rs index c0bda742..2e7a6959 100644 --- a/src-tauri/src/workspaces/tests.rs +++ b/src-tauri/src/workspaces/tests.rs @@ -46,6 +46,7 @@ fn workspace_with_id_and_kind( codex_home: None, codex_args: None, launch_script: None, + launch_scripts: None, worktree_setup_script: None, }, } diff --git a/src/App.tsx b/src/App.tsx index 85490fbf..f19840c4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -91,6 +91,7 @@ import { useLiquidGlassEffect } from "./features/app/hooks/useLiquidGlassEffect" import { useCopyThread } from "./features/threads/hooks/useCopyThread"; import { useTerminalController } from "./features/terminal/hooks/useTerminalController"; import { useWorkspaceLaunchScript } from "./features/app/hooks/useWorkspaceLaunchScript"; +import { useWorkspaceLaunchScripts } from "./features/app/hooks/useWorkspaceLaunchScripts"; import { useWorktreeSetupScript } from "./features/app/hooks/useWorktreeSetupScript"; import { useGitCommitController } from "./features/app/hooks/useGitCommitController"; import { WorkspaceHome } from "./features/workspaces/components/WorkspaceHome"; @@ -772,6 +773,23 @@ function MainApp() { activeTerminalId, }); + const launchScriptsState = useWorkspaceLaunchScripts({ + activeWorkspace, + updateWorkspaceSettings, + openTerminal, + ensureLaunchTerminal: (workspaceId, entry, title) => { + const label = entry.label?.trim() || entry.icon; + return ensureTerminalWithTitle( + workspaceId, + `launch:${entry.id}`, + title || `Launch ${label}`, + ); + }, + restartLaunchSession: restartTerminalSession, + terminalState, + activeTerminalId, + }); + const worktreeSetupScriptState = useWorktreeSetupScript({ ensureTerminalWithTitle, restartTerminalSession, @@ -1717,6 +1735,7 @@ function MainApp() { onCloseLaunchScriptEditor: launchScriptState.onCloseEditor, onLaunchScriptDraftChange: launchScriptState.onDraftScriptChange, onSaveLaunchScript: launchScriptState.onSaveLaunchScript, + launchScriptsState, mainHeaderActionsNode: ( void; onDraftChange: (value: string) => void; onSave: () => void; + showNew?: boolean; + newEditorOpen?: boolean; + newDraftScript?: string; + newDraftIcon?: string; + newDraftLabel?: string; + newError?: string | null; + onOpenNew?: () => void; + onCloseNew?: () => void; + onNewDraftChange?: (value: string) => void; + onNewDraftIconChange?: (value: string) => void; + onNewDraftLabelChange?: (value: string) => void; + onCreateNew?: () => void; }; export function LaunchScriptButton({ @@ -25,6 +38,18 @@ export function LaunchScriptButton({ onCloseEditor, onDraftChange, onSave, + showNew = false, + newEditorOpen = false, + newDraftScript = "", + newDraftIcon = "play", + newDraftLabel = "", + newError = null, + onOpenNew, + onCloseNew, + onNewDraftChange, + onNewDraftIconChange, + onNewDraftLabelChange, + onCreateNew, }: LaunchScriptButtonProps) { const popoverRef = useRef(null); const hasLaunchScript = Boolean(launchScript?.trim()); @@ -39,6 +64,7 @@ export function LaunchScriptButton({ return; } onCloseEditor(); + onCloseNew?.(); }; window.addEventListener("mousedown", handleClick); return () => { @@ -80,11 +106,24 @@ export function LaunchScriptButton({ + {showNew && onOpenNew && ( + + )}