Repository: jupyter/nbdime (language: python)

Issue:
Gap element wrongly positioned in diff widget
The gap element in the diff view is still added in the DOM but is not properly positioned between the 2 editors.
<!-- Edit the body of your new issue then click the ✓ "Create Issue" button in the top right of the editor. The first line will be the issue title. Assignees and Labels follow after a blank line. Leave an empty line before beginning the body of the issue. -->


Discussion / hints:

Fix patch:
diff --git a/nbdime/webapp/templates/diff.html b/nbdime/webapp/templates/diff.html
index d077b67e..7d16c40b 100644
--- a/nbdime/webapp/templates/diff.html
+++ b/nbdime/webapp/templates/diff.html
@@ -22,7 +22,7 @@ <h3>Notebook Diff</h3>
         <button id="nbdime-close" type="checkbox" style="display: none">Close tool</button>
         <button id="nbdime-export" type="checkbox" style="display: none">Export diff</button>
       </div>
-      <div id=nbdime-header-banner>
+      <div id="nbdime-header-banner" class="cm-merge-2pane">
         <span id="nbdime-header-base">Base</span>
         <span id="nbdime-header-remote">Remote</span>
       </div>
diff --git a/nbdime/webapp/templates/difftool.html b/nbdime/webapp/templates/difftool.html
index 8064884e..ff7a5f11 100644
--- a/nbdime/webapp/templates/difftool.html
+++ b/nbdime/webapp/templates/difftool.html
@@ -10,7 +10,7 @@ <h3>Notebook Diff</h3>
         <button id="nbdime-close" style="display: none">Close tool</button>
         <button id="nbdime-export" style="display: none">Export diff</button>
       </div>
-      <div id=nbdime-header-banner>
+      <div id="nbdime-header-banner" class="cm-merge-2pane">
         <span id="nbdime-header-base">Base</span>
         <span id="nbdime-header-remote">Remote</span>
       </div>
