Merge "Add ChecksApi types and interface"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 5ed8ccf..e7c0d13 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1705,6 +1705,12 @@
 ----
   javaOptions = -Dlog4j.configuration=file:///home/gerrit/site/etc/log4j.properties
 ----
++
+Gerrit built-in loggers are then ignored: error logger (`error_log` file),
+link:#httpd.requestLog[httpd.requestLog] and
+link:#sshd.requestLog[sshd.requestLog]. The
+link:#log.jsonLogging[log.jsonLogging] and
+link:#log.textLogging[log.textLogging] options are also ignored.
 
 [[container.daemonOpt]]container.daemonOpt::
 +
@@ -3768,8 +3774,13 @@
 
 [[log.jsonLogging]]log.jsonLogging::
 +
-If set to true, enables error, ssh and http logging in JSON format (file name:
-"logs/{error|sshd|httpd}_log.json").
+If set to true, enables error, ssh and http logging in JSON format (file names:
+`logs/error_log.json`, `logs/sshd_log.json` and `logs/httpd_log.json`).
++
+The option only applies to Gerrit built-in loggers. It is ignored when a log4j
+configuration is specified via
+link:#container.javaOptions[container.javaOptions], for example
+`-Dlog4j.configuration=file://etc/log4j.properties`.
 +
 Defaults to false.
 
@@ -3778,6 +3789,11 @@
 If set to true, enables error logging in regular plain text format. Can only be disabled
 if `jsonLogging` is enabled.
 +
+The option only applies to Gerrit built-in loggers. It is ignored when a log4j
+configuration is specified via
+link:#container.javaOptions[container.javaOptions], for example
+`-Dlog4j.configuration=file://etc/log4j.properties`.
++
 Defaults to true.
 
 [[log.compress]]log.compress::
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 96cc67f..0ae038a 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -287,6 +287,18 @@
   bazel-bin/withdocs.war
 ----
 
+Alternatively, one can generate the documentation as flat files:
+
+----
+  bazel build Documentation:Documentation
+----
+
+The html, css, js files are placed in:
+
+----
+ `bazel-bin/Documentation/`
+----
+
 [[tests]]
 == Running Unit Tests
 
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index d128613..43c3b9e 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -216,15 +216,18 @@
 [[labels]]
 --
 * `LABELS`: a summary of each label required for submit, and
-  approvers that have granted (or rejected) with that label.
+  approvers that have granted (or rejected) with that label
+  as well as all reviewers by state, and reviewers that may
+  be removed by the current user.
 --
 
 [[detailed-labels]]
 --
 * `DETAILED_LABELS`: detailed label information, including numeric
   values of all existing approvals, recognized label values, values
-  permitted to be set by the current user, all reviewers by state, and
-  reviewers that may be removed by the current user.
+  permitted to be set by any reviewer and the change owner, all
+  reviewers by state, and reviewers that may be removed by the
+  current user.
 --
 
 [[current-revision]]
@@ -6412,7 +6415,8 @@
 |`removable_reviewers`|optional|
 The reviewers that can be removed by the calling user as a list of
 link:rest-api-accounts.html#account-info[AccountInfo] entities. +
-Only set if link:#detailed-labels[detailed labels] are requested.
+Only set if link:#labels[labels] or
+link:#detailed-labels[detailed labels] are requested.
 |`reviewers`          |optional|
 The reviewers as a map that maps a reviewer state to a list of
 link:rest-api-accounts.html#account-info[AccountInfo] entities.
@@ -6421,13 +6425,15 @@
 `CC`: Users that were added to the change, but have not voted. +
 `REMOVED`: Users that were previously reviewers on the change, but have
 been removed. +
-Only set if link:#detailed-labels[detailed labels] are requested.
+Only set if link:#labels[labels] or
+link:#detailed-labels[detailed labels] are requested.
 |`pending_reviewers`  |optional|
 Updates to `reviewers` that have been made while the change was in the
 WIP state. Only present on WIP changes and only if there are pending
 reviewer updates to report. These are reviewers who have not yet been
 notified about being added to or removed from the change. +
-Only set if link:#detailed-labels[detailed labels] are requested.
+Only set if link:#labels[labels] or
+link:#detailed-labels[detailed labels] are requested.
 |`reviewer_updates`|optional|
 Updates to reviewers set for the change as
 link:#review-update-info[ReviewerUpdateInfo] entities.
diff --git a/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java b/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
index 30514a6..681d0bd 100644
--- a/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
+++ b/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
@@ -31,7 +31,9 @@
     PULL,
     CHECKOUT,
     CHERRY_PICK,
