Merge "Revert "Add lit element based gr-diff elements""
diff --git a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section.ts b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section.ts
deleted file mode 100644
index 77ba8cd..0000000
--- a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../elements/shared/gr-button/gr-button';
-import {html, LitElement} from 'lit';
-import {customElement, property, state} from 'lit/decorators';
-import {DiffInfo, DiffViewMode, RenderPreferences} from '../../../api/diff';
-import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {diffClasses} from '../gr-diff/gr-diff-utils';
-import {getShowConfig} from './gr-context-controls';
-import {ifDefined} from 'lit/directives/if-defined';
-
-@customElement('gr-context-controls-section')
-export class GrContextControlsSection extends LitElement {
-  /** Should context controls be rendered for expanding above the section? */
-  @property({type: Boolean}) showAbove = false;
-
-  /** Should context controls be rendered for expanding below the section? */
-  @property({type: Boolean}) showBelow = false;
-
-  @property({type: Object}) viewMode = DiffViewMode.SIDE_BY_SIDE;
-
-  /** Must be of type GrDiffGroupType.CONTEXT_CONTROL. */
-  @property({type: Object})
-  group?: GrDiffGroup;
-
-  @property({type: Object})
-  diff?: DiffInfo;
-
-  @property({type: Object})
-  renderPrefs?: RenderPreferences;
-
-  /**
-   * Semantic DOM diff testing does not work with just table fragments, so when
-   * running such tests the render() method has to wrap the DOM in a proper
-   * <table> element.
-   */
-  @state()
-  addTableWrapperForTesting = false;
-
-  /**
-   * The browser API for handling selection does not (yet) work for selection
-   * across multiple shadow DOM elements. So we are rendering gr-diff components
-   * into the light DOM instead of the shadow DOM by overriding this method,
-   * which was the recommended workaround by the lit team.
-   * See also https://github.com/WICG/webcomponents/issues/79.
-   */
-  override createRenderRoot() {
-    return this;
-  }
-
-  private renderPaddingRow(whereClass: 'above' | 'below') {
-    if (!this.showAbove && whereClass === 'above') return;
-    if (!this.showBelow && whereClass === 'below') return;
-    const sideBySide = this.viewMode === DiffViewMode.SIDE_BY_SIDE;
-    const modeClass = sideBySide ? 'side-by-side' : 'unified';
-    const type = sideBySide ? GrDiffGroupType.CONTEXT_CONTROL : undefined;
-    return html`
-      <tr
-        class=${diffClasses('contextBackground', modeClass, whereClass)}
-        left-type=${ifDefined(type)}
-        right-type=${ifDefined(type)}
-      >
-        <td class=${diffClasses('blame')} data-line-number="0"></td>
-        <td class=${diffClasses('contextLineNum')}></td>
-        ${sideBySide ? html`<td class=${diffClasses()}></td>` : ''}
-        <td class=${diffClasses('contextLineNum')}></td>
-        <td class=${diffClasses()}></td>
-      </tr>
-    `;
-  }
-
-  private createContextControlRow() {
-    const sideBySide = this.viewMode === DiffViewMode.SIDE_BY_SIDE;
-    const showConfig = getShowConfig(this.showAbove, this.showBelow);
-    return html`
-      <tr class=${diffClasses('dividerRow', `show-${showConfig}`)}>
-        <td class=${diffClasses('blame')} data-line-number="0"></td>
-        ${sideBySide ? html`<td class=${diffClasses()}></td>` : ''}
-        <td class=${diffClasses('dividerCell')} colspan="3">
-          <gr-context-controls
-            .diff=${this.diff}
-            .renderPreferences=${this.renderPrefs}
-            .group=${this.group}
-            .showConfig=${showConfig}
-          >
-          </gr-context-controls>
-        </td>
-      </tr>
-    `;
-  }
-
-  override render() {
-    const rows = html`
-      ${this.renderPaddingRow('above')} ${this.createContextControlRow()}
-      ${this.renderPaddingRow('below')}
-    `;
-    if (this.addTableWrapperForTesting) {
-      return html`<table>
-        ${rows}
-      </table>`;
-    }
-    return rows;
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    'gr-context-controls-section': GrContextControlsSection;
-  }
-}
diff --git a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section_test.ts b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section_test.ts
deleted file mode 100644
index 2c1043d..0000000
--- a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section_test.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup-karma';
-import './gr-context-controls-section';
-import {GrContextControlsSection} from './gr-context-controls-section';
-import {html} from 'lit';
-import {fixture} from '@open-wc/testing-helpers';
-
-suite('gr-context-controls-section test', () => {
-  let element: GrContextControlsSection;
-
-  setup(async () => {
-    element = await fixture<GrContextControlsSection>(
-      html`<gr-context-controls-section></gr-context-controls-section>`
-    );
-    element.addTableWrapperForTesting = true;
-    await element.updateComplete;
-  });
-
-  test('render: normal with showAbove and showBelow', async () => {
-    element.showAbove = true;
-    element.showBelow = true;
-    await element.updateComplete;
-    expect(element).lightDom.to.equal(/* HTML */ `
-      <table>
-        <tbody>
-          <tr
-            class="above contextBackground gr-diff side-by-side style-scope"
-            left-type="contextControl"
-            right-type="contextControl"
-          >
-            <td class="blame gr-diff style-scope" data-line-number="0"></td>
-            <td class="contextLineNum gr-diff style-scope"></td>
-            <td class="gr-diff style-scope"></td>
-            <td class="contextLineNum gr-diff style-scope"></td>
-            <td class="gr-diff style-scope"></td>
-          </tr>
-          <tr class="dividerRow gr-diff show-both style-scope">
-            <td class="blame gr-diff style-scope" data-line-number="0"></td>
-            <td class="gr-diff style-scope"></td>
-            <td class="dividerCell gr-diff style-scope" colspan="3">
-              <gr-context-controls showconfig="both"> </gr-context-controls>
-            </td>
-          </tr>
-          <tr
-            class="below contextBackground gr-diff side-by-side style-scope"
-            left-type="contextControl"
-            right-type="contextControl"
-          >
-            <td class="blame gr-diff style-scope" data-line-number="0"></td>
-            <td class="contextLineNum gr-diff style-scope"></td>
-            <td class="gr-diff style-scope"></td>
-            <td class="contextLineNum gr-diff style-scope"></td>
-            <td class="gr-diff style-scope"></td>
-          </tr>
-        </tbody>
-      </table>
-    `);
-  });
-});
diff --git a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
index a451700..77a5dfb 100644
--- a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
+++ b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
@@ -80,19 +80,6 @@
 
 export type GrContextControlsShowConfig = 'above' | 'below' | 'both';
 
