diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx index fa472c63e..94c893620 100644 --- a/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx +++ b/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx @@ -1,10 +1,8 @@ /* eslint-disable max-lines -- Why: the floating surface owns both terminal chrome and local notes tabs until the shared floating shell is extracted. */ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { Maximize2, Minimize2, Minus } from 'lucide-react' import TabBar from '@/components/tab-bar/TabBar' import TerminalPane from '@/components/terminal-pane/TerminalPane' import { Button } from '@/components/ui/button' -import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { focusTerminalTabSurface } from '@/lib/focus-terminal-tab-surface' import { ORCHESTRATION_SETUP_DISMISSED_STORAGE_KEY, @@ -23,6 +21,7 @@ import type { TerminalTab } from '../../../../shared/types' import { FloatingTerminalOrchestrationDialog } from './FloatingTerminalOrchestrationDialog' import ProjectNotesTabContent from '@/components/notes/ProjectNotesTabContent' import { FloatingTerminalResizeHandles } from './FloatingTerminalResizeHandles' +import { FloatingTerminalWindowControls } from './FloatingTerminalWindowControls' export { FloatingTerminalToggleButton } from './FloatingTerminalToggleButton' import { clampFloatingTerminalBounds, @@ -361,7 +360,7 @@ export function FloatingTerminalPanel({ >
-
- - - - - - {maximized ? 'Restore' : 'Maximize'} - - - - - - - - Minimize - - -
+ onOpenChange(false)} + />
diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalToggleButton.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalToggleButton.tsx index 314f00efb..986f16126 100644 --- a/src/renderer/src/components/floating-terminal/FloatingTerminalToggleButton.tsx +++ b/src/renderer/src/components/floating-terminal/FloatingTerminalToggleButton.tsx @@ -23,7 +23,7 @@ export function FloatingTerminalToggleButton({ type="button" variant="outline" size="icon-sm" - className="bg-card/95 shadow-xs" + className="border-border bg-secondary text-secondary-foreground shadow-xs hover:bg-accent hover:text-accent-foreground" data-floating-terminal-toggle aria-label={open ? 'Minimize floating terminal' : 'Show floating terminal'} aria-pressed={open} diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx new file mode 100644 index 000000000..f09949cb5 --- /dev/null +++ b/src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx @@ -0,0 +1,144 @@ +import { useCallback, useMemo } from 'react' +import { Maximize2, Minimize2, Minus } from 'lucide-react' +import { toast } from 'sonner' +import { Button } from '@/components/ui/button' +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' +import { AGENT_CATALOG, AgentIcon } from '@/lib/agent-catalog' +import { focusTerminalTabSurface } from '@/lib/focus-terminal-tab-surface' +import { CLIENT_PLATFORM } from '@/lib/new-workspace' +import { buildAgentStartupPlan } from '@/lib/tui-agent-startup' +import { tuiAgentToAgentKind } from '@/lib/telemetry' +import { useAppStore } from '@/store' +import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants' + +type FloatingTerminalWindowControlsProps = { + maximized: boolean + onToggleMaximized: () => void + onMinimize: () => void +} + +const controlButtonClassName = + 'border-border bg-secondary text-secondary-foreground shadow-xs hover:bg-accent hover:text-accent-foreground' + +export function FloatingTerminalWindowControls({ + maximized, + onToggleMaximized, + onMinimize +}: FloatingTerminalWindowControlsProps): React.JSX.Element { + const defaultTuiAgent = useAppStore((s) => s.settings?.defaultTuiAgent ?? null) + const createTab = useAppStore((s) => s.createTab) + const setActiveTabForWorktree = useAppStore((s) => s.setActiveTabForWorktree) + + const defaultAgent = defaultTuiAgent && defaultTuiAgent !== 'blank' ? defaultTuiAgent : null + const defaultAgentLabel = useMemo( + () => + defaultAgent + ? (AGENT_CATALOG.find((agent) => agent.id === defaultAgent)?.label ?? defaultAgent) + : null, + [defaultAgent] + ) + + const launchDefaultAgent = useCallback(() => { + if (!defaultAgent) { + return + } + const state = useAppStore.getState() + const startupPlan = buildAgentStartupPlan({ + agent: defaultAgent, + prompt: '', + cmdOverrides: state.settings?.agentCmdOverrides ?? {}, + platform: CLIENT_PLATFORM, + allowEmptyPromptLaunch: true + }) + if (!startupPlan) { + toast.error(`Could not build launch command for ${defaultAgentLabel ?? defaultAgent}.`) + return + } + const tab = createTab(FLOATING_TERMINAL_WORKTREE_ID, undefined, undefined, { activate: false }) + state.queueTabStartupCommand(tab.id, { + command: startupPlan.launchCommand, + ...(startupPlan.env ? { env: startupPlan.env } : {}), + telemetry: { + agent_kind: tuiAgentToAgentKind(defaultAgent), + launch_source: 'shortcut', + request_kind: 'new' + } + }) + setActiveTabForWorktree(FLOATING_TERMINAL_WORKTREE_ID, tab.id) + const fresh = useAppStore.getState() + const currentTabs = fresh.tabsByWorktree[FLOATING_TERMINAL_WORKTREE_ID] ?? [] + const stored = fresh.tabBarOrderByWorktree[FLOATING_TERMINAL_WORKTREE_ID] ?? [] + const validIds = new Set(currentTabs.map((entry) => entry.id)) + const order = stored.filter((id) => validIds.has(id) && id !== tab.id) + for (const entry of currentTabs) { + if (entry.id !== tab.id && !order.includes(entry.id)) { + order.push(entry.id) + } + } + order.push(tab.id) + fresh.setTabBarOrder(FLOATING_TERMINAL_WORKTREE_ID, order) + focusTerminalTabSurface(tab.id) + }, [createTab, defaultAgent, defaultAgentLabel, setActiveTabForWorktree]) + + return ( +
+ + + + + + {defaultAgentLabel ? `Open ${defaultAgentLabel}` : 'Choose a default agent first'} + + + + + + + + {maximized ? 'Restore' : 'Maximize'} + + + + + + + + Minimize + + +
+ ) +} diff --git a/src/renderer/src/components/status-bar/StatusBar.tsx b/src/renderer/src/components/status-bar/StatusBar.tsx index e5b935f5e..418292e0e 100644 --- a/src/renderer/src/components/status-bar/StatusBar.tsx +++ b/src/renderer/src/components/status-bar/StatusBar.tsx @@ -908,7 +908,7 @@ function StatusBarInner({ floatingTerminalOpen }: StatusBarProps): React.JSX.Ele