@@ -60,9 +60,10 @@ export function DiffChecker({ className, instanceId }: DiffCheckerProps) {
6060 const [ options , setOptions ] = useState < DiffOptions > ( DEFAULT_DIFF_OPTIONS ) ;
6161 const [ stats , setStats ] = useState < DiffStats > ( { additions : 0 , deletions : 0 , changes : 0 } ) ;
6262 const [ isHydrated , setIsHydrated ] = useState ( false ) ;
63- const [ originalWrapText , setOriginalWrapText ] = useState ( true ) ;
64- const [ modifiedWrapText , setModifiedWrapText ] = useState ( true ) ;
63+ const [ originalWrapText , setOriginalWrapText ] = useState ( false ) ;
64+ const [ modifiedWrapText , setModifiedWrapText ] = useState ( false ) ;
6565 const [ editorsReady , setEditorsReady ] = useState ( false ) ;
66+ const [ zoomEpoch , setZoomEpoch ] = useState ( 0 ) ;
6667
6768 // Editor refs for decorations
6869 const originalEditorRef = useRef < any > ( null ) ;
@@ -118,6 +119,9 @@ export function DiffChecker({ className, instanceId }: DiffCheckerProps) {
118119 interface ViewZoneData {
119120 afterLineNumber : number ;
120121 heightInLines : number ;
122+ // Lines in the OTHER editor that this gap is aligning with (for pixel-accurate sizing)
123+ otherEditorStart : number ;
124+ otherEditorCount : number ;
121125 }
122126
123127 // Calculate diff and apply decorations with character-level highlighting
@@ -154,7 +158,7 @@ export function DiffChecker({ className, instanceId }: DiffCheckerProps) {
154158 } ) ;
155159 } ) ;
156160 // Pad original side so unchanged lines below stay aligned
157- originalViewZones . push ( { afterLineNumber : originalLine - 1 , heightInLines : lineCount } ) ;
161+ originalViewZones . push ( { afterLineNumber : originalLine - 1 , heightInLines : lineCount , otherEditorStart : modifiedLine , otherEditorCount : lineCount } ) ;
158162 modifiedLine += lineCount ;
159163 } else if ( change . removed ) {
160164 // Check if next is added (paired change for inline diff)
@@ -231,11 +235,15 @@ export function DiffChecker({ className, instanceId }: DiffCheckerProps) {
231235 modifiedViewZones . push ( {
232236 afterLineNumber : modifiedLine + addedLines . length - 1 ,
233237 heightInLines : removedLines . length - addedLines . length ,
238+ otherEditorStart : originalLine + addedLines . length ,
239+ otherEditorCount : removedLines . length - addedLines . length ,
234240 } ) ;
235241 } else if ( addedLines . length > removedLines . length ) {
236242 originalViewZones . push ( {
237243 afterLineNumber : originalLine + removedLines . length - 1 ,
238244 heightInLines : addedLines . length - removedLines . length ,
245+ otherEditorStart : modifiedLine + removedLines . length ,
246+ otherEditorCount : addedLines . length - removedLines . length ,
239247 } ) ;
240248 }
241249
@@ -249,7 +257,7 @@ export function DiffChecker({ className, instanceId }: DiffCheckerProps) {
249257 type : 'removed' ,
250258 } ) ;
251259 } ) ;
252- modifiedViewZones . push ( { afterLineNumber : modifiedLine - 1 , heightInLines : lineCount } ) ;
260+ modifiedViewZones . push ( { afterLineNumber : modifiedLine - 1 , heightInLines : lineCount , otherEditorStart : originalLine , otherEditorCount : lineCount } ) ;
253261 }
254262 originalLine += lineCount ;
255263 } else {
@@ -270,6 +278,10 @@ export function DiffChecker({ className, instanceId }: DiffCheckerProps) {
270278 const monaco = ( window as any ) . monaco ;
271279 if ( ! monaco ) return ;
272280
281+ // Only insert alignment zones when both panels have content — avoids blocking
282+ // the cursor in the empty editor with phantom rows at afterLineNumber: 0.
283+ const shouldAlign = originalText . trim ( ) . length > 0 && modifiedText . trim ( ) . length > 0 ;
284+
273285 if ( originalEditorRef . current ) {
274286 const editor = originalEditorRef . current ;
275287 const decorations = [
@@ -280,6 +292,10 @@ export function DiffChecker({ className, instanceId }: DiffCheckerProps) {
280292 isWholeLine : true ,
281293 className : 'diff-line-removed' ,
282294 glyphMarginClassName : 'diff-glyph-removed' ,
295+ overviewRuler : {
296+ color : 'rgba(239, 68, 68, 0.8)' ,
297+ position : monaco . editor . OverviewRulerLane . Full ,
298+ } ,
283299 } ,
284300 } ) ) ,
285301 // Inline character decorations
@@ -295,21 +311,6 @@ export function DiffChecker({ className, instanceId }: DiffCheckerProps) {
295311 decorations
296312 ) ;
297313
298- // Apply alignment view zones
299- editor . changeViewZones ( ( accessor : any ) => {
300- originalViewZoneIdsRef . current . forEach ( ( id ) => accessor . removeZone ( id ) ) ;
301- originalViewZoneIdsRef . current = [ ] ;
302- originalViewZones . forEach ( ( zone ) => {
303- const domNode = document . createElement ( 'div' ) ;
304- domNode . className = 'diff-placeholder-zone' ;
305- const id = accessor . addZone ( {
306- afterLineNumber : zone . afterLineNumber ,
307- heightInLines : zone . heightInLines ,
308- domNode,
309- } ) ;
310- originalViewZoneIdsRef . current . push ( id ) ;
311- } ) ;
312- } ) ;
313314 }
314315
315316 if ( modifiedEditorRef . current ) {
@@ -322,6 +323,10 @@ export function DiffChecker({ className, instanceId }: DiffCheckerProps) {
322323 isWholeLine : true ,
323324 className : 'diff-line-added' ,
324325 glyphMarginClassName : 'diff-glyph-added' ,
326+ overviewRuler : {
327+ color : 'rgba(34, 197, 94, 0.8)' ,
328+ position : monaco . editor . OverviewRulerLane . Full ,
329+ } ,
325330 } ,
326331 } ) ) ,
327332 // Inline character decorations
@@ -336,31 +341,77 @@ export function DiffChecker({ className, instanceId }: DiffCheckerProps) {
336341 modifiedDecorationsRef . current ,
337342 decorations
338343 ) ;
344+ }
345+
346+ if ( ! shouldAlign ) return ;
339347
340- // Apply alignment view zones
341- editor . changeViewZones ( ( accessor : any ) => {
348+ // Phase 1 — remove stale zones from both editors so getTopForLineNumber
349+ // returns accurate positions unaffected by previously applied zones.
350+ if ( originalEditorRef . current ) {
351+ originalEditorRef . current . changeViewZones ( ( accessor : any ) => {
352+ originalViewZoneIdsRef . current . forEach ( ( id ) => accessor . removeZone ( id ) ) ;
353+ originalViewZoneIdsRef . current = [ ] ;
354+ } ) ;
355+ }
356+ if ( modifiedEditorRef . current ) {
357+ modifiedEditorRef . current . changeViewZones ( ( accessor : any ) => {
342358 modifiedViewZoneIdsRef . current . forEach ( ( id ) => accessor . removeZone ( id ) ) ;
343359 modifiedViewZoneIdsRef . current = [ ] ;
360+ } ) ;
361+ }
362+
363+ // Phase 2 — add new zones. When either panel has word wrap on, derive heightInPx
364+ // from getTopForLineNumber on the other editor so wrapped visual rows are accounted
365+ // for. Fall back to heightInLines when wrap is off so Monaco auto-scales with zoom.
366+ const usePixelZones = originalWrapText || modifiedWrapText ;
367+
368+ if ( originalEditorRef . current ) {
369+ originalEditorRef . current . changeViewZones ( ( accessor : any ) => {
370+ originalViewZones . forEach ( ( zone ) => {
371+ const domNode = document . createElement ( 'div' ) ;
372+ domNode . className = 'diff-placeholder-zone' ;
373+ let zoneDef : Record < string , unknown > = { afterLineNumber : zone . afterLineNumber , domNode } ;
374+ if ( usePixelZones && modifiedEditorRef . current ) {
375+ const topStart = modifiedEditorRef . current . getTopForLineNumber ( zone . otherEditorStart ) ;
376+ const topEnd = modifiedEditorRef . current . getTopForLineNumber ( zone . otherEditorStart + zone . otherEditorCount ) ;
377+ const heightInPx = topEnd - topStart ;
378+ zoneDef = heightInPx > 0 ? { ...zoneDef , heightInPx } : { ...zoneDef , heightInLines : zone . heightInLines } ;
379+ } else {
380+ zoneDef . heightInLines = zone . heightInLines ;
381+ }
382+ const id = accessor . addZone ( zoneDef ) ;
383+ originalViewZoneIdsRef . current . push ( id ) ;
384+ } ) ;
385+ } ) ;
386+ }
387+
388+ if ( modifiedEditorRef . current ) {
389+ modifiedEditorRef . current . changeViewZones ( ( accessor : any ) => {
344390 modifiedViewZones . forEach ( ( zone ) => {
345391 const domNode = document . createElement ( 'div' ) ;
346392 domNode . className = 'diff-placeholder-zone' ;
347- const id = accessor . addZone ( {
348- afterLineNumber : zone . afterLineNumber ,
349- heightInLines : zone . heightInLines ,
350- domNode,
351- } ) ;
393+ let zoneDef : Record < string , unknown > = { afterLineNumber : zone . afterLineNumber , domNode } ;
394+ if ( usePixelZones && originalEditorRef . current ) {
395+ const topStart = originalEditorRef . current . getTopForLineNumber ( zone . otherEditorStart ) ;
396+ const topEnd = originalEditorRef . current . getTopForLineNumber ( zone . otherEditorStart + zone . otherEditorCount ) ;
397+ const heightInPx = topEnd - topStart ;
398+ zoneDef = heightInPx > 0 ? { ...zoneDef , heightInPx } : { ...zoneDef , heightInLines : zone . heightInLines } ;
399+ } else {
400+ zoneDef . heightInLines = zone . heightInLines ;
401+ }
402+ const id = accessor . addZone ( zoneDef ) ;
352403 modifiedViewZoneIdsRef . current . push ( id ) ;
353404 } ) ;
354405 } ) ;
355406 }
356- } , [ originalText , modifiedText , options . ignoreWhitespace ] ) ;
407+ } , [ originalText , modifiedText , options . ignoreWhitespace , originalWrapText , modifiedWrapText ] ) ;
357408
358- // Apply decorations when diff calculation changes OR when editors become ready
409+ // Apply decorations when diff calculation changes, editors become ready, or zoom changes
359410 useEffect ( ( ) => {
360411 if ( editorsReady ) {
361412 calculateDiffAndDecorate ( ) ;
362413 }
363- } , [ calculateDiffAndDecorate , editorsReady ] ) ;
414+ } , [ calculateDiffAndDecorate , editorsReady , zoomEpoch ] ) ;
364415
365416 // Scroll sync between editors
366417 useEffect ( ( ) => {
@@ -482,15 +533,35 @@ export function DiffChecker({ className, instanceId }: DiffCheckerProps) {
482533
483534 const handleOriginalEditorMount = ( editor : any ) => {
484535 originalEditorRef . current = editor ;
485- // Check if both editors are ready - the effect will handle decorations
536+ // Increment zoomEpoch when font size or line height changes so pixel-based
537+ // alignment zones are recomputed at the new scale.
538+ editor . onDidChangeConfiguration ( ( e : any ) => {
539+ const monaco = ( window as any ) . monaco ;
540+ if (
541+ monaco &&
542+ ( e . hasChanged ( monaco . editor . EditorOption . fontSize ) ||
543+ e . hasChanged ( monaco . editor . EditorOption . lineHeight ) )
544+ ) {
545+ setZoomEpoch ( ( n ) => n + 1 ) ;
546+ }
547+ } ) ;
486548 if ( modifiedEditorRef . current ) {
487549 setEditorsReady ( true ) ;
488550 }
489551 } ;
490552
491553 const handleModifiedEditorMount = ( editor : any ) => {
492554 modifiedEditorRef . current = editor ;
493- // Check if both editors are ready - the effect will handle decorations
555+ editor . onDidChangeConfiguration ( ( e : any ) => {
556+ const monaco = ( window as any ) . monaco ;
557+ if (
558+ monaco &&
559+ ( e . hasChanged ( monaco . editor . EditorOption . fontSize ) ||
560+ e . hasChanged ( monaco . editor . EditorOption . lineHeight ) )
561+ ) {
562+ setZoomEpoch ( ( n ) => n + 1 ) ;
563+ }
564+ } ) ;
494565 if ( originalEditorRef . current ) {
495566 setEditorsReady ( true ) ;
496567 }
@@ -553,8 +624,8 @@ export function DiffChecker({ className, instanceId }: DiffCheckerProps) {
553624 </ div >
554625
555626 { /* Body Section */ }
556- < div className = "flex-1 bg-background px-[24px] pt-6 pb-10" >
557- < div className = "flex flex-col gap-4" >
627+ < div className = "flex-1 flex flex-col bg-background px-[24px] pt-6 pb-10 min-h-0 overflow-y-auto " >
628+ < div className = "flex-1 flex flex -col gap-4 min-h-0 " >
558629 { /* Controls */ }
559630 < div className = "flex flex-col gap-4" >
560631 { /* Main Controls Row */ }
@@ -604,9 +675,9 @@ export function DiffChecker({ className, instanceId }: DiffCheckerProps) {
604675 </ div >
605676
606677 { /* Side-by-side Editor Panels */ }
607- < div className = "grid grid-cols-1 lg:grid-cols-2 gap-4" >
678+ < div className = "flex-1 grid grid-cols-1 lg:grid-cols-2 gap-4 min-h-0 " >
608679 { /* Original Panel */ }
609- < CodePanel
680+ < CodePanel fillHeight = { true }
610681 title = "Original"
611682 value = { originalText }
612683 onChange = { ( value ) => setOriginalText ( value ) }
@@ -655,7 +726,7 @@ export function DiffChecker({ className, instanceId }: DiffCheckerProps) {
655726 />
656727
657728 { /* Modified Panel */ }
658- < CodePanel
729+ < CodePanel fillHeight = { true }
659730 title = "Modified"
660731 value = { modifiedText }
661732 onChange = { ( value ) => setModifiedText ( value ) }
0 commit comments