diff --git a/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx b/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx index 2adcd198f..c2161bdf0 100644 --- a/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx +++ b/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx @@ -122,6 +122,7 @@ const TAB_ROW_PADDING_HORIZONTAL = 4; const TAB_ICON_WIDTH = 14; const TAB_CONTENT_GAP = 4; const TAB_DROP_INDICATOR_WIDTH = 4; +const TAB_MODIFIED_DOT_SIZE = 8; const TAB_MIN_WIDTH = 96; const TAB_MAX_WIDTH = 160; const TAB_CLOSE_BUTTON_RESERVED_WIDTH = 0; @@ -607,6 +608,7 @@ interface ResolvedWorkspaceDesktopTabRowItem extends WorkspaceDesktopTabRowItem interface WorkspaceTabLabel { key: string; label: string; + modified: boolean; } interface WorkspaceTabLabelMeasurement { @@ -630,12 +632,15 @@ function completeWorkspaceTabLabelWidths( measurements: Map, ): number[] | null { const widths: number[] = []; - for (const { key, label } of labels) { + for (const { key, label, modified } of labels) { const measurement = measurements.get(key); if (!measurement || measurement.label !== label || measurement.width <= 0) { return null; } - widths.push(measurement.width + TAB_LABEL_LAYOUT_ALLOWANCE); + // The modified dot sits in the content row, so a modified tab needs that much more width + // before its label starts truncating. + const modifiedAllowance = modified ? TAB_CONTENT_GAP + TAB_MODIFIED_DOT_SIZE : 0; + widths.push(measurement.width + TAB_LABEL_LAYOUT_ALLOWANCE + modifiedAllowance); } return widths; } @@ -803,6 +808,7 @@ function TabHandleContent({ backdrop, tabLabelSkeletonStyle, tabLabelStyle, + modifiedTestId, }: { presentation: WorkspaceTabPresentation; isHighlighted: boolean; @@ -810,7 +816,9 @@ function TabHandleContent({ backdrop: SurfaceBackdrop; tabLabelSkeletonStyle: React.ComponentProps["style"]; tabLabelStyle: React.ComponentProps["style"]; + modifiedTestId: string; }) { + const { t } = useTranslation(); const tabHandleDataSet = useMemo( () => ({ statusBucket: presentation.statusBucket ?? "none" }), [presentation.statusBucket], @@ -829,6 +837,15 @@ function TabHandleContent({ {presentation.label} ) : null} + {/* The dot is a laid-out sibling of the label, not an overlay, so a truncated label ends + before it instead of running underneath it. */} + {presentation.modified ? ( + + ) : null} ); } @@ -868,7 +885,6 @@ function TabChip({ onCloseTab: (tabId: string) => Promise | void; dragHandleProps: DraggableListDragHandleProps | undefined; }) { - const { t } = useTranslation(); const { closeButtonTestId, contextMenuTestId, menuEntries } = resolvedTab; const middleClickRef = useMiddleClickClose( useCallback(() => void onCloseTab(tab.tabId), [onCloseTab, tab.tabId]), @@ -982,16 +998,8 @@ function TabChip({ backdrop={chipBackdrop} tabLabelSkeletonStyle={tabLabelSkeletonStyle} tabLabelStyle={tabLabelStyle} + modifiedTestId={`workspace-tab-modified-${buildDeterministicWorkspaceTabId(tab.target)}`} /> - {presentation.modified ? ( - - - - ) : null} ({ justifyContent: "center", zIndex: 1, }, - tabModifiedIndicator: { - position: "absolute", - right: 8, - top: 0, - bottom: 0, - alignItems: "center", - justifyContent: "center", - }, tabModifiedDot: { - width: 8, - height: 8, + width: TAB_MODIFIED_DOT_SIZE, + height: TAB_MODIFIED_DOT_SIZE, + flexShrink: 0, borderRadius: theme.borderRadius.full, backgroundColor: theme.colors.foregroundMuted, },