-export function getShowConfig(
-  showAbove: boolean,
-  showBelow: boolean
-): GrContextControlsShowConfig {
-  if (showAbove && !showBelow) return 'above';
-  if (!showAbove && showBelow) return 'below';
-
-  // Note that !showAbove && !showBelow also intentionally returns 'both'.
-  // This means the file is completely collapsed, which is unusual, but at least
-  // happens in one test.
-  return 'both';
-}
-
 @customElement('gr-context-controls')
 export class GrContextControls extends LitElement {
   @property({type: Object}) renderPreferences?: RenderPreferences;
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts
index 27ebe4e..aabdf57 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts
@@ -25,7 +25,6 @@
 import {GrDiffBuilderImage} from './gr-diff-builder-image';
 import {GrDiffBuilderUnified} from './gr-diff-builder-unified';
 import {GrDiffBuilderBinary} from './gr-diff-builder-binary';
-import {GrDiffBuilderLit} from './gr-diff-builder-lit';
 import {CancelablePromise, util} from '../../../scripts/util';
 import {customElement, property, observe} from '@polymer/decorators';
 import {BlameInfo, ImageInfo} from '../../../types/common';
@@ -464,24 +463,13 @@
       // If the diff is binary, but not an image.
       return new GrDiffBuilderBinary(this.diff, localPrefs, this.diffElement);
     } else if (this.viewMode === DiffViewMode.SIDE_BY_SIDE) {
-      const useLit = this.renderPrefs?.use_lit_components;
-      if (useLit) {
-        builder = new GrDiffBuilderLit(
-          this.diff,
-          localPrefs,
-          this.diffElement,
-          this._layers,
-          this.renderPrefs
-        );
-      } else {
-        builder = new GrDiffBuilderSideBySide(
-          this.diff,
-          localPrefs,
-          this.diffElement,
-          this._layers,
-          this.renderPrefs
-        );
-      }
+      builder = new GrDiffBuilderSideBySide(
+        this.diff,
+        localPrefs,
+        this.diffElement,
+        this._layers,
+        this.renderPrefs
+      );
     } else if (this.viewMode === DiffViewMode.UNIFIED) {
       builder = new GrDiffBuilderUnified(
         this.diff,
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-lit.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-lit.ts
deleted file mode 100644
index 3687747..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-lit.ts
+++ /dev/null
@@ -1,185 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {RenderPreferences} from '../../../api/diff';
-import {LineNumber} from '../gr-diff/gr-diff-line';
-import {GrDiffGroup} from '../gr-diff/gr-diff-group';
-import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
-import {Side} from '../../../constants/constants';
-import {DiffLayer, notUndefined} from '../../../types/types';
-import {diffClasses} from '../gr-diff/gr-diff-utils';
-import {GrDiffBuilder} from './gr-diff-builder';
-import {BlameInfo} from '../../../types/common';
-import {html, render} from 'lit';
-import {GrDiffSection} from './gr-diff-section';
-import '../gr-context-controls/gr-context-controls';
-import './gr-diff-section';
-import {GrDiffRow} from './gr-diff-row';
-
-/**
- * Base class for builders that are creating the diff using Lit elements.
- */
-export class GrDiffBuilderLit extends GrDiffBuilder {
-  constructor(
-    diff: DiffInfo,
-    prefs: DiffPreferencesInfo,
-    outputEl: HTMLElement,
-    layers: DiffLayer[] = [],
-    renderPrefs?: RenderPreferences
-  ) {
-    super(diff, prefs, outputEl, layers, renderPrefs);
-  }
-
-  override getContentTdByLine(
-    lineNumber: LineNumber,
-    side?: Side,
-    _root: Element = this.outputEl
-  ): HTMLTableCellElement | null {
-    if (!side) return null;
-    const row = this.findRow(lineNumber, side);
-    return row?.getContentCell(side) ?? null;
-  }
-
-  override getLineElByNumber(lineNumber: LineNumber, side: Side) {
-    const row = this.findRow(lineNumber, side);
-    return row?.getLineNumberCell(side) ?? null;
-  }
-
-  private findRow(lineNumber?: LineNumber, side?: Side): GrDiffRow | undefined {
-    if (!side || !lineNumber) return undefined;
-    const group = this.findGroup(side, lineNumber);
-    if (!group) return undefined;
-    const section = this.findSection(group);
-    if (!section) return undefined;
-    return section.findRow(side, lineNumber);
-  }
-
-  private getDiffRows() {
-    const sections = [
-      ...this.outputEl.querySelectorAll<GrDiffSection>('gr-diff-section'),
-    ];
-    return sections.map(s => s.getDiffRows()).flat();
-  }
-
-  override getLineNumberRows(): HTMLTableRowElement[] {
-    const rows = this.getDiffRows();
-    return rows.map(r => r.getTableRow()).filter(notUndefined);
-  }
-
-  override getLineNumEls(side: Side): HTMLTableCellElement[] {
-    const rows = this.getDiffRows();
-    return rows.map(r => r.getLineNumberCell(side)).filter(notUndefined);
-  }
-
-  override getBlameTdByLine(lineNumber: number): Element | undefined {
-    return this.findRow(lineNumber, Side.LEFT)?.getBlameCell();
-  }
-
-  override getContentByLine(
-    lineNumber: LineNumber,
-    side?: Side,
-    _root?: HTMLElement
-  ): HTMLElement | null {
-    const cell = this.getContentTdByLine(lineNumber, side);
-    return (cell?.firstChild ?? null) as HTMLElement | null;
-  }
-
-  override renderContentByRange(
-    start: LineNumber,
-    end: LineNumber,
-    side: Side
-  ) {
-    // TODO: Revisit whether there is maybe a more efficient and reliable
-    // approach. renderContentByRange() is only used when layers announce
-    // updates. We have to look deeper into the design of layers anyway. So
-    // let's defer optimizing this code until a refactor of layers in general.
-    const groups = this.getGroupsByLineRange(start, end, side);
-    for (const group of groups) {
-      const section = this.findSection(group);
-      for (const row of section?.getDiffRows() ?? []) {
-        row.requestUpdate();
-      }
-    }
-  }
-
-  private findSection(group?: GrDiffGroup): GrDiffSection | undefined {
-    if (!group) return undefined;
-    const leftClass = `left-${group.lineRange.left.start_line}`;
-    const rightClass = `right-${group.lineRange.right.start_line}`;
-    return (
-      this.outputEl.querySelector<GrDiffSection>(
-        `gr-diff-section.${leftClass}.${rightClass}`
-      ) ?? undefined
-    );
-  }
-
-  override renderBlameByRange(
-    blameInfo: BlameInfo,
-    start: number,
-    end: number
-  ) {
-    for (let lineNumber = start; lineNumber <= end; lineNumber++) {
-      const row = this.findRow(lineNumber, Side.LEFT);
-      if (!row) continue;
-      row.blameInfo = blameInfo;
-    }
-  }
-
-  // TODO: Refactor this such that adding the move controls becomes part of the
-  // lit element.
-  protected override getMoveControlsConfig() {
-    return {
-      numberOfCells: 4, // How many cells does the diff table have?
-      movedOutIndex: 1, // Index of left content column in diff table.
-      movedInIndex: 3, // Index of right content column in diff table.
-      lineNumberCols: [0, 2], // Indices of line number columns in diff table.
-    };
-  }
-
-  protected override buildSectionElement(group: GrDiffGroup) {
-    const leftCl = `left-${group.lineRange.left.start_line}`;
-    const rightCl = `right-${group.lineRange.right.start_line}`;
-    const section = html`
-      <gr-diff-section
-        class="${leftCl} ${rightCl}"
-        .group=${group}
-        .diff=${this._diff}
-        .layers=${this.layers}
-        .diffPrefs=${this._prefs}
-        .renderPrefs=${this.renderPrefs}
-      ></gr-diff-section>
-    `;
-    // TODO: Refactor GrDiffBuilder.emitGroup() and buildSectionElement()
-    // such that we can render directly into the correct container.
-    const tempContainer = document.createElement('div');
-    render(section, tempContainer);
-    return tempContainer.firstElementChild as GrDiffSection;
-  }
-
-  override addColumns(outputEl: HTMLElement, lineNumberWidth: number): void {
-    render(
-      html`
-        <colgroup>
-         <col class=${diffClasses('blame')}></col>
-         <col class=${diffClasses(Side.LEFT)} width=${lineNumberWidth}></col>
-         <col class=${diffClasses(Side.LEFT)}></col>
-         <col class=${diffClasses(Side.RIGHT)} width=${lineNumberWidth}></col>
-         <col class=${diffClasses(Side.RIGHT)}></col>
-        </colgroup>
-      `,
-      outputEl
-    );
-  }
-
-  protected override getNextContentOnSide(
-    _content: HTMLElement,
-    _side: Side
-  ): HTMLElement | null {
-    // TODO: getNextContentOnSide() is not required by lit based rendering.
-    // So let's refactor it to be moved into gr-diff-builder-legacy.
-    console.warn('unimplemented method getNextContentOnSide() called');
-    return null;
-  }
-}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder.ts
index 4efa238..add7ffa 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder.ts
@@ -194,7 +194,7 @@
     group.element = element;
   }
 
-  protected getGroupsByLineRange(
+  private getGroupsByLineRange(
     startLine: LineNumber,
     endLine: LineNumber,
     side: Side
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row.ts
deleted file mode 100644
index ae18e59..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row.ts
+++ /dev/null
@@ -1,368 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {html, LitElement, TemplateResult} from 'lit';
-import {customElement, property, state} from 'lit/decorators';
-import {ifDefined} from 'lit/directives/if-defined';
-import {createRef, Ref, ref} from 'lit/directives/ref';
-import {
-  DiffResponsiveMode,
-  Side,
-  LineNumber,
-  DiffLayer,
-} from '../../../api/diff';
-import {BlameInfo} from '../../../types/common';
-import {assertIsDefined} from '../../../utils/common-util';
-import {fire} from '../../../utils/event-util';
-import {getBaseUrl} from '../../../utils/url-util';
-import './gr-diff-text';
-import {GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line';
-import {diffClasses, isResponsive} from '../gr-diff/gr-diff-utils';
-
-@customElement('gr-diff-row')
-export class GrDiffRow extends LitElement {
-  contentLeftRef: Ref<HTMLDivElement> = createRef();
-
-  contentRightRef: Ref<HTMLDivElement> = createRef();
-
-  lineNumberLeftRef: Ref<HTMLTableCellElement> = createRef();
-
-  lineNumberRightRef: Ref<HTMLTableCellElement> = createRef();
-
-  blameCellRef: Ref<HTMLTableCellElement> = createRef();
-
-  tableRowRef: Ref<HTMLTableRowElement> = createRef();
-
-  @property({type: Object})
-  left?: GrDiffLine;
-
-  @property({type: Object})
-  right?: GrDiffLine;
-
-  @property({type: Object})
-  blameInfo?: BlameInfo;
-
-  @property({type: Object})
-  responsiveMode?: DiffResponsiveMode;
-
-  @property({type: Number})
-  tabSize = 2;
-
-  @property({type: Number})
-  lineLength = 80;
-
-  @property({type: Boolean})
-  hideFileCommentButton = false;
-
-  @property({type: Object})
-  layers: DiffLayer[] = [];
-
-  /**
-   * While not visible we are trying to optimize rendering performance by
-   * rendering a simpler version of the diff. Once this has become true it
-   * cannot be set back to false.
-   */
-  @state()
-  isVisible = false;
-
-  /**
-   * Semantic DOM diff testing does not work with just table fragments, so when
-   * running such tests the render() method has to wrap the DOM in a proper
-   * <table> element.
-   */
-  @state()
-  addTableWrapperForTesting = false;
-
-  /**
-   * The browser API for handling selection does not (yet) work for selection
-   * across multiple shadow DOM elements. So we are rendering gr-diff components
-   * into the light DOM instead of the shadow DOM by overriding this method,
-   * which was the recommended workaround by the lit team.
-   * See also https://github.com/WICG/webcomponents/issues/79.
-   */
-  override createRenderRoot() {
-    return this;
-  }
-
-  override updated() {
-    this.updateLayers(Side.LEFT);
-    this.updateLayers(Side.RIGHT);
-  }
-
-  /**
-   * TODO: This needs some refinement, because layers do not detect whether they
-   * have already applied their information, so at the moment all layers would
-   * constantly re-apply their information to the diff in each lit rendering
-   * pass.
-   */
-  private updateLayers(side: Side) {
-    if (!this.isVisible) return;
-    const line = this.line(side);
-    const contentEl = this.contentRef(side).value;
-    const lineNumberEl = this.lineNumberRef(side).value;
-    if (!line || !contentEl || !lineNumberEl) return;
-    for (const layer of this.layers) {
-      if (typeof layer.annotate === 'function') {
-        layer.annotate(contentEl, lineNumberEl, line, side);
-      }
-    }
-  }
-
-  private renderInvisible() {
-    return html`
-      <tr>
-        <td class="style-scope gr-diff blame"></td>
-        <td class="style-scope gr-diff left"></td>
-        <td class="style-scope gr-diff left content">
-          <div>${this.left?.text ?? ''}</div>
-        </td>
-        <td class="style-scope gr-diff right"></td>
-        <td class="style-scope gr-diff right content">
-          <div>${this.right?.text ?? ''}</div>
-        </td>
-      </tr>
-    `;
-  }
-
-  override render() {
-    if (!this.left || !this.right) return;
-    if (!this.isVisible) return this.renderInvisible();
-    const row = html`
-      <tr
-        ${ref(this.tableRowRef)}
-        class=${diffClasses('diff-row', 'side-by-side')}
-        left-type=${this.left.type}
-        right-type=${this.right.type}
-        tabindex="-1"
-      >
-        ${this.renderBlameCell()} ${this.renderLineNumberCell(Side.LEFT)}
-        ${this.renderContentCell(Side.LEFT)}
-        ${this.renderLineNumberCell(Side.RIGHT)}
-        ${this.renderContentCell(Side.RIGHT)}
-      </tr>
-    `;
-    if (this.addTableWrapperForTesting) {
-      return html`<table>
-        ${row}
-      </table>`;
-    }
-    return row;
-  }
-
-  getTableRow(): HTMLTableRowElement | undefined {
-    return this.tableRowRef.value;
-  }
-
-  getLineNumberCell(side: Side): HTMLTableCellElement | undefined {
-    return this.lineNumberRef(side).value;
-  }
-
-  getContentCell(side: Side) {
-    const div = this.contentRef(side)?.value;
-    if (!div) return undefined;
-    return div.parentElement as HTMLTableCellElement;
-  }
-
-  getBlameCell() {
-    return this.blameCellRef.value;
-  }
-
-  private renderBlameCell() {
-    // td.blame has `white-space: pre`, so prettier must not add spaces.
-    // prettier-ignore
-    return html`
-      <td
-        ${ref(this.blameCellRef)}
-        class=${diffClasses('blame')}
-        data-line-number=${this.left?.beforeNumber ?? 0}
-      >${this.renderBlameElement()}</td>
-    `;
-  }
-
-  private renderBlameElement() {
-    const lineNum = this.left?.beforeNumber;
-    const commit = this.blameInfo;
-    if (!lineNum || !commit) return;
-
-    const isStartOfRange = commit.ranges.some(r => r.start === lineNum);
-    const extras: string[] = [];
-    if (isStartOfRange) extras.push('startOfRange');
-    const date = new Date(commit.time * 1000).toLocaleDateString();
-    const shortName = commit.author.split(' ')[0];
-    const url = `${getBaseUrl()}/q/${commit.id}`;
-
-    // td.blame has `white-space: pre`, so prettier must not add spaces.
-    // prettier-ignore
-    return html`<span class=${diffClasses(...extras)}
-        ><a href=${url} class=${diffClasses('blameDate')}>${date}</a
-        ><span class=${diffClasses('blameAuthor')}> ${shortName}</span
-        ><gr-hovercard class=${diffClasses()}>
-          <span class=${diffClasses('blameHoverCard')}>
-            Commit ${commit.id}<br />
-            Author: ${commit.author}<br />
-            Date: ${date}<br />
-            <br />
-            ${commit.commit_msg}
-          </span>
-        </gr-hovercard
-      ></span>`;
-  }
-
-  private renderLineNumberCell(side: Side): TemplateResult {
-    const line = this.line(side);
-    const lineNumber = this.lineNumber(side);
-    if (!line || !lineNumber || line.type === GrDiffLineType.BLANK) {
-      return html`<td
-        ${ref(this.lineNumberRef(side))}
-        class=${diffClasses(side)}
-      ></td>`;
-    }
-
-    return html`<td
-      ${ref(this.lineNumberRef(side))}
-      class=${diffClasses(side, 'lineNum')}
-      data-value=${lineNumber}
-    >
-      ${this.renderLineNumberButton(line, lineNumber, side)}
-    </td>`;
-  }
-
-  private renderLineNumberButton(
-    line: GrDiffLine,
-    lineNumber: LineNumber,
-    side: Side
-  ) {
-    if (this.hideFileCommentButton && lineNumber === 'FILE') return;
-    if (lineNumber === 'LOST') return;
-    // .lineNumButton has `white-space: pre`, so prettier must not add spaces.
-    // prettier-ignore
-    return html`
-      <button
-        class=${diffClasses('lineNumButton', side)}
-        tabindex="-1"
-        data-value=${lineNumber}
-        aria-label=${ifDefined(
-          this.computeLineNumberAriaLabel(line, lineNumber)
-        )}
-        @mouseenter=${() =>
-          fire(this, 'line-mouse-enter', {lineNum: lineNumber, side})}
-        @mouseleave=${() =>
-          fire(this, 'line-mouse-leave', {lineNum: lineNumber, side})}
-      >${lineNumber === 'FILE' ? 'File' : lineNumber.toString()}</button>
-    `;
-  }
-
-  private computeLineNumberAriaLabel(line: GrDiffLine, lineNumber: LineNumber) {
-    if (lineNumber === 'FILE') return 'Add file comment';
-
-    // Add aria-labels for valid line numbers.
-    // For unified diff, this method will be called with number set to 0 for
-    // the empty line number column for added/removed lines. This should not
-    // be announced to the screenreader.
-    if (lineNumber <= 0) return undefined;
-
-    switch (line.type) {
-      case GrDiffLineType.REMOVE:
-        return `${lineNumber} removed`;
-      case GrDiffLineType.ADD:
-        return `${lineNumber} added`;
-      case GrDiffLineType.BOTH:
-      case GrDiffLineType.BLANK:
-        return undefined;
-    }
-  }
-
-  private renderContentCell(side: Side): TemplateResult {
-    const line = this.line(side);
-    const lineNumber = this.lineNumber(side);
-    assertIsDefined(line, 'line');
-    const extras: string[] = [line.type, side];
-    if (line.type !== GrDiffLineType.BLANK) extras.push('content');
-    if (!line.hasIntralineInfo) extras.push('no-intraline-info');
-    if (line.beforeNumber === 'FILE') extras.push('file');
-    if (line.beforeNumber === 'LOST') extras.push('lost');
-
-    // .content has `white-space: pre`, so prettier must not add spaces.
-    // prettier-ignore
-    return html`
-      <td
-        class=${diffClasses(...extras)}
-        @mouseenter=${() => {
-          if (lineNumber)
-            fire(this, 'line-mouse-enter', {lineNum: lineNumber, side});
-        }}
-        @mouseleave=${() => {
-          if (lineNumber)
-            fire(this, 'line-mouse-leave', {lineNum: lineNumber, side});
-        }}
-      >${this.renderText(side)}${this.renderThreadGroup(side, lineNumber)}</td>
-    `;
-  }
-
-  private renderThreadGroup(side: Side, lineNumber?: LineNumber) {
-    if (!lineNumber) return;
-    // TODO: For the LOST line number the convention is that a <tr> will always
-    // be rendered, but it will not be visible, because of all cells being
-    // empty. For this to work with lit-based rendering we may only render a
-    // thread-group and a <slot> when there is a thread using that slot. The
-    // cleanest solution for that is probably introducing a gr-diff-model, where
-    // each diff row can look up or observe comment threads.
-    // .content has `white-space: pre`, so prettier must not add spaces.
-    // prettier-ignore
-    return html`<div class="thread-group" data-side=${side}><slot name="${side}-${lineNumber}"></slot></div>`;
-  }
-
-  private contentRef(side: Side) {
-    return side === Side.LEFT ? this.contentLeftRef : this.contentRightRef;
-  }
-
-  private lineNumberRef(side: Side) {
-    return side === Side.LEFT
-      ? this.lineNumberLeftRef
-      : this.lineNumberRightRef;
-  }
-
-  private lineNumber(side: Side) {
-    return this.line(side)?.lineNumber(side);
-  }
-
-  private line(side: Side) {
-    return side === Side.LEFT ? this.left : this.right;
-  }
-
-  /**
-   * Returns a 'div' element containing the supplied |text| as its innerText,
-   * with '\t' characters expanded to a width determined by |tabSize|, and the
-   * text wrapped at column |lineLimit|, which may be Infinity if no wrapping is
-   * desired.
-   */
-  private renderText(side: Side) {
-    const line = this.line(side);
-    const lineNumber = this.lineNumber(side);
-    if (lineNumber === 'FILE' || lineNumber === 'LOST') return;
-    // prettier-ignore
-    const textElement = line?.text
-      ? html`<gr-diff-text
-          ${ref(this.contentRef(side))}
-          .text=${line?.text}
-          .tabSize=${this.tabSize}
-          .lineLimit=${this.lineLength}
-          .isResponsive=${isResponsive(this.responsiveMode)}
-        ></gr-diff-text>` : '';
-    // .content has `white-space: pre`, so prettier must not add spaces.
-    // prettier-ignore
-    return html`<div
-        class=${diffClasses('contentText', side)}
-        .ariaLabel=${line?.text ?? ''}
-        data-side=${ifDefined(side)}
-      >${textElement}</div>`;
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    'gr-diff-row': GrDiffRow;
-  }
-}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row_test.ts
deleted file mode 100644
index 757d906..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row_test.ts
+++ /dev/null
@@ -1,195 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup-karma';
-import './gr-diff-row';
-import {GrDiffRow} from './gr-diff-row';
-import {html} from 'lit';
-import {fixture} from '@open-wc/testing-helpers';
-import {GrDiffLine} from '../gr-diff/gr-diff-line';
-import {GrDiffLineType} from '../../../api/diff';
-
-suite('gr-diff-row test', () => {
-  let element: GrDiffRow;
-
-  setup(async () => {
-    element = await fixture<GrDiffRow>(html`<gr-diff-row></gr-diff-row>`);
-    element.isVisible = true;
-    element.addTableWrapperForTesting = true;
-    await element.updateComplete;
-  });
-
-  test('both', async () => {
-    const line = new GrDiffLine(GrDiffLineType.BOTH, 1, 1);
-    line.text = 'lorem ipsum';
-    element.left = line;
-    element.right = line;
-    await element.updateComplete;
-    expect(element).lightDom.to.equal(/* HTML */ `
-      <table>
-        <tbody>
-          <tr
-            class="diff-row gr-diff side-by-side style-scope"
-            left-type="both"
-            right-type="both"
-            tabindex="-1"
-          >
-            <td class="blame gr-diff style-scope" data-line-number="1"></td>
-            <td class="gr-diff left lineNum style-scope" data-value="1">
-              <button
-                class="gr-diff left lineNumButton style-scope"
-                data-value="1"
-                tabindex="-1"
-              >
-                1
-              </button>
-            </td>
-            <td class="both content gr-diff left no-intraline-info style-scope">
-              <div
-                aria-label="lorem ipsum"
-                class="contentText gr-diff left style-scope"
-                data-side="left"
-              >
-                <gr-diff-text>lorem ipsum</gr-diff-text>
-              </div>
-              <div class="thread-group" data-side="left">
-                <slot name="left-1"> </slot>
-              </div>
-            </td>
-            <td class="gr-diff lineNum right style-scope" data-value="1">
-              <button
-                class="gr-diff lineNumButton right style-scope"
-                data-value="1"
-                tabindex="-1"
-              >
-                1
-              </button>
-            </td>
-            <td
-              class="both content gr-diff no-intraline-info right style-scope"
-            >
-              <div
-                aria-label="lorem ipsum"
-                class="contentText gr-diff right style-scope"
-                data-side="right"
-              >
-                <gr-diff-text>lorem ipsum</gr-diff-text>
-              </div>
-              <div class="thread-group" data-side="right">
-                <slot name="right-1"> </slot>
-              </div>
-            </td>
-          </tr>
-        </tbody>
-      </table>
-    `);
-  });
-
-  test('add', async () => {
-    const line = new GrDiffLine(GrDiffLineType.ADD, 0, 1);
-    line.text = 'lorem ipsum';
-    element.left = new GrDiffLine(GrDiffLineType.BLANK);
-    element.right = line;
-    await element.updateComplete;
-    expect(element).lightDom.to.equal(/* HTML */ `
-      <table>
-        <tbody>
-          <tr
-            class="diff-row gr-diff side-by-side style-scope"
-            left-type="blank"
-            right-type="add"
-            tabindex="-1"
-          >
-            <td class="blame gr-diff style-scope" data-line-number="0"></td>
-            <td class="gr-diff left style-scope"></td>
-            <td class="blank gr-diff left no-intraline-info style-scope">
-              <div
-                aria-label=""
-                class="contentText gr-diff left style-scope"
-                data-side="left"
-              ></div>
-            </td>
-            <td class="gr-diff lineNum right style-scope" data-value="1">
-              <button
-                aria-label="1 added"
-                class="gr-diff lineNumButton right style-scope"
-                data-value="1"
-                tabindex="-1"
-              >
-                1
-              </button>
-            </td>
-            <td class="add content gr-diff no-intraline-info right style-scope">
-              <div
-                aria-label="lorem ipsum"
-                class="contentText gr-diff right style-scope"
-                data-side="right"
-              >
-                <gr-diff-text>lorem ipsum</gr-diff-text>
-              </div>
-              <div class="thread-group" data-side="right">
-                <slot name="right-1"> </slot>
-              </div>
-            </td>
-          </tr>
-        </tbody>
-      </table>
-    `);
-  });
-
-  test('remove', async () => {
-    const line = new GrDiffLine(GrDiffLineType.REMOVE, 1, 0);
-    line.text = 'lorem ipsum';
-    element.left = line;
-    element.right = new GrDiffLine(GrDiffLineType.BLANK);
-    await element.updateComplete;
-    expect(element).lightDom.to.equal(/* HTML */ `
-      <table>
-        <tbody>
-          <tr
-            class="diff-row gr-diff side-by-side style-scope"
-            left-type="remove"
-            right-type="blank"
-            tabindex="-1"
-          >
-            <td class="blame gr-diff style-scope" data-line-number="1"></td>
-            <td class="gr-diff left lineNum style-scope" data-value="1">
-              <button
-                aria-label="1 removed"
-                class="gr-diff left lineNumButton style-scope"
-                data-value="1"
-                tabindex="-1"
-              >
-                1
-              </button>
-            </td>
-            <td
-              class="content gr-diff left no-intraline-info remove style-scope"
-            >
-              <div
-                aria-label="lorem ipsum"
-                class="contentText gr-diff left style-scope"
-                data-side="left"
-              >
-                <gr-diff-text>lorem ipsum</gr-diff-text>
-              </div>
-              <div class="thread-group" data-side="left">
-                <slot name="left-1"> </slot>
-              </div>
-            </td>
-            <td class="gr-diff right style-scope"></td>
-            <td class="blank gr-diff no-intraline-info right style-scope">
-              <div
-                aria-label=""
-                class="contentText gr-diff right style-scope"
-                data-side="right"
-              ></div>
-            </td>
-          </tr>
-        </tbody>
-      </table>
-    `);
-  });
-});
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
deleted file mode 100644
index b11d767..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
+++ /dev/null
@@ -1,240 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {html, LitElement} from 'lit';
-import {customElement, property, state} from 'lit/decorators';
-import {
-  DiffInfo,
-  DiffLayer,
-  DiffViewMode,
-  MovedLinkClickedEventDetail,
-  RenderPreferences,
-  Side,
-  LineNumber,
-  DiffPreferencesInfo,
-} from '../../../api/diff';
-import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {countLines, diffClasses} from '../gr-diff/gr-diff-utils';
-import {GrDiffRow} from './gr-diff-row';
-import '../gr-context-controls/gr-context-controls-section';
-import '../gr-context-controls/gr-context-controls';
-import '../gr-range-header/gr-range-header';
-import './gr-diff-row';
-import {whenVisible} from '../../../utils/dom-util';
-
-@customElement('gr-diff-section')
-export class GrDiffSection extends LitElement {
-  @property({type: Object})
-  group?: GrDiffGroup;
-
-  @property({type: Object})
-  diff?: DiffInfo;
-
-  @property({type: Object})
-  renderPrefs?: RenderPreferences;
-
-  @property({type: Object})
-  diffPrefs?: DiffPreferencesInfo;
-
-  @property({type: Object})
-  layers: DiffLayer[] = [];
-
-  /**
-   * While not visible we are trying to optimize rendering performance by
-   * rendering a simpler version of the diff.
-   */
-  @state()
-  isVisible = false;
-
-  /**
-   * Semantic DOM diff testing does not work with just table fragments, so when
-   * running such tests the render() method has to wrap the DOM in a proper
-   * <table> element.
-   */
-  @state()
-  addTableWrapperForTesting = false;
-
-  /**
-   * The browser API for handling selection does not (yet) work for selection
-   * across multiple shadow DOM elements. So we are rendering gr-diff components
-   * into the light DOM instead of the shadow DOM by overriding this method,
-   * which was the recommended workaround by the lit team.
-   * See also https://github.com/WICG/webcomponents/issues/79.
-   */
-  override createRenderRoot() {
-    return this;
-  }
-
-  override connectedCallback() {
-    super.connectedCallback();
-    // TODO: Refine this obviously simplistic approach to optimized rendering.
-    whenVisible(this.parentElement!, () => (this.isVisible = true), 1000);
-  }
-
-  override render() {
-    if (!this.group) return;
-    const extras: string[] = [];
-    extras.push('section');
-    extras.push(this.group.type);
-    if (this.group.isTotal()) extras.push('total');
-    if (this.group.dueToRebase) extras.push('dueToRebase');
-    if (this.group.moveDetails) extras.push('dueToMove');
-    if (this.group.ignoredWhitespaceOnly) extras.push('ignoredWhitespaceOnly');
-
-    const isControl = this.group.type === GrDiffGroupType.CONTEXT_CONTROL;
-    const pairs = isControl ? [] : this.group.getSideBySidePairs();
-    const body = html`
-      <tbody class=${diffClasses(...extras)}>
-        ${this.renderContextControls()} ${this.renderMoveControls()}
-        ${pairs.map(pair => {
-          const leftCl = `left-${pair.left.lineNumber(Side.LEFT)}`;
-          const rightCl = `right-${pair.right.lineNumber(Side.RIGHT)}`;
-          return html`
-            <gr-diff-row
-              class="${leftCl} ${rightCl}"
-              .left=${pair.left}
-              .right=${pair.right}
-              .layers=${this.layers}
-              .lineLength=${this.diffPrefs?.line_length ?? 80}
-              .tabSize=${this.diffPrefs?.tab_size ?? 2}
-              .isVisible=${this.isVisible}
-            >
-            </gr-diff-row>
-          `;
-        })}
-      </tbody>
-    `;
-    if (this.addTableWrapperForTesting) {
-      return html`<table>
-        ${body}
-      </table>`;
-    }
-    return body;
-  }
-
-  getDiffRows(): GrDiffRow[] {
-    return [...this.querySelectorAll<GrDiffRow>('gr-diff-row')];
-  }
-
-  private renderContextControls() {
-    if (this.group?.type !== GrDiffGroupType.CONTEXT_CONTROL) return;
-
-    const leftStart = this.group.lineRange.left.start_line;
-    const leftEnd = this.group.lineRange.left.end_line;
-    const firstGroupIsSkipped = !!this.group.contextGroups[0].skip;
-    const lastGroupIsSkipped =
-      !!this.group.contextGroups[this.group.contextGroups.length - 1].skip;
-    const lineCountLeft = countLines(this.diff, Side.LEFT);
-    const containsWholeFile = lineCountLeft === leftEnd - leftStart + 1;
-    const showAbove =
-      (leftStart > 1 && !firstGroupIsSkipped) || containsWholeFile;
-    const showBelow = leftEnd < lineCountLeft && !lastGroupIsSkipped;
-
-    return html`
-      <gr-context-controls-section
-        .showAbove=${showAbove}
-        .showBelow=${showBelow}
-        .group=${this.group}
-        .diff=${this.diff}
-        .renderPrefs=${this.renderPrefs}
-        .viewMode=${DiffViewMode.SIDE_BY_SIDE}
-      >
-      </gr-context-controls-section>
-    `;
-  }
-
-  findRow(side: Side, lineNumber: LineNumber): GrDiffRow | undefined {
-    return (
-      this.querySelector<GrDiffRow>(`gr-diff-row.${side}-${lineNumber}`) ??
-      undefined
-    );
-  }
-
-  private renderMoveControls() {
-    if (!this.group?.moveDetails) return;
-    const movedIn = this.group.adds.length > 0;
-    const plainCell = html`<td class=${diffClasses()}></td>`;
-    const lineNumberCell = html`
-      <td class=${diffClasses('moveControlsLineNumCol')}></td>
-    `;
-    const moveCell = html`
-      <td class=${diffClasses('moveHeader')}>
-        <gr-range-header class=${diffClasses()} icon="gr-icons:move-item">
-          ${this.renderMoveDescription(movedIn)}
-        </gr-range-header>
-      </td>
-    `;
-    return html`
-      <tr
-        class=${diffClasses('moveControls', movedIn ? 'movedIn' : 'movedOut')}
-      >
-        ${lineNumberCell} ${movedIn ? plainCell : moveCell} ${lineNumberCell}
-        ${movedIn ? moveCell : plainCell}
-      </tr>
-    `;
-  }
-
-  private renderMoveDescription(movedIn: boolean) {
-    if (this.group?.moveDetails?.range) {
-      const {changed, range} = this.group.moveDetails;
-      const otherSide = movedIn ? Side.LEFT : Side.RIGHT;
-      const andChangedLabel = changed ? 'and changed ' : '';
-      const direction = movedIn ? 'from' : 'to';
-      const textLabel = `Moved ${andChangedLabel}${direction} lines `;
-      return html`
-        <div class=${diffClasses()}>
-          <span class=${diffClasses()}>${textLabel}</span>
-          ${this.renderMovedLineAnchor(range.start, otherSide)}
-          <span class=${diffClasses()}> - </span>
-          ${this.renderMovedLineAnchor(range.end, otherSide)}
-        </div>
-      `;
-    }
-
-    return html`
-      <div class=${diffClasses()}>
-        <span class=${diffClasses()}
-          >${movedIn ? 'Moved in' : 'Moved out'}</span
-        >
-      </div>
-    `;
-  }
-
-  private renderMovedLineAnchor(line: number, side: Side) {
-    const listener = (e: MouseEvent) => {
-      e.preventDefault();
-      this.handleMovedLineAnchorClick(e.target, side, line);
-    };
-    // `href` is not actually used but important for Screen Readers
-    return html`
-      <a class=${diffClasses()} href=${`#${line}`} @click=${listener}
-        >${line}</a
-      >
-    `;
-  }
-
-  private handleMovedLineAnchorClick(
-    anchor: EventTarget | null,
-    side: Side,
-    line: number
-  ) {
-    anchor?.dispatchEvent(
-      new CustomEvent<MovedLinkClickedEventDetail>('moved-link-clicked', {
-        detail: {
-          lineNum: line,
-          side,
-        },
-        composed: true,
-        bubbles: true,
-      })
-    );
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    'gr-diff-section': GrDiffSection;
-  }
-}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section_test.ts
deleted file mode 100644
index 88c0e83..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section_test.ts
+++ /dev/null
@@ -1,213 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup-karma';
-import './gr-diff-section';
-import {GrDiffSection} from './gr-diff-section';
-import {html} from 'lit';
-import {fixture} from '@open-wc/testing-helpers';
-import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {GrDiffLine} from '../gr-diff/gr-diff-line';
-import {GrDiffLineType} from '../../../api/diff';
-
-suite('gr-diff-section test', () => {
-  let element: GrDiffSection;
-
-  setup(async () => {
-    element = await fixture<GrDiffSection>(
-      html`<gr-diff-section></gr-diff-section>`
-    );
-    element.addTableWrapperForTesting = true;
-    element.isVisible = true;
-    await element.updateComplete;
-  });
-
-  test('3 normal unchanged rows', async () => {
-    const lines = [
-      new GrDiffLine(GrDiffLineType.BOTH, 1, 1),
-      new GrDiffLine(GrDiffLineType.BOTH, 1, 1),
-      new GrDiffLine(GrDiffLineType.BOTH, 1, 1),
-    ];
-    lines[0].text = 'asdf';
-    lines[1].text = 'qwer';
-    lines[2].text = 'zxcv';
-    const group = new GrDiffGroup({type: GrDiffGroupType.BOTH, lines});
-    element.group = group;
-    await element.updateComplete;
-    expect(element).dom.to.equal(/* HTML */ `
-      <gr-diff-section>
-        <gr-diff-row class="left-1 right-1"> </gr-diff-row>
-        <gr-diff-row class="left-1 right-1"> </gr-diff-row>
-        <gr-diff-row class="left-1 right-1"> </gr-diff-row>
-        <table>
-          <tbody class="both gr-diff section style-scope">
-            <tr
-              class="diff-row gr-diff side-by-side style-scope"
-              left-type="both"
-              right-type="both"
-              tabindex="-1"
-            >
-              <td class="blame gr-diff style-scope" data-line-number="1"></td>
-              <td class="gr-diff left lineNum style-scope" data-value="1">
-                <button
-                  class="gr-diff left lineNumButton style-scope"
-                  data-value="1"
-                  tabindex="-1"
-                >
-                  1
-                </button>
-              </td>
-              <td
-                class="both content gr-diff left no-intraline-info style-scope"
-              >
-                <div
-                  aria-label="asdf"
-                  class="contentText gr-diff left style-scope"
-                  data-side="left"
-                >
-                  <gr-diff-text></gr-diff-text>
-                </div>
-                <div class="thread-group" data-side="left">
-                  <slot name="left-1"> </slot>
-                </div>
-              </td>
-              <td class="gr-diff lineNum right style-scope" data-value="1">
-                <button
-                  class="gr-diff lineNumButton right style-scope"
-                  data-value="1"
-                  tabindex="-1"
-                >
-                  1
-                </button>
-              </td>
-              <td
-                class="both content gr-diff no-intraline-info right style-scope"
-              >
-                <div
-                  aria-label="asdf"
-                  class="contentText gr-diff right style-scope"
-                  data-side="right"
-                >
-                  <gr-diff-text></gr-diff-text>
-                </div>
-                <div class="thread-group" data-side="right">
-                  <slot name="right-1"> </slot>
-                </div>
-              </td>
-            </tr>
-            <tr
-              class="diff-row gr-diff side-by-side style-scope"
-              left-type="both"
-              right-type="both"
-              tabindex="-1"
-            >
-              <td class="blame gr-diff style-scope" data-line-number="1"></td>
-              <td class="gr-diff left lineNum style-scope" data-value="1">
-                <button
-                  class="gr-diff left lineNumButton style-scope"
-                  data-value="1"
-                  tabindex="-1"
-                >
-                  1
-                </button>
-              </td>
-              <td
-                class="both content gr-diff left no-intraline-info style-scope"
-              >
-                <div
-                  aria-label="qwer"
-                  class="contentText gr-diff left style-scope"
-                  data-side="left"
-                >
-                  <gr-diff-text></gr-diff-text>
-                </div>
-                <div class="thread-group" data-side="left">
-                  <slot name="left-1"> </slot>
-                </div>
-              </td>
-              <td class="gr-diff lineNum right style-scope" data-value="1">
-                <button
-                  class="gr-diff lineNumButton right style-scope"
-                  data-value="1"
-                  tabindex="-1"
-                >
-                  1
-                </button>
-              </td>
-              <td
-                class="both content gr-diff no-intraline-info right style-scope"
-              >
-                <div
-                  aria-label="qwer"
-                  class="contentText gr-diff right style-scope"
-                  data-side="right"
-                >
-                  <gr-diff-text></gr-diff-text>
-                </div>
-                <div class="thread-group" data-side="right">
-                  <slot name="right-1"> </slot>
-                </div>
-              </td>
-            </tr>
-            <tr
-              class="diff-row gr-diff side-by-side style-scope"
-              left-type="both"
-              right-type="both"
-              tabindex="-1"
-            >
-              <td class="blame gr-diff style-scope" data-line-number="1"></td>
-              <td class="gr-diff left lineNum style-scope" data-value="1">
-                <button
-                  class="gr-diff left lineNumButton style-scope"
-                  data-value="1"
-                  tabindex="-1"
-                >
-                  1
-                </button>
-              </td>
-              <td
-                class="both content gr-diff left no-intraline-info style-scope"
-              >
-                <div
-                  aria-label="zxcv"
-                  class="contentText gr-diff left style-scope"
-                  data-side="left"
-                >
-                  <gr-diff-text></gr-diff-text>
-                </div>
-                <div class="thread-group" data-side="left">
-                  <slot name="left-1"> </slot>
-                </div>
-              </td>
-              <td class="gr-diff lineNum right style-scope" data-value="1">
-                <button
-                  class="gr-diff lineNumButton right style-scope"
-                  data-value="1"
-                  tabindex="-1"
-                >
-                  1
-                </button>
-              </td>
-              <td
-                class="both content gr-diff no-intraline-info right style-scope"
-              >
-                <div
-                  aria-label="zxcv"
-                  class="contentText gr-diff right style-scope"
-                  data-side="right"
-                >
-                  <gr-diff-text></gr-diff-text>
-                </div>
-                <div class="thread-group" data-side="right">
-                  <slot name="right-1"> </slot>
-                </div>
-              </td>
-            </tr>
-          </tbody>
-        </table>
-      </gr-diff-section>
-    `);
-  });
-});
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts
deleted file mode 100644
index bb37c43..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {LitElement, html, TemplateResult} from 'lit';
-import {customElement, property} from 'lit/decorators';
-import {diffClasses} from '../gr-diff/gr-diff-utils';
-
-const SURROGATE_PAIR = /[\uD800-\uDBFF][\uDC00-\uDFFF]/;
-
-const TAB = '\t';
-
-@customElement('gr-diff-text')
-export class GrDiffText extends LitElement {
-  /**
-   * The browser API for handling selection does not (yet) work for selection
-   * across multiple shadow DOM elements. So we are rendering gr-diff components
-   * into the light DOM instead of the shadow DOM by overriding this method,
-   * which was the recommended workaround by the lit team.
-   * See also https://github.com/WICG/webcomponents/issues/79.
-   */
-  override createRenderRoot() {
-    return this;
-  }
-
-  @property({type: String})
-  text = '';
-
-  @property({type: Boolean})
-  isResponsive = false;
-
-  @property({type: Number})
-  tabSize = 2;
-
-  @property({type: Number})
-  lineLimit = 80;
-
-  /** Temporary state while rendering. */
-  private textOffset = 0;
-
-  /** Temporary state while rendering. */
-  private columnPos = 0;
-
-  /** Temporary state while rendering. */
-  private pieces: (string | TemplateResult)[] = [];
-
-  /** Split up the string into tabs, surrogate pairs and regular segments. */
-  override render() {
-    this.textOffset = 0;
-    this.columnPos = 0;
-    this.pieces = [];
-    const splitByTab = this.text.split('\t');
-    for (let i = 0; i < splitByTab.length; i++) {
-      const splitBySurrogate = splitByTab[i].split(SURROGATE_PAIR);
-      for (let j = 0; j < splitBySurrogate.length; j++) {
-        this.renderSegment(splitBySurrogate[j]);
-        if (j < splitBySurrogate.length - 1) {
-          this.renderSurrogatePair();
-        }
-      }
-      if (i < splitByTab.length - 1) {
-        this.renderTab();
-      }
-    }
-    if (this.textOffset !== this.text.length) throw new Error('unfinished');
-    return this.pieces;
-  }
-
-  /** Render regular characters, but insert line breaks appropriately. */
-  private renderSegment(segment: string) {
-    let segmentOffset = 0;
-    while (segmentOffset < segment.length) {
-      const newOffset = Math.min(
-        segment.length,
-        segmentOffset + this.lineLimit - this.columnPos
-      );
-      this.renderString(segment.substring(segmentOffset, newOffset));
-      segmentOffset = newOffset;
-      if (segmentOffset < segment.length && this.columnPos === this.lineLimit) {
-        this.renderLineBreak();
-      }
-    }
-  }
-
-  /** Render regular characters. */
-  private renderString(s: string) {
-    if (s.length === 0) return;
-    this.pieces.push(s);
-    this.textOffset += s.length;
-    this.columnPos += s.length;
-    if (this.columnPos > this.lineLimit) throw new Error('over line limit');
-  }
-
-  /** Render a tab character. */
-  private renderTab() {
-    let tabSize = this.tabSize - (this.columnPos % this.tabSize);
-    if (this.columnPos + tabSize > this.lineLimit) {
-      this.renderLineBreak();
-      tabSize = this.tabSize;
-    }
-    const piece = html`<span
-      class=${diffClasses('tab')}
-      style="tab-size: ${tabSize}; -moz-tab-size: ${tabSize};"
-      >${TAB}</span
-    >`;
-    this.pieces.push(piece);
-    this.textOffset += 1;
-    this.columnPos += tabSize;
-  }
-
-  /** Render a surrogate pair: string length is 2, but is just 1 char. */
-  private renderSurrogatePair() {
-    if (this.columnPos === this.lineLimit) {
-      this.renderLineBreak();
-    }
-    this.pieces.push(this.text.substring(this.textOffset, this.textOffset + 2));
-    this.textOffset += 2;
-    this.columnPos += 1;
-  }
-
-  /** Render a line break, don't advance text offset, reset col position. */
-  private renderLineBreak() {
-    if (this.isResponsive) {
-      this.pieces.push(html`<wbr class=${diffClasses()}></wbr>`);
-    } else {
-      this.pieces.push(html`<span class=${diffClasses('br')}></span>`);
-    }
-    // this.textOffset += 0;
-    this.columnPos = 0;
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    'gr-diff-text': GrDiffText;
-  }
-}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts
deleted file mode 100644
index 21c0936..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts
+++ /dev/null
@@ -1,154 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup-karma';
-import './gr-diff-text';
-import {GrDiffText} from './gr-diff-text';
-import {html} from 'lit';
-import {fixture} from '@open-wc/testing-helpers';
-
-const LINE_BREAK = '<span class="style-scope gr-diff br"></span>';
-
-const TAB = '<span class="" style=""></span>';
-
-const TAB_IGNORE = ['class', 'style'];
-
-suite('gr-diff-text test', () => {
-  let element: GrDiffText;
-
-  setup(async () => {
-    element = await fixture<GrDiffText>(html`<gr-diff-text></gr-diff-text>`);
-    await element.updateComplete;
-  });
-
-  const check = async (
-    text: string,
-    html: string,
-    ignoreAttributes: string[] = []
-  ) => {
-    element.text = text;
-    element.tabSize = 4;
-    element.lineLimit = 10;
-    await element.updateComplete;
-    expect(element).lightDom.to.equal(html, {ignoreAttributes});
-  };
-
-  suite('lit rendering', () => {
-    test('renderText newlines 1', () => {
-      check('abcdef', 'abcdef');
-      check('a'.repeat(20), `aaaaaaaaaa${LINE_BREAK}aaaaaaaaaa`);
-    });
-
-    test('renderText newlines 2', () => {
-      check(
-        '<span class="thumbsup">👍</span>',
-        '&lt;span clas' +
-          LINE_BREAK +
-          's="thumbsu' +
-          LINE_BREAK +
-          'p"&gt;👍&lt;/span' +
-          LINE_BREAK +
-          '&gt;'
-      );
-    });
-
-    test('renderText newlines 3', () => {
-      check(
-        '01234\t56789',
-        '01234' + TAB + '56' + LINE_BREAK + '789',
-        TAB_IGNORE
-      );
-    });
-
-    test('renderText newlines 4', async () => {
-      element.lineLimit = 20;
-      await element.updateComplete;
-      check(
-        '👍'.repeat(58),
-        '👍'.repeat(20) +
-          LINE_BREAK +
-          '👍'.repeat(20) +
-          LINE_BREAK +
-          '👍'.repeat(18)
-      );
-    });
-
-    test('tab wrapper style', async () => {
-      for (const size of [1, 3, 8, 55]) {
-        element.tabSize = size;
-        await element.updateComplete;
-        check(
-          '\t',
-          /* HTML */ `
-            <span
-              class="style-scope gr-diff tab"
-              style="tab-size: ${size}; -moz-tab-size: ${size};"
-            >
-            </span>
-          `
-        );
-      }
-    });
-
-    test('tab wrapper insertion', () => {
-      check('abc\tdef', 'abc' + TAB + 'def', TAB_IGNORE);
-    });
-
-    test('escaping HTML', async () => {
-      element.lineLimit = 100;
-      await element.updateComplete;
-      check(
-        '<script>alert("XSS");<' + '/script>',
-        '&lt;script&gt;alert("XSS");&lt;/script&gt;'
-      );
-      check('& < > " \' / `', '&amp; &lt; &gt; " \' / `');
-    });
-
-    test('text length with tabs and unicode', async () => {
-      async function expectTextLength(
-        text: string,
-        tabSize: number,
-        expected: number
-      ) {
-        element.text = text;
-        element.tabSize = tabSize;
-        element.lineLimit = expected;
-        await element.updateComplete;
-        const result = element.innerHTML;
-
-        // Must not contain a line break.
-        assert.isNotOk(element.querySelector('span.br'));
-
-        // Increasing the line limit by 1 should not change anything.
-        element.lineLimit = expected + 1;
-        await element.updateComplete;
-        const resultPlusOne = element.innerHTML;
-        assert.equal(resultPlusOne, result);
-
-        // Increasing the line limit to infinity should not change anything.
-        element.lineLimit = Infinity;
-        await element.updateComplete;
-        const resultInf = element.innerHTML;
-        assert.equal(resultInf, result);
-
-        // Decreasing the line limit by 1 should introduce a line break.
-        element.lineLimit = expected + 1;
-        await element.updateComplete;
-        assert.isNotOk(element.querySelector('span.br'));
-      }
-      expectTextLength('12345', 4, 5);
-      expectTextLength('\t\t12', 4, 10);
-      expectTextLength('abc💢123', 4, 7);
-      expectTextLength('abc\t', 8, 8);
-      expectTextLength('abc\t\t', 10, 20);
-      expectTextLength('', 10, 0);
-      // 17 Thai combining chars.
-      expectTextLength('ก้้้้้้้้้้้้้้้้', 4, 17);
-      expectTextLength('abc\tde', 10, 12);
-      expectTextLength('abc\tde\t', 10, 20);
-      expectTextLength('\t\t\t\t\t', 20, 100);
-    });
-  });
-});
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts b/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts
index 4ecdcb0..dfe8a15 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts
@@ -228,7 +228,6 @@
     path?: string,
     intentionalMove?: boolean
   ) {
-    this._updateStops();
     const row = this._findRowByNumberAndFile(number, side, path);
     if (row) {
       this.side = side;
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection.ts b/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection.ts
index 22af7e3..2665ef0 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection.ts
@@ -23,11 +23,7 @@
   normalize,
   NormalizedRange,
 } from '../gr-diff-highlight/gr-range-normalizer';
-import {
-  descendedFromClass,
-  isElementTarget,
-  querySelectorAll,
-} from '../../../utils/dom-util';
+import {descendedFromClass, querySelectorAll} from '../../../utils/dom-util';
 import {customElement, property, observe} from '@polymer/decorators';
 import {DiffInfo} from '../../../types/diff';
 import {Side} from '../../../constants/constants';
@@ -113,9 +109,8 @@
   }
 
   _handleDown(e: Event) {
-    const target = e.composedPath()[0];
-    if (!isElementTarget(target)) return;
-
+    const target = e.target;
+    if (!(target instanceof Element)) return;
     // Handle the down event on comment thread in Polymer 2
     const handled = this._handleDownOnRangeComment(target);
     if (handled) return;
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-line.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-line.ts
index 8593e1b..2927101 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-line.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-line.ts
@@ -19,7 +19,6 @@
   GrDiffLine as GrDiffLineApi,
   GrDiffLineType,
   LineNumber,
-  Side,
 } from '../../../api/diff';
 
 export {GrDiffLineType, LineNumber};