diff --git a/packages/labextension/package.json b/packages/labextension/package.json
index e2777ad1..290b7e2d 100644
--- a/packages/labextension/package.json
+++ b/packages/labextension/package.json
@@ -42,7 +42,6 @@
     "watch": "tsc --build --watch"
   },
   "dependencies": {
-
     "@jupyterlab/apputils": "^4.0.0",
     "@jupyterlab/coreutils": "^6.0.0",
     "@jupyterlab/codeeditor": "^4.0.0",
diff --git a/packages/labextension/src/widget.ts b/packages/labextension/src/widget.ts
index cbc41cf9..f4de2035 100644
--- a/packages/labextension/src/widget.ts
+++ b/packages/labextension/src/widget.ts
@@ -223,7 +223,7 @@ namespace Private {
         <label><input class="nbdime-hide-unchanged" type="checkbox">Hide unchanged cells</label>
         <button class="nbdime-export" style="display: none">Export diff</button>
       </div>
-      <div class=nbdime-header-banner>
+      <div class="nbdime-header-banner">
         <span class="nbdime-header-base"></span>
         <span class="nbdime-header-remote"></span>
       </div>`;
diff --git a/packages/labextension/style/index.css b/packages/labextension/style/index.css
index 1d9b2cec..0a1e4ee2 100644
--- a/packages/labextension/style/index.css
+++ b/packages/labextension/style/index.css
@@ -11,135 +11,138 @@
 @import url('nbdime/lib/styles/merge.css');
 
 .nbdime-Widget {
-display: flex;
-flex-direction: column;
+  display: flex;
+  flex-direction: column;
 }
 
 .nbdime-root {
-padding: var(--jp-notebook-padding);
-min-width: 50px;
-min-height: 50px;
-outline: none;
-overflow: auto;
-background: var(--jp-layout-color0);
-color: var(--jp-ui-font-color0);
-flex: 1 1 auto;
+  padding: var(--jp-notebook-padding);
+  min-width: 50px;
+  min-height: 50px;
+  outline: none;
+  overflow: auto;
+  background: var(--jp-layout-color0);
+  color: var(--jp-ui-font-color0);
+  flex: 1 1 auto;
 }
 
 .jp-ToolbarButtonComponent-icon.action-notebook-diff {
-color: var(--jp-ui-font-color0);
+  color: var(--jp-ui-font-color0);
 }
 
 .nbdime-toolbarButton .jp-ToolbarButtonComponent-label {
-display: none;
+  display: none;
 }
 
 .jp-PluginList-icon.nbdime-icon {
-font-size: 16px;
-vertical-align: text-bottom;
+  font-size: 16px;
+  vertical-align: text-bottom;
 }
 
 /* Header syling */
 
 .jp-fa-tabIcon {
-padding-top: 6px;
+  padding-top: 6px;
 }
 
 .nbdime-Diff {
-border-bottom: var(--jp-border-width) solid var(--jp-toolbar-border-color);
-box-shadow: var(--jp-toolbar-box-shadow);
-background: var(--jp-toolbar-background);
-color: var(--jp-ui-font-color1);
-flex: 0 0 auto;
-padding: 2px;
-z-index: 1;
+  border-bottom: var(--jp-border-width) solid var(--jp-toolbar-border-color);
+  box-shadow: var(--jp-toolbar-box-shadow);
+  background: var(--jp-toolbar-background);
+  color: var(--jp-ui-font-color1);
+  flex: 0 0 auto;
+  padding: 2px;
+  z-index: 1;
 }
 
 .nbdime-header-buttonrow > * {
-padding-left: 8px;
-padding-right: 8px;
+  padding-left: 8px;
+  padding-right: 8px;
+}
+
+.nbdime-Diff .nbdime-header-banner {
+  display: grid;
+  /* editor | gap | editor */
+  grid-template-columns: 47% 6% 47%;
 }
 
 .nbdime-Diff .nbdime-header-base {
-display: inline-block;
-width: calc(50% - 8px);
-background-color: var(--jp-diff-deleted-color0);
-padding: 0px 4px;
-vertical-align: top;
+  grid-column: 1;
+  display: inline-block;
+  background-color: var(--jp-diff-deleted-color0);
+  padding: 0px 4px;
 }
 
 .nbdime-Diff .nbdime-header-remote {
-display: inline-block;
-width: calc(49% - 8px);
-background-color: var(--jp-diff-added-color0);
-padding: 0px 4px;
-vertical-align: top;
+  grid-column: 3;
+  display: inline-block;
+  background-color: var(--jp-diff-added-color0);
+  padding: 0px 4px;
 }
 
 .nbdime-Diff .nbdime-header-local {
-display: none;
-width: calc(49% - 8px);
-padding: 0px 4px;
-vertical-align: top;
+  display: none;
+  width: calc(49% - 8px);
+  padding: 0px 4px;
+  vertical-align: top;
 }
 
 /* Hiding unchanged cells if told to */
 .nbdime-root.jp-mod-hideUnchanged .jp-Cell-diff.jp-Diff-unchanged {
-display: none;
+  display: none;
 }
 
 /* Show a marker with the number of cells hidden before */
 .nbdime-root.jp-mod-hideUnchanged
-.jp-Cell-diff[data-nbdime-NCellsHiddenBefore]::before,
+  .jp-Cell-diff[data-nbdime-NCellsHiddenBefore]::before,
 .nbdime-root.jp-mod-hideUnchanged
-.jp-Diff-addremchunk[data-nbdime-NCellsHiddenBefore]::before {
-content: attr(data-nbdime-NCellsHiddenBefore) ' unchanged cell(s) hidden';
-position: absolute;
-width: 100%;
-top: 0;
-background-color: var(--jp-layout-color2);
-border-top: solid var(--jp-layout-color3) 1px;
-border-bottom: solid var(--jp-layout-color3) 1px;
-text-align: center;
+  .jp-Diff-addremchunk[data-nbdime-NCellsHiddenBefore]::before {
+  content: attr(data-nbdime-NCellsHiddenBefore) ' unchanged cell(s) hidden';
+  position: absolute;
+  width: 100%;
+  top: 0;
+  background-color: var(--jp-layout-color2);
+  border-top: solid var(--jp-layout-color3) 1px;
+  border-bottom: solid var(--jp-layout-color3) 1px;
+  text-align: center;
 }
 
 /* Show a marker with the number of cells hidden after (for hidden cells at end) */
 .nbdime-root.jp-mod-hideUnchanged
-.jp-Cell-diff[data-nbdime-NCellsHiddenAfter]::after,
+  .jp-Cell-diff[data-nbdime-NCellsHiddenAfter]::after,
 .nbdime-root.jp-mod-hideUnchanged
-.jp-Diff-addremchunk[data-nbdime-NCellsHiddenAfter]::after {
-content: attr(data-nbdime-NCellsHiddenAfter) ' unchanged cell(s) hidden';
-position: absolute;
-width: 100%;
-bottom: 0;
-background-color: var(--jp-layout-color2);
-border-top: solid var(--jp-layout-color3) 1px;
-border-bottom: solid var(--jp-layout-color3) 1px;
-text-align: center;
+  .jp-Diff-addremchunk[data-nbdime-NCellsHiddenAfter]::after {
+  content: attr(data-nbdime-NCellsHiddenAfter) ' unchanged cell(s) hidden';
+  position: absolute;
+  width: 100%;
+  bottom: 0;
+  background-color: var(--jp-layout-color2);
+  border-top: solid var(--jp-layout-color3) 1px;
+  border-bottom: solid var(--jp-layout-color3) 1px;
+  text-align: center;
 }
 
 .nbdime-root.jp-mod-hideUnchanged .jp-Cell-diff[data-nbdime-NCellsHiddenBefore],
 .nbdime-root.jp-mod-hideUnchanged
-.jp-Diff-addremchunk[data-nbdime-NCellsHiddenBefore] {
-padding-top: 40px;
+  .jp-Diff-addremchunk[data-nbdime-NCellsHiddenBefore] {
+  padding-top: 40px;
 }
 
 .nbdime-root.jp-mod-hideUnchanged .jp-Cell-diff[data-nbdime-NCellsHiddenAfter],
 .nbdime-root.jp-mod-hideUnchanged
-.jp-Diff-addremchunk[data-nbdime-NCellsHiddenAfter] {
-padding-bottom: 40px;
+  .jp-Diff-addremchunk[data-nbdime-NCellsHiddenAfter] {
+  padding-bottom: 40px;
 }
 
 /* Marker for when all cells are unchanged and hidden */
 .nbdime-root.jp-mod-hideUnchanged
-.jp-Notebook-diff[data-nbdime-AllCellsHidden]::after {
-content: 'No changes, ' attr(data-nbdime-AllCellsHidden)
+  .jp-Notebook-diff[data-nbdime-AllCellsHidden]::after {
+  content: 'No changes, ' attr(data-nbdime-AllCellsHidden)
     ' unchanged cell(s) hidden';
-display: block;
-width: 100%;
-background-color: var(--jp-layout-color2);
-border-top: solid var(--jp-layout-color3) 1px;
-border-bottom: solid var(--jp-layout-color3) 1px;
-text-align: center;
+  display: block;
+  width: 100%;
+  background-color: var(--jp-layout-color2);
+  border-top: solid var(--jp-layout-color3) 1px;
+  border-bottom: solid var(--jp-layout-color3) 1px;
+  text-align: center;
 }
-
diff --git a/packages/nbdime/src/common/mergeview.ts b/packages/nbdime/src/common/mergeview.ts
index 9b3939e8..db2ba639 100644
--- a/packages/nbdime/src/common/mergeview.ts
+++ b/packages/nbdime/src/common/mergeview.ts
@@ -123,14 +123,23 @@ const mergeViewDecorationDict: MergeViewDecorationDict = {
   mixedMerge: Private.buildEditorDecorationDict('m', 'mixed'),
 };
 
-function getCommonEditorExtensions(): Extension {
-  return [
-    gutterMarkerField,
-    highlightField,
-    paddingWidgetField,
-    pickerLineChunkMappingField,
-    conflictMarkerLineChunkMappingField,
-  ];
+/**
+ * Get common editor extensions
+ *
+ * @param isMergeView Whether the editor is within a merge view or not.
+ * @returns Editor extensions
+ */
+function getCommonEditorExtensions(isMergeView = true): Extension {
+  const extensions = [highlightField, paddingWidgetField];
+
+  return isMergeView
+    ? [
+        ...extensions,
+        gutterMarkerField,
+        pickerLineChunkMappingField,
+        conflictMarkerLineChunkMappingField,
+      ]
+    : extensions;
 }
 
 function applyMapping({ from, to }: any, mapping: ChangeDesc) {
@@ -535,8 +544,8 @@ export class DiffView {
     this._chunks = updatedChunks;
   }
   /**
-Add a gap DOM element between 2 editors
- */
+   * Add a gap DOM element between 2 editors
+   */
   buildGap(): HTMLElement {
     let lock = (this._lockButton = elt(
       'div',
@@ -544,23 +553,29 @@ Add a gap DOM element between 2 editors
       'cm-merge-scrolllock',
     ));
     lock.title = 'Toggle locked scrolling';
-    let lockWrap = elt('div', [lock], 'cm-merge-scrolllock-wrap');
-    lock.innerHTML = '\u21db&nbsp;&nbsp;\u21da';
-    lock.addEventListener('scroll', event => {
+    this.setScrollLock(this._lockScroll);
+    lock.addEventListener('click', event => {
       this.setScrollLock(!this._lockScroll);
     });
-    let gap = elt('div', [lockWrap], 'cm-merge-gap');
+    let gap = elt('div', [lock], 'cm-merge-gap');
     this._gap = gap;
     return this._gap;
   }
 
-  setScrollLock(val: boolean, action?: boolean) {
+  setScrollLock(val: boolean) {
     this._lockScroll = val;
     if (this._lockButton) {
       this._lockButton.innerHTML = val
         ? '\u21db\u21da'
         : '\u21db&nbsp;&nbsp;\u21da';
     }
+    // Force scroll synchronization in case it was not synchronize
+    window.requestAnimationFrame(() => {
+      this._remoteEditorWidget.cm.scrollDOM.scrollLeft =
+        this._baseEditorWidget.cm.scrollDOM.scrollLeft;
+      this._remoteEditorWidget.cm.scrollDOM.scrollTop =
+        this._baseEditorWidget.cm.scrollDOM.scrollTop;
+    });
   }
 
   private modelInvalid(): boolean {
@@ -580,17 +595,35 @@ Add a gap DOM element between 2 editors
     }
     let srcScroller = srcEditor.scrollDOM;
     let destScroller = destEditor.scrollDOM;
-    srcScroller.addEventListener('scroll', event => {
-      window.requestAnimationFrame(function () {
-        destScroller.scrollLeft = srcScroller.scrollLeft;
-      });
-    });
+    srcScroller.addEventListener(
+      'scroll',
+      event => {
+        if (!this._lockScroll) {
+          return;
+        }
 
-    destScroller.addEventListener('scroll', event => {
-      window.requestAnimationFrame(function () {
-        srcScroller.scrollLeft = destScroller.scrollLeft;
-      });
-    });
+        window.requestAnimationFrame(function () {
+          destScroller.scrollLeft = srcScroller.scrollLeft;
+          destScroller.scrollTop = srcScroller.scrollTop;
+        });
+      },
+      { passive: true },
+    );
+
+    destScroller.addEventListener(
+      'scroll',
+      event => {
+        if (!this._lockScroll) {
+          return;
+        }
+
+        window.requestAnimationFrame(function () {
+          srcScroller.scrollLeft = destScroller.scrollLeft;
+          srcScroller.scrollTop = destScroller.scrollTop;
+        });
+      },
+      { passive: true },
+    );
   }
 
   /**
@@ -953,7 +986,7 @@ Add a gap DOM element between 2 editors
   private _chunks: Chunk[];
   private _lineChunks: Chunk[];
   private _gap: HTMLElement;
-  private _lockScroll: boolean;
+  private _lockScroll = true;
   private _lockButton: HTMLElement;
 }
 
@@ -1201,14 +1234,14 @@ export class MergeView extends Panel {
      *       but with different classes
      *     - Partial changes: Use base + right editor
      */
+    const inMergeView = !!merged;
+    const additionalExtensions: Extension = inMergeView
+      ? [listener, mergeControlGutter, getCommonEditorExtensions(inMergeView)]
+      : getCommonEditorExtensions(inMergeView);
+
     this._base = new EditorWidget({
       ...options,
-      extensions: [
-        ...(options.extensions ?? []),
-        listener,
-        mergeControlGutter,
-        getCommonEditorExtensions(),
-      ],
+      extensions: [options.extensions ?? [], additionalExtensions],
       value,
     });
 
@@ -1235,12 +1268,7 @@ export class MergeView extends Panel {
           ...options,
           // Copy configuration
           config: { ...options.config },
-          extensions: [
-            ...(options.extensions ?? []),
-            listener,
-            mergeControlGutter,
-            getCommonEditorExtensions(),
-          ],
+          extensions: [options.extensions ?? [], additionalExtensions],
         });
         this._diffViews.push(left);
         leftWidget = left.remoteEditorWidget;
@@ -1265,12 +1293,7 @@ export class MergeView extends Panel {
           ...options,
           // Copy configuration
           config: { ...options.config },
-          extensions: [
-            ...(options.extensions ?? []),
-            listener,
-            mergeControlGutter,
-            getCommonEditorExtensions(),
-          ],
+          extensions: [options.extensions ?? [], additionalExtensions],
         });
         this._diffViews.push(right);
         rightWidget = right.remoteEditorWidget;
@@ -1283,12 +1306,7 @@ export class MergeView extends Panel {
         ...options,
         // Copy configuration
         config: { ...options.config, readOnly },
-        extensions: [
-          ...(options.extensions ?? []),
-          listener,
-          mergeControlGutter,
-          getCommonEditorExtensions(),
-        ],
+        extensions: [options.extensions ?? [], additionalExtensions],
       });
       this._diffViews.push(merge);
       let mergeWidget = merge.remoteEditorWidget;
@@ -1298,12 +1316,9 @@ export class MergeView extends Panel {
       panes = 3 + (showBase ? 1 : 0);
       // START DIFF CASE
     } else if (remote) {
-      this._gridPanel = new Panel();
-      this.addWidget(this._gridPanel);
-      this._gridPanel.addClass('cm-single-panel');
-      // If in place for type guard
-      this._gridPanel.addWidget(this._base);
       if (remote.unchanged || remote.added || remote.deleted) {
+        panes = 1;
+        this.addWidget(this._base);
         if (remote.unchanged) {
           this._base.addClass('cm-merge-pane-unchanged');
         } else if (remote.added) {
@@ -1311,31 +1326,21 @@ export class MergeView extends Panel {
         } else if (remote.deleted) {
           this._base.addClass('cm-merge-pane-deleted');
         }
-        panes = 1;
       } else {
-        this._gridPanel = new Panel();
-        this.addWidget(this._gridPanel);
-        this._gridPanel.addClass('cm-diff-grid-panel');
-        // If in place for type guard
-        this._gridPanel.addWidget(this._base);
+        panes = 2;
+        this.addWidget(this._base);
         this._base.addClass('cm-diff-left-editor');
         right = this._right = new DiffView(remote, 'right', {
           ...options,
           // Copy configuration
           config: { ...options.config },
-          extensions: [
-            ...(options.extensions ?? []),
-            listener,
-            mergeControlGutter,
-            getCommonEditorExtensions(),
-          ],
+          extensions: [options.extensions ?? [], additionalExtensions],
         });
         this._diffViews.push(right);
         let rightWidget = right.remoteEditorWidget;
         rightWidget.addClass('cm-diff-right-editor');
         this.addWidget(new Widget({ node: right.buildGap() }));
-        this._gridPanel.addWidget(rightWidget);
-        panes = 2;
+        this.addWidget(rightWidget);
       }
     }
 
@@ -1451,8 +1456,7 @@ export class MergeView extends Panel {
    */
   scheduleAlignViews() {
     if (this._measuring < 0) {
-      let win = this._gridPanel.node.ownerDocument.defaultView || window;
-      this._measuring = win.requestAnimationFrame(() => {
+      this._measuring = window.requestAnimationFrame(() => {
         this._measuring = -1;
         this.alignViews();
       });
diff --git a/packages/nbdime/src/diff/widget/cell.ts b/packages/nbdime/src/diff/widget/cell.ts
index 338e3b74..345890ef 100644
--- a/packages/nbdime/src/diff/widget/cell.ts
+++ b/packages/nbdime/src/diff/widget/cell.ts
@@ -8,8 +8,6 @@ import type { CodeEditor } from '@jupyterlab/codeeditor';
 
 import { IRenderMimeRegistry, MimeModel } from '@jupyterlab/rendermime';
 
-import { FlexPanel } from '../../upstreaming/flexpanel';
-
 import { CollapsiblePanel } from '../../common/collapsiblepanel';
 
 import type { ICellDiffWidgetOptions } from '../../common/interfaces';
@@ -199,13 +197,13 @@ export class CellDiffWidget extends Panel {
       let remoteStr = `In [${remote || ' '}]:`;
       prompts.push(remoteStr);
     }
-    let container = new FlexPanel({ direction: 'left-to-right' });
+    const container = new Panel();
+    container.addClass(`cm-merge-${prompts.length}pane`);
     for (let text of prompts) {
       let w = new Widget();
       w.node.innerText = text;
       w.addClass(PROMPT_CLASS);
       container.addWidget(w);
-      FlexPanel.setGrow(w, 1);
     }
     container.addClass(EXECUTIONCOUNT_ROW_CLASS);
     return container;
diff --git a/packages/nbdime/src/styles/common.css b/packages/nbdime/src/styles/common.css
index 6b8ea05d..fff17509 100644
--- a/packages/nbdime/src/styles/common.css
+++ b/packages/nbdime/src/styles/common.css
@@ -1,3 +1,25 @@
+.nbdime-root .cm-merge-1pane {
+  display: grid;
+  /* editor */
+  grid-template-columns: 100%;
+  grid-auto-rows: minmax(18px, auto);
+}
+
+.nbdime-root .cm-merge-2pane {
+  display: grid;
+  /* editor | gap | editor */
+  grid-template-columns: 47% 6% 47%;
+  grid-auto-rows: minmax(18px, auto);
+}
+
+.nbdime-root .cm-merge-2pane > div:first-child {
+  grid-column: 1;
+}
+
+.nbdime-root .cm-merge-2pane > div:last-child {
+  grid-column: 3;
+}
+
 .nbdime-root .cm-merge {
   position: relative;
   white-space: pre;
@@ -34,23 +56,6 @@
   grid-row: 2;
 }
 
-.nbdime-root .cm-diff-grid-panel {
-  display: grid;
-  grid-template-columns: 50% 50%;
-  gap: 10px;
-  grid-auto-rows: minmax(18px, auto);
-}
-
-.nbdime-root .cm-diff-left-editor {
-  grid-column: 1;
-  grid-row: 1;
-}
-
-.nbdime-root .cm-diff-right-editor {
-  grid-column: 2;
-  grid-row: 1;
-}
-
 .nbdime-root .cm-merge-spacer {
   background-image: repeating-linear-gradient(
     45deg,
@@ -62,28 +67,24 @@
 }
 
 .nbdime-root .cm-merge-gap {
+  grid-column: 2;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: end;
+
   z-index: 2;
-  display: inline-block;
   height: 100%;
-  -moz-box-sizing: border-box;
   box-sizing: border-box;
   overflow: hidden;
   border-left: var(--codemirror-border);
   border-right: var(--codemirror-border);
-  position: absolute;
   color: var(--jp-ui-font-color2);
   background: var(--jp-layout-color2);
-}
-
-.nbdime-root .cm-merge-scrolllock-wrap {
-  position: absolute;
-  bottom: 0;
-  left: 50%;
+  user-select: none;
 }
 
 .nbdime-root .cm-merge-scrolllock {
-  position: relative;
-  left: -50%;
   cursor: pointer;
   line-height: 1;
 }
diff --git a/packages/nbdime/src/styles/diff.css b/packages/nbdime/src/styles/diff.css
index 991632ee..57f6a316 100644
--- a/packages/nbdime/src/styles/diff.css
+++ b/packages/nbdime/src/styles/diff.css
@@ -235,6 +235,7 @@
 
 .jp-Notebook-diff .jp-Cellrow-source .jp-InputPrompt {
   text-align: left;
+  width: 100%;
 }
 
 .jp-CollapsiblePanel-container {
diff --git a/packages/webapp/src/app/common.css b/packages/webapp/src/app/common.css
index 23919e21..4f0eade6 100644
--- a/packages/webapp/src/app/common.css
+++ b/packages/webapp/src/app/common.css
@@ -17,6 +17,8 @@ body {
 }
 
 #nbdime-header-banner {
+  display: grid;
+  grid-auto-rows: minmax(18px, auto);
   margin-top: 10px;
 }
 
diff --git a/packages/webapp/src/app/diff.css b/packages/webapp/src/app/diff.css
index cf3f8794..2a9c13c3 100644
--- a/packages/webapp/src/app/diff.css
+++ b/packages/webapp/src/app/diff.css
@@ -6,15 +6,20 @@
 }
 
 /* Comparing base vs remote, default */
+.nbdime-Diff #nbdime-header-banner {
+  /* editor | gap | editor */
+  grid-template-columns: 47% 6% 47%;
+}
+
 .nbdime-Diff #nbdime-header-base {
+  grid-column: 1;
   display: inline-block;
-  width: 50%;
   background-color: var(--jp-diff-deleted-color0);
 }
 
 .nbdime-Diff #nbdime-header-remote {
+  grid-column: 3;
   display: inline-block;
-  width: 49%;
   background-color: var(--jp-diff-added-color0);
 }
 
diff --git a/tsconfig.json b/tsconfig.json
index 8a6a4135..a2f969b0 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -5,5 +5,5 @@
     { "path": "./packages/nbdime" },
     { "path": "./packages/webapp" },
     { "path": "./packages/labextension" }
-  ],
+  ]
 }


Test patch:
diff --git a/ui-tests/tests/nbdime-diff-test1.spec.ts-snapshots/diff-test1-take-a-snapshot-at-opening-1-linux.png b/ui-tests/tests/nbdime-diff-test1.spec.ts-snapshots/diff-test1-take-a-snapshot-at-opening-1-linux.png
index 7ea294f0..cb674c0c 100644
Binary files a/ui-tests/tests/nbdime-diff-test1.spec.ts-snapshots/diff-test1-take-a-snapshot-at-opening-1-linux.png and b/ui-tests/tests/nbdime-diff-test1.spec.ts-snapshots/diff-test1-take-a-snapshot-at-opening-1-linux.png differ
diff --git a/ui-tests/tests/nbdime-diff-test1.spec.ts-snapshots/diff-test1-take-a-snapshot-at-opening-1-win32.png b/ui-tests/tests/nbdime-diff-test1.spec.ts-snapshots/diff-test1-take-a-snapshot-at-opening-1-win32.png
index 5aea0a78..94246be1 100644
Binary files a/ui-tests/tests/nbdime-diff-test1.spec.ts-snapshots/diff-test1-take-a-snapshot-at-opening-1-win32.png and b/ui-tests/tests/nbdime-diff-test1.spec.ts-snapshots/diff-test1-take-a-snapshot-at-opening-1-win32.png differ
diff --git a/ui-tests/tests/nbdime-diff-test2.spec.ts-snapshots/diff-test2-take-a-snapshot-at-opening-1-linux.png b/ui-tests/tests/nbdime-diff-test2.spec.ts-snapshots/diff-test2-take-a-snapshot-at-opening-1-linux.png
index ad187f9f..6534b3d4 100644
Binary files a/ui-tests/tests/nbdime-diff-test2.spec.ts-snapshots/diff-test2-take-a-snapshot-at-opening-1-linux.png and b/ui-tests/tests/nbdime-diff-test2.spec.ts-snapshots/diff-test2-take-a-snapshot-at-opening-1-linux.png differ
diff --git a/ui-tests/tests/nbdime-diff-test2.spec.ts-snapshots/diff-test2-take-a-snapshot-at-opening-1-win32.png b/ui-tests/tests/nbdime-diff-test2.spec.ts-snapshots/diff-test2-take-a-snapshot-at-opening-1-win32.png
index ba492803..6989c02e 100644
Binary files a/ui-tests/tests/nbdime-diff-test2.spec.ts-snapshots/diff-test2-take-a-snapshot-at-opening-1-win32.png and b/ui-tests/tests/nbdime-diff-test2.spec.ts-snapshots/diff-test2-take-a-snapshot-at-opening-1-win32.png differ
diff --git a/ui-tests/tests/nbdime-diff-test3.spec.ts-snapshots/diff-test3-take-a-snapshot-at-opening-1-linux.png b/ui-tests/tests/nbdime-diff-test3.spec.ts-snapshots/diff-test3-take-a-snapshot-at-opening-1-linux.png
index 282f9c5b..fe406d11 100644
Binary files a/ui-tests/tests/nbdime-diff-test3.spec.ts-snapshots/diff-test3-take-a-snapshot-at-opening-1-linux.png and b/ui-tests/tests/nbdime-diff-test3.spec.ts-snapshots/diff-test3-take-a-snapshot-at-opening-1-linux.png differ
diff --git a/ui-tests/tests/nbdime-diff-test3.spec.ts-snapshots/diff-test3-take-a-snapshot-at-opening-1-win32.png b/ui-tests/tests/nbdime-diff-test3.spec.ts-snapshots/diff-test3-take-a-snapshot-at-opening-1-win32.png
index fab0d4f6..5e87a561 100644
Binary files a/ui-tests/tests/nbdime-diff-test3.spec.ts-snapshots/diff-test3-take-a-snapshot-at-opening-1-win32.png and b/ui-tests/tests/nbdime-diff-test3.spec.ts-snapshots/diff-test3-take-a-snapshot-at-opening-1-win32.png differ