Skip to content

Commit cd7fb3d

Browse files
authored
🤖 fix: diff padding background colors match first/last line type (#1154)
When a diff starts with an addition or ends with a deletion, the top/bottom padding of the diff container now matches that line's background color (green for additions, red for deletions). Previously, the container used a neutral background for all padding regardless of the first/last line types, causing a visual inconsistency. ## Changes - Added `firstLineType`/`lastLineType` props to `DiffContainer` - Replaced `py-1.5` padding with separate colored padding strips - Updated `DiffRenderer` and `SelectableDiffRenderer` to pass line types - Added `DiffPaddingColors` story to capture the behavior --- _Generated with `mux` • Model: `anthropic:claude-opus-4-5` • Thinking: `high`_
1 parent 9f4c41e commit cd7fb3d

File tree

3 files changed

+171
-5
lines changed

3 files changed

+171
-5
lines changed

docs/AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ Avoid mock-heavy tests that verify implementation details rather than behavior.
170170

171171
- Prefer fixes that simplify existing code; such simplifications often do not need new tests.
172172
- When adding complexity, add or extend tests. If coverage requires new infrastructure, propose the harness and then add the tests there.
173+
- When asked to reduce LoC, focus on simplifying production logic—not stripping comments, docs, or tests.
173174

174175
## Mode: Exec
175176

src/browser/components/shared/DiffRenderer.tsx

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,29 @@ const DiffIndicator: React.FC<DiffIndicatorProps> = ({
216216
* Used by FileEditToolCall for wrapping custom diff content
217217
*/
218218
export const DiffContainer: React.FC<
219-
React.PropsWithChildren<{ fontSize?: string; maxHeight?: string; className?: string }>
220-
> = ({ children, fontSize, maxHeight, className }) => {
219+
React.PropsWithChildren<{
220+
fontSize?: string;
221+
maxHeight?: string;
222+
className?: string;
223+
/** Type of the first line in the diff (for top padding background) */
224+
firstLineType?: DiffLineType;
225+
/** Type of the last line in the diff (for bottom padding background) */
226+
lastLineType?: DiffLineType;
227+
/** Line number column widths for accurate gutter sizing */
228+
lineNumberWidths?: { oldWidthCh: number; newWidthCh: number };
229+
/** Whether line numbers are shown (affects gutter width calculation) */
230+
showLineNumbers?: boolean;
231+
}>
232+
> = ({
233+
children,
234+
fontSize,
235+
maxHeight,
236+
className,
237+
firstLineType,
238+
lastLineType,
239+
lineNumberWidths,
240+
showLineNumbers = true,
241+
}) => {
221242
const resolvedMaxHeight = maxHeight ?? "400px";
222243
const [isExpanded, setIsExpanded] = React.useState(false);
223244
const contentRef = React.useRef<HTMLDivElement>(null);
@@ -255,13 +276,39 @@ export const DiffContainer: React.FC<
255276

256277
const showOverflowControls = clampContent && isOverflowing;
257278

279+
// Calculate gutter width to match DiffLineGutter layout:
280+
// px-1 (4px) + oldWidthCh + gap-0.5 (2px) + ml-3 (12px) + newWidthCh + px-1 (4px)
281+
// When line numbers hidden, gutter is just px-1 (8px total)
282+
const gutterWidth =
283+
showLineNumbers && lineNumberWidths
284+
? `calc(8px + ${lineNumberWidths.oldWidthCh}ch + 14px + ${lineNumberWidths.newWidthCh}ch)`
285+
: "8px";
286+
287+
// Padding strip mirrors gutter/content background split of diff lines
288+
const PaddingStrip = ({ lineType }: { lineType?: DiffLineType }) => (
289+
<div className="flex h-1.5">
290+
<div
291+
className="shrink-0"
292+
style={{
293+
width: gutterWidth,
294+
background: lineType ? getDiffLineGutterBackground(lineType) : undefined,
295+
}}
296+
/>
297+
<div
298+
className="flex-1"
299+
style={{ background: lineType ? getDiffLineBackground(lineType) : undefined }}
300+
/>
301+
</div>
302+
);
303+
258304
return (
259305
<div
260306
className={cn(
261-
"relative m-0 rounded-sm border border-border-light bg-code-bg py-1.5 [&_*]:text-[inherit]",
307+
"relative m-0 rounded-sm border border-border-light bg-code-bg [&_*]:text-[inherit]",
262308
className
263309
)}
264310
>
311+
<PaddingStrip lineType={firstLineType} />
265312
<div
266313
ref={contentRef}
267314
className={cn(
@@ -278,6 +325,7 @@ export const DiffContainer: React.FC<
278325
>
279326
{children}
280327
</div>
328+
<PaddingStrip lineType={lastLineType} />
281329

282330
{showOverflowControls && (
283331
<>
@@ -485,8 +533,20 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
485533
);
486534
}
487535

536+
// Get first and last line types for padding background colors
537+
const firstLineType = highlightedChunks[0]?.type;
538+
const lastLineType = highlightedChunks[highlightedChunks.length - 1]?.type;
539+
488540
return (
489-
<DiffContainer fontSize={fontSize} maxHeight={maxHeight} className={className}>
541+
<DiffContainer
542+
fontSize={fontSize}
543+
maxHeight={maxHeight}
544+
className={className}
545+
firstLineType={firstLineType}
546+
lastLineType={lastLineType}
547+
lineNumberWidths={lineNumberWidths}
548+
showLineNumbers={showLineNumbers}
549+
>
490550
{highlightedChunks.flatMap((chunk) =>
491551
chunk.lines.map((line) => {
492552
return (
@@ -906,8 +966,20 @@ export const SelectableDiffRenderer = React.memo<SelectableDiffRendererProps>(
906966
);
907967
}
908968

969+
// Get first and last line types for padding background colors
970+
const firstLineType = highlightedLineData[0]?.type;
971+
const lastLineType = highlightedLineData[highlightedLineData.length - 1]?.type;
972+
909973
return (
910-
<DiffContainer fontSize={fontSize} maxHeight={maxHeight} className={className}>
974+
<DiffContainer
975+
fontSize={fontSize}
976+
maxHeight={maxHeight}
977+
className={className}
978+
firstLineType={firstLineType}
979+
lastLineType={lastLineType}
980+
lineNumberWidths={lineNumberWidths}
981+
showLineNumbers={showLineNumbers}
982+
>
911983
{highlightedLineData.map((lineInfo, displayIndex) => {
912984
const isSelected = isLineSelected(displayIndex);
913985

src/browser/stories/App.chat.stories.tsx

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,3 +755,96 @@ export const EditingMessage: AppStory = {
755755
},
756756
},
757757
};
758+
759+
/**
760+
* Diff padding colors - verifies that the top/bottom padding of diff blocks
761+
* matches the first/last line type (addition=green, deletion=red, context=default).
762+
*
763+
* This story shows three diffs:
764+
* 1. Diff starting with addition (green top padding)
765+
* 2. Diff ending with deletion (red bottom padding)
766+
* 3. Diff with context lines at both ends (default padding)
767+
*/
768+
export const DiffPaddingColors: AppStory = {
769+
render: () => (
770+
<AppWithMocks
771+
setup={() =>
772+
setupSimpleChatStory({
773+
workspaceId: "ws-diff-padding",
774+
messages: [
775+
createUserMessage("msg-1", "Show me different diff edge cases", {
776+
historySequence: 1,
777+
timestamp: STABLE_TIMESTAMP - 100000,
778+
}),
779+
createAssistantMessage(
780+
"msg-2",
781+
"Here are diffs with different first/last line types:",
782+
{
783+
historySequence: 2,
784+
timestamp: STABLE_TIMESTAMP - 90000,
785+
toolCalls: [
786+
// Diff starting with addition - top padding should be green
787+
createFileEditTool(
788+
"call-1",
789+
"src/addition-first.ts",
790+
[
791+
"--- src/addition-first.ts",
792+
"+++ src/addition-first.ts",
793+
"@@ -1,3 +1,5 @@",
794+
"+import { newModule } from './new';",
795+
"+import { anotherNew } from './another';",
796+
" export function existing() {",
797+
" return 'unchanged';",
798+
" }",
799+
].join("\n")
800+
),
801+
// Diff ending with deletion - bottom padding should be red
802+
createFileEditTool(
803+
"call-2",
804+
"src/deletion-last.ts",
805+
[
806+
"--- src/deletion-last.ts",
807+
"+++ src/deletion-last.ts",
808+
"@@ -1,6 +1,3 @@",
809+
" export function keep() {",
810+
" return 'still here';",
811+
" }",
812+
"-export function remove() {",
813+
"- return 'goodbye';",
814+
"-}",
815+
].join("\n")
816+
),
817+
// Diff with context at both ends - default padding
818+
createFileEditTool(
819+
"call-3",
820+
"src/context-both.ts",
821+
[
822+
"--- src/context-both.ts",
823+
"+++ src/context-both.ts",
824+
"@@ -1,4 +1,4 @@",
825+
" function before() {",
826+
"+ console.log('added');",
827+
"- console.log('removed');",
828+
" }",
829+
].join("\n")
830+
),
831+
],
832+
}
833+
),
834+
],
835+
})
836+
}
837+
/>
838+
),
839+
parameters: {
840+
docs: {
841+
description: {
842+
story:
843+
"Verifies diff container padding colors match first/last line types. " +
844+
"The first diff should have green top padding (starts with +), " +
845+
"the second should have red bottom padding (ends with -), " +
846+
"and the third should have default padding (context at both ends).",
847+
},
848+
},
849+
},
850+
};

0 commit comments

Comments
 (0)