-    FORMAT_PATCH
+    FORMAT_PATCH,
+    BRANCH,
+    RESET,
   }
 
   public enum DateFormat {
diff --git a/java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java b/java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java
index d92da18..e3e96df 100644
--- a/java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java
+++ b/java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java
@@ -43,8 +43,8 @@
   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
     CacheHeaders.setNotCacheable(res);
-    res.setContentLength(0);
     if (user.get().isIdentifiedUser()) {
+      res.setContentLength(0);
       res.setStatus(HttpServletResponse.SC_NO_CONTENT);
     } else {
       res.setStatus(HttpServletResponse.SC_FORBIDDEN);
diff --git a/java/com/google/gerrit/httpd/raw/CatServlet.java b/java/com/google/gerrit/httpd/raw/CatServlet.java
index 7a4f4e6..f5d72b2 100644
--- a/java/com/google/gerrit/httpd/raw/CatServlet.java
+++ b/java/com/google/gerrit/httpd/raw/CatServlet.java
@@ -123,7 +123,7 @@
     final Change.Id changeId = patchKey.patchSetId().changeId();
     String revision;
     try {
-      ChangeNotes notes = changeNotesFactory.createChecked(changeId);
+      ChangeNotes notes = changeNotesFactory.createCheckedUsingIndexLookup(changeId);
       permissionBackend.currentUser().change(notes).check(ChangePermission.READ);
       projectCache
           .get(notes.getProjectName())
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 0cfc6cb..6ab0c61 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -602,7 +602,9 @@
                 ? labelsJson.permittedLabels(user.getAccountId(), cd)
                 : ImmutableMap.of();
       }
+    }
 
+    if (has(LABELS) || has(DETAILED_LABELS)) {
       out.reviewers = reviewerMap(cd.reviewers(), cd.reviewersByEmail(), false);
       out.pendingReviewers = reviewerMap(cd.pendingReviewers(), cd.pendingReviewersByEmail(), true);
       out.removableReviewers = removableReviewers(cd, out);
diff --git a/java/com/google/gerrit/server/change/ReviewerAdder.java b/java/com/google/gerrit/server/change/ReviewerAdder.java
index 3d986d2..5d55b4d 100644
--- a/java/com/google/gerrit/server/change/ReviewerAdder.java
+++ b/java/com/google/gerrit/server/change/ReviewerAdder.java
@@ -19,7 +19,6 @@
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
 import static com.google.gerrit.extensions.client.ReviewerState.CC;
-import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
 import static com.google.gerrit.server.project.ProjectCache.illegalState;
 import static java.util.Comparator.comparing;
 import static java.util.Objects.requireNonNull;
@@ -140,12 +139,12 @@
     }
 
     logger.atFine().log(
-        "Adding account %d from author/committer identity of commit %s as reviewer to change %d",
+        "Adding account %d from author/committer identity of commit %s as cc to change %d",
         accountId.get(), commitId.name(), change.getChangeId());
 
     InternalAddReviewerInput in = new InternalAddReviewerInput();
     in.reviewer = accountId.toString();
-    in.state = REVIEWER;
+    in.state = CC;
     in.notify = notify;
     in.otherFailureBehavior = FailureBehavior.IGNORE;
     return Optional.of(in);
diff --git a/java/com/google/gerrit/server/events/StreamEventsApiListener.java b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
index 1f90187..439f53e 100644
--- a/java/com/google/gerrit/server/events/StreamEventsApiListener.java
+++ b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
@@ -140,7 +140,8 @@
 
   private ChangeNotes getNotes(ChangeInfo info) {
     try {
-      return changeNotesFactory.createChecked(Change.id(info._number));
+      return changeNotesFactory.createChecked(
+          Project.nameKey(info.project), Change.id(info._number));
     } catch (NoSuchChangeException e) {
       throw new StorageException(e);
     }
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 2ec9a8d..fb8a9d3 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -2692,7 +2692,7 @@
   private void readChangesForReplace() {
     try (TraceTimer traceTimer = newTimer("readChangesForReplace")) {
       Collection<ChangeNotes> allNotes =
-          notesFactory.create(
+          notesFactory.createUsingIndexLookup(
               replaceByChange.values().stream().map(r -> r.ontoChange).collect(toList()));
       for (ChangeNotes notes : allNotes) {
         replaceByChange.get(notes.getChangeId()).notes = notes;
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotes.java b/java/com/google/gerrit/server/notedb/ChangeNotes.java
index 41263a4..d48cbc4 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -118,19 +118,6 @@
       return new ChangeNotes(args, change, true, null).load();
     }
 
-    public ChangeNotes createChecked(Change.Id changeId) {
-      InternalChangeQuery query = queryProvider.get().noFields();
-      List<ChangeData> changes = query.byLegacyChangeId(changeId);
-      if (changes.isEmpty()) {
-        throw new NoSuchChangeException(changeId);
-      }
-      if (changes.size() != 1) {
-        logger.atSevere().log("Multiple changes found for %d", changeId.get());
-        throw new NoSuchChangeException(changeId);
-      }
-      return changes.get(0).notes();
-    }
-
     public static Change newChange(Project.NameKey project, Change.Id changeId) {
       return new Change(
           null, changeId, null, BranchNameKey.create(project, "INVALID_NOTE_DB_ONLY"), null);
@@ -149,11 +136,33 @@
       return new ChangeNotes(args, change, true, refs).load();
     }
 
-    public List<ChangeNotes> create(Collection<Change.Id> changeIds) {
+    /**
+     * Create change notes based on a {@link Change.Id}. This requires using the Change index and
+     * should only be used when {@link Project.NameKey} and the numeric change ID are not available.
+     */
+    public ChangeNotes createCheckedUsingIndexLookup(Change.Id changeId) {
+      InternalChangeQuery query = queryProvider.get().noFields();
+      List<ChangeData> changes = query.byLegacyChangeId(changeId);
+      if (changes.isEmpty()) {
+        throw new NoSuchChangeException(changeId);
+      }
+      if (changes.size() != 1) {
+        logger.atSevere().log("Multiple changes found for %d", changeId.get());
+        throw new NoSuchChangeException(changeId);
+      }
+      return changes.get(0).notes();
+    }
+
+    /**
+     * Create change notes based on a list of {@link Change.Id}s. This requires using the Change
+     * index and should only be used when {@link Project.NameKey} and the numeric change ID are not
+     * available.
+     */
+    public List<ChangeNotes> createUsingIndexLookup(Collection<Change.Id> changeIds) {
       List<ChangeNotes> notes = new ArrayList<>();
       for (Change.Id changeId : changeIds) {
         try {
-          notes.add(createChecked(changeId));
+          notes.add(createCheckedUsingIndexLookup(changeId));
         } catch (NoSuchChangeException e) {
           // Ignore missing changes to match Access#get(Iterable) behavior.
         }
diff --git a/java/com/google/gerrit/server/patch/DiffMappings.java b/java/com/google/gerrit/server/patch/DiffMappings.java
index 921d66e..a6f026e 100644
--- a/java/com/google/gerrit/server/patch/DiffMappings.java
+++ b/java/com/google/gerrit/server/patch/DiffMappings.java
@@ -15,12 +15,17 @@
 package com.google.gerrit.server.patch;
 
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static java.util.stream.Collectors.toList;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.Patch;
 import com.google.gerrit.server.patch.GitPositionTransformer.FileMapping;
 import com.google.gerrit.server.patch.GitPositionTransformer.Mapping;
 import com.google.gerrit.server.patch.GitPositionTransformer.Range;
 import com.google.gerrit.server.patch.GitPositionTransformer.RangeMapping;
+import com.google.gerrit.server.patch.entities.Edit;
+import com.google.gerrit.server.patch.entities.FileEdits;
+import java.util.List;
 
 /** Mappings derived from diffs. */
 public class DiffMappings {
@@ -33,31 +38,47 @@
     return Mapping.create(fileMapping, rangeMappings);
   }
 
-  private static FileMapping toFileMapping(PatchListEntry patchListEntry) {
-    switch (patchListEntry.getChangeType()) {
+  public static Mapping toMapping(FileEdits fileEdits) {
+    FileMapping fileMapping = FileMapping.forFile(fileEdits.oldPath(), fileEdits.newPath());
+    ImmutableSet<RangeMapping> rangeMappings = toRangeMappings(fileEdits.edits());
+    return Mapping.create(fileMapping, rangeMappings);
+  }
+
+  private static FileMapping toFileMapping(PatchListEntry ple) {
+    return toFileMapping(ple.getChangeType(), ple.getOldName(), ple.getNewName());
+  }
+
+  private static FileMapping toFileMapping(
+      Patch.ChangeType changeType, String oldName, String newName) {
+    switch (changeType) {
       case ADDED:
-        return FileMapping.forAddedFile(patchListEntry.getNewName());
+        return FileMapping.forAddedFile(newName);
       case MODIFIED:
       case REWRITE:
-        return FileMapping.forModifiedFile(patchListEntry.getNewName());
+        return FileMapping.forModifiedFile(newName);
       case DELETED:
         // Name of deleted file is mentioned as newName.
-        return FileMapping.forDeletedFile(patchListEntry.getNewName());
+        return FileMapping.forDeletedFile(newName);
       case RENAMED:
       case COPIED:
-        return FileMapping.forRenamedFile(patchListEntry.getOldName(), patchListEntry.getNewName());
+        return FileMapping.forRenamedFile(oldName, newName);
       default:
-        throw new IllegalStateException("Unmapped diff type: " + patchListEntry.getChangeType());
+        throw new IllegalStateException("Unmapped diff type: " + changeType);
     }
   }
 
   private static ImmutableSet<RangeMapping> toRangeMappings(PatchListEntry patchListEntry) {
-    return patchListEntry.getEdits().stream()
+    return toRangeMappings(
+        patchListEntry.getEdits().stream().map(Edit::fromJGitEdit).collect(toList()));
+  }
+
+  private static ImmutableSet<RangeMapping> toRangeMappings(List<Edit> edits) {
+    return edits.stream()
         .map(
             edit ->
                 RangeMapping.create(
-                    Range.create(edit.getBeginA(), edit.getEndA()),
-                    Range.create(edit.getBeginB(), edit.getEndB())))
+                    Range.create(edit.beginA(), edit.endA()),
+                    Range.create(edit.beginB(), edit.endB())))
         .collect(toImmutableSet());
   }
 }
diff --git a/java/com/google/gerrit/server/patch/EditTransformer.java b/java/com/google/gerrit/server/patch/EditTransformer.java
index 6288270..06e6f72 100644
--- a/java/com/google/gerrit/server/patch/EditTransformer.java
+++ b/java/com/google/gerrit/server/patch/EditTransformer.java
@@ -19,7 +19,6 @@
 import static com.google.common.collect.Multimaps.toMultimap;
 
 import com.google.auto.value.AutoValue;
-import com.google.common.base.MoreObjects;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -31,12 +30,13 @@
 import com.google.gerrit.server.patch.GitPositionTransformer.Position;
 import com.google.gerrit.server.patch.GitPositionTransformer.PositionedEntity;
 import com.google.gerrit.server.patch.GitPositionTransformer.Range;
+import com.google.gerrit.server.patch.entities.Edit;
+import com.google.gerrit.server.patch.entities.FileEdits;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Function;
 import java.util.stream.Stream;
-import org.eclipse.jgit.diff.Edit;
 
 /**
  * Transformer of edits regarding their base trees. An edit describes a difference between {@code
@@ -54,40 +54,42 @@
 
   /**
    * Creates a new {@code EditTransformer} for the edits contained in the specified {@code
-   * PatchListEntry}s.
+   * FileEdits}s.
    *
-   * @param patchListEntries a list of {@code PatchListEntry}s containing the edits
+   * @param fileEdits a list of {@code FileEdits}s containing the edits
    */
-  public EditTransformer(List<PatchListEntry> patchListEntries) {
-    edits = patchListEntries.stream().flatMap(EditTransformer::toEdits).collect(toImmutableList());
+  public EditTransformer(List<FileEdits> fileEdits) {
+    // TODO(ghareeb): Can we replace FileEdits with another entity from the new refactored
+    // diff cache implementation? e.g. one of the GitFileDiffCache entities
+    edits = fileEdits.stream().flatMap(EditTransformer::toEdits).collect(toImmutableList());
   }
 
   /**
    * Transforms the references of side A of the edits. If the edits describe differences between
-   * {@code treeA} and {@code treeB} and the specified {@code PatchListEntry}s define a
-   * transformation from {@code treeA} to {@code treeA'}, the resulting edits will be defined as
-   * differences between {@code treeA'} and {@code treeB}. Edits which can't be transformed due to
-   * conflicts with the transformation are omitted.
+   * {@code treeA} and {@code treeB} and the specified {@code FileEdits}s define a transformation
+   * from {@code treeA} to {@code treeA'}, the resulting edits will be defined as differences
+   * between {@code treeA'} and {@code treeB}. Edits which can't be transformed due to conflicts
+   * with the transformation are omitted.
    *
-   * @param transformationEntries a list of {@code PatchListEntry}s defining the transformation of
-   *     {@code treeA} to {@code treeA'}
+   * @param transformingEntries a list of {@code FileEdits}s defining the transformation of {@code
+   *     treeA} to {@code treeA'}
    */
-  public void transformReferencesOfSideA(List<PatchListEntry> transformationEntries) {
-    transformEdits(transformationEntries, SideAStrategy.INSTANCE);
+  public void transformReferencesOfSideA(ImmutableList<FileEdits> transformingEntries) {
+    transformEdits(transformingEntries, SideAStrategy.INSTANCE);
   }
 
   /**
    * Transforms the references of side B of the edits. If the edits describe differences between
-   * {@code treeA} and {@code treeB} and the specified {@code PatchListEntry}s define a
-   * transformation from {@code treeB} to {@code treeB'}, the resulting edits will be defined as
-   * differences between {@code treeA} and {@code treeB'}. Edits which can't be transformed due to
-   * conflicts with the transformation are omitted.
+   * {@code treeA} and {@code treeB} and the specified {@code FileEdits}s define a transformation
+   * from {@code treeB} to {@code treeB'}, the resulting edits will be defined as differences
+   * between {@code treeA} and {@code treeB'}. Edits which can't be transformed due to conflicts
+   * with the transformation are omitted.
    *
-   * @param transformationEntries a list of {@code PatchListEntry}s defining the transformation of
+   * @param transformingEntries a list of {@code PatchListEntry}s defining the transformation of
    *     {@code treeB} to {@code treeB'}
    */
-  public void transformReferencesOfSideB(List<PatchListEntry> transformationEntries) {
-    transformEdits(transformationEntries, SideBStrategy.INSTANCE);
+  public void transformReferencesOfSideB(ImmutableList<FileEdits> transformingEntries) {
+    transformEdits(transformingEntries, SideBStrategy.INSTANCE);
   }
 
   /**
@@ -99,25 +101,33 @@
     return edits.stream()
         .collect(
             toMultimap(
-                ContextAwareEdit::getNewFilePath, Function.identity(), ArrayListMultimap::create));
+                c -> {
+                  String path =
+                      c.getNewFilePath().isPresent()
+                          ? c.getNewFilePath().get()
+                          : c.getOldFilePath().get();
+                  return path;
+                },
+                Function.identity(),
+                ArrayListMultimap::create));
   }
 
-  public static Stream<ContextAwareEdit> toEdits(PatchListEntry patchListEntry) {
-    ImmutableList<Edit> edits = patchListEntry.getEdits();
+  public static Stream<ContextAwareEdit> toEdits(FileEdits in) {
+    List<Edit> edits = in.edits();
     if (edits.isEmpty()) {
-      return Stream.of(ContextAwareEdit.createForNoContentEdit(patchListEntry));
+      return Stream.of(ContextAwareEdit.createForNoContentEdit(in.oldPath(), in.newPath()));
     }
 
-    return edits.stream().map(edit -> ContextAwareEdit.create(patchListEntry, edit));
+    return edits.stream().map(edit -> ContextAwareEdit.create(in.oldPath(), in.newPath(), edit));
   }
 
-  private void transformEdits(List<PatchListEntry> transformingEntries, SideStrategy sideStrategy) {
+  private void transformEdits(List<FileEdits> inputs, SideStrategy sideStrategy) {
     ImmutableList<PositionedEntity<ContextAwareEdit>> positionedEdits =
         edits.stream()
             .map(edit -> toPositionedEntity(edit, sideStrategy))
             .collect(toImmutableList());
     ImmutableSet<Mapping> mappings =
-        transformingEntries.stream().map(DiffMappings::toMapping).collect(toImmutableSet());
+        inputs.stream().map(DiffMappings::toMapping).collect(toImmutableSet());
 
     edits =
         positionTransformer.transform(positionedEdits, mappings).stream()
@@ -133,41 +143,41 @@
 
   @AutoValue
   abstract static class ContextAwareEdit {
-    static ContextAwareEdit create(PatchListEntry patchListEntry, Edit edit) {
+    static ContextAwareEdit create(Optional<String> oldPath, Optional<String> newPath, Edit edit) {
+      // TODO(ghareeb): Look if the new FileEdits class is capable of representing renames/copies
+      // and in this case we can get rid of the ContextAwareEdit class.
       return create(
-          patchListEntry.getOldName(),
-          patchListEntry.getNewName(),
-          edit.getBeginA(),
-          edit.getEndA(),
-          edit.getBeginB(),
-          edit.getEndB(),
-          false);
+          oldPath, newPath, edit.beginA(), edit.endA(), edit.beginB(), edit.endB(), false);
     }
 
-    static ContextAwareEdit createForNoContentEdit(PatchListEntry patchListEntry) {
+    static ContextAwareEdit createForNoContentEdit(
+        Optional<String> oldPath, Optional<String> newPath) {
       // Remove the warning in createEditAtNewPosition() if we switch to an empty range instead of
       // (-1:-1, -1:-1) in the future.
-      return create(
-          patchListEntry.getOldName(), patchListEntry.getNewName(), -1, -1, -1, -1, false);
+      return create(oldPath, newPath, -1, -1, -1, -1, false);
     }
 
     static ContextAwareEdit create(
-        String oldFilePath,
-        String newFilePath,
+        Optional<String> oldFilePath,
+        Optional<String> newFilePath,
         int beginA,
         int endA,
         int beginB,
         int endB,
         boolean filePathAdjusted) {
-      String adjustedOldFilePath = MoreObjects.firstNonNull(oldFilePath, newFilePath);
-      boolean implicitRename = !Objects.equals(oldFilePath, newFilePath) && filePathAdjusted;
+      Optional<String> adjustedFilePath = oldFilePath.isPresent() ? oldFilePath : newFilePath;
+      boolean implicitRename =
+          newFilePath.isPresent()
+              && oldFilePath.isPresent()
+              && !Objects.equals(oldFilePath.get(), newFilePath.get())
+              && filePathAdjusted;
       return new AutoValue_EditTransformer_ContextAwareEdit(
-          adjustedOldFilePath, newFilePath, beginA, endA, beginB, endB, implicitRename);
+          adjustedFilePath, newFilePath, beginA, endA, beginB, endB, implicitRename);
     }
 
-    public abstract String getOldFilePath();
+    public abstract Optional<String> getOldFilePath();
 
-    public abstract String getNewFilePath();
+    public abstract Optional<String> getNewFilePath();
 
     public abstract int getBeginA();
 
@@ -180,12 +190,13 @@
     // Used for equals(), for which this value is important.
     public abstract boolean isImplicitRename();
 
-    public Optional<Edit> toEdit() {
+    public Optional<org.eclipse.jgit.diff.Edit> toEdit() {
       if (getBeginA() < 0) {
         return Optional.empty();
       }
 
-      return Optional.of(new Edit(getBeginA(), getEndA(), getBeginB(), getEndB()));
+      return Optional.of(
+          new org.eclipse.jgit.diff.Edit(getBeginA(), getEndA(), getBeginB(), getEndB()));
     }
   }
 
@@ -200,8 +211,12 @@
 
     @Override
     public Position extractPosition(ContextAwareEdit edit) {
+      String filePath =
+          edit.getOldFilePath().isPresent()
+              ? edit.getOldFilePath().get()
+              : edit.getNewFilePath().get();
       return Position.builder()
-          .filePath(edit.getOldFilePath())
+          .filePath(filePath)
           .lineRange(Range.create(edit.getBeginA(), edit.getEndA()))
           .build();
     }
@@ -227,13 +242,13 @@
             newPosition);
       }
       return ContextAwareEdit.create(
-          updatedFilePath,
+          Optional.of(updatedFilePath),
           edit.getNewFilePath(),
           updatedRange.start(),
           updatedRange.end(),
           edit.getBeginB(),
           edit.getEndB(),
-          !Objects.equals(edit.getOldFilePath(), updatedFilePath));
+          !Objects.equals(edit.getOldFilePath(), Optional.of(updatedFilePath)));
     }
   }
 
@@ -242,8 +257,12 @@
 
     @Override
     public Position extractPosition(ContextAwareEdit edit) {
+      String filePath =
+          edit.getNewFilePath().isPresent()
+              ? edit.getNewFilePath().get()
+              : edit.getOldFilePath().get();
       return Position.builder()
-          .filePath(edit.getNewFilePath())
+          .filePath(filePath)
           .lineRange(Range.create(edit.getBeginB(), edit.getEndB()))
           .build();
     }
@@ -255,7 +274,8 @@
       // in the future.
       Range updatedRange = newPosition.lineRange().orElseGet(() -> Range.create(-1, -1));
       // Same as far the range above. PATCHSET_LEVEL is a safe fallback.
-      String updatedFilePath = newPosition.filePath().orElse(Patch.PATCHSET_LEVEL);
+      Optional<String> updatedFilePath =
+          Optional.of(newPosition.filePath().orElse(Patch.PATCHSET_LEVEL));
       return ContextAwareEdit.create(
           edit.getOldFilePath(),
           updatedFilePath,
diff --git a/java/com/google/gerrit/server/patch/GitPositionTransformer.java b/java/com/google/gerrit/server/patch/GitPositionTransformer.java
index d890bc2..f00f909 100644
--- a/java/com/google/gerrit/server/patch/GitPositionTransformer.java
+++ b/java/com/google/gerrit/server/patch/GitPositionTransformer.java
@@ -330,6 +330,11 @@
       return new AutoValue_GitPositionTransformer_FileMapping(
           Optional.of(oldPath), Optional.of(newPath));
     }
+
+    /** Creates a {@link FileMapping} using the old and new paths. */
+    public static FileMapping forFile(Optional<String> oldPath, Optional<String> newPath) {
+      return new AutoValue_GitPositionTransformer_FileMapping(oldPath, newPath);
+    }
   }
 
   /**
diff --git a/java/com/google/gerrit/server/patch/PatchListLoader.java b/java/com/google/gerrit/server/patch/PatchListLoader.java
index 2e9d58c..df34aa6 100644
--- a/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -39,6 +39,7 @@
 import com.google.gerrit.server.git.InMemoryInserter;
 import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.patch.EditTransformer.ContextAwareEdit;
+import com.google.gerrit.server.patch.entities.FileEdits;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 import java.io.IOException;
@@ -306,13 +307,39 @@
         getRelevantPatchListEntries(
             parentDiffEntries, parentCommitA, parentCommitB, touchedFilePaths, df);
 
-    EditTransformer editTransformer = new EditTransformer(parentPatchListEntries);
-    editTransformer.transformReferencesOfSideA(oldPatches);
-    editTransformer.transformReferencesOfSideB(newPatches);
+    EditTransformer editTransformer = new EditTransformer(toFileEditsList(parentPatchListEntries));
+    editTransformer.transformReferencesOfSideA(toFileEditsList(oldPatches));
+    editTransformer.transformReferencesOfSideB(toFileEditsList(newPatches));
     return EditsDueToRebaseResult.create(
         relevantDiffEntries, editTransformer.getEditsPerFilePath());
   }
 
+  private ImmutableList<FileEdits> toFileEditsList(List<PatchListEntry> entries) {
+    return entries.stream().map(PatchListLoader::toFileEdits).collect(toImmutableList());
+  }
+
+  private static FileEdits toFileEdits(PatchListEntry patchListEntry) {
+    Optional<String> oldName = Optional.empty();
+    Optional<String> newName = Optional.empty();
+    switch (patchListEntry.getChangeType()) {
+      case DELETED:
+        oldName = Optional.of(patchListEntry.getNewName());
+        break;
+      case ADDED:
+      case MODIFIED:
+      case REWRITE:
+        newName = Optional.of(patchListEntry.getNewName());
+        break;
+
+      case COPIED:
+      case RENAMED:
+        oldName = Optional.of(patchListEntry.getOldName());
+        newName = Optional.of(patchListEntry.getNewName());
+        break;
+    }
+    return FileEdits.create(patchListEntry.getEdits(), oldName, newName);
+  }
+
   private static boolean isRootOrMergeCommit(RevCommit commit) {
     return commit.getParentCount() != 1;
   }
@@ -405,7 +432,7 @@
     PatchListEntry patchListEntry =
         newEntry(treeA, fileHeader, contentEditsDueToRebase, newSize, newSize - oldSize);
     // All edits in a file are due to rebase -> exclude the file from the diff.
-    if (EditTransformer.toEdits(patchListEntry).allMatch(editsDueToRebase::contains)) {
+    if (EditTransformer.toEdits(toFileEdits(patchListEntry)).allMatch(editsDueToRebase::contains)) {
       return Optional.empty();
     }
     return Optional.of(patchListEntry);
diff --git a/java/com/google/gerrit/server/patch/entities/Edit.java b/java/com/google/gerrit/server/patch/entities/Edit.java
new file mode 100644
index 0000000..683bbec
--- /dev/null
+++ b/java/com/google/gerrit/server/patch/entities/Edit.java
@@ -0,0 +1,46 @@
+//  Copyright (C) 2020 The Android Open Source Project
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+
+package com.google.gerrit.server.patch.entities;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * A modified region between 2 versions of the same content. This is the Gerrit entity class
+ * corresponding to {@link org.eclipse.jgit.diff.Edit} and is needed to ensure immutability when
+ * included as fields of the diff persisted caches.
+ */
+@AutoValue
+public abstract class Edit {
+  static Edit create(int beginA, int endA, int beginB, int endB) {
+    return new AutoValue_Edit(beginA, endA, beginB, endB);
+  }
+
+  public static Edit fromJGitEdit(org.eclipse.jgit.diff.Edit jgitEdit) {
+    return create(
+        jgitEdit.getBeginA(), jgitEdit.getEndA(), jgitEdit.getBeginB(), jgitEdit.getEndB());
+  }
+
+  /** Start of a region in sequence A. */
+  public abstract int beginA();
+
+  /** End of a region in sequence A. */
+  public abstract int endA();
+
+  /** Start of a region in sequence B. */
+  public abstract int beginB();
+
+  /** End of a region in sequence B. */
+  public abstract int endB();
+}
diff --git a/java/com/google/gerrit/server/patch/entities/FileEdits.java b/java/com/google/gerrit/server/patch/entities/FileEdits.java
new file mode 100644
index 0000000..c914919
--- /dev/null
+++ b/java/com/google/gerrit/server/patch/entities/FileEdits.java
@@ -0,0 +1,44 @@
+//  Copyright (C) 2020 The Android Open Source Project
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+
+package com.google.gerrit.server.patch.entities;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * An entity class containing the list of edits between 2 commits for a file, and the old and new
+ * paths.
+ */
+@AutoValue
+public abstract class FileEdits {
+  public static FileEdits create(
+      List<org.eclipse.jgit.diff.Edit> jgitEdits,
+      Optional<String> oldPath,
+      Optional<String> newPath) {
+    ImmutableList<Edit> edits =
+        jgitEdits.stream().map(Edit::fromJGitEdit).collect(toImmutableList());
+    return new AutoValue_FileEdits(edits, oldPath, newPath);
+  }
+
+  public abstract ImmutableList<Edit> edits();
+
+  public abstract Optional<String> oldPath();
+
+  public abstract Optional<String> newPath();
+}
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java b/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java
index f79209d..5b44957 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.ChangeMessage;
 import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.api.changes.DeleteChangeMessageInput;
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
 import com.google.gerrit.extensions.common.Input;
@@ -93,13 +94,17 @@
     }
 
     ChangeMessageInfo updatedMessageInfo =
-        createUpdatedChangeMessageInfo(resource.getChangeId(), resource.getChangeMessageIndex());
+        createUpdatedChangeMessageInfo(
+            resource.getChangeResource().getId(),
+            resource.getChangeResource().getProject(),
+            resource.getChangeMessageIndex());
     return Response.created(updatedMessageInfo);
   }
 
-  private ChangeMessageInfo createUpdatedChangeMessageInfo(Change.Id id, int targetIdx)
-      throws PermissionBackendException {
-    List<ChangeMessage> messages = changeMessagesUtil.byChange(notesFactory.createChecked(id));
+  private ChangeMessageInfo createUpdatedChangeMessageInfo(
+      Change.Id cId, Project.NameKey project, int targetIdx) throws PermissionBackendException {
+    List<ChangeMessage> messages =
+        changeMessagesUtil.byChange(notesFactory.createChecked(project, cId));
     ChangeMessage updatedChangeMessage = messages.get(targetIdx);
     AccountLoader accountLoader = accountLoaderFactory.create(true);
     ChangeMessageInfo info = createChangeMessageInfo(updatedChangeMessage, accountLoader);
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteComment.java b/java/com/google/gerrit/server/restapi/change/DeleteComment.java
index 8580229..044fd77 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteComment.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteComment.java
@@ -89,7 +89,9 @@
     }
 
     ChangeNotes updatedNotes =
-        notesFactory.createChecked(rsrc.getRevisionResource().getChange().getId());
+        notesFactory.createChecked(
+            rsrc.getRevisionResource().getProject(),
+            rsrc.getRevisionResource().getChangeResource().getId());
     List<HumanComment> changeComments = commentsUtil.publishedHumanCommentsByChange(updatedNotes);
     Optional<HumanComment> updatedComment =
         changeComments.stream().filter(c -> c.key.equals(rsrc.getComment().key)).findFirst();
diff --git a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
index ca39a57..cb91faa 100644
--- a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
+++ b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
@@ -358,7 +358,11 @@
         commitUtil.createRevertChange(changeNotes, user.get(), revertInput, timestamp);
     results.add(json.noOptions().format(changeNotes.getProjectName(), revertId));
     cherryPickInput.base =
-        changeNotesFactory.createChecked(revertId).getCurrentPatchSet().commitId().getName();
+        changeNotesFactory
+            .createChecked(changeNotes.getProjectName(), revertId)
+            .getCurrentPatchSet()
+            .commitId()
+            .getName();
   }
 
   private CherryPickInput createCherryPickInput(RevertInput revertInput) {
@@ -591,7 +595,7 @@
       // save the commit as base for next cherryPick of that branch
       cherryPickInput.base =
           changeNotesFactory
-              .createChecked(cherryPickResult.changeId())
+              .createChecked(ctx.getProject(), cherryPickResult.changeId())
               .getCurrentPatchSet()
               .commitId()
               .getName();
@@ -612,7 +616,9 @@
     @Override
     public void postUpdate(Context ctx) throws Exception {
       changeReverted.fire(
-          change, changeNotesFactory.createChecked(revertChangeId).getChange(), ctx.getWhen());
+          change,
+          changeNotesFactory.createChecked(ctx.getProject(), revertChangeId).getChange(),
+          ctx.getWhen());
       try {
         RevertedSender emailSender = revertedSenderFactory.create(ctx.getProject(), change.getId());
         emailSender.setFrom(ctx.getAccountId());
diff --git a/java/com/google/gerrit/sshd/ChangeArgumentParser.java b/java/com/google/gerrit/sshd/ChangeArgumentParser.java
index 4a1489739..92019ad 100644
--- a/java/com/google/gerrit/sshd/ChangeArgumentParser.java
+++ b/java/com/google/gerrit/sshd/ChangeArgumentParser.java
@@ -116,7 +116,7 @@
   }
 
   private List<ChangeNotes> changeFromNotesFactory(String id) throws UnloggedFailure {
-    return changeNotesFactory.create(parseId(id));
+    return changeNotesFactory.createUsingIndexLookup(parseId(id));
   }
 
   private List<Change.Id> parseId(String id) throws UnloggedFailure {
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 496a2b4..ccfa60e 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -1499,19 +1499,19 @@
     assertThat(commit.author.email).isEqualTo(user.email());
     assertThat(commit.committer.email).isEqualTo(user.email());
 
-    // check that the author/committer was added as reviewer
-    Collection<AccountInfo> reviewers = change.reviewers.get(REVIEWER);
+    // check that the author/committer was added as cc
+    Collection<AccountInfo> reviewers = change.reviewers.get(CC);
     assertThat(reviewers).isNotNull();
     assertThat(reviewers).hasSize(1);
     assertThat(reviewers.iterator().next()._accountId).isEqualTo(user.id().get());
-    assertThat(change.reviewers.get(CC)).isNull();
+    assertThat(change.reviewers.get(REVIEWER)).isNull();
 
     List<Message> messages = sender.getMessages();
     assertThat(messages).hasSize(1);
     Message m = messages.get(0);
     assertThat(m.from().name()).isEqualTo("Administrator (Code Review)");
     assertThat(m.rcpt()).containsExactly(user.getNameEmail());
-    assertThat(m.body()).contains("I'd like you to do a code review");
+    assertThat(m.body()).contains("has uploaded this change for review");
     assertThat(m.body()).contains("Change subject: " + PushOneCommit.SUBJECT + "\n");
     assertMailReplyTo(m, admin.email());
   }
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
index 4350072..39366bd 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
@@ -931,6 +931,37 @@
   }
 
   @Test
+  public void intralineEditsAreIdentified() throws Exception {
+    // TODO(ghareeb): This test asserts the wrong behavior due to the following issue
+    // bugs.chromium.org/p/gerrit/issues/detail?id=13563
+    // Please remove this comment and assert the correct behavior when the bug is fixed.
+
+    assume().that(intraline).isTrue();
+
+    String orig = "[-9999,9999]";
+    String replace = "[-999,999]";
+
+    addModifiedPatchSet(changeId, FILE_NAME, fileContent -> fileContent.concat(orig));
+    String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
+    addModifiedPatchSet(changeId, FILE_NAME, fileContent -> fileContent.replace(orig, replace));
+
+    // TODO(ghareeb): remove this comment when the issue is fixed.
+    // The returned diff incorrectly contains:
+    // replace [-9999{,99}99] with [-999{,}999].
+    // If this replace edit is done, the resulting string incorrectly becomes [-9999,99].
+
+    DiffInfo diffInfo =
+        getDiffRequest(changeId, CURRENT, FILE_NAME).withBase(previousPatchSetId).get();
+
+    List<List<Integer>> editsA = diffInfo.content.get(1).editA;
+    List<List<Integer>> editsB = diffInfo.content.get(1).editB;
+    String reconstructed = transformStringUsingEditList(orig, replace, editsA, editsB);
+
+    // TODO(ghareeb): assert equals when the issue is fixed.
+    assertThat(reconstructed).isNotEqualTo(replace);
+  }
+
+  @Test
   public void intralineEditsForModifiedLastLineArePreservedWhenNewlineIsAlsoAddedAtEnd()
       throws Exception {
     assume().that(intraline).isTrue();
@@ -2896,4 +2927,29 @@
         .diffRequest()
         .withIntraline(intraline);
   }
+
+  /**
+   * This method transforms the {@code orig} input String using the list of replace edits {@code
+   * editsA}, {@code editsB} and the resulting {@code replace} String. This method currently assumes
+   * that all input edits are replace edits, and that the edits are sorted according to their
+   * indices.
+   *
+   * @return The transformed String after applying the list of replace edits to the original String.
+   */
+  private String transformStringUsingEditList(
+      String orig, String replace, List<List<Integer>> editsA, List<List<Integer>> editsB) {
+    assertThat(editsA).hasSize(editsB.size());
+    StringBuilder process = new StringBuilder(orig);
+    // The edits are processed right to left to avoid recomputation of indices when characters
+    // are removed.
+    for (int i = editsA.size() - 1; i >= 0; i--) {
+      List<Integer> leftEdit = editsA.get(i);
+      List<Integer> rightEdit = editsB.get(i);
+      process.replace(
+          leftEdit.get(0),
+          leftEdit.get(0) + leftEdit.get(1),
+          replace.substring(rightEdit.get(0), rightEdit.get(0) + rightEdit.get(1)));
+    }
+    return process.toString();
+  }
 }
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 2f9530c..839b051 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -872,7 +872,7 @@
     String changeId = project.get() + "~master~" + result.getChangeId();
 
     // 'user' cherry-picks the change to a new branch, the source change's author/committer('admin')
-    // will be added as a reviewer of the newly created change.
+    // will be added as cc of the newly created change.
     requestScopeOperations.setApiUser(user.id());
     CherryPickInput input = new CherryPickInput();
     input.message = "it goes to a new branch";
@@ -882,7 +882,7 @@
     input.notify = NotifyHandling.ALL;
     sender.clear();
     gApi.changes().id(changeId).current().cherryPick(input);
-    assertNotifyTo(admin);
+    assertNotifyCc(admin);
 
     // Disable the notification. 'admin' as a reviewer should not be notified any more.
     input.destination = "branch-2";
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index c526e31..763e7b1 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -87,6 +87,7 @@
 import com.google.gerrit.extensions.client.ReviewerState;
 import com.google.gerrit.extensions.client.Side;
 import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.ApprovalInfo;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
 import com.google.gerrit.extensions.common.CommentInfo;
@@ -1123,7 +1124,7 @@
 
     String changeId = GitUtil.getChangeId(testRepo, c).get();
     assertThat(getOwnerEmail(changeId)).isEqualTo(admin.email());
-    assertThat(getReviewerEmails(changeId, ReviewerState.REVIEWER))
+    assertThat(getReviewerEmails(changeId, ReviewerState.CC))
         .containsExactly(user.email(), user2.email());
 
     assertThat(sender.getMessages()).hasSize(1);
@@ -1148,7 +1149,7 @@
     pushHead(testRepo, "refs/for/master");
 
     assertThat(getOwnerEmail(r.getChangeId())).isEqualTo(admin.email());
-    assertThat(getReviewerEmails(r.getChangeId(), ReviewerState.REVIEWER))
+    assertThat(getReviewerEmails(r.getChangeId(), ReviewerState.CC))
         .containsExactly(user.email(), user2.email());
 
     assertThat(sender.getMessages()).hasSize(1);
@@ -1180,27 +1181,20 @@
     // Push this commit as "Administrator" (requires Forge Committer Identity)
     pushHead(testRepo, "refs/for/master%l=Code-Review+1", false);
 
-    // Expected Code-Review votes:
-    // 1. 0 from User (committer):
-    //    When the committer is forged, the committer is automatically added as
-    //    reviewer, hence we expect a dummy 0 vote for the committer.
-    // 2. +1 from Administrator (uploader):
-    //    On push Code-Review+1 was specified, hence we expect a +1 vote from
-    //    the uploader.
+    // Expected Code-Review vote:
+    // +1 from Administrator (uploader):
+    // On push Code-Review+1 was specified, hence we expect a +1 vote from the uploader. When the
+    // committer is forged, the committer is automatically added as cc, but that doesn't add votes
+    // (as opposted to being added as reviewer that adds a dummy +0 vote). We ensure there are no
+    // votes from the committer.
     ChangeInfo ci =
         get(GitUtil.getChangeId(testRepo, c).get(), DETAILED_LABELS, MESSAGES, DETAILED_ACCOUNTS);
     LabelInfo cr = ci.labels.get("Code-Review");
-    assertThat(cr.all).hasSize(2);
-    int indexAdmin = admin.fullName().equals(cr.all.get(0).name) ? 0 : 1;
-    int indexUser = indexAdmin == 0 ? 1 : 0;
-    assertThat(cr.all.get(indexAdmin).name).isEqualTo(admin.fullName());
-    assertThat(cr.all.get(indexAdmin).value.intValue()).isEqualTo(1);
-    assertThat(cr.all.get(indexUser).name).isEqualTo(user.fullName());
-    assertThat(cr.all.get(indexUser).value.intValue()).isEqualTo(0);
+    ApprovalInfo approvalInfo = Iterables.getOnlyElement(cr.all);
+    assertThat(approvalInfo.name).isEqualTo(admin.fullName());
+    assertThat(approvalInfo.value.intValue()).isEqualTo(1);
     assertThat(Iterables.getLast(ci.messages).message)
         .isEqualTo("Uploaded patch set 1: Code-Review+1.");
-    // Check that the user who pushed the change was added as a reviewer since they added a vote
-    assertThatUserIsOnlyReviewer(ci, admin);
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/acceptance/rest/auth/AuthenticationCheckIT.java b/javatests/com/google/gerrit/acceptance/rest/auth/AuthenticationCheckIT.java
index 9298b43..00b1c55 100644
--- a/javatests/com/google/gerrit/acceptance/rest/auth/AuthenticationCheckIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/auth/AuthenticationCheckIT.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.acceptance.rest.auth;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.acceptance.RestSession;
@@ -34,6 +32,5 @@
     RestSession anonymous = new RestSession(server, null);
     RestResponse r = anonymous.get("/auth-check");
     r.assertForbidden();
-    assertThat(r.getHeader("Content-Length")).isEqualTo("0");
   }
 }
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java b/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
index 9b12f29..b2a349e 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
@@ -1714,7 +1714,7 @@
         .sent("newpatchset", sc)
         .notTo(sc.owner) // TODO(logan): This shouldn't be sent *from* the owner.
         .to(sc.reviewer)
-        .to(other)
+        .cc(other)
         .cc(sc.ccer)
         .cc(StagedUsers.REVIEWER_BY_EMAIL, StagedUsers.CC_BY_EMAIL)
         .noOneElse();
@@ -1730,7 +1730,7 @@
         .sent("newpatchset", sc)
         .notTo(sc.owner) // TODO(logan): This shouldn't be sent *from* the owner.
         .to(sc.reviewer)
-        .to(other)
+        .cc(other)
         .cc(sc.ccer)
         .cc(StagedUsers.REVIEWER_BY_EMAIL, StagedUsers.CC_BY_EMAIL)
         .noOneElse();
diff --git a/package.json b/package.json
index 46f20c7..913b7a8 100644
--- a/package.json
+++ b/package.json
@@ -30,8 +30,8 @@
     "eslint": "npm run safe_bazelisk test polygerrit-ui/app:lint_test",
     "eslintfix": "npm run safe_bazelisk run polygerrit-ui/app:lint_bin -- -- --fix $(pwd)/polygerrit-ui/app",
     "polylint": "npm run safe_bazelisk test polygerrit-ui/app:polylint_test",
-    "test:debug": "npm run safe_bazelisk run //polygerrit-ui:karma_bin -- -- start $(pwd)/polygerrit-ui/karma.conf.js --browsers ChromeDev --no-single-run --testFiles",
-    "test:single": "npm run safe_bazelisk run //polygerrit-ui:karma_bin -- -- start $(pwd)/polygerrit-ui/karma.conf.js --testFiles"
+    "test:debug": "npm run compile:local && npm run safe_bazelisk run //polygerrit-ui:karma_bin -- -- start $(pwd)/polygerrit-ui/karma.conf.js --browsers ChromeDev --no-single-run --testFiles",
+    "test:single": "npm run compile:local && npm run safe_bazelisk run //polygerrit-ui:karma_bin -- -- start $(pwd)/polygerrit-ui/karma.conf.js --testFiles"
   },
   "repository": {
     "type": "git",
diff --git a/polygerrit-ui/app/constants/constants.ts b/polygerrit-ui/app/constants/constants.ts
index b64a7d1..6029dc1 100644
--- a/polygerrit-ui/app/constants/constants.ts
+++ b/polygerrit-ui/app/constants/constants.ts
@@ -327,17 +327,6 @@
 }
 
 /**
- * Whether whitespace changes should be ignored and if yes, which whitespace changes should be ignored
- * https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#diff-preferences-input
- */
-export enum IgnoreWhitespaceType {
-  IGNORE_NONE = 'IGNORE_NONE',
-  IGNORE_TRAILING = 'IGNORE_TRAILING',
-  IGNORE_LEADING_AND_TRAILING = 'IGNORE_LEADING_AND_TRAILING',
-  IGNORE_ALL = 'IGNORE_ALL',
-}
-
-/**
  * how draft comments are handled
  */
 export enum DraftsAction {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
index d70e891..64342f4 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
@@ -152,15 +152,19 @@
     if (!label || category === LabelCategory.NOT_APPLICABLE) {
       return 'Label not applicable';
     }
+    const titleParts: string[] = [];
     if (category === LabelCategory.UNRESOLVED_COMMENTS) {
       const num = change?.unresolved_comment_count ?? 0;
       const plural = num > 1 ? 's' : '';
-      return `${num} unresolved comment${plural}`;
+      titleParts.push(`${num} unresolved comment${plural}`);
     }
     const significantLabel =
       label.rejected || label.approved || label.disliked || label.recommended;
-    if (significantLabel && significantLabel.name) {
-      return `${labelName}\nby ${significantLabel.name}`;
+    if (significantLabel?.name) {
+      titleParts.push(`${labelName} by ${significantLabel.name}`);
+    }
+    if (titleParts.length > 0) {
+      return titleParts.join(',\n');
     }
     return labelName;
   }
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.js b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.js
index d3274f3..38cc772 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.js
@@ -88,35 +88,35 @@
         'Label not applicable');
     assert.equal(element._computeLabelTitle(
         {labels: {Verified: {approved: {name: 'Diffy'}}}}, 'Verified'),
-    'Verified\nby Diffy');
+    'Verified by Diffy');
     assert.equal(element._computeLabelTitle(
         {labels: {Verified: {approved: {name: 'Diffy'}}}}, 'Code-Review'),
     'Label not applicable');
     assert.equal(element._computeLabelTitle(
         {labels: {Verified: {rejected: {name: 'Diffy'}}}}, 'Verified'),
-    'Verified\nby Diffy');
+    'Verified by Diffy');
     assert.equal(element._computeLabelTitle(
         {labels: {'Code-Review': {disliked: {name: 'Diffy'}, value: -1}}},
-        'Code-Review'), 'Code-Review\nby Diffy');
+        'Code-Review'), 'Code-Review by Diffy');
     assert.equal(element._computeLabelTitle(
         {labels: {'Code-Review': {recommended: {name: 'Diffy'}, value: 1}}},
-        'Code-Review'), 'Code-Review\nby Diffy');
+        'Code-Review'), 'Code-Review by Diffy');
     assert.equal(element._computeLabelTitle(
         {labels: {'Code-Review': {recommended: {name: 'Diffy'},
           rejected: {name: 'Admin'}}}}, 'Code-Review'),
-    'Code-Review\nby Admin');
+    'Code-Review by Admin');
     assert.equal(element._computeLabelTitle(
         {labels: {'Code-Review': {approved: {name: 'Diffy'},
           rejected: {name: 'Admin'}}}}, 'Code-Review'),
-    'Code-Review\nby Admin');
+    'Code-Review by Admin');
     assert.equal(element._computeLabelTitle(
         {labels: {'Code-Review': {recommended: {name: 'Diffy'},
           disliked: {name: 'Admin'}, value: -1}}}, 'Code-Review'),
-    'Code-Review\nby Admin');
+    'Code-Review by Admin');
     assert.equal(element._computeLabelTitle(
         {labels: {'Code-Review': {approved: {name: 'Diffy'},
           disliked: {name: 'Admin'}, value: -1}}}, 'Code-Review'),
-    'Code-Review\nby Diffy');
+    'Code-Review by Diffy');
     assert.equal(element._computeLabelTitle(
         {
           labels: {'Code-Review': {approved: true, value: 1}},
@@ -125,6 +125,12 @@
     '1 unresolved comment');
     assert.equal(element._computeLabelTitle(
         {
+          labels: {'Code-Review': {approved: {name: 'Diffy'}, value: 1}},
+          unresolved_comment_count: 1,
+        }, 'Code-Review'),
+    '1 unresolved comment,\nCode-Review by Diffy');
+    assert.equal(element._computeLabelTitle(
+        {
           labels: {'Code-Review': {approved: true, value: 1}},
           unresolved_comment_count: 2,
         }, 'Code-Review'),
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index 1119a20..2c9b31c 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -98,7 +98,6 @@
   ConfigInfo,
   PreferencesInfo,
   CommitInfo,
-  DiffPreferencesInfo,
   RevisionInfo,
   EditInfo,
   LabelNameToInfoMap,
@@ -107,6 +106,7 @@
   ApprovalInfo,
   ElementPropertyDeepChange,
 } from '../../../types/common';
+import {DiffPreferencesInfo} from '../../../types/diff';
 import {GrReplyDialog, FocusTarget} from '../gr-reply-dialog/gr-reply-dialog';
 import {GrIncludedInDialog} from '../gr-included-in-dialog/gr-included-in-dialog';
 import {GrDownloadDialog} from '../gr-download-dialog/gr-download-dialog';
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
index b86dd90..c9a7058 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
@@ -46,10 +46,10 @@
   PatchSetNum,
   CommitInfo,
   ServerInfo,
-  DiffPreferencesInfo,
   RevisionInfo,
   NumericChangeId,
 } from '../../../types/common';
+import {DiffPreferencesInfo} from '../../../types/diff';
 import {ChangeComments} from '../../diff/gr-comment-api/gr-comment-api';
 import {GrDiffModeSelector} from '../../diff/gr-diff-mode-selector/gr-diff-mode-selector';
 import {DiffViewMode} from '../../../constants/constants';
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index e188254..108f9ed 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -57,7 +57,6 @@
 import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api';
 import {
   ConfigInfo,
-  DiffPreferencesInfo,
   ElementPropertyDeepChange,
   FileInfo,
   FileNameToFileInfoMap,
@@ -67,6 +66,7 @@
   RevisionInfo,
   UrlEncodedCommentId,
 } from '../../../types/common';
+import {DiffPreferencesInfo} from '../../../types/diff';
 import {GrDiffHost} from '../../diff/gr-diff-host/gr-diff-host';
 import {GrDiffPreferencesDialog} from '../../diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog';
 import {hasOwnProperty} from '../../../utils/common-util';
@@ -998,6 +998,7 @@
       return;
     }
     e.preventDefault();