@@ -39,10 +38,6 @@
 
   text = '';
 
-  lineNumber(side: Side) {
-    return side === Side.LEFT ? this.beforeNumber : this.afterNumber;
-  }
-
   // TODO(TS): remove this properties
   static readonly Type = GrDiffLineType;
 
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
index a12867f..182d48e 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
@@ -45,20 +45,12 @@
  *   Graphemes: http://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
  *   A proposed JS API: https://github.com/tc39/proposal-intl-segmenter
  */
-export const REGEX_TAB_OR_SURROGATE_PAIR = /\t|[\uD800-\uDBFF][\uDC00-\uDFFF]/;
+const REGEX_TAB_OR_SURROGATE_PAIR = /\t|[\uD800-\uDBFF][\uDC00-\uDFFF]/;
 
 // If any line of the diff is more than the character limit, then disable
 // syntax highlighting for the entire file.
 export const SYNTAX_MAX_LINE_LENGTH = 500;
 
-export function countLines(diff?: DiffInfo, side?: Side) {
-  if (!diff?.content || !side) return 0;
-  return diff.content.reduce((sum, chunk) => {
-    const sideChunk = side === Side.LEFT ? chunk.a : chunk.b;
-    return sum + (sideChunk?.length ?? chunk.ab?.length ?? chunk.skip ?? 0);
-  }, 0);
-}
-
 export function getResponsiveMode(
   prefs: DiffPreferencesInfo,
   renderPrefs?: RenderPreferences
@@ -73,7 +65,7 @@
   return 'NONE';
 }
 
