From b92fe124e042f5ee01ec91f205da26b8dbbb6b99 Mon Sep 17 00:00:00 2001 From: James Ritchie Date: Tue, 28 Jul 2026 23:01:58 +0100 Subject: [PATCH 1/3] fix(webapp): fade overflowing side menu selector labels The org, project, and environment selector rows each rendered their own plain span, so long names hard-clipped mid-character (and the environment name ended in an ellipsis). They now use SideMenuLabel, which masks the right edge only while the text actually overflows, matching the rest of the menu. The mask moved into a shared labelOverflowFade module so EnvironmentLabel can reuse it; that component already measured overflow for its tooltip, so no second observer was needed. --- .server-changes/side-menu-label-fade.md | 6 ++++++ .../components/environments/EnvironmentLabel.tsx | 7 ++++++- .../components/navigation/EnvironmentSelector.tsx | 4 ++-- apps/webapp/app/components/navigation/SideMenu.tsx | 10 +++++----- .../app/components/navigation/SideMenuItem.tsx | 11 ++--------- .../app/components/primitives/labelOverflowFade.ts | 14 ++++++++++++++ 6 files changed, 35 insertions(+), 17 deletions(-) create mode 100644 .server-changes/side-menu-label-fade.md create mode 100644 apps/webapp/app/components/primitives/labelOverflowFade.ts diff --git a/.server-changes/side-menu-label-fade.md b/.server-changes/side-menu-label-fade.md new file mode 100644 index 0000000000..9c0dd2a5f0 --- /dev/null +++ b/.server-changes/side-menu-label-fade.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: improvement +--- + +Long organization, project, and environment names in the side menu now fade out at the right edge instead of being cut off mid-character or ending in an ellipsis. diff --git a/apps/webapp/app/components/environments/EnvironmentLabel.tsx b/apps/webapp/app/components/environments/EnvironmentLabel.tsx index 3fd5d526fc..8c07b28b29 100644 --- a/apps/webapp/app/components/environments/EnvironmentLabel.tsx +++ b/apps/webapp/app/components/environments/EnvironmentLabel.tsx @@ -6,6 +6,7 @@ import { } from "~/assets/icons/EnvironmentIcons"; import type { RuntimeEnvironment } from "~/models/runtimeEnvironment.server"; import { cn } from "~/utils/cn"; +import { labelOverflowFadeStyle } from "~/components/primitives/labelOverflowFade"; import { SimpleTooltip } from "~/components/primitives/Tooltip"; import { useEffect, useRef, useState } from "react"; @@ -88,7 +89,10 @@ export function EnvironmentLabel({ tooltipSideOffset?: number; tooltipSide?: "top" | "right" | "bottom" | "left"; disableTooltip?: boolean; - /** When false, the label clips without an ellipsis (side menu fades it in place). Defaults true. */ + /** + * When false, an overflowing label fades out at its right edge instead of ending in an ellipsis, + * matching the other side menu labels. Defaults true. + */ truncate?: boolean; }) { const spanRef = useRef(null); @@ -122,6 +126,7 @@ export function EnvironmentLabel({ environmentTextClassName(environment), className )} + style={truncate ? undefined : labelOverflowFadeStyle(isTruncated)} > {text} diff --git a/apps/webapp/app/components/navigation/EnvironmentSelector.tsx b/apps/webapp/app/components/navigation/EnvironmentSelector.tsx index 0f51b22545..b2c1d593a0 100644 --- a/apps/webapp/app/components/navigation/EnvironmentSelector.tsx +++ b/apps/webapp/app/components/navigation/EnvironmentSelector.tsx @@ -94,7 +94,7 @@ export function EnvironmentSelector({ fades in place and scales its width to 0 so it never holds width mid-drag. The selector is also reused outside the side menu (BlankStatePanels, limits) where the var is unset — the 0.2 max-width fallback pins a ~200px cap (0.2 * 1000px) so long names - ellipsis-truncate there instead of widening the control, while opacity stays 1. + fade out there instead of widening the control, while opacity stays 1. */} diff --git a/apps/webapp/app/components/navigation/SideMenu.tsx b/apps/webapp/app/components/navigation/SideMenu.tsx index d5a8172fe6..29eb1fc852 100644 --- a/apps/webapp/app/components/navigation/SideMenu.tsx +++ b/apps/webapp/app/components/navigation/SideMenu.tsx @@ -1682,9 +1682,9 @@ function OrgSelector({ className="flex min-w-0 items-center gap-1.5 overflow-hidden" style={SIDE_MENU_SELECTOR_LABEL_STYLE} > - + {organization.title} - + - + {project.name ?? "Select a project"} - + - {p.name} + {p.name} } isSelected={isSelected} diff --git a/apps/webapp/app/components/navigation/SideMenuItem.tsx b/apps/webapp/app/components/navigation/SideMenuItem.tsx index 035fcc5437..d162c06462 100644 --- a/apps/webapp/app/components/navigation/SideMenuItem.tsx +++ b/apps/webapp/app/components/navigation/SideMenuItem.tsx @@ -12,12 +12,10 @@ import { motion } from "framer-motion"; import { usePathName } from "~/hooks/usePathName"; import { cn } from "~/utils/cn"; import { type RenderIcon, Icon } from "../primitives/Icon"; +import { labelOverflowFadeStyle } from "../primitives/labelOverflowFade"; import { SimpleTooltip } from "../primitives/Tooltip"; import { useActiveFavoriteId } from "./favoritePages"; -/** Right-edge fade shown instead of a hard clip, only while the label actually overflows. */ -const LABEL_OVERFLOW_MASK = "linear-gradient(to right, black calc(100% - 1.5rem), transparent)"; - /** * A menu label that fades out at its right edge when (and only when) the text overflows. Text * that fits renders exactly as before, with no mask. Overflow is re-measured when the element @@ -56,12 +54,7 @@ export function SideMenuLabel({ {children} diff --git a/apps/webapp/app/components/primitives/labelOverflowFade.ts b/apps/webapp/app/components/primitives/labelOverflowFade.ts new file mode 100644 index 0000000000..3188439c17 --- /dev/null +++ b/apps/webapp/app/components/primitives/labelOverflowFade.ts @@ -0,0 +1,14 @@ +/** + * Right-edge gradient fade for labels that clip, used instead of a hard cut or an ellipsis. Apply + * it only while the text actually overflows — masking a label that fits would fade its last + * characters for no reason. + */ +export const LABEL_OVERFLOW_MASK = + "linear-gradient(to right, black calc(100% - 1.5rem), transparent)"; + +/** Mask style for a clipping label, or nothing when the text fits. Spread into a `style` prop. */ +export function labelOverflowFadeStyle(isOverflowing: boolean) { + return isOverflowing + ? { maskImage: LABEL_OVERFLOW_MASK, WebkitMaskImage: LABEL_OVERFLOW_MASK } + : undefined; +} From 469936ed3179c23bd3ef679613c9331abc6dd737 Mon Sep 17 00:00:00 2001 From: James Ritchie Date: Tue, 28 Jul 2026 23:05:03 +0100 Subject: [PATCH 2/3] chore(webapp): drop server-changes note, tighten fade helper Un-export the mask constant (only used inside the module) and fold the truncate check into the overflow argument. --- .server-changes/side-menu-label-fade.md | 6 ------ .../webapp/app/components/environments/EnvironmentLabel.tsx | 2 +- apps/webapp/app/components/primitives/labelOverflowFade.ts | 3 +-- 3 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 .server-changes/side-menu-label-fade.md diff --git a/.server-changes/side-menu-label-fade.md b/.server-changes/side-menu-label-fade.md deleted file mode 100644 index 9c0dd2a5f0..0000000000 --- a/.server-changes/side-menu-label-fade.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: improvement ---- - -Long organization, project, and environment names in the side menu now fade out at the right edge instead of being cut off mid-character or ending in an ellipsis. diff --git a/apps/webapp/app/components/environments/EnvironmentLabel.tsx b/apps/webapp/app/components/environments/EnvironmentLabel.tsx index 8c07b28b29..5a7296e5dd 100644 --- a/apps/webapp/app/components/environments/EnvironmentLabel.tsx +++ b/apps/webapp/app/components/environments/EnvironmentLabel.tsx @@ -126,7 +126,7 @@ export function EnvironmentLabel({ environmentTextClassName(environment), className )} - style={truncate ? undefined : labelOverflowFadeStyle(isTruncated)} + style={labelOverflowFadeStyle(!truncate && isTruncated)} > {text} diff --git a/apps/webapp/app/components/primitives/labelOverflowFade.ts b/apps/webapp/app/components/primitives/labelOverflowFade.ts index 3188439c17..c521680448 100644 --- a/apps/webapp/app/components/primitives/labelOverflowFade.ts +++ b/apps/webapp/app/components/primitives/labelOverflowFade.ts @@ -3,8 +3,7 @@ * it only while the text actually overflows — masking a label that fits would fade its last * characters for no reason. */ -export const LABEL_OVERFLOW_MASK = - "linear-gradient(to right, black calc(100% - 1.5rem), transparent)"; +const LABEL_OVERFLOW_MASK = "linear-gradient(to right, black calc(100% - 1.5rem), transparent)"; /** Mask style for a clipping label, or nothing when the text fits. Spread into a `style` prop. */ export function labelOverflowFadeStyle(isOverflowing: boolean) { From 6a628e21d7fdbfc0d85b1933b1d2fde504c65ad5 Mon Sep 17 00:00:00 2001 From: James Ritchie Date: Tue, 28 Jul 2026 23:08:23 +0100 Subject: [PATCH 3/3] chore(webapp): trim comments on the label fade helper --- .../webapp/app/components/environments/EnvironmentLabel.tsx | 5 +---- apps/webapp/app/components/primitives/labelOverflowFade.ts | 6 ------ 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/apps/webapp/app/components/environments/EnvironmentLabel.tsx b/apps/webapp/app/components/environments/EnvironmentLabel.tsx index 5a7296e5dd..6f253676e0 100644 --- a/apps/webapp/app/components/environments/EnvironmentLabel.tsx +++ b/apps/webapp/app/components/environments/EnvironmentLabel.tsx @@ -89,10 +89,7 @@ export function EnvironmentLabel({ tooltipSideOffset?: number; tooltipSide?: "top" | "right" | "bottom" | "left"; disableTooltip?: boolean; - /** - * When false, an overflowing label fades out at its right edge instead of ending in an ellipsis, - * matching the other side menu labels. Defaults true. - */ + /** When false, an overflowing label fades at its right edge instead of ending in an ellipsis. */ truncate?: boolean; }) { const spanRef = useRef(null); diff --git a/apps/webapp/app/components/primitives/labelOverflowFade.ts b/apps/webapp/app/components/primitives/labelOverflowFade.ts index c521680448..199d81a0fb 100644 --- a/apps/webapp/app/components/primitives/labelOverflowFade.ts +++ b/apps/webapp/app/components/primitives/labelOverflowFade.ts @@ -1,11 +1,5 @@ -/** - * Right-edge gradient fade for labels that clip, used instead of a hard cut or an ellipsis. Apply - * it only while the text actually overflows — masking a label that fits would fade its last - * characters for no reason. - */ const LABEL_OVERFLOW_MASK = "linear-gradient(to right, black calc(100% - 1.5rem), transparent)"; -/** Mask style for a clipping label, or nothing when the text fits. Spread into a `style` prop. */ export function labelOverflowFadeStyle(isOverflowing: boolean) { return isOverflowing ? { maskImage: LABEL_OVERFLOW_MASK, WebkitMaskImage: LABEL_OVERFLOW_MASK }