+    this.classList.remove('hideComments');
     this.$.diffCursor.createCommentInPlace();
   }
 
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index 0e73516..66f3b81 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -29,14 +29,13 @@
 import {ParsedChangeInfo} from '../../shared/gr-rest-api-interface/gr-reviewer-updates-parser';
 import {
   NumericChangeId,
-  DiffInfo,
-  DiffPreferencesInfo,
   EditPatchSetNum,
   FixId,
   FixSuggestionInfo,
   PatchSetNum,
   RobotId,
 } from '../../../types/common';
+import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
 import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
 import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api';
 import {isRobot} from '../../../utils/comment-util';
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts
index 367bbd6..bbe99b9 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts
@@ -520,12 +520,16 @@
         comments: [comment],
         patchNum: comment.patch_set,
         path: comment.__path || comment.path!,
-        line: comment.line,
         rootId: comment.id,
+        line: comment.line,
+        range: comment.range,
       };
       if (comment.side) {
         newThread.commentSide = comment.side;
       }
+      if (!comment.line && !comment.range) {
+        newThread.line = 'FILE';
+      }
       threads.push(newThread);
       idThreadMap[comment.id] = newThread;
     }
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.js b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.js
index be6f646..d9b6b53 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.js
@@ -543,6 +543,12 @@
             path: 'file/one',
             line: 1,
             rootId: '01',
