From ced62e84f0f95932f7abca4fa6153c3b1cb1ace6 Mon Sep 17 00:00:00 2001 From: Billy V Date: Mon, 21 Sep 2026 21:52:10 +0000 Subject: [PATCH 1/2] fix(ui): trim Tag's label text box so it shares a baseline with adjacent text `Text`, `Heading`, `Link` and `InlineCode` all set `text-box-trim: trim-both`, so their boxes hug the glyphs. `Tag` did not: its label kept the font's half-leading, and `TagPill` centers that taller box. Centering a line box rather than the glyphs left the label sitting ~1.4px above the baseline of any text next to it. Most visible in the Seer markdown block embeds, whose header band centers a tag against the "View " link. The embeds that pass a `` as the badge (dashboard, log) already lined up; the six that pass a `` (monitor, saved query, metrics, errors, spans, logs) did not. Measured in Chromium at 14px/Rubik, header band 36px, pill 20px: before tag -> link baseline: 1.41px title -> link baseline: 0.50px after tag -> link baseline: 0.50px title -> link baseline: 0.50px The tag now sits exactly where the header's own title does; the remaining 0.5px is the inherent offset between co-centered 12px and 14px text, and both land on the same device pixel. Co-Authored-By: Claude Opus 5 (1M context) --- static/app/components/core/badge/tag.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/static/app/components/core/badge/tag.tsx b/static/app/components/core/badge/tag.tsx index a93e746c3284..223ed0849ef5 100644 --- a/static/app/components/core/badge/tag.tsx +++ b/static/app/components/core/badge/tag.tsx @@ -120,6 +120,13 @@ const TagText = styled('div')` text-overflow: ellipsis; min-width: 0; + /* Trim the label's half-leading, the way Text, Heading and Link all do. The + * pill centers this box, so an untrimmed one centers the font's line box + * rather than the glyphs -- leaving the label riding ~1.5px above the + * baseline of any text sitting beside the tag. */ + text-box-edge: text text; + text-box-trim: trim-both; + &:has(> *) { display: flex; align-items: center; From a28edaa77b772e00b6f016a841b0ea114c2aa1e3 Mon Sep 17 00:00:00 2001 From: Billy V Date: Tue, 22 Sep 2026 16:20:03 +0000 Subject: [PATCH 2/2] fix(seer): scope the embed tag baseline fix to the Seer embeds Replaces the change to core `Tag` with `SeerEmbedTag`, used by the seven badge-slot tags in the Seer block embeds. `Tag` renders a bare label whose box keeps the font's half-leading, and the pill centers that box. The title and link beside it come from `Heading` and `Link`, which set `text-box-trim: trim-both` so their boxes hug the glyphs instead. Centering the two kinds of box against each other left the tag's label ~1.4px high. Wrapping the label in `Text` -- which the frontend guide asks for anyway -- gives it the same trimmed box as its neighbours, without shifting every other tag in the product. `variant="inherit"` keeps the pill's own text color; `Text` would otherwise paint the label with the primary content color. Measured in Chromium at 14px/Rubik, header band 36px, pill 20px -- baseline delta to the link, and the pill's own box: before 1.41px pill 8.00..28.00 w53.0 after 0.50px pill 8.00..28.00 w53.0 Identical to what the core change produced, with the pill's geometry and color unchanged. Co-Authored-By: Claude Opus 5 (1M context) --- static/app/components/core/badge/tag.tsx | 7 ---- .../embeds/components/errorsQueryBlock.tsx | 5 ++- .../embeds/components/logsQueryBlock.tsx | 7 ++-- .../embeds/components/metricsQueryBlock.tsx | 7 ++-- .../components/monitor/monitorBlock.tsx | 6 ++-- .../embeds/components/savedQueryBlock.tsx | 3 +- .../embeds/components/seerEmbedTag.tsx | 36 +++++++++++++++++++ .../embeds/components/spansQueryBlock.tsx | 7 ++-- 8 files changed, 52 insertions(+), 26 deletions(-) create mode 100644 static/app/components/seer/markdown/embeds/components/seerEmbedTag.tsx diff --git a/static/app/components/core/badge/tag.tsx b/static/app/components/core/badge/tag.tsx index 223ed0849ef5..a93e746c3284 100644 --- a/static/app/components/core/badge/tag.tsx +++ b/static/app/components/core/badge/tag.tsx @@ -120,13 +120,6 @@ const TagText = styled('div')` text-overflow: ellipsis; min-width: 0; - /* Trim the label's half-leading, the way Text, Heading and Link all do. The - * pill centers this box, so an untrimmed one centers the font's line box - * rather than the glyphs -- leaving the label riding ~1.5px above the - * baseline of any text sitting beside the tag. */ - text-box-edge: text text; - text-box-trim: trim-both; - &:has(> *) { display: flex; align-items: center; diff --git a/static/app/components/seer/markdown/embeds/components/errorsQueryBlock.tsx b/static/app/components/seer/markdown/embeds/components/errorsQueryBlock.tsx index 29a51ca79110..cb2f0839c781 100644 --- a/static/app/components/seer/markdown/embeds/components/errorsQueryBlock.tsx +++ b/static/app/components/seer/markdown/embeds/components/errorsQueryBlock.tsx @@ -1,5 +1,3 @@ -import {Tag} from '@sentry/scraps/badge'; - import {QueryEmbedCard} from 'sentry/components/seer/markdown/embeds/components/queryEmbed/queryEmbedCard'; import { QueryEmbedChart, @@ -15,6 +13,7 @@ import { eventRowKey, QueryEmbedTable, } from 'sentry/components/seer/markdown/embeds/components/queryEmbed/queryEmbedTable'; +import {SeerEmbedTag} from 'sentry/components/seer/markdown/embeds/components/seerEmbedTag'; import {IconSearch} from 'sentry/icons'; import {t} from 'sentry/locale'; import {aggregateOutputType} from 'sentry/utils/discover/fields'; @@ -77,7 +76,7 @@ export default function ErrorsQueryBlock({data}: {data: ErrorsQueryData}) { return ( {isAggregate ? t('Aggregate') : t('Events')}} + badge={{isAggregate ? t('Aggregate') : t('Events')}} href={getErrorsQueryHref(eventView, organization)} icon={IconSearch} linkLabel={t('View Errors')} diff --git a/static/app/components/seer/markdown/embeds/components/logsQueryBlock.tsx b/static/app/components/seer/markdown/embeds/components/logsQueryBlock.tsx index 4ea4815ce668..5aae9b215abc 100644 --- a/static/app/components/seer/markdown/embeds/components/logsQueryBlock.tsx +++ b/static/app/components/seer/markdown/embeds/components/logsQueryBlock.tsx @@ -1,5 +1,3 @@ -import {Tag} from '@sentry/scraps/badge'; - import {QueryEmbedCard} from 'sentry/components/seer/markdown/embeds/components/queryEmbed/queryEmbedCard'; import { chartUnitFromTimeSeries, @@ -14,6 +12,7 @@ import { QueryEmbedTable, } from 'sentry/components/seer/markdown/embeds/components/queryEmbed/queryEmbedTable'; import {toPageFilters} from 'sentry/components/seer/markdown/embeds/components/queryEmbedParams'; +import {SeerEmbedTag} from 'sentry/components/seer/markdown/embeds/components/seerEmbedTag'; import {IconList} from 'sentry/icons'; import {t} from 'sentry/locale'; import type {Sort} from 'sentry/utils/discover/fields'; @@ -90,9 +89,9 @@ export default function LogsQueryBlock({data}: {data: LogsQueryData}) { return ( + {data.mode === 'aggregate' ? t('Aggregate') : t('Logs')} - + } href={getLogsQueryHref(data, organization)} icon={IconList} diff --git a/static/app/components/seer/markdown/embeds/components/metricsQueryBlock.tsx b/static/app/components/seer/markdown/embeds/components/metricsQueryBlock.tsx index a71514c7eb0e..83b1de37b7a9 100644 --- a/static/app/components/seer/markdown/embeds/components/metricsQueryBlock.tsx +++ b/static/app/components/seer/markdown/embeds/components/metricsQueryBlock.tsx @@ -1,5 +1,3 @@ -import {Tag} from '@sentry/scraps/badge'; - import {QueryEmbedCard} from 'sentry/components/seer/markdown/embeds/components/queryEmbed/queryEmbedCard'; import { chartUnitFromTimeSeries, @@ -14,6 +12,7 @@ import { QueryEmbedTable, } from 'sentry/components/seer/markdown/embeds/components/queryEmbed/queryEmbedTable'; import {toPageFilters} from 'sentry/components/seer/markdown/embeds/components/queryEmbedParams'; +import {SeerEmbedTag} from 'sentry/components/seer/markdown/embeds/components/seerEmbedTag'; import {IconGraph} from 'sentry/icons'; import {t} from 'sentry/locale'; import {DiscoverDatasets} from 'sentry/utils/discover/types'; @@ -87,9 +86,9 @@ export default function MetricsQueryBlock({data}: {data: MetricsQueryData}) { return ( + {data.mode === 'aggregate' ? t('Aggregate') : t('Samples')} - + } href={getMetricsQueryHref(data, organization)} icon={IconGraph} diff --git a/static/app/components/seer/markdown/embeds/components/monitor/monitorBlock.tsx b/static/app/components/seer/markdown/embeds/components/monitor/monitorBlock.tsx index eef00deb0122..0f978657f071 100644 --- a/static/app/components/seer/markdown/embeds/components/monitor/monitorBlock.tsx +++ b/static/app/components/seer/markdown/embeds/components/monitor/monitorBlock.tsx @@ -2,7 +2,6 @@ import type {ComponentType} from 'react'; import * as Sentry from '@sentry/react'; import {useQuery} from '@tanstack/react-query'; -import {Tag} from '@sentry/scraps/badge'; import {Flex} from '@sentry/scraps/layout'; import {Text} from '@sentry/scraps/text'; @@ -13,6 +12,7 @@ import {MetricMonitor} from 'sentry/components/seer/markdown/embeds/components/m import {MobileBuildMonitor} from 'sentry/components/seer/markdown/embeds/components/monitor/monitorTypes/mobileBuild'; import {UptimeMonitor} from 'sentry/components/seer/markdown/embeds/components/monitor/monitorTypes/uptime'; import {SeerEmbedBlock} from 'sentry/components/seer/markdown/embeds/components/seerEmbedBlock'; +import {SeerEmbedTag} from 'sentry/components/seer/markdown/embeds/components/seerEmbedTag'; import type {EmbedOutput} from 'sentry/components/seer/markdown/embeds/utils'; import { IconClock, @@ -130,8 +130,8 @@ export default function MonitorBlock({id, name}: EmbedOutput<'monitor'>) { badge={ detector ? ( - {getDetectorTypeLabel(detector.type)} - {detector.enabled ? null : {t('Disabled')}} + {getDetectorTypeLabel(detector.type)} + {detector.enabled ? null : {t('Disabled')}} ) : null } diff --git a/static/app/components/seer/markdown/embeds/components/savedQueryBlock.tsx b/static/app/components/seer/markdown/embeds/components/savedQueryBlock.tsx index 111fbc264662..428b36e2a474 100644 --- a/static/app/components/seer/markdown/embeds/components/savedQueryBlock.tsx +++ b/static/app/components/seer/markdown/embeds/components/savedQueryBlock.tsx @@ -4,6 +4,7 @@ import {Text} from '@sentry/scraps/text'; import {LoadingIndicator} from 'sentry/components/loadingIndicator'; import {QueryEmbedCard} from 'sentry/components/seer/markdown/embeds/components/queryEmbed/queryEmbedCard'; +import {SeerEmbedTag} from 'sentry/components/seer/markdown/embeds/components/seerEmbedTag'; import {TimeSince} from 'sentry/components/timeSince'; import {IconStar} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; @@ -45,7 +46,7 @@ export default function SavedQueryBlock({data}: {data: SavedQueryData}) { {getSavedQueryDatasetLabel(savedQuery.dataset)} + {getSavedQueryDatasetLabel(savedQuery.dataset)} } href={getSavedQueryTraceItemUrl({savedQuery, organization})} icon={IconStar} diff --git a/static/app/components/seer/markdown/embeds/components/seerEmbedTag.tsx b/static/app/components/seer/markdown/embeds/components/seerEmbedTag.tsx new file mode 100644 index 000000000000..68dedee0c6f3 --- /dev/null +++ b/static/app/components/seer/markdown/embeds/components/seerEmbedTag.tsx @@ -0,0 +1,36 @@ +import type {ReactNode} from 'react'; + +import {Tag} from '@sentry/scraps/badge'; +import {Text} from '@sentry/scraps/text'; + +import type {TagVariant} from 'sentry/utils/theme'; + +/** + * A `Tag` for `SeerEmbedBlock`'s badge slot, whose label sits on the same + * baseline as the header's title and its "View ..." link. + * + * The `Text` wrapper is load-bearing rather than decorative. `Tag` renders a + * bare label whose box keeps the font's half-leading, and the pill centers that + * box; the title and link beside it come from `Heading` and `Link`, which set + * `text-box-trim: trim-both` so their boxes hug the glyphs instead. Centering + * the two kinds of box against each other left the tag's label ~1.4px high. + * `Text` gives the label the same trimmed box as its neighbours. + * + * `variant="inherit"` keeps the pill's own text color -- `Text` would otherwise + * paint the label with the primary content color and drop the tag's. + */ +export function SeerEmbedTag({ + children, + variant = 'muted', +}: { + children: ReactNode; + variant?: TagVariant; +}) { + return ( + + + {children} + + + ); +} diff --git a/static/app/components/seer/markdown/embeds/components/spansQueryBlock.tsx b/static/app/components/seer/markdown/embeds/components/spansQueryBlock.tsx index d12b4b0ef0d6..1c1d1cff1189 100644 --- a/static/app/components/seer/markdown/embeds/components/spansQueryBlock.tsx +++ b/static/app/components/seer/markdown/embeds/components/spansQueryBlock.tsx @@ -1,5 +1,3 @@ -import {Tag} from '@sentry/scraps/badge'; - import {QueryEmbedCard} from 'sentry/components/seer/markdown/embeds/components/queryEmbed/queryEmbedCard'; import { QueryEmbedChart, @@ -15,6 +13,7 @@ import { eventRowKey, QueryEmbedTable, } from 'sentry/components/seer/markdown/embeds/components/queryEmbed/queryEmbedTable'; +import {SeerEmbedTag} from 'sentry/components/seer/markdown/embeds/components/seerEmbedTag'; import {IconSpan} from 'sentry/icons'; import {t} from 'sentry/locale'; import {aggregateOutputType} from 'sentry/utils/discover/fields'; @@ -74,9 +73,9 @@ export default function SpansQueryBlock({data}: {data: SpansQueryData}) { return ( + {data.mode === 'aggregate' ? t('Aggregate') : t('Spans')} - + } href={getSpansQueryHref(data, organization)} icon={IconSpan}