From 6dc0be6a98db2594fdd7e18ef17fb889eda47a9a Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:24:52 -0700 Subject: [PATCH] fix: refine floating terminal chrome --- .../floating-terminal/FloatingTerminalPanel.tsx | 8 ++++++-- .../src/components/tab-bar/BrowserTab.tsx | 7 +++++-- .../src/components/tab-bar/EditorFileTab.tsx | 7 +++++-- .../src/components/tab-bar/SortableTab.tsx | 7 +++++-- src/renderer/src/components/tab-bar/TabBar.tsx | 10 +++++++++- .../components/tab-bar/drop-indicator.test.ts | 17 ++++++++++++++++- .../src/components/tab-bar/drop-indicator.ts | 10 ++++++++++ 7 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx index 4c3b2bdf5f5..6bf497ab968 100644 --- a/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx +++ b/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx @@ -1238,12 +1238,15 @@ export function FloatingTerminalPanel({ return ( // Why: root notification cards use z-40; keep the floating workspace below // them so alerts are never hidden behind an open terminal panel. + // Drop shadow on the outer shell, border on an inner shell — mixing both on + // one rounded node made corners look stubby. Floating tabs skip their top + // border so the titlebar curve stays clean.
setFloatingTerminalInputFocused(event.relatedTarget)} onKeyDownCapture={handleShortcutSurfaceKeyDown} > -
+
void dragData: TabDragItemData dropIndicator?: DropIndicator + includeTopTabBorder?: boolean }): React.JSX.Element { // Why: no transform/transition/isDragging styling — the drag design is // that tabs stay visually anchored; only the blue insertion bar moves. @@ -171,7 +174,7 @@ export default function BrowserTab({ data-pinned={isPinned ? 'true' : 'false'} {...attributes} {...listeners} - className={`group relative flex items-center h-full px-1.5 text-xs cursor-pointer select-none shrink-0 outline-none focus:outline-none focus-visible:outline-none border-t ${hasTabsToRight ? 'border-r' : ''} border-border ${getDropIndicatorClasses(dropIndicator ?? null)} ${getTabRootStateClasses(isActive)}`} + className={`group relative flex items-center h-full px-1.5 text-xs cursor-pointer select-none shrink-0 outline-none focus:outline-none focus-visible:outline-none ${getTabStripBorderClasses(hasTabsToRight, { includeTopBorder: includeTopTabBorder })} ${getDropIndicatorClasses(dropIndicator ?? null)} ${getTabRootStateClasses(isActive)}`} onPointerDown={(e) => { if (e.button !== 0) { return diff --git a/src/renderer/src/components/tab-bar/EditorFileTab.tsx b/src/renderer/src/components/tab-bar/EditorFileTab.tsx index 4f82dd42878..4136abe77e8 100644 --- a/src/renderer/src/components/tab-bar/EditorFileTab.tsx +++ b/src/renderer/src/components/tab-bar/EditorFileTab.tsx @@ -21,6 +21,7 @@ import { ACTIVE_TAB_INDICATOR_CLASSES, getDropIndicatorClasses, getTabRootStateClasses, + getTabStripBorderClasses, type DropIndicator } from './drop-indicator' import { canOpenMarkdownPreview } from '@/components/editor/markdown-preview-controls' @@ -40,7 +41,8 @@ export default function EditorFileTab({ onTogglePin, onSplitGroup, dragData, - dropIndicator + dropIndicator, + includeTopTabBorder = true }: { file: OpenFile & { tabId?: string } isActive: boolean @@ -56,6 +58,7 @@ export default function EditorFileTab({ onSplitGroup: (direction: 'left' | 'right' | 'up' | 'down', sourceVisibleTabId: string) => void dragData: TabDragItemData dropIndicator?: DropIndicator + includeTopTabBorder?: boolean }): React.JSX.Element { const worktree = useWorktreeById(file.worktreeId) const repo = useRepoById(worktree?.repoId ?? null) @@ -200,7 +203,7 @@ export default function EditorFileTab({ data-pinned={isPinned ? 'true' : 'false'} {...attributes} {...listeners} - className={`group relative flex items-center h-full px-1.5 text-xs cursor-pointer select-none shrink-0 outline-none focus:outline-none focus-visible:outline-none border-t ${hasTabsToRight ? 'border-r' : ''} border-border ${getDropIndicatorClasses(dropIndicator ?? null)} ${getTabRootStateClasses(isActive)}`} + className={`group relative flex items-center h-full px-1.5 text-xs cursor-pointer select-none shrink-0 outline-none focus:outline-none focus-visible:outline-none ${getTabStripBorderClasses(hasTabsToRight, { includeTopBorder: includeTopTabBorder })} ${getDropIndicatorClasses(dropIndicator ?? null)} ${getTabRootStateClasses(isActive)}`} onPointerDown={(e) => { if (e.button !== 0) { return diff --git a/src/renderer/src/components/tab-bar/SortableTab.tsx b/src/renderer/src/components/tab-bar/SortableTab.tsx index 89fd74f71f6..78477fdd0fc 100644 --- a/src/renderer/src/components/tab-bar/SortableTab.tsx +++ b/src/renderer/src/components/tab-bar/SortableTab.tsx @@ -15,6 +15,7 @@ import { ACTIVE_TAB_INDICATOR_CLASSES, getDropIndicatorClasses, getTabRootStateClasses, + getTabStripBorderClasses, type DropIndicator } from './drop-indicator' import { preventMiddleButtonDefault } from './middle-button-default-guard' @@ -38,6 +39,7 @@ type SortableTabProps = { onSplitGroup: (direction: 'left' | 'right' | 'up' | 'down', sourceVisibleTabId: string) => void dragData: TabDragItemData dropIndicator?: DropIndicator + includeTopTabBorder?: boolean } export const CLOSE_ALL_CONTEXT_MENUS_EVENT = 'orca-close-all-context-menus' @@ -59,7 +61,8 @@ export default function SortableTab({ onToggleExpand, onSplitGroup, dragData, - dropIndicator + dropIndicator, + includeTopTabBorder = true }: SortableTabProps): React.JSX.Element { // Why: subscribe to the per-tab boolean directly so only the tab whose unread // status actually flipped re-renders. Reading the whole `unreadTerminalTabs` @@ -213,7 +216,7 @@ export default function SortableTab({ // tab still reads as "selected + has activity". The wash is // rendered as an absolutely-positioned child below so the ::after // pseudo-element stays free for the drop indicator. - className={`group relative flex items-center h-full px-1.5 text-xs cursor-pointer select-none shrink-0 outline-none focus:outline-none focus-visible:outline-none border-t ${hasTabsToRight ? 'border-r' : ''} border-border ${getDropIndicatorClasses(dropIndicator ?? null)} ${getTabRootStateClasses(isActive)}`} + className={`group relative flex items-center h-full px-1.5 text-xs cursor-pointer select-none shrink-0 outline-none focus:outline-none focus-visible:outline-none ${getTabStripBorderClasses(hasTabsToRight, { includeTopBorder: includeTopTabBorder })} ${getDropIndicatorClasses(dropIndicator ?? null)} ${getTabRootStateClasses(isActive)}`} onDoubleClick={(e) => { if (isEditing) { return diff --git a/src/renderer/src/components/tab-bar/TabBar.tsx b/src/renderer/src/components/tab-bar/TabBar.tsx index 228a91bce46..6f6b11bc672 100644 --- a/src/renderer/src/components/tab-bar/TabBar.tsx +++ b/src/renderer/src/components/tab-bar/TabBar.tsx @@ -108,6 +108,8 @@ type TabBarProps = { sourceVisibleTabId?: string ) => void hoveredTabInsertion?: HoveredTabInsertion | null + /** Floating workspace panels are rounded; skip tab top borders that clash with the curve. */ + tabStripChrome?: 'default' | 'floating-panel' } type TabItem = @@ -206,8 +208,10 @@ function TabBarInner({ onPinFile, tabBarOrder, onCreateSplitGroup, - hoveredTabInsertion + hoveredTabInsertion, + tabStripChrome = 'default' }: TabBarProps): React.JSX.Element { + const includeTopTabBorder = tabStripChrome !== 'floating-panel' const newTerminalShortcut = useShortcutLabel('tab.newTerminal') const newBrowserShortcut = useShortcutLabel('tab.newBrowser') const newSimulatorShortcut = useShortcutLabel('tab.newSimulator') @@ -865,6 +869,7 @@ function TabBarInner({ } dragData={dragData} dropIndicator={dropIndicatorByVisibleId.get(item.id) ?? null} + includeTopTabBorder={includeTopTabBorder} /> ) } @@ -886,6 +891,7 @@ function TabBarInner({ onTogglePin={() => togglePinned(item)} dragData={dragData} dropIndicator={dropIndicatorByVisibleId.get(item.id) ?? null} + includeTopTabBorder={includeTopTabBorder} /> ) } @@ -921,6 +927,7 @@ function TabBarInner({ } dragData={dragData} dropIndicator={dropIndicatorByVisibleId.get(item.id) ?? null} + includeTopTabBorder={includeTopTabBorder} /> ) } @@ -946,6 +953,7 @@ function TabBarInner({ } dragData={dragData} dropIndicator={dropIndicatorByVisibleId.get(item.id) ?? null} + includeTopTabBorder={includeTopTabBorder} /> ) })} diff --git a/src/renderer/src/components/tab-bar/drop-indicator.test.ts b/src/renderer/src/components/tab-bar/drop-indicator.test.ts index aa6e6209c2b..db3a8140c91 100644 --- a/src/renderer/src/components/tab-bar/drop-indicator.test.ts +++ b/src/renderer/src/components/tab-bar/drop-indicator.test.ts @@ -2,7 +2,8 @@ import { describe, expect, it } from 'vitest' import { ACTIVE_TAB_INDICATOR_CLASSES, getDropIndicatorClasses, - getTabRootStateClasses + getTabRootStateClasses, + getTabStripBorderClasses } from './drop-indicator' describe('getDropIndicatorClasses', () => { @@ -55,6 +56,20 @@ describe('ACTIVE_TAB_INDICATOR_CLASSES', () => { }) }) +describe('getTabStripBorderClasses', () => { + it('includes top and right borders by default', () => { + expect(getTabStripBorderClasses(true)).toBe('border-t border-r border-border') + expect(getTabStripBorderClasses(false)).toBe('border-t border-border') + }) + + it('can omit the top border for rounded floating panel titlebars', () => { + expect(getTabStripBorderClasses(true, { includeTopBorder: false })).toBe( + 'border-r border-border' + ) + expect(getTabStripBorderClasses(false, { includeTopBorder: false })).toBe('border-border') + }) +}) + describe('getTabRootStateClasses', () => { it('returns the shared selected-tab surface treatment', () => { const classes = getTabRootStateClasses(true) diff --git a/src/renderer/src/components/tab-bar/drop-indicator.ts b/src/renderer/src/components/tab-bar/drop-indicator.ts index 90cc7fa2c33..ab9f0ef5623 100644 --- a/src/renderer/src/components/tab-bar/drop-indicator.ts +++ b/src/renderer/src/components/tab-bar/drop-indicator.ts @@ -33,3 +33,13 @@ export function getTabRootStateClasses(isActive: boolean): string { ? 'bg-[color-mix(in_srgb,var(--foreground)_6%,var(--card))] text-foreground' : 'bg-card text-muted-foreground hover:text-foreground' } + +export function getTabStripBorderClasses( + hasTabsToRight: boolean, + options?: { includeTopBorder?: boolean } +): string { + const includeTopBorder = options?.includeTopBorder ?? true + return [includeTopBorder ? 'border-t' : '', hasTabsToRight ? 'border-r' : '', 'border-border'] + .filter(Boolean) + .join(' ') +}