webui: fix monaco diff editor jumping behavior

This is Sketch's attempt to fix the jumping. It seems to
be better for me.
diff --git a/webui/src/web-components/sketch-monaco-view.ts b/webui/src/web-components/sketch-monaco-view.ts
index 12dc00a..917b816 100644
--- a/webui/src/web-components/sketch-monaco-view.ts
+++ b/webui/src/web-components/sketch-monaco-view.ts
@@ -1145,9 +1145,23 @@
   setOptions(value: monaco.editor.IDiffEditorConstructionOptions) {
     if (this.editor) {
       this.editor.updateOptions(value);
-      // Re-fit content after options change
+      // Re-fit content after options change with scroll preservation
       if (this.fitEditorToContent) {
-        setTimeout(() => this.fitEditorToContent!(), 50);
+        setTimeout(() => {
+          // Preserve scroll positions during options change
+          const originalScrollTop =
+            this.editor!.getOriginalEditor().getScrollTop();
+          const modifiedScrollTop =
+            this.editor!.getModifiedEditor().getScrollTop();
+
+          this.fitEditorToContent!();
+
+          // Restore scroll positions
+          requestAnimationFrame(() => {
+            this.editor!.getOriginalEditor().setScrollTop(originalScrollTop);
+            this.editor!.getModifiedEditor().setScrollTop(modifiedScrollTop);
+          });
+        }, 50);
       }
     }
   }
@@ -1165,9 +1179,23 @@
           revealLineCount: 10,
         },
       });
-      // Re-fit content after toggling
+      // Re-fit content after toggling with scroll preservation
       if (this.fitEditorToContent) {
-        setTimeout(() => this.fitEditorToContent!(), 100);
+        setTimeout(() => {
+          // Preserve scroll positions during toggle
+          const originalScrollTop =
+            this.editor!.getOriginalEditor().getScrollTop();
+          const modifiedScrollTop =
+            this.editor!.getModifiedEditor().getScrollTop();
+
+          this.fitEditorToContent!();
+
+          // Restore scroll positions
+          requestAnimationFrame(() => {
+            this.editor!.getOriginalEditor().setScrollTop(originalScrollTop);
+            this.editor!.getModifiedEditor().setScrollTop(modifiedScrollTop);
+          });
+        }, 100);
       }
     }
   }
@@ -1266,15 +1294,46 @@
       // Fix cursor positioning issues by ensuring fonts are loaded
       document.fonts.ready.then(() => {
         if (this.editor) {
+          // Preserve scroll positions during font remeasuring
+          const originalScrollTop = this.editor
+            .getOriginalEditor()
+            .getScrollTop();
+          const modifiedScrollTop = this.editor
+            .getModifiedEditor()
+            .getScrollTop();
+
           monaco.editor.remeasureFonts();
-          this.fitEditorToContent();
+
+          if (this.fitEditorToContent) {
+            this.fitEditorToContent();
+          }
+
+          // Restore scroll positions after font remeasuring
+          requestAnimationFrame(() => {
+            this.editor!.getOriginalEditor().setScrollTop(originalScrollTop);
+            this.editor!.getModifiedEditor().setScrollTop(modifiedScrollTop);
+          });
         }
       });
 
-      // Force layout recalculation after a short delay
+      // Force layout recalculation after a short delay with scroll preservation
       setTimeout(() => {
-        if (this.editor) {
+        if (this.editor && this.fitEditorToContent) {
+          // Preserve scroll positions
+          const originalScrollTop = this.editor
+            .getOriginalEditor()
+            .getScrollTop();
+          const modifiedScrollTop = this.editor
+            .getModifiedEditor()
+            .getScrollTop();
+
           this.fitEditorToContent();
+
+          // Restore scroll positions
+          requestAnimationFrame(() => {
+            this.editor!.getOriginalEditor().setScrollTop(originalScrollTop);
+            this.editor!.getModifiedEditor().setScrollTop(modifiedScrollTop);
+          });
         }
       }, 100);
     } catch (error) {
@@ -1350,9 +1409,23 @@
           },
         });
 
-        // Fit content after setting new models
+        // Fit content after setting new models with scroll preservation
         if (this.fitEditorToContent) {
-          setTimeout(() => this.fitEditorToContent!(), 50);
+          setTimeout(() => {
+            // Preserve scroll positions when fitting content after model changes
+            const originalScrollTop =
+              this.editor!.getOriginalEditor().getScrollTop();
+            const modifiedScrollTop =
+              this.editor!.getModifiedEditor().getScrollTop();
+
+            this.fitEditorToContent!();
+
+            // Restore scroll positions
+            requestAnimationFrame(() => {
+              this.editor!.getOriginalEditor().setScrollTop(originalScrollTop);
+              this.editor!.getModifiedEditor().setScrollTop(modifiedScrollTop);
+            });
+          }, 50);
         }
 
         // Add glyph decorations after setting new models
@@ -1377,10 +1450,24 @@
         this.updateModels();
 
         // Force auto-sizing after model updates
-        // Use a slightly longer delay to ensure layout is stable
+        // Use a slightly longer delay to ensure layout is stable with scroll preservation
         setTimeout(() => {
-          if (this.fitEditorToContent) {
+          if (this.fitEditorToContent && this.editor) {
+            // Preserve scroll positions during model update layout
+            const originalScrollTop = this.editor
+              .getOriginalEditor()
+              .getScrollTop();
+            const modifiedScrollTop = this.editor
+              .getModifiedEditor()
+              .getScrollTop();
+
             this.fitEditorToContent();
+
+            // Restore scroll positions
+            requestAnimationFrame(() => {
+              this.editor!.getOriginalEditor().setScrollTop(originalScrollTop);
+              this.editor!.getModifiedEditor().setScrollTop(modifiedScrollTop);
+            });
           }
         }, 100);
       } else {
@@ -1470,9 +1557,23 @@
       // Debounce the resize to avoid excessive layout calls
       resizeTimeout = window.setTimeout(() => {
         if (this.editor && this.container.value) {
-          // Trigger layout recalculation
+          // Trigger layout recalculation with scroll preservation
           if (this.fitEditorToContent) {
+            // Preserve scroll positions during window resize
+            const originalScrollTop = this.editor
+              .getOriginalEditor()
+              .getScrollTop();
+            const modifiedScrollTop = this.editor
+              .getModifiedEditor()
+              .getScrollTop();
+
             this.fitEditorToContent();
+
+            // Restore scroll positions
+            requestAnimationFrame(() => {
+              this.editor!.getOriginalEditor().setScrollTop(originalScrollTop);
+              this.editor!.getModifiedEditor().setScrollTop(modifiedScrollTop);
+            });
           } else {
             // Fallback: just trigger a layout with current container dimensions
             // Use clientWidth/Height instead of offsetWidth/Height to avoid border overflow