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(' ')
+}