From f2ac03196fb532ec2e2d2945c86f5ac2074ae4d8 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sun, 26 Jul 2026 04:25:29 -0500 Subject: [PATCH] fix(desktop): restore the pane tab strip's bottom rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The strip's rule is an inset shadow painted in the container's last pixel row, and full-height tabs covered it — so each tab read as overhanging the bar by 1px. Inactive tabs compensated with their own border, stacking a second translucent line that darkened the seam. Inactive tabs now stop 1px short and draw no bottom border, leaving the container as the sole owner of one continuous rule; the active tab keeps full height so it alone cuts through. Hover also darkens rather than lightens, since lightening moved a hovered tab toward the active surface's look. --- apps/desktop/src/components/ui/pane-tab.tsx | 24 +++++++++++++-------- apps/desktop/src/styles.css | 5 +++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/apps/desktop/src/components/ui/pane-tab.tsx b/apps/desktop/src/components/ui/pane-tab.tsx index da8baae2aec9..a9156824ec86 100644 --- a/apps/desktop/src/components/ui/pane-tab.tsx +++ b/apps/desktop/src/components/ui/pane-tab.tsx @@ -12,17 +12,23 @@ export const PANE_TAB_STRIP_LINE_RIGHT = 'shadow-[inset_-1px_0_0_var(--ui-stroke const TAB = 'group/tab relative flex shrink-0 items-center border-transparent bg-(--tab-bg) text-[0.6875rem] font-medium [-webkit-app-region:no-drag]' -const TAB_HORIZONTAL = 'h-full min-w-0 max-w-48 border-b not-first:border-l not-first:border-l-(--ui-stroke-quaternary)' +// Stop 1px short: the strip's rule is an INSET shadow painted in the +// container's own last pixel row, so a full-height tab covers it and reads as +// overhanging the bar. The container owns the line; nothing redraws it. +const TAB_HORIZONTAL = + 'h-[calc(100%-1px)] min-w-0 max-w-48 not-first:border-l not-first:border-l-(--ui-stroke-quaternary)' const TAB_VERTICAL = 'w-full max-h-48 justify-center not-first:border-t not-first:border-t-(--ui-stroke-quaternary) [writing-mode:vertical-rl]' -const TAB_ACTIVE = 'text-foreground [--tab-bg:var(--pane-tab-active-bg,var(--ui-editor-surface-background))]' +// Full height, so the active tab alone covers that row and cuts the rule. +const TAB_ACTIVE = + 'h-full text-foreground [--tab-bg:var(--pane-tab-active-bg,var(--ui-editor-surface-background))]' -// Inactive = gutter. Hover = 4% translucent wash (VS Code/GitHub alpha hover), -// not an opaque recolor — and never touch borders. +// Inactive = gutter. Hover DARKENS: active is the lighter content surface, so a +// lightening wash made the two nearly indistinguishable. const TAB_IDLE = - 'text-(--ui-text-tertiary) [--tab-bg:var(--pane-tab-strip-bg,var(--theme-card-seed))] hover:shadow-[inset_0_0_0_100vmax_color-mix(in_srgb,var(--ui-base)_4%,transparent)] hover:text-(--ui-text-secondary)' + 'text-(--ui-text-tertiary) [--tab-bg:var(--pane-tab-strip-bg,var(--theme-card-seed))] hover:shadow-[inset_0_0_0_100vmax_color-mix(in_srgb,#000_var(--ui-tab-hover-darken),transparent)] hover:text-(--ui-text-secondary)' interface PaneTabProps extends React.ComponentProps<'div'> { active?: boolean @@ -66,9 +72,9 @@ export const PaneTab = React.forwardRef(function P }, ref ) { - // Content-facing edge: horizontal cuts the bottom strip line; vertical cuts - // the side that faces the editor (left rail → right edge, right rail → left). - const edge = vertical ? (side === 'right' ? 'border-l' : 'border-r') : 'border-b' + // Vertical rails only. Horizontal tabs draw no bottom border — the strip owns + // that rule, and a per-tab border stacked a second translucent line over it. + const edge = vertical ? (side === 'right' ? 'border-l' : 'border-r') : undefined return (
(function P TAB, vertical ? TAB_VERTICAL : TAB_HORIZONTAL, edge, - active ? TAB_ACTIVE : cn(TAB_IDLE, `${edge}-(--ui-stroke-tertiary)`), + active ? TAB_ACTIVE : cn(TAB_IDLE, edge && `${edge}-(--ui-stroke-tertiary)`), className )} data-active={active} diff --git a/apps/desktop/src/styles.css b/apps/desktop/src/styles.css index 624594484687..0f7fdbdd3b47 100644 --- a/apps/desktop/src/styles.css +++ b/apps/desktop/src/styles.css @@ -317,6 +317,10 @@ :root.dark bumps the alpha since a dark card swallows shadow. Removed on focus (the focus border carries the state). */ --dt-input-inset: inset 0 1px 1px color-mix(in srgb, #000 10%, transparent); + /* Hovered inactive tab: deepen it AWAY from the lighter active surface, so + hover can never be mistaken for selected. Dark mode needs more — black + over a dark gutter shows less. */ + --ui-tab-hover-darken: 2.5%; --dt-ring: var(--ui-stroke-primary); --dt-midground: var(--theme-midground); --dt-composer-ring: var(--ui-base); @@ -446,6 +450,7 @@ --backdrop-invert-mul: 0; /* Dark mode: a dark card needs a stronger black inset to show the recess. */ --dt-input-inset: inset 0 1px 1px color-mix(in srgb, #000 38%, transparent); + --ui-tab-hover-darken: 6%; /* Dark needs a lighter resting border than light mode. */ --dt-input-border: 4%;