mirror of
https://github.com/stablyai/orca.git
synced 2026-10-29 00:03:37 +00:00
fix(tabs): clean up workspace chrome seams
Remove the duplicated 1px border below the drag strip where the rightmost split pane's border-r stacked next to the right sidebar's borderLeft. Extend the left-sidebar seam through the drag strip when splits exist so it reaches y=0, matching the full-height right-sidebar line.
This commit is contained in:
@@ -961,26 +961,7 @@ function App(): React.JSX.Element {
|
||||
filesystem watchers + cached directory trees survive across
|
||||
open/close toggles. Unmount on the tasks view since that
|
||||
surface is intentionally distraction-free. */}
|
||||
{showRightSidebarControls ? (
|
||||
workspaceActive ? (
|
||||
/* Why: in workspace view the right sidebar must start at the
|
||||
tab row, not the window edge, so the 8px drag strip that
|
||||
runs across the top of the center column continues uninterrupted
|
||||
over the right sidebar. Wrapping adds a matching 8px drag
|
||||
surface above and shrinks the sidebar to fill the remainder. */
|
||||
<div className="flex min-h-0 flex-col shrink-0">
|
||||
<div
|
||||
className="h-2 shrink-0 bg-card"
|
||||
style={{ WebkitAppRegion: 'drag' } as React.CSSProperties}
|
||||
/>
|
||||
<div className="flex min-h-0 flex-1">
|
||||
<RightSidebar />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<RightSidebar />
|
||||
)
|
||||
) : null}
|
||||
{showRightSidebarControls ? <RightSidebar /> : null}
|
||||
</div>
|
||||
<StatusBar />
|
||||
{/* Why: NewWorkspaceComposerCard renders Radix <Tooltip>s that crash
|
||||
|
||||
@@ -111,7 +111,7 @@ export default function BrowserTab({
|
||||
ref={setNodeRef}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className={`group relative flex items-center h-full px-1.5 text-xs cursor-pointer select-none shrink-0 border-t ${hasTabsToRight ? 'border-r' : ''} border-border bg-card ${getDropIndicatorClasses(dropIndicator ?? null)} ${
|
||||
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 bg-card ${getDropIndicatorClasses(dropIndicator ?? null)} ${
|
||||
isActive ? 'text-foreground' : 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
onPointerDown={(e) => {
|
||||
|
||||
@@ -175,7 +175,7 @@ export default function EditorFileTab({
|
||||
ref={setNodeRef}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className={`group relative flex items-center h-full px-1.5 text-xs cursor-pointer select-none shrink-0 border-t ${hasTabsToRight ? 'border-r' : ''} border-border bg-card ${getDropIndicatorClasses(dropIndicator ?? null)} ${
|
||||
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 bg-card ${getDropIndicatorClasses(dropIndicator ?? null)} ${
|
||||
isActive ? 'text-foreground' : 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
onPointerDown={(e) => {
|
||||
|
||||
@@ -179,7 +179,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 border-t ${hasTabsToRight ? 'border-r' : ''} border-border bg-card ${getDropIndicatorClasses(dropIndicator ?? null)} ${
|
||||
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 bg-card ${getDropIndicatorClasses(dropIndicator ?? null)} ${
|
||||
isActive ? 'text-foreground' : 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
onDoubleClick={(e) => {
|
||||
|
||||
@@ -337,11 +337,13 @@ function TabBarInner({
|
||||
"+" button remains window-draggable. */}
|
||||
<div
|
||||
ref={tabStripRef}
|
||||
// Why: `border-r` on the strip itself guarantees a visible vertical
|
||||
// separator between the last tab and the "+" button even when the
|
||||
// strip is scrolled. Each tab's own right border is clipped by
|
||||
// overflow-x-auto once tabs overflow, so relying on the per-tab
|
||||
// border alone leaves no boundary on the trailing edge.
|
||||
// Why: only `border-r` on the strip — the trailing edge must stay
|
||||
// visible even when tabs overflow-scroll past the last tab. The
|
||||
// left edge is instead painted by the FIRST tab's own `border-l`
|
||||
// (see per-tab components) so its rendering is identical to every
|
||||
// between-tab separator. A strip-level `border-l` would render at
|
||||
// a different box than the tab's own `border-t`, producing a
|
||||
// heavier-looking L-corner at the leftmost tab when inactive.
|
||||
className="terminal-tab-strip flex items-stretch overflow-x-auto overflow-y-hidden border-r border-border"
|
||||
style={{ WebkitAppRegion: 'no-drag' } as React.CSSProperties}
|
||||
>
|
||||
|
||||
@@ -19,6 +19,10 @@ export function getDropIndicatorClasses(dropIndicator: DropIndicator): string {
|
||||
// positioned with z-10 so it overlays the tab chrome without shifting layout
|
||||
// and without conflicting with drop-indicator pseudo-elements during a drag.
|
||||
// `-top-px` pulls it onto the tab's own 1px top border so the blue bar
|
||||
// REPLACES the faint gray line rather than stacking below it.
|
||||
// REPLACES the faint gray line rather than stacking below it. Horizontal
|
||||
// inset is 0 (not -1px): negative insets on the last tab bleed into the
|
||||
// strip's scrollWidth, so clicking between active tabs flips the strip
|
||||
// between "fits exactly" and "overflows by 1px", which jitters every tab by
|
||||
// 1px because the browser preserves scrollLeft near the end.
|
||||
export const ACTIVE_TAB_INDICATOR_CLASSES =
|
||||
'pointer-events-none absolute inset-x-0 -top-px h-px bg-[#1e3d9c] z-10'
|
||||
|
||||
@@ -28,6 +28,7 @@ export default function TabGroupPanel({
|
||||
isWorktreeActive,
|
||||
isFocused,
|
||||
hasSplitGroups,
|
||||
touchesRightEdge,
|
||||
reserveClosedExplorerToggleSpace,
|
||||
reserveCollapsedSidebarHeaderSpace,
|
||||
isTabDragActive = false,
|
||||
@@ -39,6 +40,7 @@ export default function TabGroupPanel({
|
||||
isWorktreeActive: boolean
|
||||
isFocused: boolean
|
||||
hasSplitGroups: boolean
|
||||
touchesRightEdge: boolean
|
||||
reserveClosedExplorerToggleSpace: boolean
|
||||
reserveCollapsedSidebarHeaderSpace: boolean
|
||||
isTabDragActive?: boolean
|
||||
@@ -176,8 +178,27 @@ export default function TabGroupPanel({
|
||||
|
||||
return (
|
||||
<div
|
||||
// Why: vertical borders are always `border-border` so the focus
|
||||
// highlight doesn't introduce a near-white strip next to the split
|
||||
// resize handle (--accent is ~#f5f5f5 in light mode, which reads as a
|
||||
// visible gap between the dragger and the tab row). Only the bottom
|
||||
// border changes color on focus, which is enough to cue the focused
|
||||
// group without painting a bright line along the vertical edges.
|
||||
// Why: unfocused split groups dim very subtly so the focused group
|
||||
// reads as "selected" without making the unfocused content look
|
||||
// washed out or hard to read. Only applied when `hasSplitGroups`
|
||||
// because a lone group has nothing to contrast against.
|
||||
className={`group/tab-group flex flex-col flex-1 min-w-0 min-h-0 overflow-hidden${
|
||||
hasSplitGroups ? ` border-x border-b ${isFocused ? 'border-accent' : 'border-border'}` : ''
|
||||
hasSplitGroups
|
||||
? // Why: drop only the RIGHT border on the rightmost group. The right
|
||||
// sidebar paints its own `borderLeft` that already extends full
|
||||
// height, so painting our own border-r in that spot stacks a
|
||||
// second 1px line next to it — reading as a 2px-thick bar below
|
||||
// the 8px drag strip (where the sidebar border continues alone
|
||||
// above). The left edge has no such double-up, so the left
|
||||
// border is always kept.
|
||||
` border-l ${touchesRightEdge ? '' : 'border-r'} border-border border-b ${isFocused ? 'border-b-accent' : 'opacity-95'}`
|
||||
: ''
|
||||
}`}
|
||||
onPointerDown={commands.focusGroup}
|
||||
// Why: keyboard and assistive-tech users can move focus into an unfocused
|
||||
|
||||
@@ -125,6 +125,7 @@ function SplitNode({
|
||||
// "focused", Cmd/Ctrl+W and split shortcuts can hit the wrong worktree.
|
||||
isFocused={isWorktreeActive && node.groupId === focusedGroupId}
|
||||
hasSplitGroups={hasSplitGroups}
|
||||
touchesRightEdge={touchesRightEdge}
|
||||
reserveClosedExplorerToggleSpace={touchesTopEdge && touchesRightEdge}
|
||||
reserveCollapsedSidebarHeaderSpace={touchesTopEdge && touchesLeftEdge}
|
||||
isTabDragActive={isTabDragActive}
|
||||
@@ -198,6 +199,7 @@ export default function TabGroupSplitLayout({
|
||||
isWorktreeActive: boolean
|
||||
}): React.JSX.Element {
|
||||
const dragSplit = useTabDragSplit({ worktreeId, enabled: isWorktreeActive })
|
||||
const hasSplits = layout.type === 'split'
|
||||
|
||||
return (
|
||||
<DndContext
|
||||
@@ -219,10 +221,16 @@ export default function TabGroupSplitLayout({
|
||||
each pane — so vertical split resize handles don't extend into the
|
||||
window-drag region at the top. Only the split layout's own panes
|
||||
own the resize handles, while this strip keeps the whole top of the
|
||||
center column draggable regardless of how the splits are arranged. */}
|
||||
center column draggable regardless of how the splits are arranged.
|
||||
Why conditional `border-l`: when splits exist, the leftmost pane
|
||||
paints its own `border-l` starting at y=8. Mirroring that border
|
||||
on the drag strip extends the seam up to y=0 so the left-sidebar /
|
||||
center-column line reaches the top of the window, matching the
|
||||
full-height right-sidebar seam. Without splits the pane has no
|
||||
`border-l`, so we skip it here too to avoid a dangling 8px stub. */}
|
||||
<div className="flex flex-col flex-1 min-w-0 min-h-0 overflow-hidden">
|
||||
<div
|
||||
className="h-2 shrink-0 bg-card"
|
||||
className={`h-2 shrink-0 bg-card${hasSplits ? ' border-l border-border' : ''}`}
|
||||
style={{ WebkitAppRegion: 'drag' } as React.CSSProperties}
|
||||
/>
|
||||
<div className="flex flex-1 min-w-0 min-h-0 overflow-hidden">
|
||||
@@ -232,7 +240,7 @@ export default function TabGroupSplitLayout({
|
||||
worktreeId={worktreeId}
|
||||
focusedGroupId={focusedGroupId}
|
||||
isWorktreeActive={isWorktreeActive}
|
||||
hasSplitGroups={layout.type === 'split'}
|
||||
hasSplitGroups={hasSplits}
|
||||
touchesTopEdge={true}
|
||||
touchesRightEdge={true}
|
||||
touchesLeftEdge={true}
|
||||
|
||||
Reference in New Issue
Block a user