From 94dd0e42873ea0c2be39085b42d2ae6c1cc346e8 Mon Sep 17 00:00:00 2001 From: Eswaraiahsapram Date: Wed, 18 Feb 2026 23:27:53 +0530 Subject: [PATCH 1/4] fix(scorecard): fix tooltip display and error title clipping for some languages --- .../plugins/scorecard/report-alpha.api.md | 6 +- .../src/components/Common/ErrorTooltip.tsx | 40 +++ .../src/components/Scorecard/Scorecard.tsx | 272 +++++++++++++----- .../EmptyStatePanel.tsx | 41 ++- .../ResponsivePieChart.tsx | 32 +++ 5 files changed, 305 insertions(+), 86 deletions(-) create mode 100644 workspaces/scorecard/plugins/scorecard/src/components/Common/ErrorTooltip.tsx diff --git a/workspaces/scorecard/plugins/scorecard/report-alpha.api.md b/workspaces/scorecard/plugins/scorecard/report-alpha.api.md index 299f70ef3d..27fea4b7e4 100644 --- a/workspaces/scorecard/plugins/scorecard/report-alpha.api.md +++ b/workspaces/scorecard/plugins/scorecard/report-alpha.api.md @@ -10,13 +10,13 @@ import { TranslationResource } from '@backstage/frontend-plugin-api'; export const scorecardTranslationRef: TranslationRef< 'plugin.scorecard', { - readonly 'emptyState.button': string; readonly 'emptyState.title': string; readonly 'emptyState.description': string; + readonly 'emptyState.button': string; readonly 'emptyState.altText': string; - readonly 'permissionRequired.button': string; readonly 'permissionRequired.title': string; readonly 'permissionRequired.description': string; + readonly 'permissionRequired.button': string; readonly 'permissionRequired.altText': string; readonly 'errors.entityMissingProperties': string; readonly 'errors.invalidApiResponse': string; @@ -35,8 +35,8 @@ export const scorecardTranslationRef: TranslationRef< readonly 'metric.jira.open_issues.title': string; readonly 'metric.jira.open_issues.description': string; readonly 'thresholds.success': string; - readonly 'thresholds.error': string; readonly 'thresholds.warning': string; + readonly 'thresholds.error': string; readonly 'thresholds.noEntities': string; readonly 'thresholds.entities_one': string; readonly 'thresholds.entities_other': string; diff --git a/workspaces/scorecard/plugins/scorecard/src/components/Common/ErrorTooltip.tsx b/workspaces/scorecard/plugins/scorecard/src/components/Common/ErrorTooltip.tsx new file mode 100644 index 0000000000..e6d50c03b6 --- /dev/null +++ b/workspaces/scorecard/plugins/scorecard/src/components/Common/ErrorTooltip.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import MuiTooltip from '@mui/material/Tooltip'; + +export const ErrorTooltip = ({ + title, + tooltipPosition, +}: { + title: string | undefined; + tooltipPosition: { x: number; y: number } | undefined; +}) => { + return ( +
+ + {/* Anchor for tooltip so it appears under the pie chart */} +
Tooltip
+
+
+ ); +}; diff --git a/workspaces/scorecard/plugins/scorecard/src/components/Scorecard/Scorecard.tsx b/workspaces/scorecard/plugins/scorecard/src/components/Scorecard/Scorecard.tsx index 6b793b6b87..612328f321 100644 --- a/workspaces/scorecard/plugins/scorecard/src/components/Scorecard/Scorecard.tsx +++ b/workspaces/scorecard/plugins/scorecard/src/components/Scorecard/Scorecard.tsx @@ -14,6 +14,8 @@ * limitations under the License. */ +import { useLayoutEffect, useRef, useState } from 'react'; + import { MetricValue, ThresholdResult, @@ -27,14 +29,14 @@ import { Legend, Tooltip, } from 'recharts'; - import Box from '@mui/material/Box'; import { useTheme } from '@mui/material/styles'; -import MuiTooltip from '@mui/material/Tooltip'; + import { useTranslation } from '../../hooks/useTranslation'; import { CardWrapper } from '../Common/CardWrapper'; import CustomLegend from './CustomLegend'; import { getRingColor } from '../../utils/utils'; +import { ErrorTooltip } from '../Common/ErrorTooltip'; interface ScorecardProps { cardTitle: string; @@ -49,6 +51,130 @@ interface ScorecardProps { thresholdError?: string; } +const ScorecardCenterLabel = ({ + cx, + cy, + statusColor, + StatusIcon, + value, + isErrorState, + errorLabel, + color, + onLabelMouseEnter, + onLabelMouseLeave, +}: { + cx: number; + cy: number; + statusColor: string; + StatusIcon: React.ElementType; + value: MetricValue | null; + isErrorState: boolean; + errorLabel: string; + color: string | undefined; + onLabelMouseEnter: (e: React.MouseEvent) => void; + onLabelMouseLeave: (e: React.MouseEvent) => void; +}) => { + const fontSize = 14; + const lineHeight = 1.2; + + const textRef = useRef(null); + const [layout, setLayout] = useState({ yOffset: -10, height: 40 }); + + const getYOffset = (lineCount: number) => { + switch (lineCount) { + case 2: + return -17; + case 3: + return -24; + default: + return -8; + } + }; + + const getHeight = (lineCount: number) => { + switch (lineCount) { + case 2: + return 48; + case 3: + return 56; + default: + return 40; + } + }; + + useLayoutEffect(() => { + if (!isErrorState) return; + const el = textRef.current; + if (!el) return; + + const lineHeightPx = fontSize * lineHeight; + const lineCount = Math.round(el.scrollHeight / lineHeightPx); + + const nextOffset = getYOffset(lineCount); + const nextHeight = getHeight(lineCount); + + setLayout(prev => + prev.yOffset === nextOffset && prev.height === nextHeight + ? prev + : { yOffset: nextOffset, height: nextHeight }, + ); + }, [isErrorState, errorLabel]); + + return ( + + + + muiTheme.palette[statusColor.split('.')[0]][ + statusColor.split('.')[1] + ], + }} + /> + + {!isErrorState && ( + + {value} + + )} + {isErrorState && ( + +
+ {errorLabel} +
+
+ )} +
+ ); +}; + const Scorecard = ({ cardTitle, description, @@ -64,6 +190,8 @@ const Scorecard = ({ const theme = useTheme(); const { t } = useTranslation(); + const [isPieAreaActive, setIsPieAreaActive] = useState(false); + const isErrorState = isMetricDataError || isThresholdError; const ringColor = getRingColor(theme, statusColor, isErrorState); @@ -100,6 +228,27 @@ const Scorecard = ({ }} > + {/* This is the circle that is used to trigger the tooltip */} + {isErrorState && ( + + { + setIsPieAreaActive(true); + e.stopPropagation(); + }} + onMouseLeave={e => { + setIsPieAreaActive(false); + e.stopPropagation(); + }} + /> + + )} + - - - muiTheme.palette[statusColor.split('.')[0]][ - statusColor.split('.')[1] - ], - }} - /> - - {!isErrorState && ( - - {value} - - )} - - {isErrorState && ( - -
- {isMetricDataError && - t('errors.metricDataUnavailable')} - {!isMetricDataError && - isThresholdError && - t('errors.invalidThresholds')} -
-
- )} - + { + setIsPieAreaActive(true); + e.stopPropagation(); + }} + onLabelMouseLeave={e => { + setIsPieAreaActive(false); + e.stopPropagation(); + }} + /> ); }} + onMouseEnter={() => { + setIsPieAreaActive(true); + }} + onMouseLeave={() => { + setIsPieAreaActive(false); + }} > {pieData.map(entry => ( @@ -210,35 +343,26 @@ const Scorecard = ({ /> { - if (!active) return null; + content={({ coordinate }) => { + let errorTooltipTitle: string | undefined; + if (isMetricDataError) { + errorTooltipTitle = metricDataError; + } else if (isThresholdError) { + errorTooltipTitle = thresholdError; + } else { + errorTooltipTitle = undefined; + } + if (!isPieAreaActive || coordinate === undefined) return null; return ( - - {/* Need to hide the tooltip content because we are using the position prop to position the tooltip */} -
Tooltip content
-
+ /> ); }} /> diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardHomepageSection/EmptyStatePanel.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardHomepageSection/EmptyStatePanel.tsx index 7446017003..5c7d9535f8 100644 --- a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardHomepageSection/EmptyStatePanel.tsx +++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardHomepageSection/EmptyStatePanel.tsx @@ -22,7 +22,7 @@ import { CardWrapper } from '../Common/CardWrapper'; import { useTranslation } from '../../hooks/useTranslation'; import { getStatusConfig, getRingColor } from '../../utils/utils'; import CustomLegend from '../Scorecard/CustomLegend'; -import { CustomTooltip } from './CustomTooltip'; +import { ErrorTooltip } from '../Common/ErrorTooltip'; import { ResponsivePieChart } from './ResponsivePieChart'; const CenterLabel = ({ @@ -30,11 +30,15 @@ const CenterLabel = ({ cy, label, color, + onLabelMouseEnter, + onLabelMouseLeave, }: { cx: number; cy: number; label: string; color?: string; + onLabelMouseEnter?: (e: React.MouseEvent) => void; + onLabelMouseLeave?: (e: React.MouseEvent) => void; }) => { const fontSize = 14; const lineHeight = 1.2; @@ -99,7 +103,10 @@ const CenterLabel = ({ textAlign: 'center', lineHeight, wordBreak: 'break-word', + cursor: 'pointer', }} + onMouseEnter={onLabelMouseEnter} + onMouseLeave={onLabelMouseLeave} > {label} @@ -120,6 +127,9 @@ export const EmptyStatePanel = ({ const theme = useTheme(); const { t } = useTranslation(); + const [isLabelHovered, setIsLabelHovered] = useState(false); + const [isInsidePieCircle, setIsInsidePieCircle] = useState(false); + const titleKey = `metric.${metricId}.title`; const descriptionKey = `metric.${metricId}.description`; @@ -194,20 +204,33 @@ export const EmptyStatePanel = ({ cy={centerY} label={label} color={color} + onLabelMouseEnter={e => { + setIsLabelHovered(true); + e.stopPropagation(); + }} + onLabelMouseLeave={e => { + setIsLabelHovered(false); + e.stopPropagation(); + }} /> ); }} legendContent={props => ( )} - tooltipContent={props => ( - - )} + tooltipContent={({ coordinate }) => { + const showTooltip = isLabelHovered || isInsidePieCircle; + + if (!showTooltip || coordinate === undefined) return null; + return ( + + ); + }} + isErrorState + setIsInsidePieCircle={setIsInsidePieCircle} /> diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardHomepageSection/ResponsivePieChart.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardHomepageSection/ResponsivePieChart.tsx index 01a446b043..a630a1c855 100644 --- a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardHomepageSection/ResponsivePieChart.tsx +++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardHomepageSection/ResponsivePieChart.tsx @@ -46,6 +46,7 @@ export interface PieLegendContentProps { interface PieTooltipContentProps { active?: boolean; + coordinate?: { x: number; y: number }; payload?: readonly PieTooltipPayload[]; label?: string | number; } @@ -54,6 +55,8 @@ interface ResponsivePieChartProps { LabelContent?: (props: PieLabelRenderProps) => React.ReactNode; legendContent: (props: PieLegendContentProps) => React.ReactNode; tooltipContent: (props: PieTooltipContentProps) => React.ReactNode; + isErrorState?: boolean; + setIsInsidePieCircle?: (isInside: boolean) => void; } export const ResponsivePieChart = ({ @@ -61,10 +64,33 @@ export const ResponsivePieChart = ({ LabelContent, legendContent, tooltipContent, + isErrorState, + setIsInsidePieCircle, }: ResponsivePieChartProps) => { return ( + {/* This is the circle that is used to trigger the tooltip */} + {isErrorState && ( + + { + setIsInsidePieCircle?.(true); + e.stopPropagation?.(); + }} + onMouseLeave={e => { + setIsInsidePieCircle?.(false); + e.stopPropagation(); + }} + /> + + )} + { + setIsInsidePieCircle?.(true); + }} + onMouseLeave={() => { + setIsInsidePieCircle?.(false); + }} > {pieData.map(category => ( From 0716baf7ddb76ce171f2df28ecffd2760de418c0 Mon Sep 17 00:00:00 2001 From: Eswaraiahsapram Date: Thu, 19 Feb 2026 09:57:39 +0530 Subject: [PATCH 2/4] fix api reports --- workspaces/scorecard/plugins/scorecard/report-alpha.api.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/workspaces/scorecard/plugins/scorecard/report-alpha.api.md b/workspaces/scorecard/plugins/scorecard/report-alpha.api.md index 27fea4b7e4..299f70ef3d 100644 --- a/workspaces/scorecard/plugins/scorecard/report-alpha.api.md +++ b/workspaces/scorecard/plugins/scorecard/report-alpha.api.md @@ -10,13 +10,13 @@ import { TranslationResource } from '@backstage/frontend-plugin-api'; export const scorecardTranslationRef: TranslationRef< 'plugin.scorecard', { + readonly 'emptyState.button': string; readonly 'emptyState.title': string; readonly 'emptyState.description': string; - readonly 'emptyState.button': string; readonly 'emptyState.altText': string; + readonly 'permissionRequired.button': string; readonly 'permissionRequired.title': string; readonly 'permissionRequired.description': string; - readonly 'permissionRequired.button': string; readonly 'permissionRequired.altText': string; readonly 'errors.entityMissingProperties': string; readonly 'errors.invalidApiResponse': string; @@ -35,8 +35,8 @@ export const scorecardTranslationRef: TranslationRef< readonly 'metric.jira.open_issues.title': string; readonly 'metric.jira.open_issues.description': string; readonly 'thresholds.success': string; - readonly 'thresholds.warning': string; readonly 'thresholds.error': string; + readonly 'thresholds.warning': string; readonly 'thresholds.noEntities': string; readonly 'thresholds.entities_one': string; readonly 'thresholds.entities_other': string; From 4a8fb0172e1b366125655c4ae89530039622cf3f Mon Sep 17 00:00:00 2001 From: Eswaraiahsapram Date: Thu, 19 Feb 2026 11:14:44 +0530 Subject: [PATCH 3/4] fix e2e sonarqube --- .../packages/app/e2e-tests/pages/HomePage.ts | 6 +- .../Common/__tests__/ErrorTooltip.test.tsx | 73 +++++++++++++++++++ .../src/components/Scorecard/Scorecard.tsx | 32 ++------ .../EmptyStatePanel.tsx | 33 ++------- .../plugins/scorecard/src/utils/utils.ts | 22 ++++++ 5 files changed, 112 insertions(+), 54 deletions(-) create mode 100644 workspaces/scorecard/plugins/scorecard/src/components/Common/__tests__/ErrorTooltip.test.tsx diff --git a/workspaces/scorecard/packages/app/e2e-tests/pages/HomePage.ts b/workspaces/scorecard/packages/app/e2e-tests/pages/HomePage.ts index 94ee05f01a..3b0431da1f 100644 --- a/workspaces/scorecard/packages/app/e2e-tests/pages/HomePage.ts +++ b/workspaces/scorecard/packages/app/e2e-tests/pages/HomePage.ts @@ -93,7 +93,7 @@ export class HomePage { ) { const card = this.getCard(metricId); await expect(card).toContainText( - this.translations.errors.missingPermissionMessage, + this.translations.errors.missingPermission, ); } @@ -102,8 +102,6 @@ export class HomePage { ) { const card = this.getCard(metricId); await expect(card).toContainText(this.translations.errors.noDataFound); - await expect(card).toContainText( - this.translations.errors.noDataFoundMessage, - ); + await expect(card).toContainText(this.translations.errors.noDataFound); } } diff --git a/workspaces/scorecard/plugins/scorecard/src/components/Common/__tests__/ErrorTooltip.test.tsx b/workspaces/scorecard/plugins/scorecard/src/components/Common/__tests__/ErrorTooltip.test.tsx new file mode 100644 index 0000000000..47b45a21f3 --- /dev/null +++ b/workspaces/scorecard/plugins/scorecard/src/components/Common/__tests__/ErrorTooltip.test.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render, screen } from '@testing-library/react'; + +import { ErrorTooltip } from '../ErrorTooltip'; + +// MUI Tooltip uses portals + transitions +// disable them for stable tests +jest.mock('@mui/material/Tooltip', () => { + return ({ children, title, open }: any) => ( +
+ {children} + {open && title && {title}} +
+ ); +}); + +describe('ErrorTooltip Component', () => { + it('should render tooltip when title is provided', () => { + render( + , + ); + + expect(screen.getByTestId('tooltip')).toHaveTextContent('Error occurred'); + }); + + it('should position tooltip correctly', () => { + const { container } = render( + , + ); + + const wrapper = container.firstChild as HTMLElement; + + expect(wrapper).toHaveStyle({ + left: '50px', + top: '75px', + position: 'absolute', + }); + }); + + it('should not render tooltip text when title is undefined', () => { + render( + , + ); + + expect(screen.queryByTestId('tooltip')).not.toBeInTheDocument(); + }); + + it('should render even if tooltipPosition is undefined', () => { + const { container } = render( + , + ); + + expect(container.firstChild).toBeInTheDocument(); + }); +}); diff --git a/workspaces/scorecard/plugins/scorecard/src/components/Scorecard/Scorecard.tsx b/workspaces/scorecard/plugins/scorecard/src/components/Scorecard/Scorecard.tsx index 612328f321..1e7e1469f3 100644 --- a/workspaces/scorecard/plugins/scorecard/src/components/Scorecard/Scorecard.tsx +++ b/workspaces/scorecard/plugins/scorecard/src/components/Scorecard/Scorecard.tsx @@ -35,7 +35,11 @@ import { useTheme } from '@mui/material/styles'; import { useTranslation } from '../../hooks/useTranslation'; import { CardWrapper } from '../Common/CardWrapper'; import CustomLegend from './CustomLegend'; -import { getRingColor } from '../../utils/utils'; +import { + getHeightForCenterLabel, + getRingColor, + getYOffsetForCenterLabel, +} from '../../utils/utils'; import { ErrorTooltip } from '../Common/ErrorTooltip'; interface ScorecardProps { @@ -80,28 +84,6 @@ const ScorecardCenterLabel = ({ const textRef = useRef(null); const [layout, setLayout] = useState({ yOffset: -10, height: 40 }); - const getYOffset = (lineCount: number) => { - switch (lineCount) { - case 2: - return -17; - case 3: - return -24; - default: - return -8; - } - }; - - const getHeight = (lineCount: number) => { - switch (lineCount) { - case 2: - return 48; - case 3: - return 56; - default: - return 40; - } - }; - useLayoutEffect(() => { if (!isErrorState) return; const el = textRef.current; @@ -110,8 +92,8 @@ const ScorecardCenterLabel = ({ const lineHeightPx = fontSize * lineHeight; const lineCount = Math.round(el.scrollHeight / lineHeightPx); - const nextOffset = getYOffset(lineCount); - const nextHeight = getHeight(lineCount); + const nextOffset = getYOffsetForCenterLabel(lineCount); + const nextHeight = getHeightForCenterLabel(lineCount); setLayout(prev => prev.yOffset === nextOffset && prev.height === nextHeight diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardHomepageSection/EmptyStatePanel.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardHomepageSection/EmptyStatePanel.tsx index 5c7d9535f8..bfebb60456 100644 --- a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardHomepageSection/EmptyStatePanel.tsx +++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardHomepageSection/EmptyStatePanel.tsx @@ -20,7 +20,12 @@ import { useTheme } from '@mui/material/styles'; import { CardWrapper } from '../Common/CardWrapper'; import { useTranslation } from '../../hooks/useTranslation'; -import { getStatusConfig, getRingColor } from '../../utils/utils'; +import { + getStatusConfig, + getRingColor, + getYOffsetForCenterLabel, + getHeightForCenterLabel, +} from '../../utils/utils'; import CustomLegend from '../Scorecard/CustomLegend'; import { ErrorTooltip } from '../Common/ErrorTooltip'; import { ResponsivePieChart } from './ResponsivePieChart'; @@ -46,28 +51,6 @@ const CenterLabel = ({ const textRef = useRef(null); const [layout, setLayout] = useState({ yOffset: -10, height: 40 }); - const getYOffset = (lineCount: number) => { - switch (lineCount) { - case 2: - return -17; - case 3: - return -24; - default: - return -8; - } - }; - - const getHeight = (lineCount: number) => { - switch (lineCount) { - case 2: - return 48; - case 3: - return 56; - default: - return 40; - } - }; - useLayoutEffect(() => { const el = textRef.current; if (!el) return; @@ -75,8 +58,8 @@ const CenterLabel = ({ const lineHeightPx = fontSize * lineHeight; const lineCount = Math.round(el.scrollHeight / lineHeightPx); - const nextOffset = getYOffset(lineCount); - const nextHeight = getHeight(lineCount); + const nextOffset = getYOffsetForCenterLabel(lineCount); + const nextHeight = getHeightForCenterLabel(lineCount); setLayout(prev => prev.yOffset === nextOffset && prev.height === nextHeight diff --git a/workspaces/scorecard/plugins/scorecard/src/utils/utils.ts b/workspaces/scorecard/plugins/scorecard/src/utils/utils.ts index 59bf84ef68..299aef3651 100644 --- a/workspaces/scorecard/plugins/scorecard/src/utils/utils.ts +++ b/workspaces/scorecard/plugins/scorecard/src/utils/utils.ts @@ -70,3 +70,25 @@ export const getRingColor = ( const [paletteKey, shade] = statusColor.split('.'); return theme.palette?.[paletteKey]?.[shade] ?? statusColor; }; + +export const getYOffsetForCenterLabel = (lineCount: number) => { + switch (lineCount) { + case 2: + return -17; + case 3: + return -24; + default: + return -8; + } +}; + +export const getHeightForCenterLabel = (lineCount: number) => { + switch (lineCount) { + case 2: + return 48; + case 3: + return 56; + default: + return 40; + } +}; From c4d145f2091b527aeb1a53fc16aa4db9b2928317 Mon Sep 17 00:00:00 2001 From: Eswaraiahsapram Date: Thu, 19 Feb 2026 11:25:37 +0530 Subject: [PATCH 4/4] add changeset --- workspaces/scorecard/.changeset/dirty-turtles-pick.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 workspaces/scorecard/.changeset/dirty-turtles-pick.md diff --git a/workspaces/scorecard/.changeset/dirty-turtles-pick.md b/workspaces/scorecard/.changeset/dirty-turtles-pick.md new file mode 100644 index 0000000000..6846ff0007 --- /dev/null +++ b/workspaces/scorecard/.changeset/dirty-turtles-pick.md @@ -0,0 +1,5 @@ +--- +'@red-hat-developer-hub/backstage-plugin-scorecard': patch +--- + +Fix tooltip display and error title clipping for some languages