-export function isResponsive(responsiveMode?: DiffResponsiveMode) {
+export function isResponsive(responsiveMode: DiffResponsiveMode) {
   return (
     responsiveMode === 'FULL_RESPONSIVE' || responsiveMode === 'SHRINK_ONLY'
   );
@@ -124,12 +116,7 @@
         return null;
       }
     }
-    node =
-      (node as Element).assignedSlot ??
-      (node as ShadowRoot).host ??
-      node.previousSibling ??
-      node.parentNode ??
-      undefined;
+    node = node.previousSibling ?? node.parentElement ?? undefined;
   }
   return null;
 }
@@ -208,19 +195,6 @@
 }
 
 /**
- * Simple helper method for creating element classes in the context of
- * gr-diff.
- *
- * We are adding 'style-scope', 'gr-diff' classes for compatibility with
- * Shady DOM. TODO: Is that still required??
- *
- * Otherwise this is just a super simple convenience function.
- */
-export function diffClasses(...additionalClasses: string[]) {
-  return ['style-scope', 'gr-diff', ...additionalClasses].join(' ');
-}
-
-/**
  * Simple helper method for creating elements in the context of gr-diff.
  *
  * We are adding 'style-scope', 'gr-diff' classes for compatibility with
@@ -281,8 +255,6 @@
 }
 
 /**
- * Deprecated: Lit based rendering uses the textToPieces() function above.
- *
  * Returns a 'div' element containing the supplied |text| as its innerText,
  * with '\t' characters expanded to a width determined by |tabSize|, and the
  * text wrapped at column |lineLimit|, which may be Infinity if no wrapping is
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts
index 793b0ef..600913e 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts
@@ -4,171 +4,150 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 import '../../../test/common-test-setup-karma';
-import {
-  createElementDiff,
-  formatText,
-  createTabWrapper,
-  diffClasses,
-} from './gr-diff-utils';
+import {createElementDiff, formatText, createTabWrapper} from './gr-diff-utils';
 
 const LINE_BREAK_HTML = '<span class="style-scope gr-diff br"></span>';
 
 suite('gr-diff-utils tests', () => {
-  suite('legacy rendering', () => {
-    test('createElementDiff classStr applies all classes', () => {
-      const node = createElementDiff('div', 'test classes');
-      assert.isTrue(node.classList.contains('gr-diff'));
-      assert.isTrue(node.classList.contains('test'));
-      assert.isTrue(node.classList.contains('classes'));
-    });
-
-    test('formatText newlines 1', () => {
-      let text = 'abcdef';
-
-      assert.equal(formatText(text, 'NONE', 4, 10).innerHTML, text);
-      text = 'a'.repeat(20);
-      assert.equal(
-        formatText(text, 'NONE', 4, 10).innerHTML,
-        'a'.repeat(10) + LINE_BREAK_HTML + 'a'.repeat(10)
-      );
-    });
-
-    test('formatText newlines 2', () => {
-      const text = '<span class="thumbsup">👍</span>';
-      assert.equal(
-        formatText(text, 'NONE', 4, 10).innerHTML,
-        '&lt;span clas' +
-          LINE_BREAK_HTML +
-          's="thumbsu' +
-          LINE_BREAK_HTML +
-          'p"&gt;👍&lt;/span' +
-          LINE_BREAK_HTML +
-          '&gt;'
-      );
-    });
-
-    test('formatText newlines 3', () => {
-      const text = '01234\t56789';
-      assert.equal(
-        formatText(text, 'NONE', 4, 10).innerHTML,
-        '01234' + createTabWrapper(3).outerHTML + '56' + LINE_BREAK_HTML + '789'
-      );
-    });
-
-    test('formatText newlines 4', () => {
-      const text = '👍'.repeat(58);
-      assert.equal(
-        formatText(text, 'NONE', 4, 20).innerHTML,
-        '👍'.repeat(20) +
-          LINE_BREAK_HTML +
-          '👍'.repeat(20) +
-          LINE_BREAK_HTML +
-          '👍'.repeat(18)
-      );
-    });
-
-    test('tab wrapper style', () => {
-      const pattern = new RegExp(
-        '^<span class="style-scope gr-diff tab" ' +
-          'style="((?:-moz-)?tab-size: (\\d+);.?)+">\\t<\\/span>$'
-      );
-
-      for (const size of [1, 3, 8, 55]) {
-        const html = createTabWrapper(size).outerHTML;
-        expect(html).to.match(pattern);
-        assert.equal(html.match(pattern)?.[2], size.toString());
-      }
-    });
-
-    test('tab wrapper insertion', () => {
-      const html = 'abc\tdef';
-      const tabSize = 8;
-      const wrapper = createTabWrapper(tabSize - 3);
-      assert.ok(wrapper);
-      assert.equal(wrapper.innerText, '\t');
-      assert.equal(
-        formatText(html, 'NONE', tabSize, Infinity).innerHTML,
-        'abc' + wrapper.outerHTML + 'def'
-      );
-    });
-
-    test('escaping HTML', () => {
-      let input = '<script>alert("XSS");<' + '/script>';
-      let expected = '&lt;script&gt;alert("XSS");&lt;/script&gt;';
-
-      let result = formatText(
-        input,
-        'NONE',
-        1,
-        Number.POSITIVE_INFINITY
-      ).innerHTML;
-      assert.equal(result, expected);
-
-      input = '& < > " \' / `';
-      expected = '&amp; &lt; &gt; " \' / `';
-      result = formatText(input, 'NONE', 1, Number.POSITIVE_INFINITY).innerHTML;
-      assert.equal(result, expected);
-    });
-
-    test('text length with tabs and unicode', () => {
-      function expectTextLength(
-        text: string,
-        tabSize: number,
-        expected: number
-      ) {
-        // Formatting to |expected| columns should not introduce line breaks.
-        const result = formatText(text, 'NONE', tabSize, expected);
-        assert.isNotOk(
-          result.querySelector('.contentText > .br'),
-          '  Expected the result of: \n' +
-            `      _formatText(${text}', 'NONE',  ${tabSize}, ${expected})\n` +
-            '  to not contain a br. But the actual result HTML was:\n' +
-            `      '${result.innerHTML}'\nwhereupon`
-        );
-
-        // Increasing the line limit should produce the same markup.
-        assert.equal(
-          formatText(text, 'NONE', tabSize, Infinity).innerHTML,
-          result.innerHTML
-        );
-        assert.equal(
-          formatText(text, 'NONE', tabSize, expected + 1).innerHTML,
-          result.innerHTML
-        );
-
-        // Decreasing the line limit should introduce line breaks.
-        if (expected > 0) {
-          const tooSmall = formatText(text, 'NONE', tabSize, expected - 1);
-          assert.isOk(
-            tooSmall.querySelector('.contentText > .br'),
-            '  Expected the result of: \n' +
-              `      _formatText(${text}', ${tabSize}, ${expected - 1})\n` +
-              '  to contain a br. But the actual result HTML was:\n' +
-              `      '${tooSmall.innerHTML}'\nwhereupon`
-          );
-        }
-      }
-      expectTextLength('12345', 4, 5);
-      expectTextLength('\t\t12', 4, 10);
-      expectTextLength('abc💢123', 4, 7);
-      expectTextLength('abc\t', 8, 8);
-      expectTextLength('abc\t\t', 10, 20);
-      expectTextLength('', 10, 0);
-      // 17 Thai combining chars.
-      expectTextLength('ก้้้้้้้้้้้้้้้้', 4, 17);
-      expectTextLength('abc\tde', 10, 12);
-      expectTextLength('abc\tde\t', 10, 20);
-      expectTextLength('\t\t\t\t\t', 20, 100);
-    });
+  test('createElementDiff classStr applies all classes', () => {
+    const node = createElementDiff('div', 'test classes');
+    assert.isTrue(node.classList.contains('gr-diff'));
+    assert.isTrue(node.classList.contains('test'));
+    assert.isTrue(node.classList.contains('classes'));
   });
 
-  suite('lit rendering', () => {
-    test('diffClasses', () => {
-      const c = diffClasses('div', 'test classes').split(' ');
-      assert.include(c, 'gr-diff');
-      assert.include(c, 'style-scope');
-      assert.include(c, 'test');
-      assert.include(c, 'classes');
-    });
+  test('formatText newlines 1', () => {
+    let text = 'abcdef';
+
+    assert.equal(formatText(text, 'NONE', 4, 10).innerHTML, text);
+    text = 'a'.repeat(20);
+    assert.equal(
+      formatText(text, 'NONE', 4, 10).innerHTML,
+      'a'.repeat(10) + LINE_BREAK_HTML + 'a'.repeat(10)
+    );
+  });
+
+  test('formatText newlines 2', () => {
+    const text = '<span class="thumbsup">👍</span>';
+    assert.equal(
+      formatText(text, 'NONE', 4, 10).innerHTML,
+      '&lt;span clas' +
+        LINE_BREAK_HTML +
+        's="thumbsu' +
+        LINE_BREAK_HTML +
+        'p"&gt;👍&lt;/span' +
+        LINE_BREAK_HTML +
+        '&gt;'
+    );
+  });
+
+  test('formatText newlines 3', () => {
+    const text = '01234\t56789';
+    assert.equal(
+      formatText(text, 'NONE', 4, 10).innerHTML,
+      '01234' + createTabWrapper(3).outerHTML + '56' + LINE_BREAK_HTML + '789'
+    );
+  });
+
+  test('formatText newlines 4', () => {
+    const text = '👍'.repeat(58);
+    assert.equal(
+      formatText(text, 'NONE', 4, 20).innerHTML,
+      '👍'.repeat(20) +
+        LINE_BREAK_HTML +
+        '👍'.repeat(20) +
+        LINE_BREAK_HTML +
+        '👍'.repeat(18)
+    );
+  });
+
+  test('tab wrapper style', () => {
+    const pattern = new RegExp(
+      '^<span class="style-scope gr-diff tab" ' +
+        'style="((?:-moz-)?tab-size: (\\d+);.?)+">\\t<\\/span>$'
+    );
+
+    for (const size of [1, 3, 8, 55]) {
+      const html = createTabWrapper(size).outerHTML;
+      expect(html).to.match(pattern);
+      assert.equal(html.match(pattern)?.[2], size.toString());
+    }
+  });
+
+  test('tab wrapper insertion', () => {
+    const html = 'abc\tdef';
+    const tabSize = 8;
+    const wrapper = createTabWrapper(tabSize - 3);
+    assert.ok(wrapper);
+    assert.equal(wrapper.innerText, '\t');
+    assert.equal(
+      formatText(html, 'NONE', tabSize, Infinity).innerHTML,
+      'abc' + wrapper.outerHTML + 'def'
+    );
+  });
+
+  test('escaping HTML', () => {
+    let input = '<script>alert("XSS");<' + '/script>';
+    let expected = '&lt;script&gt;alert("XSS");&lt;/script&gt;';
+
+    let result = formatText(
+      input,
+      'NONE',
+      1,
+      Number.POSITIVE_INFINITY
+    ).innerHTML;
+    assert.equal(result, expected);
+
+    input = '& < > " \' / `';
+    expected = '&amp; &lt; &gt; " \' / `';
+    result = formatText(input, 'NONE', 1, Number.POSITIVE_INFINITY).innerHTML;
+    assert.equal(result, expected);
+  });
+
+  test('text length with tabs and unicode', () => {
+    function expectTextLength(text: string, tabSize: number, expected: number) {
+      // Formatting to |expected| columns should not introduce line breaks.
+      const result = formatText(text, 'NONE', tabSize, expected);
+      assert.isNotOk(
+        result.querySelector('.contentText > .br'),
+        '  Expected the result of: \n' +
+          `      _formatText(${text}', 'NONE',  ${tabSize}, ${expected})\n` +
+          '  to not contain a br. But the actual result HTML was:\n' +
+          `      '${result.innerHTML}'\nwhereupon`
+      );
+
+      // Increasing the line limit should produce the same markup.
+      assert.equal(
+        formatText(text, 'NONE', tabSize, Infinity).innerHTML,
+        result.innerHTML
+      );
+      assert.equal(
+        formatText(text, 'NONE', tabSize, expected + 1).innerHTML,
+        result.innerHTML
+      );
+
+      // Decreasing the line limit should introduce line breaks.
+      if (expected > 0) {
+        const tooSmall = formatText(text, 'NONE', tabSize, expected - 1);
+        assert.isOk(
+          tooSmall.querySelector('.contentText > .br'),
+          '  Expected the result of: \n' +
+            `      _formatText(${text}', ${tabSize}, ${expected - 1})\n` +
+            '  to contain a br. But the actual result HTML was:\n' +
+            `      '${tooSmall.innerHTML}'\nwhereupon`
+        );
+      }
+    }
+    expectTextLength('12345', 4, 5);
+    expectTextLength('\t\t12', 4, 10);
+    expectTextLength('abc💢123', 4, 7);
+    expectTextLength('abc\t', 8, 8);
+    expectTextLength('abc\t\t', 10, 20);
+    expectTextLength('', 10, 0);
+    // 17 Thai combining chars.
+    expectTextLength('ก้้้้้้้้้้้้้้้้', 4, 17);
+    expectTextLength('abc\tde', 10, 12);
+    expectTextLength('abc\tde\t', 10, 20);
+    expectTextLength('\t\t\t\t\t', 20, 100);
   });
 });
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
index f7e40f9..a38ec91 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
@@ -24,7 +24,7 @@
 import '../gr-ranged-comment-themes/gr-ranged-comment-theme';
 import '../gr-ranged-comment-hint/gr-ranged-comment-hint';
 import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {dom} from '@polymer/polymer/lib/legacy/polymer.dom';
+import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
 import {htmlTemplate} from './gr-diff_html';
 import {LineNumber} from './gr-diff-line';
 import {
@@ -77,7 +77,7 @@
   GrDiff as GrDiffApi,
   DisplayLine,
 } from '../../../api/diff';
-import {isElementTarget, isSafari, toggleClass} from '../../../utils/dom-util';
+import {isSafari, toggleClass} from '../../../utils/dom-util';
 import {assertIsDefined} from '../../../utils/common-util';
 import {debounce, DelayedTask} from '../../../utils/async-util';
 
@@ -530,8 +530,7 @@
   }
 
   _handleTap(e: CustomEvent) {
-    const el = e.composedPath()[0];
-    if (!isElementTarget(el)) return;
+    const el = (dom(e) as EventApi).localTarget as Element;
 
     if (
       el.getAttribute('data-value') !== 'LOST' &&
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_html.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_html.ts
index c2e5550..e05e85a 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_html.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_html.ts
@@ -670,12 +670,6 @@
     .token-highlight {
       background-color: var(--token-highlighting-color, #fffd54);
     }
-
-    gr-diff-section,
-    gr-context-controls-section,
-    gr-diff-row {
-      display: contents;
-    }
   </style>
   <style include="gr-syntax-theme">
     /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */