From 975373704f2e9943f9a2dff5e0942830ab853a37 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Mon, 23 Mar 2026 16:21:53 +0000 Subject: [PATCH] fix(site): unify diff header styling between conversation and panel viewers (#23422) --- site/src/components/ai-elements/tool/utils.ts | 84 +++++++++++++++++++ .../components/DiffViewer/DiffViewer.tsx | 80 +----------------- 2 files changed, 88 insertions(+), 76 deletions(-) diff --git a/site/src/components/ai-elements/tool/utils.ts b/site/src/components/ai-elements/tool/utils.ts index a461502c55..d1e6bcdd1a 100644 --- a/site/src/components/ai-elements/tool/utils.ts +++ b/site/src/components/ai-elements/tool/utils.ts @@ -229,6 +229,89 @@ const SEPARATOR_CSS = [ "}", ].join(" "); +// Shared header styling applied to all diff viewers (both the +// conversation-inline diffs and the right-tab panel). This gives +// every diff header the same font sizing, change-type badges, +// and stat-count pills regardless of where it appears. +const DIFF_HEADER_CSS = [ + // Header layout: consistent sizing and padding across contexts. + "[data-diffs-header] {", + " font-size: 13px;", + " min-height: 32px !important;", + " padding-block: 0 !important;", + " padding-inline: 12px !important;", + " border-bottom: 1px solid hsl(var(--border-default));", + "}", + + // Title text: sans-serif, slightly smaller than header chrome. + "[data-diffs-header] [data-title] {", + " font-size: 12px;", + " color: hsl(var(--content-primary));", + "}", + + // Replace the library's built-in SVG change-type icons with + // single-letter badges (A/D/M/R) via CSS-generated content. + "[data-change-icon] { display: none !important; }", + // Baseline-align the badge letter with the filename so their + // text baselines match despite different font sizes (11px vs + // 12px). Without this the box-centering default shifts the + // badge a fraction of a pixel above the title. + "[data-diffs-header] [data-header-content] { align-items: baseline; }", + "[data-diffs-header] [data-header-content]::before {", + " font-size: 11px;", + " font-weight: 600;", + " flex-shrink: 0;", + "}", + "[data-diffs-header][data-change-type='new'] [data-header-content]::before {", + " content: 'A';", + " color: hsl(var(--git-added));", + "}", + "[data-diffs-header][data-change-type='change'] [data-header-content]::before {", + " content: 'M';", + " color: hsl(var(--git-modified));", + "}", + "[data-diffs-header][data-change-type='deleted'] [data-header-content]::before {", + " content: 'D';", + " color: hsl(var(--git-deleted));", + "}", + "[data-diffs-header][data-change-type='rename-pure'] [data-header-content]::before,", + "[data-diffs-header][data-change-type='rename-changed'] [data-header-content]::before {", + " content: 'R';", + " color: hsl(var(--git-modified));", + "}", + + // Stat counts styled as compact pill badges. + "[data-diffs-header] [data-metadata] {", + " flex-direction: row-reverse;", + " gap: 0 !important;", + "}", + "[data-diffs-header] [data-additions-count],", + "[data-diffs-header] [data-deletions-count] {", + " font-family: var(--diffs-font-family, var(--diffs-font-fallback));", + " font-size: 12px;", + " font-weight: 500;", + " line-height: 20px;", + " padding-inline: 6px;", + " border-radius: 3px;", + "}", + "[data-diffs-header] [data-additions-count] {", + " color: hsl(var(--git-added-bright)) !important;", + " background-color: hsl(var(--surface-git-added));", + "}", + "[data-diffs-header] [data-deletions-count] {", + " color: hsl(var(--git-deleted-bright)) !important;", + " background-color: hsl(var(--surface-git-deleted));", + "}", + // Joined badge: flatten touching inner edges. DOM order is + // [deletions][additions]; row-reverse puts additions left. + "[data-deletions-count] + [data-additions-count] {", + " border-radius: 3px 0 0 3px;", + "}", + "[data-deletions-count]:has(+ [data-additions-count]) {", + " border-radius: 0 3px 3px 0;", + "}", +].join(" "); + export const diffViewerCSS = [ // Make context lines transparent so they blend with the page, // but preserve the library's colored backgrounds on changed @@ -236,6 +319,7 @@ export const diffViewerCSS = [ // tint and word-level emphasis highlights remain visible. "pre, [data-line]:not([data-selected-line]):not([data-line-type='change-addition']):not([data-line-type='change-deletion']), [data-diffs-header] { background-color: transparent !important; }", "[data-diffs-header] { border-left: 1px solid var(--border); }", + DIFF_HEADER_CSS, SELECTION_OVERRIDE_CSS, SEPARATOR_CSS, ].join(" "); diff --git a/site/src/pages/AgentsPage/components/DiffViewer/DiffViewer.tsx b/site/src/pages/AgentsPage/components/DiffViewer/DiffViewer.tsx index 6afe09a8ac..9b97869df8 100644 --- a/site/src/pages/AgentsPage/components/DiffViewer/DiffViewer.tsx +++ b/site/src/pages/AgentsPage/components/DiffViewer/DiffViewer.tsx @@ -103,88 +103,16 @@ const FILE_TREE_THRESHOLD = 1000; /** * Extra CSS injected via the diff viewer's `unsafeCSS` option to make - * file headers sticky and adjust metadata layout. + * file headers sticky with a solid background. The shared header + * styling (font sizing, change-type badges, stat-count pills) lives + * in `diffViewerCSS` from utils.ts and is already included in the + * base options returned by `getDiffViewerOptions`. */ const STICKY_HEADER_CSS = [ - // Layout and sticky behavior. "[data-diffs-header] {", " position: sticky; top: 0; z-index: 10;", - " font-size: 13px;", - " min-height: 32px !important;", - " padding-block: 0 !important;", - " padding-inline: 12px !important;", - " border-bottom: 1px solid hsl(var(--border-default));", " background-color: hsl(var(--surface-secondary)) !important;", "}", - - // Keep the title in the site's sans-serif font, just a - // touch smaller than the surrounding header text. - "[data-diffs-header] [data-title] {", - " font-size: 12px;", - " color: hsl(var(--content-primary));", - "}", - - // Hide the library's built-in change-type SVG icons and - // replace them with a single-letter badge (A/D/M/R) via - // CSS-generated content. The letter mirrors the file tree - // sidebar and works even when the tree is hidden in narrow - // layouts. - "[data-change-icon] { display: none !important; }", - "[data-diffs-header] [data-header-content]::before {", - " font-size: 11px;", - " font-weight: 600;", - " flex-shrink: 0;", - "}", - "[data-diffs-header][data-change-type='new'] [data-header-content]::before {", - " content: 'A';", - " color: hsl(var(--git-added));", - "}", - "[data-diffs-header][data-change-type='change'] [data-header-content]::before {", - " content: 'M';", - " color: hsl(var(--git-modified));", - "}", - "[data-diffs-header][data-change-type='deleted'] [data-header-content]::before {", - " content: 'D';", - " color: hsl(var(--git-deleted));", - "}", - "[data-diffs-header][data-change-type='rename-pure'] [data-header-content]::before,", - "[data-diffs-header][data-change-type='rename-changed'] [data-header-content]::before {", - " content: 'R';", - " color: hsl(var(--git-modified));", - "}", - - // Stat counts styled as compact pill badges matching the - // DiffStatBadge component used in the PR header. - "[data-diffs-header] [data-metadata] {", - " flex-direction: row-reverse;", - " gap: 0 !important;", - "}", - "[data-diffs-header] [data-additions-count],", - "[data-diffs-header] [data-deletions-count] {", - " font-family: var(--diffs-font-family, var(--diffs-font-fallback));", - " font-size: 12px;", - " font-weight: 500;", - " line-height: 20px;", - " padding-inline: 6px;", - " border-radius: 3px;", - "}", - "[data-diffs-header] [data-additions-count] {", - " color: hsl(var(--git-added-bright)) !important;", - " background-color: hsl(var(--surface-git-added));", - "}", - "[data-diffs-header] [data-deletions-count] {", - " color: hsl(var(--git-deleted-bright)) !important;", - " background-color: hsl(var(--surface-git-deleted));", - "}", - // When both counts are present, flatten the touching inner - // edges so they form one joined badge. DOM order is - // [deletions][additions]; row-reverse puts additions left. - "[data-deletions-count] + [data-additions-count] {", - " border-radius: 3px 0 0 3px;", - "}", - "[data-deletions-count]:has(+ [data-additions-count]) {", - " border-radius: 0 3px 3px 0;", - "}", ].join(" "); export type DiffStyle = "unified" | "split";