+            range: {
+              start_line: 1,
+              start_character: 2,
+              end_line: 2,
+              end_character: 2,
+            },
           }, {
             comments: [
               {
@@ -559,6 +565,7 @@
             patchNum: 2,
             path: 'file/one',
             line: 2,
+            range: undefined,
             rootId: '03',
           }, {
             comments: [
@@ -595,6 +602,7 @@
             path: 'file/one',
             line: 1,
             rootId: '04',
+            range: undefined,
           }, {
             comments: [
               {
@@ -610,6 +618,7 @@
             path: 'file/two',
             line: 2,
             rootId: '05',
+            range: undefined,
           }, {
             comments: [
               {
@@ -625,6 +634,7 @@
             path: 'file/two',
             line: 2,
             rootId: '06',
+            range: undefined,
           }, {
             comments: [
               {
@@ -654,6 +664,7 @@
             path: 'file/three',
             line: 1,
             rootId: '07',
+            range: undefined,
           }, {
             comments: [
               {
@@ -669,6 +680,7 @@
             path: 'file/three',
             line: 1,
             rootId: '09',
+            range: undefined,
           }, {
             comments: [
               {
@@ -686,6 +698,7 @@
             path: 'file/four',
             line: 1,
             rootId: '10',
+            range: undefined,
           }, {
             comments: [
               {
@@ -701,6 +714,7 @@
             patchNum: 5,
             path: 'file/four',
             line: 1,
+            range: undefined,
           }, {
             comments: [
               {
@@ -717,6 +731,7 @@
             patchNum: 3,
             path: 'file/two',
             line: 1,
+            range: undefined,
           }, {
             comments: [
               {
@@ -735,6 +750,7 @@
             patchNum: 2,
             path: 'file/one',
             line: 1,
+            range: undefined,
           },
         ];
         const threads = element._changeComments.getAllThreadsForChange();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.ts
index 7a26e77..763a524 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.ts
@@ -16,7 +16,7 @@
  */
 
 import {GrDiffBuilderUnified} from './gr-diff-builder-unified';
-import {DiffInfo, DiffPreferencesInfo} from '../../../types/common';
+import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
 import {GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line';
 
 export class GrDiffBuilderBinary extends GrDiffBuilderUnified {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
index 6d81e9b..9e64ee6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
@@ -31,12 +31,8 @@
 import {GrDiffBuilderBinary} from './gr-diff-builder-binary';
 import {CancelablePromise, util} from '../../../scripts/util';
 import {customElement, property, observe} from '@polymer/decorators';
-import {
-  BlameInfo,
-  DiffInfo,
-  DiffPreferencesInfo,
-  ImageInfo,
-} from '../../../types/common';
+import {BlameInfo, ImageInfo} from '../../../types/common';
+import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
 import {CoverageRange, DiffLayer} from '../../../types/types';
 import {
   GrDiffProcessor,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts
index 15264ea..5b3f225 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts
@@ -16,7 +16,8 @@
  */
 
 import {GrDiffBuilderSideBySide} from './gr-diff-builder-side-by-side';
-import {DiffInfo, DiffPreferencesInfo, ImageInfo} from '../../../types/common';
+import {ImageInfo} from '../../../types/common';
+import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
 import {GrEndpointParam} from '../../plugins/gr-endpoint-param/gr-endpoint-param';
 
 // MIME types for images we allow showing. Do not include SVG, it can contain
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
index 657dfa2..efac2b1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
@@ -17,7 +17,7 @@
 
 import {GrDiffBuilder} from './gr-diff-builder';
 import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {DiffInfo, DiffPreferencesInfo} from '../../../types/common';
+import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
 import {GrDiffLine, LineNumber} from '../gr-diff/gr-diff-line';
 import {DiffViewMode, Side} from '../../../constants/constants';
 
@@ -50,7 +50,7 @@
     if (group.dueToRebase) {
       sectionEl.classList.add('dueToRebase');
     }
-    if (group.dueToMove) {
+    if (group.moveDetails) {
       sectionEl.classList.add('dueToMove');
       sectionEl.appendChild(this._buildMoveControls(group));
     }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
index 2028b0c..c4ea267 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
@@ -17,7 +17,7 @@
 import {GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line';
 import {GrDiffBuilder} from './gr-diff-builder';
 import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {DiffInfo, DiffPreferencesInfo} from '../../../types/common';
+import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
 import {DiffViewMode, Side} from '../../../constants/constants';
 
 export class GrDiffBuilderUnified extends GrDiffBuilder {
@@ -49,7 +49,7 @@
     if (group.dueToRebase) {
       sectionEl.classList.add('dueToRebase');
     }
-    if (group.dueToMove) {
+    if (group.moveDetails) {
       sectionEl.classList.add('dueToMove');
       sectionEl.appendChild(this._buildMoveControls(group));
     }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.js
index 07c6410..24d3635 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.js
@@ -105,7 +105,7 @@
       lines[0].text = 'def hello_world():';
       lines[1].text = '  print "Hello World"';
       const group = new GrDiffGroup(GrDiffGroupType.DELTA, lines);
-      group.dueToMove = true;
+      group.moveDetails = {changed: false};
 
       const sectionEl = diffBuilder.buildSectionElement(group);
 
@@ -128,7 +128,7 @@
       lines[0].text = 'def hello_world():';
       lines[1].text = '  print "Hello World"';
       const group = new GrDiffGroup(GrDiffGroupType.DELTA, lines);
-      group.dueToMove = true;
+      group.moveDetails = {changed: false};
 
       const sectionEl = diffBuilder.buildSectionElement(group);
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
index 29af31c..f4aa660 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
@@ -23,7 +23,8 @@
   hideInContextControl,
   rangeBySide,
 } from '../gr-diff/gr-diff-group';
-import {BlameInfo, DiffInfo, DiffPreferencesInfo} from '../../../types/common';
+import {BlameInfo} from '../../../types/common';
+import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
 import {DiffViewMode, Side} from '../../../constants/constants';
 import {DiffLayer} from '../../../types/types';
 
@@ -639,14 +640,13 @@
     side: Side
   ) {
     const td = this._createElement('td');
+    td.classList.add(side);
     if (line.type === GrDiffLineType.BLANK) {
       return td;
     }
     if (line.type === GrDiffLineType.BOTH || line.type === type) {
-      // Both td and button need a number of classes/attributes for various
-      // selectors to work.
-      this._decorateLineEl(td, number, side);
       td.classList.add('lineNum');
+      td.dataset['value'] = number.toString();
 
       if (this._prefs.show_file_comment_button === false && number === 'FILE') {
         return td;
@@ -655,10 +655,9 @@
       const button = this._createElement('button');
       td.appendChild(button);
       button.tabIndex = -1;
-      this._decorateLineEl(button, number, side);
-
       button.classList.add('lineNumButton');
-
+      button.classList.add(side);
+      button.dataset['value'] = number.toString();
       button.textContent = number === 'FILE' ? 'File' : number.toString();
 
       // Add aria-labels for valid line numbers.
@@ -677,11 +676,6 @@
     return td;
   }
 
-  _decorateLineEl(el: HTMLElement, number: LineNumber, side: Side) {
-    el.classList.add(side);
-    el.dataset['value'] = number.toString();
-  }
-
   _createTextEl(
     lineNumberEl: HTMLElement | null,
     line: GrDiffLine,
@@ -898,6 +892,17 @@
     }
   }
 
+  _createMoveDescription(movedIn: boolean, group: GrDiffGroup) {
+    if (group.moveDetails?.range) {
+      const {changed, range} = group.moveDetails;
+      const moveLabel = 'Moved' + (changed ? ' and changed' : '');
+      const direction = movedIn ? 'from' : 'to';
+      const lineDetails = `lines ${range.start} - ${range.end}`;
+      return `${moveLabel} ${direction} ${lineDetails}`;
+    }
+    return movedIn ? 'Moved in' : 'Moved out';
+  }
+
   _buildMoveControls(group: GrDiffGroup) {
     const movedIn = group.adds.length > 0;
     const {
@@ -907,16 +912,14 @@
     } = this._getMoveControlsConfig();
 
     let controlsClass;
-    let descriptionText;
     let descriptionIndex;
+    const descriptionText = this._createMoveDescription(movedIn, group);
     if (movedIn) {
       controlsClass = 'movedIn';
       descriptionIndex = movedInIndex;
-      descriptionText = 'Moved in';
     } else {
       controlsClass = 'movedOut';
       descriptionIndex = movedOutIndex;
-      descriptionText = 'Moved out';
     }
     const controls = document.createElement('tr');
     const cells = [...Array(numberOfCells).keys()].map(() =>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts
index e42eb84..81c6303 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts
@@ -64,14 +64,6 @@
     return htmlTemplate;
   }
 
-  private _boundHandleWindowScroll: () => void;
-
-  private _boundHandleDiffRenderStart: () => void;
-
-  private _boundHandleDiffRenderContent: () => void;
-
-  private _boundHandleDiffLineSelected: (e: Event) => void;
-
   private _preventAutoScrollOnManualScroll = false;
 
   private lastDisplayedNavigateToNextFileToast: number | null = null;
@@ -110,15 +102,6 @@
   @property({type: Boolean})
   _listeningForScroll = false;
 
-  constructor() {
-    super();
-    this._boundHandleWindowScroll = () => this._handleWindowScroll();
-    this._boundHandleDiffRenderStart = () => this._handleDiffRenderStart();
-    this._boundHandleDiffRenderContent = () => this._handleDiffRenderContent();
-    this._boundHandleDiffLineSelected = (e: Event) =>
-      this._handleDiffLineSelected(e);
-  }
-
   /** @override */
   ready() {
     super.ready();
@@ -171,21 +154,21 @@
 
   moveDown() {
     if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE) {
-      this.$.cursorManager.next({
+      return this.$.cursorManager.next({
         filter: (row: Element) => this._rowHasSide(row),
       });
     } else {
-      this.$.cursorManager.next();
+      return this.$.cursorManager.next();
     }
   }
 
   moveUp() {
     if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE) {
-      this.$.cursorManager.previous({
+      return this.$.cursorManager.previous({
         filter: (row: Element) => this._rowHasSide(row),
       });
     } else {
-      this.$.cursorManager.previous();
+      return this.$.cursorManager.previous();
     }
   }
 
@@ -199,7 +182,10 @@
     }
   }
 
-  moveToNextChunk(clipToTop?: boolean, navigateToNextFile?: boolean) {
+  moveToNextChunk(
+    clipToTop?: boolean,
+    navigateToNextFile?: boolean
+  ): CursorMoveResult {
     const result = this.$.cursorManager.next({
       filter: (row: HTMLElement) => this._isFirstRowOfChunk(row),
       getTargetHeight: target =>
@@ -243,27 +229,31 @@
     }
 
     this._fixSide();
+    return result;
   }
 
-  moveToPreviousChunk() {
-    this.$.cursorManager.previous({
+  moveToPreviousChunk(): CursorMoveResult {
+    const result = this.$.cursorManager.previous({
       filter: (row: HTMLElement) => this._isFirstRowOfChunk(row),
     });
     this._fixSide();
+    return result;
   }
 
-  moveToNextCommentThread() {
-    this.$.cursorManager.next({
+  moveToNextCommentThread(): CursorMoveResult {
+    const result = this.$.cursorManager.next({
       filter: (row: HTMLElement) => this._rowHasThread(row),
     });
     this._fixSide();
+    return result;
   }
 
-  moveToPreviousCommentThread() {
-    this.$.cursorManager.previous({
+  moveToPreviousCommentThread(): CursorMoveResult {
+    const result = this.$.cursorManager.previous({
       filter: (row: HTMLElement) => this._rowHasThread(row),
     });
     this._fixSide();
+    return result;
   }
 
   moveToLineNumber(number: number, side: Side, path?: string) {
@@ -340,13 +330,13 @@
     this._scrollMode = ScrollMode.KEEP_VISIBLE;
   }
 
-  _handleWindowScroll() {
+  private _boundHandleWindowScroll = () => {
     if (this._preventAutoScrollOnManualScroll) {
       this._scrollMode = ScrollMode.NEVER;
       this._focusOnMove = false;
       this._preventAutoScrollOnManualScroll = false;
     }
-  }
+  };
 
   reInitAndUpdateStops() {
     this.reInit();
@@ -358,25 +348,29 @@
     this.reInitCursor();
   }
 
-  _handleDiffRenderStart() {
-    this._preventAutoScrollOnManualScroll = true;
-  }
+  private boundHandleDiffLoadingChanged = () => {
+    this._updateStops();
+  };
 
-  _handleDiffRenderContent() {
+  private _boundHandleDiffRenderStart = () => {
+    this._preventAutoScrollOnManualScroll = true;
+  };
+
+  private _boundHandleDiffRenderContent = () => {
     this._updateStops();
     // When done rendering, turn focus on move and automatic scrolling back on
     this._focusOnMove = true;
     this._preventAutoScrollOnManualScroll = false;
-  }
+  };
 
-  _handleDiffLineSelected(event: Event) {
+  private _boundHandleDiffLineSelected = (event: Event) => {
     const customEvent = event as CustomEvent;
     this.moveToLineNumber(
       customEvent.detail.number,
       customEvent.detail.side,
       customEvent.detail.path
     );
-  }
+  };
 
   createCommentInPlace() {
     const diffWithRangeSelected = this.diffs.find(diff =>
@@ -399,7 +393,6 @@
    * {leftSide: true, number: 321} for line 321 of the base patch.
    * Returns null if an address is not available.
    *
-   * @return
    */
   getAddress() {
     if (!this.diffRow) {
@@ -556,6 +549,10 @@
       // might be the same.
       for (i = 0; i < splice?.removed.length; i++) {
         splice.removed[i].removeEventListener(
+          'loading-changed',
+          this.boundHandleDiffLoadingChanged
+        );
+        splice.removed[i].removeEventListener(
           'render-start',
           this._boundHandleDiffRenderStart
         );
@@ -571,6 +568,10 @@
 
       for (i = splice.index; i < splice.index + splice.addedCount; i++) {
         this.diffs[i].addEventListener(
+          'loading-changed',
+          this.boundHandleDiffLoadingChanged
+        );
+        this.diffs[i].addEventListener(
           'render-start',
           this._boundHandleDiffRenderStart
         );
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
index 5619acc..718e11b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
@@ -101,14 +101,14 @@
   test('cursor scroll behavior', () => {
     assert.equal(cursorElement._scrollMode, 'keep-visible');
 
-    cursorElement._handleDiffRenderStart();
+    diffElement.dispatchEvent(new Event('render-start'));
     assert.isTrue(cursorElement._focusOnMove);
 
-    cursorElement._handleWindowScroll();
+    window.dispatchEvent(new Event('scroll'));
     assert.equal(cursorElement._scrollMode, 'never');
     assert.isFalse(cursorElement._focusOnMove);
 
-    cursorElement._handleDiffRenderContent();
+    diffElement.dispatchEvent(new Event('render-content'));
     assert.isTrue(cursorElement._focusOnMove);
 
     cursorElement.reInitCursor();
@@ -118,7 +118,7 @@
   test('moves to selected line', () => {
     const moveToNumStub = sinon.stub(cursorElement, 'moveToLineNumber');
 
-    cursorElement._handleDiffLineSelected(
+    diffElement.dispatchEvent(
         new CustomEvent('line-selected', {
           detail: {number: '123', side: 'right', path: 'some/file'},
         }));
@@ -226,6 +226,7 @@
     assert.equal(cursorElement.side, 'left');
   });
 
+  // To be removed as soon due_to_move (deprecated) is removed
   suite('moved chunks (dueToMove=true)', () => {
     setup(done => {
       const renderHandler = function() {
@@ -269,27 +270,62 @@
       ]};
     });
 
-    test('chunk skip functionality', () => {
-      const chunks = diffElement.root.querySelectorAll(
-          '.section.delta');
-      const indexOfChunk = function(chunk) {
-        return Array.prototype.indexOf.call(chunks, chunk);
+    test('renders moveControls with simple descriptions', () => {
+      const [movedIn, movedOut] = diffElement.root
+          .querySelectorAll('.dueToMove .moveControls');
+      assert.equal(movedIn.textContent, 'Moved in');
+      assert.equal(movedOut.textContent, 'Moved out');
+    });
+  });
+
+  suite('moved chunks (moveDetails)', () => {
+    setup(done => {
+      const renderHandler = function() {
+        diffElement.removeEventListener('render', renderHandler);
+        cursorElement.reInitCursor();
+        done();
       };
+      diffElement.addEventListener('render', renderHandler);
+      diffElement.diff = {...diff, content: [
+        {
+          ab: [
+            'Lorem ipsum dolor sit amet, suspendisse inceptos vehicula, ',
+          ],
+        },
+        {
+          b: [
+            'Nullam neque, ligula ac, id blandit.',
+            'Sagittis tincidunt torquent, tempor nunc amet.',
+            'At rhoncus id.',
+          ],
+          move_details: {changed: false, range: {start: 4, end: 6}},
+        },
+        {
+          ab: [
+            'Sem nascetur, erat ut, non in.',
+          ],
+        },
+        {
+          a: [
+            'Nullam neque, ligula ac, id blandit.',
+            'Sagittis tincidunt torquent, tempor nunc amet.',
+            'At rhoncus id.',
+          ],
+          move_details: {changed: false, range: {start: 2, end: 4}},
+        },
+        {
+          ab: [
+            'Arcu eget, rhoncus amet cursus, ipsum elementum.',
+          ],
+        },
+      ]};
+    });
 
-      // We should be initialized to the first chunk (b)
-      let currentIndex = indexOfChunk(cursorElement.diffRow.parentElement);
-      assert.equal(currentIndex, 0);
-      assert.equal(cursorElement.side, 'right');
-
-      // Move to the next chunk.
-      cursorElement.moveToNextChunk();
-
-      // Since the next chunk only has content on the left side (a). we should have been
-      // automatically moved over.
-      const previousIndex = currentIndex;
-      currentIndex = indexOfChunk(cursorElement.diffRow.parentElement);
-      assert.equal(currentIndex, previousIndex + 1);
-      assert.equal(cursorElement.side, 'left');
+    test('renders moveControls with simple descriptions', () => {
+      const [movedIn, movedOut] = diffElement.root
+          .querySelectorAll('.dueToMove .moveControls');
+      assert.equal(movedIn.textContent, 'Moved from lines 4 - 6');
+      assert.equal(movedOut.textContent, 'Moved to lines 2 - 4');
     });
   });
 
@@ -474,6 +510,12 @@
     });
   });
 
+  test('updates stops when loading changes', () => {
+    sinon.spy(cursorElement, '_updateStops');
+    diffElement.dispatchEvent(new Event('loading-changed'));
+    assert.isTrue(cursorElement._updateStops.called);
+  });
+
   suite('gr-diff-cursor event tests', () => {
     let someEmptyDiv;
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
index da7d900..cb75956 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
@@ -44,34 +44,34 @@
   CoverageRange,
   DiffLayer,
   DiffLayerListener,
+  PatchSetFile,
 } from '../../../types/types';
 import {
   Base64ImageFile,
   BlameInfo,
   ChangeInfo,
   CommentRange,
-  DiffInfo,
-  DiffPreferencesInfo,
   NumericChangeId,
   PatchRange,
   PatchSetNum,
   RepoName,
 } from '../../../types/common';
+import {
+  DiffInfo,
+  DiffPreferencesInfo,
+  IgnoreWhitespaceType,
+} from '../../../types/diff';
 import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api';
 import {JsApiService} from '../../shared/gr-js-api-interface/gr-js-api-types';
 import {GrDiff, LineOfInterest} from '../gr-diff/gr-diff';
 import {GrSyntaxLayer} from '../gr-syntax-layer/gr-syntax-layer';
-import {
-  DiffViewMode,
-  IgnoreWhitespaceType,
-  Side,
-} from '../../../constants/constants';
+import {DiffViewMode, Side} from '../../../constants/constants';
 import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
 import {FilesWebLinks} from '../gr-patch-range-select/gr-patch-range-select';
-import {LineNumber} from '../gr-diff/gr-diff-line';
+import {LineNumber, FILE} from '../gr-diff/gr-diff-line';
 import {GrCommentThread} from '../../shared/gr-comment-thread/gr-comment-thread';
-import {PatchSetFile} from '../../../types/types';
 import {KnownExperimentId} from '../../../services/flags/flags';
+import {EventType, fire} from '../../../utils/event-util';
 
 const MSG_EMPTY_BLAME = 'No blame information for this diff.';
 
@@ -329,7 +329,6 @@
   /**
    * @param shouldReportMetric indicate a new Diff Page. This is a
    * signal to report metrics event that started on location change.
-   * @return
    */
   async reload(shouldReportMetric?: boolean) {
     this.clear();
@@ -527,13 +526,7 @@
       .getBlame(this.changeNum, this.patchRange.patchNum, this.path, true)
       .then(blame => {
         if (!blame || !blame.length) {
-          this.dispatchEvent(
-            new CustomEvent('show-alert', {
-              detail: {message: MSG_EMPTY_BLAME},
-              composed: true,
-              bubbles: true,
-            })
-          );
+          fire(this, EventType.SHOW_ALERT, MSG_EMPTY_BLAME);
           return Promise.reject(MSG_EMPTY_BLAME);
         }
 
@@ -914,9 +907,7 @@
     function matchesFileComment(threadEl: GrCommentThread) {
       return (
         threadEl.getAttribute('comment-side') === side &&
-        // line/range comments have 1-based line set, if line is falsy it's
-        // a file comment
-        !threadEl.getAttribute('line-num')
+        threadEl.getAttribute('line-num') === FILE
       );
     }
 
@@ -938,7 +929,7 @@
 
   _getIgnoreWhitespace(): IgnoreWhitespaceType {
     if (!this.prefs || !this.prefs.ignore_whitespace) {
-      return IgnoreWhitespaceType.IGNORE_NONE;
+      return 'IGNORE_NONE';
     }
     return this.prefs.ignore_whitespace;
   }
@@ -1070,18 +1061,28 @@
     >,
     diff?: DiffInfo
   ) {
-    if (
-      !preferenceChangeRecord ||
-      !preferenceChangeRecord.base ||
-      !preferenceChangeRecord.base.syntax_highlighting ||
-      !diff
-    ) {
+    if (!preferenceChangeRecord?.base?.syntax_highlighting || !diff) {
       return false;
     }
-    return (
-      !this._anyLineTooLong(diff) &&
-      this.$.diff.getDiffLength(diff) <= SYNTAX_MAX_DIFF_LENGTH
-    );
+    if (this._anyLineTooLong(diff)) {
+      fire(
+        this,
+        EventType.SHOW_ALERT,
+        `A line is longer than ${SYNTAX_MAX_LINE_LENGTH}.` +
+          ' Syntax Highlighting was turned off.'
+      );
+      return false;
+    }
+    if (this.$.diff.getDiffLength(diff) > SYNTAX_MAX_DIFF_LENGTH) {
+      fire(
+        this,
+        EventType.SHOW_ALERT,
+        `A diff is longer than ${SYNTAX_MAX_DIFF_LENGTH}.` +
+          ' Syntax Highlighting was turned off.'
+      );
+      return false;
+    }
+    return true;
   }
 
   /**
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.ts b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.ts
index c66af58..8829afc 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.ts
@@ -26,7 +26,7 @@
 import {GrDiffPreferences} from '../../shared/gr-diff-preferences/gr-diff-preferences';
 import {GrButton} from '../../shared/gr-button/gr-button';
 import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
-import {DiffPreferencesInfo} from '../../../types/common';
+import {DiffPreferencesInfo} from '../../../types/diff';
 
 export interface GrDiffPreferencesDialog {
   $: {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
index ab7ab8a..77b5499 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
@@ -30,7 +30,7 @@
 } from '../gr-diff/gr-diff-group';
 import {CancelablePromise, util} from '../../../scripts/util';
 import {customElement, property} from '@polymer/decorators';
-import {DiffContent} from '../../../types/common';
+import {DiffContent} from '../../../types/diff';
 import {Side} from '../../../constants/constants';
 
 const WHOLE_FILE = -1;
@@ -366,7 +366,8 @@
     const group = new GrDiffGroup(type, lines);
     group.keyLocation = !!chunk.keyLocation;
     group.dueToRebase = !!chunk.due_to_rebase;
-    group.dueToMove = !!chunk.due_to_move;
+    group.moveDetails =
+      chunk.move_details || (chunk.due_to_move ? {changed: false} : undefined);
     group.skip = chunk.skip;
     group.ignoredWhitespaceOnly = !!chunk.common;
     if (chunk.skip) {
@@ -700,6 +701,9 @@
       if (chunk.due_to_move) {
         subChunk.due_to_move = true;
       }
+      if (chunk.move_details) {
+        subChunk.move_details = chunk.move_details;
+      }
       return subChunk;
     });
   }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts
index b75ba8f..03740ba 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts
@@ -27,7 +27,7 @@
 } from '../gr-diff-highlight/gr-range-normalizer';
 import {descendedFromClass, querySelectorAll} from '../../../utils/dom-util';
 import {customElement, property, observe} from '@polymer/decorators';
-import {DiffInfo} from '../../../types/common';
+import {DiffInfo} from '../../../types/diff';
 import {Side} from '../../../constants/constants';
 import {GrDiffBuilderElement} from '../gr-diff-builder/gr-diff-builder-element';
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index d0be880..7c365d2 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -75,8 +75,6 @@
   ChangeInfo,
   CommitId,
   ConfigInfo,
-  DiffInfo,
-  DiffPreferencesInfo,
   EditInfo,
   EditPatchSetNum,
   ElementPropertyDeepChange,
@@ -89,6 +87,7 @@
   RepoName,
   RevisionInfo,
 } from '../../../types/common';
+import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
 import {ChangeViewState, CommitRange, FileRange} from '../../../types/types';
 import {FilesWebLinks} from '../gr-patch-range-select/gr-patch-range-select';
 import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
@@ -296,8 +295,8 @@
       [Shortcut.NEXT_COMMENT_THREAD]: '_handleNextChunkOrCommentThread',
       [Shortcut.PREV_CHUNK]: '_handlePrevChunkOrCommentThread',
       [Shortcut.PREV_COMMENT_THREAD]: '_handlePrevChunkOrCommentThread',
-      [Shortcut.OPEN_REPLY_DIALOG]: '_handleOpenReplyDialogOrToggleLeftPane',
-      [Shortcut.TOGGLE_LEFT_PANE]: '_handleOpenReplyDialogOrToggleLeftPane',
+      [Shortcut.OPEN_REPLY_DIALOG]: '_handleOpenReplyDialog',
+      [Shortcut.TOGGLE_LEFT_PANE]: '_handleToggleLeftPane',
       [Shortcut.OPEN_DOWNLOAD_DIALOG]: '_handleOpenDownloadDialog',
       [Shortcut.UP_TO_CHANGE]: '_handleUpToChange',
       [Shortcut.OPEN_DIFF_PREFS]: '_handleCommaKey',
@@ -606,6 +605,7 @@
     if (this.modifierPressed(e)) return;
 
     e.preventDefault();
+    this.classList.remove('hideComments');
     this.$.cursor.createCommentInPlace();
   }
 
@@ -659,16 +659,8 @@
     }
   }
 
-  _handleOpenReplyDialogOrToggleLeftPane(e: CustomKeyboardEvent) {
+  _handleOpenReplyDialog(e: CustomKeyboardEvent) {
     if (this.shouldSuppressKeyboardShortcut(e)) return;
-
-    if (e.detail.keyboardEvent?.shiftKey) {
-      // Hide left diff.
-      e.preventDefault();
-      this.$.diffHost.toggleLeftDiff();
-      return;
-    }
-
     if (this.modifierPressed(e)) return;
     if (!this._loggedIn) return;
 
@@ -677,6 +669,14 @@
     this._navToChangeView();
   }
 
+  _handleToggleLeftPane(e: CustomKeyboardEvent) {
+    if (this.shouldSuppressKeyboardShortcut(e)) return;
+    if (!e.detail.keyboardEvent?.shiftKey) return;
+
+    e.preventDefault();
+    this.$.diffHost.toggleLeftDiff();
+  }
+
   _handleOpenDownloadDialog(e: CustomKeyboardEvent) {
     if (this.shouldSuppressKeyboardShortcut(e)) return;
     if (this.modifierPressed(e)) return;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.ts
index 588b9d1..eb11588 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.ts
@@ -237,8 +237,6 @@
 
   dueToRebase = false;
 
-  dueToMove = false;
-
   /**
    * True means all changes in this line are whitespace changes that should
    * not be highlighted as changed as per the user settings.
@@ -269,6 +267,14 @@
     right: {start: null, end: null},
   };
 
+  moveDetails?: {
+    changed: boolean;
+    range?: {
+      start: number;
+      end: number;
+    };
+  };
+
   /**
    * Creates a new group with the same properties but different lines.
    *
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
index 14b8666..b4d520b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
@@ -34,14 +34,16 @@
 import {
   BlameInfo,
   CommentRange,
-  DiffInfo,
-  DiffPreferencesInfo,
-  DiffPreferencesInfoKey,
   EditPatchSetNum,
   ImageInfo,
   ParentPatchSetNum,
   PatchRange,
 } from '../../../types/common';
+import {
+  DiffInfo,
+  DiffPreferencesInfo,
+  DiffPreferencesInfoKey,
+} from '../../../types/diff';
 import {GrDiffHighlight} from '../gr-diff-highlight/gr-diff-highlight';
 import {GrDiffBuilderElement} from '../gr-diff-builder/gr-diff-builder-element';
 import {
@@ -192,9 +194,18 @@
   @property({type: Object})
   lineOfInterest?: LineOfInterest;
 
-  /** True when diff is changed, until the content is done rendering. */
-  @property({type: Boolean})
-  _loading = true;
+  /**
+   * True when diff is changed, until the content is done rendering.
+   *
+   * This is readOnly, meaning one can listen for the loading-changed event, but
+   * not write to it from the outside. Code in this class should use the
+   * "private" _setLoading method.
+   */
+  @property({type: Boolean, notify: true, readOnly: true})
+  loading!: boolean;
+
+  // Polymer generated when setting readOnly above.
+  _setLoading!: (loading: boolean) => void;
 
   @property({type: Boolean})
   loggedIn = false;
@@ -276,6 +287,7 @@
   /** @override */
   created() {
     super.created();
+    this._setLoading(true);
     this.addEventListener('create-range-comment', (e: Event) =>
       this._handleCreateRangeComment(e as CustomEvent)
     );
@@ -406,7 +418,6 @@
    * The key locations based on the comments and line of interests,
    * where lines should not be collapsed.
    *
-   * @return
    */
   _computeKeyLocations() {
     const keyLocations: KeyLocations = {left: {}, right: {}};
@@ -420,7 +431,7 @@
 
     for (const threadEl of threadEls) {
       const side = getSide(threadEl);
-      const lineNum = Number(threadEl.getAttribute('line-num')) || FILE;
+      const lineNum = threadEl.getAttribute('line-num') || FILE;
       const commentRange = threadEl.range || {};
       keyLocations[side][lineNum] = true;
       // Add start_line as well if exists,
@@ -463,7 +474,7 @@
   getCursorStops(): Array<HTMLElement | AbortStop> {
     if (this.hidden && this.noAutoRender) return [];
 
-    if (this._loading) {
+    if (this.loading) {
       return [new AbortStop()];
     }
 
@@ -569,12 +580,7 @@
       return;
     }
 
-    // TODO(TS): existing logic always pass undefined lineNum
-    // for file level comment, the drafts API will reject the
-    // request if file level draft contains the `line: 'FILE'` field
-    // probably should do this inside of the _createComment, this
-    // is just to keep existing behavior.
-    this._createComment(el, lineNum === FILE ? undefined : lineNum);
+    this._createComment(el, lineNum);
   }
 
   createRangeComment() {
@@ -833,7 +839,7 @@
   }
 
   _diffChanged(newValue?: DiffInfo) {
-    this._loading = true;
+    this._setLoading(true);
     this._cleanup();
     if (newValue) {
       this._diffLength = this.getDiffLength(newValue);
@@ -893,7 +899,7 @@
   }
 
   _handleRenderContent() {
-    this._loading = false;
+    this._setLoading(false);
     this._unobserveIncrementalNodes();
     this._incrementalNodeObserver = (dom(
       this
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
index 48a4596..b587d8a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
@@ -27,7 +27,10 @@
     :host {
       font-family: var(--monospace-font-family, ''), 'Roboto Mono';
       font-size: var(--font-size, var(--font-size-code, 12px));
-      line-height: var(--line-height-code, 1.334);
+      /* usually 16px = 12px + 4px */
+      line-height: calc(
+        var(--font-size, var(--font-size-code, 12px)) + var(--spacing-s, 4px)
+      );
     }
 
     .thread-group {
@@ -142,7 +145,7 @@
       width: 100%;
     }
     .full-width .contentText {
-      white-space: pre-wrap;
+      white-space: break-spaces;
       word-wrap: break-word;
     }
     .lineNumButton,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
index 5e95d83..3c53697 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
@@ -605,7 +605,7 @@
         };
 
         element._renderDiffTable();
-        element._loading = false;
+        element._setLoading(false);
         flush();
       }
 
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts
index 334c8f4..f5e5e04 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts
@@ -23,8 +23,8 @@
 import {FILE, GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line';
 import {CancelablePromise, util} from '../../../scripts/util';
 import {customElement, property} from '@polymer/decorators';
+import {DiffFileMetaInfo, DiffInfo} from '../../../types/diff';
 import {DiffLayer, DiffLayerListener, HighlightJS} from '../../../types/types';
-import {DiffFileMetaInfo, DiffInfo} from '../../../types/common';
 import {GrLibLoader} from '../../shared/gr-lib-loader/gr-lib-loader';
 import {Side} from '../../../constants/constants';
 
diff --git a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_html.ts b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_html.ts
index ba8af3c..77b4bc6 100644
--- a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_html.ts
+++ b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_html.ts
@@ -23,7 +23,8 @@
       box-sizing: border-box;
       font-family: var(--monospace-font-family);
       font-size: var(--font-size-code);
-      line-height: var(--line-height-code);
+      /* usually 16px = 12px + 4px */
+      line-height: calc(var(--font-size-code) + var(--spacing-s));
       min-height: 60vh;
       resize: none;
       white-space: pre;
diff --git a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.ts b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.ts
index 419c8db..5c57208 100644
--- a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.ts
@@ -22,13 +22,6 @@
  * 2. we have css variables which are more recommended way to custom styling
  */
 
-/**
- * // import { useShadow } from '@polymer/polymer/lib/utils/settings';
- * TODO(TS): polymer/lib/utils/settings.d.ts is not exporting useShadow
- * while the js is, to avoid the error, re-define it here
- */
-const useShadow = !window.ShadyDOM || !window.ShadyDOM.inUse;
-
 let styleObjectCount = 0;
 
 interface PgElement extends Element {
@@ -50,10 +43,9 @@
    * if it hasn't been added yet. A root node is an document or is the
    * associated shadowRoot. This class can be added to any element with the same
    * root node.
-   *
    */
   getClassName(element: Element) {
-    let rootNodeEl = useShadow ? element.getRootNode() : document.body;
+    let rootNodeEl = element.getRootNode();
     if (rootNodeEl === document) {
       rootNodeEl = document.head;
     }
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
index 878c01c..4bad587 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
@@ -50,6 +50,7 @@
 import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
 import {GrStorage, StorageLocation} from '../gr-storage/gr-storage';
 import {CustomKeyboardEvent} from '../../../types/events';
+import {LineNumber, FILE} from '../../diff/gr-diff/gr-diff-line';
 
 const UNRESOLVED_EXPAND_COUNT = 5;
 const NEWLINE_PATTERN = /\n/g;
@@ -88,7 +89,7 @@
    * diff widget like gr-diff to show the thread in the right location:
    *
    * line-num:
-   *     1-based line number or undefined if it refers to the entire file.
+   *     1-based line number or 'FILE' if it refers to the entire file.
    *
    * comment-side:
    *     "left" or "right". These indicate which of the two diffed versions
@@ -146,8 +147,8 @@
   @property({type: Boolean})
   showFilePath = false;
 
-  @property({type: Number, reflectToAttribute: true})
-  lineNum?: number;
+  @property({type: Object, reflectToAttribute: true})
+  lineNum?: LineNumber;
 
   @property({type: Boolean, notify: true, reflectToAttribute: true})
   unresolved?: boolean;
@@ -200,7 +201,7 @@
     this._setInitialExpandedState();
   }
 
-  addOrEditDraft(lineNum?: number, rangeParam?: CommentRange) {
+  addOrEditDraft(lineNum?: LineNumber, rangeParam?: CommentRange) {
     const lastComment = this.comments[this.comments.length - 1] || {};
     if (isDraft(lastComment)) {
       const commentEl = this._commentElWithDraftID(
@@ -223,7 +224,7 @@
     }
   }
 
-  addDraft(lineNum?: number, range?: CommentRange, unresolved?: boolean) {
+  addDraft(lineNum?: LineNumber, range?: CommentRange, unresolved?: boolean) {
     const draft = this._newDraft(lineNum, range);
     draft.__editing = true;
     draft.unresolved = unresolved === false ? unresolved : true;
@@ -272,7 +273,7 @@
         path,
         patchNum,
         undefined,
-        this.lineNum
+        this.lineNum === FILE ? undefined : this.lineNum
       );
     }
     const id = this.comments[0].id;
@@ -293,14 +294,14 @@
   }
 
   _computeDisplayLine() {
-    if (this.lineNum) return `#${this.lineNum}`;
-    // If range is set, then lineNum equals the end line of the range.
-    if (!this.lineNum && !this.range) {
+    if (this.lineNum === FILE) {
       if (this.path === SpecialFilePath.PATCHSET_LEVEL_COMMENTS) {
         return '';
       }
-      return 'FILE';
+      return FILE;
     }
+    if (this.lineNum) return `#${this.lineNum}`;
+    // If range is set, then lineNum equals the end line of the range.
     if (this.range) return `#${this.range.end_line}`;
     return '';
   }
@@ -490,7 +491,7 @@
     return d;
   }
 
-  _newDraft(lineNum?: number, range?: CommentRange) {
+  _newDraft(lineNum?: LineNumber, range?: CommentRange) {
     const d: UIDraft = {
       __draft: true,
       __draftID: Math.random().toString(36),
@@ -516,7 +517,7 @@
       d.side = this._getSide(this.isOnParent);
       d.__commentSide = this.commentSide;
 
-      if (lineNum) {
+      if (lineNum && lineNum !== FILE) {
         d.line = lineNum;
       }
       if (range) {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.ts
index c021461..c3125c1 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.ts
@@ -249,12 +249,17 @@
   <div id="container" class="container">
     <div class="header" id="header" on-click="_handleToggleCollapsed">
       <div class="headerLeft">
-        <gr-account-label
-          account="[[_getAuthor(comment, _selfAccount)]]"
-          class$="[[_computeAccountLabelClass(draft)]]"
-          hide-status=""
-        >
-        </gr-account-label>
+        <template is="dom-if" if="[[comment.robot_id]]">
+          <span class="robotName"> [[comment.robot_id]] </span>
+        </template>
+        <template is="dom-if" if="[[!comment.robot_id]]">
+          <gr-account-label
+            account="[[_getAuthor(comment, _selfAccount)]]"
+            class$="[[_computeAccountLabelClass(draft)]]"
+            hide-status=""
+          >
+          </gr-account-label>
+        </template>
         <gr-tooltip-content
           class="draftTooltip"
           has-tooltip=""
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.js b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.js
index 10925af..e8d74f0 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.js
@@ -695,9 +695,8 @@
         assert.isTrue(runDetailsLink.href.indexOf(element.comment.url) !== -1);
 
         const robotServiceName = element.shadowRoot
-            .querySelector('gr-account-label')
-            .shadowRoot.querySelector('span.name');
-        assert.equal(robotServiceName.textContent.trim(), 'Display name Robot');
+            .querySelector('.robotName');
+        assert.equal(robotServiceName.textContent.trim(), 'happy_robot_id');
 
         const authorName = element.shadowRoot
             .querySelector('.robotId');
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.ts b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.ts
index 9fdbb34..352bc58 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.ts
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.ts
@@ -419,9 +419,6 @@
     return top;
   }
 
-  /**
-   * @return
-   */
   _targetIsVisible(top: number) {
     const dims = this._getWindowDims();
     return (
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts
index 02d039a..e781820c 100644
--- a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts
+++ b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts
@@ -25,7 +25,7 @@
 import {htmlTemplate} from './gr-diff-preferences_html';
 import {customElement, property} from '@polymer/decorators';
 import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api';
-import {DiffPreferencesInfo} from '../../../types/common';
+import {DiffPreferencesInfo} from '../../../types/diff';
 import {GrSelect} from '../gr-select/gr-select';
 
 export interface GrDiffPreferences {
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_html.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_html.ts
index bc1dfe0..1688a0d 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_html.ts
@@ -59,7 +59,8 @@
     gr-linked-text.pre {
       font-family: var(--monospace-font-family);
       font-size: var(--font-size-code);
-      line-height: var(--line-height-code);
+      /* usually 16px = 12px + 4px */
+      line-height: calc(var(--font-size-code) + var(--spacing-s));
     }
   </style>
   <div id="container"></div>
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts
index 342e937..3403e87 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts
@@ -24,7 +24,7 @@
 import {htmlTemplate} from './gr-list-view_html';
 import {encodeURL, getBaseUrl} from '../../../utils/url-util';
 import {page} from '../../../utils/page-wrapper-utils';
-import {property, observe, customElement} from '@polymer/decorators';
+import {property, customElement} from '@polymer/decorators';
 
 const REQUEST_DEBOUNCE_INTERVAL_MS = 200;
 
@@ -51,7 +51,7 @@
   @property({type: Number})
   itemsPerPage = 25;
 
-  @property({type: String})
+  @property({type: String, observer: '_filterChanged'})
   filter?: string;
 
   @property({type: Number})
@@ -69,8 +69,8 @@
     this.cancelDebouncer('reload');
   }
 
-  @observe('filter')
-  _filterChanged(newFilter: string, oldFilter: string) {
+  _filterChanged(newFilter?: string, oldFilter?: string) {
+    // newFilter can be empty string and then !newFilter === true
     if (!newFilter && !oldFilter) {
       return;
     }
@@ -78,7 +78,7 @@
     this._debounceReload(newFilter);
   }
 
-  _debounceReload(filter: string) {
+  _debounceReload(filter?: string) {
     this.debounce(
       'reload',
       () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts
index 1a89008..0629457 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts
@@ -72,9 +72,7 @@
   DashboardId,
   DashboardInfo,
   DeleteDraftCommentsInput,
-  DiffInfo,
   DiffPreferenceInput,
-  DiffPreferencesInfo,
   EditPatchSetNum,
   EditPreferencesInfo,
   EncodedGroupId,
@@ -142,6 +140,11 @@
   MergeableInfo,
 } from '../../../types/common';
 import {
+  DiffInfo,
+  DiffPreferencesInfo,
+  IgnoreWhitespaceType,
+} from '../../../types/diff';
+import {
   CancelConditionCallback,
   ErrorCallback,
   RestApiService,
@@ -152,7 +155,6 @@
   CommentSide,
   DiffViewMode,
   HttpMethod,
-  IgnoreWhitespaceType,
   ReviewerState,
 } from '../../../constants/constants';
 
@@ -828,7 +830,7 @@
         context: 10,
         cursor_blink_rate: 0,
         font_size: 12,
-        ignore_whitespace: IgnoreWhitespaceType.IGNORE_NONE,
+        ignore_whitespace: 'IGNORE_NONE',
         intraline_difference: true,
         line_length: 100,
         line_wrapping: false,
@@ -2525,7 +2527,7 @@
     const params: GetDiffParams = {
       context: 'ALL',
       intraline: null,
-      whitespace: whitespace || IgnoreWhitespaceType.IGNORE_NONE,
+      whitespace: whitespace || 'IGNORE_NONE',
     };
     if (isMergeParent(basePatchNum)) {
       params.parent = getParentIndex(basePatchNum);
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_html.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_html.ts
index 1f777aa..d55481b 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_html.ts
@@ -31,7 +31,8 @@
     :host(.code) {
       font-family: var(--monospace-font-family);
       font-size: var(--font-size-code);
-      line-height: var(--line-height-code);
+      /* usually 16px = 12px + 4px */
+      line-height: calc(var(--font-size-code) + var(--spacing-s));
       font-weight: var(--font-weight-normal);
     }
     #emojiSuggestions {
diff --git a/polygerrit-ui/app/services/flags/flags.ts b/polygerrit-ui/app/services/flags/flags.ts
index 047e9e0..d6b2237 100644
--- a/polygerrit-ui/app/services/flags/flags.ts
+++ b/polygerrit-ui/app/services/flags/flags.ts
@@ -25,6 +25,5 @@
  */
 export enum KnownExperimentId {
   PATCHSET_COMMENTS = 'UiFeature__patchset_comments',
-  PATCHSET_CHOICE_FOR_COMMENT_LINKS = 'UiFeature__patchset_choice_for_comment_links',
   NEW_CONTEXT_CONTROLS = 'UiFeature__new_context_controls',
 }
diff --git a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts
index 08dbb16..19a8dc5 100644
--- a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts
+++ b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 
+import {HttpMethod} from '../../../constants/constants';
 import {
   AccountDetailInfo,
   AccountExternalIdInfo,
@@ -29,7 +30,6 @@
   PatchSetNum,
   RequestPayload,
   PreferencesInput,
-  DiffPreferencesInfo,
   EditPreferencesInfo,
   DiffPreferenceInput,
   SshKeyInfo,
@@ -86,7 +86,6 @@
   EmailAddress,
   FixId,
   FilePathToDiffInfoMap,
-  DiffInfo,
   BlameInfo,
   PatchRange,
   ImagesForDiff,
@@ -101,8 +100,12 @@
   MergeableInfo,
   CommitInfo,
 } from '../../../types/common';
+import {
+  DiffInfo,
+  DiffPreferencesInfo,
+  IgnoreWhitespaceType,
+} from '../../../types/diff';
 import {ParsedChangeInfo} from '../../../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser';
-import {HttpMethod, IgnoreWhitespaceType} from '../../../constants/constants';
 
 export type ErrorCallback = (response?: Response | null, err?: Error) => void;
 export type CancelConditionCallback = () => boolean;
diff --git a/polygerrit-ui/app/styles/themes/app-theme.ts b/polygerrit-ui/app/styles/themes/app-theme.ts
index 9586c09..d7b96c8 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.ts
+++ b/polygerrit-ui/app/styles/themes/app-theme.ts
@@ -122,7 +122,6 @@
     --font-size-h3: 1.143rem;   /* 16px */
     --font-size-h2: 1.429rem;   /* 20px */
     --font-size-h1: 1.714rem;   /* 24px */
-    --line-height-code: 1.143rem;   /* 16px */
     --line-height-mono: 1.286rem;   /* 18px */
     --line-height-small: 1.143rem;  /* 16px */
     --line-height-normal: 1.429rem; /* 20px */
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 96cb02a..b454b6e 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -37,7 +37,6 @@
   TimeFormat,
   EmailStrategy,
   DefaultBase,
-  IgnoreWhitespaceType,
   UserPriority,
   DiffViewMode,
   DraftsAction,
@@ -50,6 +49,8 @@
 } from '../constants/constants';
 import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
 
+import {DiffInfo, IgnoreWhitespaceType, WebLinkInfo} from './diff';
+
 export type BrandType<T, BrandName extends string> = T &
   {[__brand in BrandName]: never};
 
@@ -716,16 +717,6 @@
 }
 
 /**
- * The WebLinkInfo entity describes a link to an external site.
- * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#web-link-info
- */
-export interface WebLinkInfo {
-  name: string;
-  url: string;
-  image_url: string;
-}
-
-/**
  * The VotingRangeInfo entity describes the continuous voting range from minto
  * max values.
  * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#voting-range-info
@@ -1240,103 +1231,9 @@
 
 export type LabelTypeInfoValues = {[value: string]: string};
 
-/**
- * The DiffContent entity contains information about the content differences in a file.
- * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#diff-content
- */
-export interface DiffContent {
-  a?: string[];
-  b?: string[];
-  ab?: string[];
-  // The inner array is always of length two. The first entry is the 'skip'
-  // length. The second entry is the 'edit' length.
-  edit_a?: number[][];
-  edit_b?: number[][];
-  due_to_rebase?: boolean;
-  due_to_move?: boolean;
-  skip?: number;
-  common?: string;
-  keyLocation?: boolean;
-}
-
-/**
- * The DiffFileMetaInfo entity contains meta information about a file diff.
- * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#diff-file-meta-info
- */
-export interface DiffFileMetaInfo {
-  name: string;
-  content_type: string;
-  lines: string;
-  web_links?: WebLinkInfo[];
-  language?: string;
-}
-
-/**
- * The DiffInfo entity contains information about the diff of a file in a revision.
- * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#diff-info
- */
-export interface DiffInfo {
-  meta_a: DiffFileMetaInfo;
-  meta_b: DiffFileMetaInfo;
-  change_type: string;
-  intraline_status: string;
-  diff_header: string[];
-  content: DiffContent[];
-  web_links?: DiffWebLinkInfo[];
-  binary: boolean;
-}
-
 export type FilePathToDiffInfoMap = {[path: string]: DiffInfo};
 
 /**
- * The DiffWebLinkInfo entity describes a link on a diff screen to an external site.
- * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#diff-web-link-info
- */
-export interface DiffWebLinkInfo {
-  name: string;
-  url: string;
-  image_url: string;
-  show_on_side_by_side_diff_view: string;
-  show_on_unified_diff_view: string;
-}
-
-/**
- * The DiffPreferencesInfo entity contains information about the diff preferences of a user.
- * https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#diff-preferences-info
- */
-export interface DiffPreferencesInfo {
-  context: number;
-  expand_all_comments?: boolean;
-  ignore_whitespace: IgnoreWhitespaceType;
-  intraline_difference?: boolean;
-  line_length: number;
-  cursor_blink_rate: number;
-  manual_review?: boolean;
-  retain_header?: boolean;
-  show_line_endings?: boolean;
-  show_tabs?: boolean;
-  show_whitespace_errors?: boolean;
-  skip_deleted?: boolean;
-  skip_uncommented?: boolean;
-  syntax_highlighting?: boolean;
-  hide_top_menu?: boolean;
-  auto_hide_diff_table_header?: boolean;
-  hide_line_numbers?: boolean;
-  tab_size: number;
-  font_size: number;
-  hide_empty_pane?: boolean;
-  match_brackets?: boolean;
-  line_wrapping?: boolean;
-  // TODO(TS): show_file_comment_button exists in JS code, but doesn't exist in the doc.
-  // Either remove or update doc
-  show_file_comment_button?: boolean;
-  // TODO(TS): theme exists in JS code, but doesn't exist in the doc.
-  // Either remove or update doc
-  theme?: string;
-}
-export type DiffPreferencesInfoKey = keyof DiffPreferencesInfo;
-
-/**
  * The RangeInfo entity stores the coordinates of a range.
  * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#range-info
  */
diff --git a/polygerrit-ui/app/types/diff.d.ts b/polygerrit-ui/app/types/diff.d.ts
new file mode 100644
index 0000000..b45b461
--- /dev/null
+++ b/polygerrit-ui/app/types/diff.d.ts
@@ -0,0 +1,239 @@
+/**
+ * @fileoverview The Gerrit diff API.
+ *
+ * This API is used by other apps embedding gr-diff and any breaking changes
+ * should be discussed with the Gerrit core team and properly versioned.
+ *
+ * Should only contain types, no values, so that other apps using gr-diff can
+ * use this solely to type check and generate externs for their separate ts
+ * bundles.
+ *
+ * Should declare all types, to avoid renaming breaking multi-bundle setups.
+ *
+ * Enums should be converted to union types to avoid values in this file.
+ *
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The DiffInfo entity contains information about the diff of a file in a
+ * revision.
+ *
+ * If the weblinks-only parameter is specified, only the web_links field is set.
+ */
+export declare interface DiffInfo {
+  /** Meta information about the file on side A as a DiffFileMetaInfo entity. */
+  meta_a: DiffFileMetaInfo;
+  /** Meta information about the file on side B as a DiffFileMetaInfo entity. */
+  meta_b: DiffFileMetaInfo;
+  /** The type of change (ADDED, MODIFIED, DELETED, RENAMED COPIED, REWRITE). */
+  change_type: ChangeType;
+  /** Intraline status (OK, ERROR, TIMEOUT). */
+  intraline_status: 'OK' | 'Error' | 'Timeout';
+  /** A list of strings representing the patch set diff header. */
+  diff_header: string[];
+  /** The content differences in the file as a list of DiffContent entities. */
+  content: DiffContent[];
+  /**
+   * Links to the file diff in external sites as a list of DiffWebLinkInfo
+   * entries.
+   */
+  web_links?: DiffWebLinkInfo[];
+  /** Whether the file is binary. */
+  binary?: boolean;
+}
+
+/**
+ * The DiffFileMetaInfo entity contains meta information about a file diff.
+ * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#diff-file-meta-info
+ */
+export declare interface DiffFileMetaInfo {
+  /** The name of the file. */
+  name: string;
+  /** The content type of the file. */
+  content_type: string;
+  /** The total number of lines in the file. */
+  lines: number;
+  /** Links to the file in external sites as a list of WebLinkInfo entries. */
+  web_links: WebLinkInfo[];
+  // TODO: Not documented.
+  language?: string;
+}
+
+export declare type ChangeType =
+  | 'ADDED'
+  | 'MODIFIED'
+  | 'DELETED'
+  | 'RENAMED'
+  | 'COPIED'
+  | 'REWRITE';
+
+/**
+ * The DiffContent entity contains information about the content differences in
+ * a file.
+ * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#diff-content
+ */
+export declare interface DiffContent {
+  /** Content only in the file on side A (deleted in B). */
+  a?: string[];
+  /** Content only in the file on side B (added in B). */
+  b?: string[];
+  /** Content in the file on both sides (unchanged). */
+  ab?: string[];
+  /**
+   * Text sections deleted from side A as a DiffIntralineInfo entity.
+   *
+   * Only present during a replace, i.e. both a and b are present.
+   */
+  edit_a?: DiffIntralineInfo[];
+  /**
+   * Text sections inserted in side B as a DiffIntralineInfo entity.
+   *
+   * Only present during a replace, i.e. both a and b are present.
+   */
+  edit_b?: DiffIntralineInfo[];
+  /** Indicates whether this entry was introduced by a rebase. */
+  due_to_rebase?: boolean;
+  /** @deprecated Use move_details instead. */
+  due_to_move?: boolean;
+
+  /**
+   * Provides info about a move operation the chunk.
+   * It's presence indicates the current chunk exists due to a move.
+   */
+  move_details?: {
+    /** Indicates whether the content of the chunk changes while moving code */
+    changed: boolean;
+    /**
+     * Indicates the range (line numbers) on the other side of the comparison
+     * where the code related to the current chunk came from/went to.
+     */
+    range: {
+      start: number;
+      end: number;
+    };
+  };
+  /**
+   * Count of lines skipped on both sides when the file is too large to include
+   * all common lines.
+   */
+  skip?: number;
+  /**
+   * Set to true if the region is common according to the requested
+   * ignore-whitespace parameter, but a and b contain differing amounts of
+   * whitespace. When present and true a and b are used instead of ab.
+   */
+  common?: boolean;
+  // TODO: Undocumented, but used in code.
+  keyLocation?: boolean;
+}
+
+/**
+ * The DiffWebLinkInfo entity describes a link on a diff screen to an external
+ * site.
+ * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#diff-web-link-info
+ */
+export declare interface DiffWebLinkInfo {
+  /** The link name. */
+  name: string;
+  /** The link URL. */
+  url: string;
+  /** URL to the icon of the link. */
+  image_url: string;
+  // TODO: Are these really of type string? Not able to trigger them, but the
+  // docs sound more like boolean.
+  show_on_side_by_side_diff_view: string;
+  show_on_unified_diff_view: string;
+}
+/**
+ * The DiffIntralineInfo entity contains information about intraline edits in a
+ * file.
+ *
+ * The information consists of a list of <skip length, mark length> pairs, where
+ * the skip length is the number of characters between the end of the previous
+ * edit and the start of this edit, and the mark length is the number of edited
+ * characters following the skip. The start of the edits is from the beginning
+ * of the related diff content lines.
+ *
+ * Note that the implied newline character at the end of each line is included
+ * in the length calculation, and thus it is possible for the edits to span
+ * newlines.
+ */
+export declare type SkipLength = number;
+export declare type MarkLength = number;
+export declare type DiffIntralineInfo = [SkipLength, MarkLength];
+
+/**
+ * The WebLinkInfo entity describes a link to an external site.
+ * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#web-link-info
+ */
+export declare interface WebLinkInfo {
+  /** The link name. */
+  name: string;
+  /** The link URL. */
+  url: string;
+  /** URL to the icon of the link. */
+  image_url: string;
+}
+
+/**
+ * The DiffPreferencesInfo entity contains information about the diff
+ * preferences of a user.
+ * https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#diff-preferences-info
+ */
+export declare interface DiffPreferencesInfo {
+  context: number;
+  expand_all_comments?: boolean;
+  ignore_whitespace: IgnoreWhitespaceType;
+  intraline_difference?: boolean;
+  line_length: number;
+  cursor_blink_rate: number;
+  manual_review?: boolean;
+  retain_header?: boolean;
+  show_line_endings?: boolean;
+  show_tabs?: boolean;
+  show_whitespace_errors?: boolean;
+  skip_deleted?: boolean;
+  skip_uncommented?: boolean;
+  syntax_highlighting?: boolean;
+  hide_top_menu?: boolean;
+  auto_hide_diff_table_header?: boolean;
+  hide_line_numbers?: boolean;
+  tab_size: number;
+  font_size: number;
+  hide_empty_pane?: boolean;
+  match_brackets?: boolean;
+  line_wrapping?: boolean;
+  // TODO(TS): show_file_comment_button exists in JS code, but doesn't exist in
+  // the doc. Either remove or update doc
+  show_file_comment_button?: boolean;
+  // TODO(TS): theme exists in JS code, but doesn't exist in the doc.
+  // Either remove or update doc
+  theme?: string;
+}
+
+export declare type DiffPreferencesInfoKey = keyof DiffPreferencesInfo;
+
+/**
+ * Whether whitespace changes should be ignored and if yes, which whitespace
+ * changes should be ignored
+ * https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#diff-preferences-input
+ */
+export declare type IgnoreWhitespaceType =
+  | 'IGNORE_NONE'
+  | 'IGNORE_TRAILING'
+  | 'IGNORE_LEADING_AND_TRAILING'
+  | 'IGNORE_ALL';
diff --git a/polygerrit-ui/app/types/globals.ts b/polygerrit-ui/app/types/globals.ts
index 2962158..28cac87 100644
--- a/polygerrit-ui/app/types/globals.ts
+++ b/polygerrit-ui/app/types/globals.ts
@@ -23,12 +23,6 @@
   interface Window {
     CANONICAL_PATH?: string;
     INITIAL_DATA?: {[key: string]: ParsedJSON};
-    ShadyCSS?: {
-      getComputedStyleValue(el: Element, name: string): string;
-    };
-    ShadyDOM?: {
-      inUse?: boolean;
-    };
     HTMLImports?: {whenReady: (cb: () => void) => void};
     linkify(
       text: string,
diff --git a/polygerrit-ui/app/utils/comment-util.ts b/polygerrit-ui/app/utils/comment-util.ts
index 5f8aa82..e1e002a 100644
--- a/polygerrit-ui/app/utils/comment-util.ts
+++ b/polygerrit-ui/app/utils/comment-util.ts
@@ -21,9 +21,11 @@
   RobotCommentInfo,
   Timestamp,
   UrlEncodedCommentId,
+  CommentRange,
 } from '../types/common';
 import {CommentSide, Side} from '../constants/constants';
 import {parseDate} from './date-util';
+import {LineNumber} from '../elements/diff/gr-diff/gr-diff-line';
 
 export interface DraftCommentProps {
   __draft?: boolean;
@@ -101,13 +103,10 @@
   comments: UIComment[];
   patchNum?: PatchSetNum;
   path: string;
-  // TODO(TS): It would be nice to use LineNumber here, but the comment thread
-  // element actually relies on line to be undefined for file comments. Be
-  // aware of element attribute getters and setters, if you try to refactor
-  // this. :-) Still worthwhile to do ...
-  line?: number;
+  line?: LineNumber;
   rootId: UrlEncodedCommentId;
   commentSide?: CommentSide;
+  range?: CommentRange;
 }
 
 export function getLastComment(thread?: CommentThread): UIComment | undefined {
diff --git a/polygerrit-ui/app/utils/dom-util.ts b/polygerrit-ui/app/utils/dom-util.ts
index 76db40b..364112b 100644
--- a/polygerrit-ui/app/utils/dom-util.ts
+++ b/polygerrit-ui/app/utils/dom-util.ts
@@ -15,7 +15,6 @@
  * limitations under the License.
  */
 
-import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
 import {EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
 import {JsApiService} from '../elements/shared/gr-js-api-interface/gr-js-api-types';
 
@@ -70,27 +69,9 @@
 
 /**
  * Get computed style value.
- *
- * If ShadyCSS is provided, use ShadyCSS api.
- * If `getComputedStyleValue` is provided on the element, use it.
- * Otherwise fallback to native method (in polymer 2).
- *
  */
-export function getComputedStyleValue(
-  name: string,
-  el: Element | LegacyElementMixin
-) {
-  let style;
-  if (window.ShadyCSS) {
-    style = window.ShadyCSS.getComputedStyleValue(el as Element, name);
-    // `getComputedStyleValue` defined through LegacyElementMixin
-    // TODO: It should be safe to just use `getComputedStyle`, but just to be safe
-  } else if ('getComputedStyleValue' in el) {
-    style = el.getComputedStyleValue(name);
-  } else {
-    style = getComputedStyle(el).getPropertyValue(name);
-  }
-  return style;
+export function getComputedStyleValue(name: string, el: Element) {
+  return getComputedStyle(el).getPropertyValue(name).trim();
 }
 
 /**
diff --git a/polygerrit-ui/app/utils/event-util.ts b/polygerrit-ui/app/utils/event-util.ts
new file mode 100644
index 0000000..5684b40
--- /dev/null
+++ b/polygerrit-ui/app/utils/event-util.ts
@@ -0,0 +1,30 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export enum EventType {
+  SHOW_ALERT = 'show-alert',
+}
+
+export function fire(target: EventTarget, type: EventType, message: string) {
+  target.dispatchEvent(
+    new CustomEvent(type, {
+      detail: {message},
+      composed: true,
+      bubbles: true,
+    })
+  );
+}
diff --git a/tools/bzl/asciidoc.bzl b/tools/bzl/asciidoc.bzl
index 1e7ec96..7977cf0 100644
--- a/tools/bzl/asciidoc.bzl
+++ b/tools/bzl/asciidoc.bzl
@@ -18,8 +18,7 @@
     ]
 
 def _replace_macros_impl(ctx):
-    cmd = [
-        ctx.file._exe.path,
+    args = [
         "--suffix",
         ctx.attr.suffix,
         "-s",
@@ -28,13 +27,14 @@
         ctx.outputs.out.path,
     ]
     if ctx.attr.searchbox:
-        cmd.append("--searchbox")
+        args.append("--searchbox")
     else:
-        cmd.append("--no-searchbox")
-    ctx.actions.run_shell(
+        args.append("--no-searchbox")
+    ctx.actions.run(
         inputs = [ctx.file._exe, ctx.file.src],
         outputs = [ctx.outputs.out],
-        command = cmd,
+        executable = ctx.file._exe.path,
+        arguments = args,
         use_default_shell_env = True,
         progress_message = "Replacing macros in %s" % ctx.file.src.short_path,
     )
diff --git a/tools/eclipse/BUILD b/tools/eclipse/BUILD
index e091fc1..61ea4fe 100644
--- a/tools/eclipse/BUILD
+++ b/tools/eclipse/BUILD
@@ -43,5 +43,14 @@
 
 classpath_collector(
     name = "autovalue_classpath_collect",
-    deps = ["//lib/auto:auto-value"],
+    deps = [
+        "//lib/auto:auto-value",
+        "@auto-value-annotations//jar",
+        "@auto-value-gson-extension//jar",
+        "@auto-value-gson-factory//jar",
+        "@auto-value-gson-runtime//jar",
+        "@autotransient//jar",
+        "@gson//jar",
+        "@javapoet//jar",
+    ],
 )