Merge changes from topic "change-view-model"

* changes:
  Remove `viewStateChanged()` method from gr-change-view
  Move ending of CHANGE_DISPLAYED and CHANGE_FULLY_LOADED timers
  Move ending the SEND_REPLY timer into model
  Move CHANGE_DATA timing from change view into model
  Move CHANGE_RELOAD timing from change view into model
  Set `loading` property in change view based on model subscription
  Move processEdit from change view into model
  Remove `patchRange` from change view
  Remove unused `hasPatchNumChanged()` method
  Remove resetting `this.editingCommitMessage`
  Move resetting scrollPosition into subscription
  Move `initActiveTab()` from change view into model
  Move `maybeShowRevertDialog()` into `updated()`
  Move `maybeScrollToMessage()` into `firstUpdated()`
  Remove obsolete `initialLoadComplete`
  Move firing of showChange event from view into model
  Move file list reset from change view into file list itself
  Update `scrollCommentId` by direct subscription
  Move firing title changes into change model
  Pure refactoring: Extract 3 methods
  Refactor title-change event
  Remove 7 year old fix of coalescing reviewer_updates to `null`
  Remove 7 year old fix of coalescing topic to `null`
  Move status and revert loading from change view into models
  Move loading of related changes into a new model
  Move loading of `mergeable` from change-view into change-model
  Move revision/commit based loading from gr-change-view into model
  Subscribe change-view to patchNum and basePatchNum
  Make showing reply dialog model based
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 4c12f9f..1faffb2 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1577,6 +1577,8 @@
 bit is indexed.
 |`enable_robot_comments`|not set if `false`|
 link:config-gerrit.html#change.enableRobotComments[Are robot comments enabled?].
+|`conflicts_predicate_enabled`|not set if `false`|
+link:config-gerrit.html#change.conflictsPredicateEnabled[Are conflicts enabled?].
 |=============================
 
 [[change-index-config-info]]
diff --git a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
index 2295922..80bf130 100644
--- a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
@@ -22,4 +22,5 @@
   public Boolean submitWholeTopic;
   public String mergeabilityComputationBehavior;
   public Boolean enableRobotComments;
+  public Boolean conflictsPredicateEnabled;
 }
diff --git a/java/com/google/gerrit/extensions/events/CommentAddedListener.java b/java/com/google/gerrit/extensions/events/CommentAddedListener.java
index 071dac1..7e0b623 100644
--- a/java/com/google/gerrit/extensions/events/CommentAddedListener.java
+++ b/java/com/google/gerrit/extensions/events/CommentAddedListener.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.events;
 
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.annotations.ExtensionPoint;
 import com.google.gerrit.extensions.common.ApprovalInfo;
 import java.util.Map;
@@ -22,6 +23,7 @@
 @ExtensionPoint
 public interface CommentAddedListener {
   interface Event extends RevisionEvent {
+    @Nullable
     String getComment();
 
     Map<String, ApprovalInfo> getApprovals();
diff --git a/java/com/google/gerrit/server/events/CommentAddedEvent.java b/java/com/google/gerrit/server/events/CommentAddedEvent.java
index dbbebe8..d59ab08d2 100644
--- a/java/com/google/gerrit/server/events/CommentAddedEvent.java
+++ b/java/com/google/gerrit/server/events/CommentAddedEvent.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.events;
 
 import com.google.common.base.Supplier;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.server.data.AccountAttribute;
 import com.google.gerrit.server.data.ApprovalAttribute;
@@ -23,7 +24,7 @@
   static final String TYPE = "comment-added";
   public Supplier<AccountAttribute> author;
   public Supplier<ApprovalAttribute[]> approvals;
-  public String comment;
+  @Nullable public String comment;
 
   public CommentAddedEvent(Change change) {
     super(TYPE, change);
diff --git a/java/com/google/gerrit/server/extensions/events/CommentAdded.java b/java/com/google/gerrit/server/extensions/events/CommentAdded.java
index 79544f2..c6661bd 100644
--- a/java/com/google/gerrit/server/extensions/events/CommentAdded.java
+++ b/java/com/google/gerrit/server/extensions/events/CommentAdded.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.extensions.events;
 
 import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
@@ -54,7 +55,7 @@
       ChangeData changeData,
       PatchSet ps,
       AccountState author,
-      String comment,
+      @Nullable String comment,
       Map<String, Short> approvals,
       Map<String, Short> oldApprovals,
       Instant when) {
@@ -86,7 +87,7 @@
   /** Event to be fired when a comment or vote has been added to a change. */
   private static class Event extends AbstractRevisionEvent implements CommentAddedListener.Event {
 
-    private final String comment;
+    @Nullable private final String comment;
     private final Map<String, ApprovalInfo> approvals;
     private final Map<String, ApprovalInfo> oldApprovals;
 
@@ -94,7 +95,7 @@
         ChangeInfo change,
         RevisionInfo revision,
         AccountInfo author,
-        String comment,
+        @Nullable String comment,
         Map<String, ApprovalInfo> approvals,
         Map<String, ApprovalInfo> oldApprovals,
         Instant when) {
@@ -105,6 +106,7 @@
     }
 
     @Override
+    @Nullable
     public String getComment() {
       return comment;
     }
diff --git a/java/com/google/gerrit/server/git/MergedByPushOp.java b/java/com/google/gerrit/server/git/MergedByPushOp.java
index 426f8db..cb46df1 100644
--- a/java/com/google/gerrit/server/git/MergedByPushOp.java
+++ b/java/com/google/gerrit/server/git/MergedByPushOp.java
@@ -26,8 +26,10 @@
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.config.SendEmailExecutor;
 import com.google.gerrit.server.extensions.events.ChangeMerged;
-import com.google.gerrit.server.mail.send.MergedSender;
+import com.google.gerrit.server.mail.EmailModule.MergedChangeEmailFactories;
+import com.google.gerrit.server.mail.send.ChangeEmailNew;
 import com.google.gerrit.server.mail.send.MessageIdGenerator;
+import com.google.gerrit.server.mail.send.OutgoingEmailNew;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.update.BatchUpdateOp;
@@ -67,7 +69,7 @@
   private final RequestScopePropagator requestScopePropagator;
   private final PatchSetInfoFactory patchSetInfoFactory;
   private final ChangeMessagesUtil cmUtil;
-  private final MergedSender.Factory mergedSenderFactory;
+  private final MergedChangeEmailFactories mergedChangeEmailFactories;
   private final PatchSetUtil psUtil;
   private final ExecutorService sendEmailExecutor;
   private final ChangeMerged changeMerged;
@@ -88,7 +90,7 @@
   MergedByPushOp(
       PatchSetInfoFactory patchSetInfoFactory,
       ChangeMessagesUtil cmUtil,
-      MergedSender.Factory mergedSenderFactory,
+      MergedChangeEmailFactories mergedChangeEmailFactories,
       PatchSetUtil psUtil,
       @SendEmailExecutor ExecutorService sendEmailExecutor,
       ChangeMerged changeMerged,
@@ -100,7 +102,7 @@
       @Assisted("mergeResultRevId") String mergeResultRevId) {
     this.patchSetInfoFactory = patchSetInfoFactory;
     this.cmUtil = cmUtil;
-    this.mergedSenderFactory = mergedSenderFactory;
+    this.mergedChangeEmailFactories = mergedChangeEmailFactories;
     this.psUtil = psUtil;
     this.sendEmailExecutor = sendEmailExecutor;
     this.changeMerged = changeMerged;
@@ -188,16 +190,18 @@
                     try {
                       // The stickyApprovalDiff is always empty here since this is not supported
                       // for direct pushes.
-                      MergedSender emailSender =
-                          mergedSenderFactory.create(
+                      ChangeEmailNew changeEmail =
+                          mergedChangeEmailFactories.createChangeEmail(
                               ctx.getProject(),
                               psId.changeId(),
                               /* stickyApprovalDiff= */ Optional.empty());
-                      emailSender.setFrom(ctx.getAccountId());
-                      emailSender.setPatchSet(patchSet, info);
-                      emailSender.setMessageId(
+                      changeEmail.setPatchSet(patchSet, info);
+                      OutgoingEmailNew outgoingEmail =
+                          mergedChangeEmailFactories.createEmail(changeEmail);
+                      outgoingEmail.setFrom(ctx.getAccountId());
+                      outgoingEmail.setMessageId(
                           messageIdGenerator.fromChangeUpdate(ctx.getRepoView(), patchSet.id()));
-                      emailSender.send();
+                      outgoingEmail.send();
                     } catch (Exception e) {
                       logger.atSevere().withCause(e).log(
                           "Cannot send email for submitted patch set %s", psId);
diff --git a/java/com/google/gerrit/server/mail/EmailModule.java b/java/com/google/gerrit/server/mail/EmailModule.java
index 0a1d059..4b90af0 100644
--- a/java/com/google/gerrit/server/mail/EmailModule.java
+++ b/java/com/google/gerrit/server/mail/EmailModule.java
@@ -30,10 +30,10 @@
 import com.google.gerrit.server.mail.send.CreateChangeSender;
 import com.google.gerrit.server.mail.send.DeleteKeySender;
 import com.google.gerrit.server.mail.send.DeleteReviewerChangeEmailDecorator;
-import com.google.gerrit.server.mail.send.DeleteVoteSender;
+import com.google.gerrit.server.mail.send.DeleteVoteChangeEmailDecorator;
 import com.google.gerrit.server.mail.send.EmailArguments;
 import com.google.gerrit.server.mail.send.HttpPasswordUpdateSender;
-import com.google.gerrit.server.mail.send.MergedSender;
+import com.google.gerrit.server.mail.send.MergedChangeEmailDecoratorFactory;
 import com.google.gerrit.server.mail.send.ModifyReviewerSender;
 import com.google.gerrit.server.mail.send.OutgoingEmailNew;
 import com.google.gerrit.server.mail.send.OutgoingEmailNewFactory;
@@ -43,6 +43,7 @@
 import com.google.gerrit.server.mail.send.RevertedSender;
 import com.google.inject.Inject;
 import java.util.Map;
+import java.util.Optional;
 import org.eclipse.jgit.lib.ObjectId;
 
 public class EmailModule extends FactoryModule {
@@ -52,9 +53,7 @@
     factory(ModifyReviewerSender.Factory.class);
     factory(CreateChangeSender.Factory.class);
     factory(DeleteKeySender.Factory.class);
-    factory(DeleteVoteSender.Factory.class);
     factory(HttpPasswordUpdateSender.Factory.class);
-    factory(MergedSender.Factory.class);
     factory(RegisterNewEmailSender.Factory.class);
     factory(ReplacePatchSetSender.Factory.class);
     factory(RestoredSender.Factory.class);
@@ -197,4 +196,62 @@
       return outgoingEmailFactory.create("deleteReviewer", changeEmail);
     }
   }
+
+  public static class DeleteVoteChangeEmailFactories {
+    private final EmailArguments args;
+    private final ChangeEmailNewFactory changeEmailFactory;
+    private final OutgoingEmailNewFactory outgoingEmailFactory;
+    private final DeleteVoteChangeEmailDecorator deleteVoteChangeEmailDecorator;
+
+    @Inject
+    DeleteVoteChangeEmailFactories(
+        EmailArguments args,
+        ChangeEmailNewFactory changeEmailFactory,
+        OutgoingEmailNewFactory outgoingEmailFactory,
+        DeleteVoteChangeEmailDecorator deleteVoteChangeEmailDecorator) {
+      this.args = args;
+      this.changeEmailFactory = changeEmailFactory;
+      this.outgoingEmailFactory = outgoingEmailFactory;
+      this.deleteVoteChangeEmailDecorator = deleteVoteChangeEmailDecorator;
+    }
+
+    public ChangeEmailNew createChangeEmail(Project.NameKey project, Change.Id changeId) {
+      return changeEmailFactory.create(
+          args.newChangeData(project, changeId), deleteVoteChangeEmailDecorator);
+    }
+
+    public OutgoingEmailNew createEmail(ChangeEmailNew changeEmail) {
+      return outgoingEmailFactory.create("deleteVote", changeEmail);
+    }
+  }
+
+  public static class MergedChangeEmailFactories {
+    private final EmailArguments args;
+    private final MergedChangeEmailDecoratorFactory mergedChangeEmailDecoratorFactory;
+    private final ChangeEmailNewFactory changeEmailFactory;
+    private final OutgoingEmailNewFactory outgoingEmailFactory;
+
+    @Inject
+    MergedChangeEmailFactories(
+        EmailArguments args,
+        MergedChangeEmailDecoratorFactory mergedChangeEmailDecoratorFactory,
+        ChangeEmailNewFactory changeEmailFactory,
+        OutgoingEmailNewFactory outgoingEmailFactory) {
+      this.args = args;
+      this.mergedChangeEmailDecoratorFactory = mergedChangeEmailDecoratorFactory;
+      this.changeEmailFactory = changeEmailFactory;
+      this.outgoingEmailFactory = outgoingEmailFactory;
+    }
+
+    public ChangeEmailNew createChangeEmail(
+        Project.NameKey project, Change.Id changeId, Optional<String> stickyApprovalDiff) {
+      return changeEmailFactory.create(
+          args.newChangeData(project, changeId),
+          mergedChangeEmailDecoratorFactory.create(stickyApprovalDiff));
+    }
+
+    public OutgoingEmailNew createEmail(ChangeEmailNew changeEmail) {
+      return outgoingEmailFactory.create("merged", changeEmail);
+    }
+  }
 }
diff --git a/java/com/google/gerrit/server/mail/send/DeleteVoteChangeEmailDecorator.java b/java/com/google/gerrit/server/mail/send/DeleteVoteChangeEmailDecorator.java
new file mode 100644
index 0000000..2bf67e5
--- /dev/null
+++ b/java/com/google/gerrit/server/mail/send/DeleteVoteChangeEmailDecorator.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2016 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.mail.send;
+
+import com.google.gerrit.entities.NotifyConfig.NotifyType;
+import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.server.mail.send.ChangeEmailNew.ChangeEmailDecorator;
+
+/** Send notice about a vote that was removed from a change. */
+public class DeleteVoteChangeEmailDecorator implements ChangeEmailDecorator {
+  private OutgoingEmailNew email;
+  private ChangeEmailNew changeEmail;
+
+  @Override
+  public void init(OutgoingEmailNew email, ChangeEmailNew changeEmail) {
+    this.email = email;
+    this.changeEmail = changeEmail;
+    changeEmail.markAsReply();
+  }
+
+  @Override
+  public void populateEmailContent() {
+    changeEmail.addAuthors(RecipientType.TO);
+    changeEmail.ccAllApprovals();
+    changeEmail.bccStarredBy();
+    changeEmail.includeWatchers(NotifyType.ALL_COMMENTS);
+
+    email.appendText(email.textTemplate("DeleteVote"));
+    if (email.useHtml()) {
+      email.appendHtml(email.soyHtmlTemplate("DeleteVoteHtml"));
+    }
+  }
+}
diff --git a/java/com/google/gerrit/server/mail/send/DeleteVoteSender.java b/java/com/google/gerrit/server/mail/send/DeleteVoteSender.java
deleted file mode 100644
index cc37325c..0000000
--- a/java/com/google/gerrit/server/mail/send/DeleteVoteSender.java
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (C) 2016 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.mail.send;
-
-import com.google.gerrit.entities.Change;
-import com.google.gerrit.entities.NotifyConfig.NotifyType;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.exceptions.EmailException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-/** Send notice about a vote that was removed from a change. */
-public class DeleteVoteSender extends ReplyToChangeSender {
-  public interface Factory extends ReplyToChangeSender.Factory<DeleteVoteSender> {
-    @Override
-    DeleteVoteSender create(Project.NameKey project, Change.Id changeId);
-  }
-
-  @Inject
-  protected DeleteVoteSender(
-      EmailArguments args, @Assisted Project.NameKey project, @Assisted Change.Id changeId) {
-    super(args, "deleteVote", newChangeData(args, project, changeId));
-  }
-
-  @Override
-  protected void populateEmailContent() throws EmailException {
-    super.populateEmailContent();
-
-    ccAllApprovals();
-    bccStarredBy();
-    includeWatchers(NotifyType.ALL_COMMENTS);
-  }
-
-  @Override
-  protected void formatChange() throws EmailException {
-    appendText(textTemplate("DeleteVote"));
-    if (useHtml()) {
-      appendHtml(soyHtmlTemplate("DeleteVoteHtml"));
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/mail/send/MergedSender.java b/java/com/google/gerrit/server/mail/send/MergedChangeEmailDecorator.java
similarity index 66%
rename from java/com/google/gerrit/server/mail/send/MergedSender.java
rename to java/com/google/gerrit/server/mail/send/MergedChangeEmailDecorator.java
index f8aa00d..dc80ba9 100644
--- a/java/com/google/gerrit/server/mail/send/MergedSender.java
+++ b/java/com/google/gerrit/server/mail/send/MergedChangeEmailDecorator.java
@@ -14,70 +14,57 @@
 
 package com.google.gerrit.server.mail.send;
 
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
 import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.Table;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.LabelType;
 import com.google.gerrit.entities.LabelTypes;
 import com.google.gerrit.entities.LabelValue;
 import com.google.gerrit.entities.NotifyConfig.NotifyType;
 import com.google.gerrit.entities.PatchSetApproval;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.exceptions.EmailException;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.api.changes.RecipientType;
 import com.google.gerrit.server.change.NotifyResolver;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
+import com.google.gerrit.server.mail.send.ChangeEmailNew.ChangeEmailDecorator;
 import java.util.Optional;
 
 /** Send notice about a change successfully merged. */
-public class MergedSender extends ReplyToChangeSender {
+@AutoFactory
+public class MergedChangeEmailDecorator implements ChangeEmailDecorator {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  public interface Factory {
-    MergedSender create(
-        Project.NameKey project, Change.Id changeId, Optional<String> stickyApprovalDiff);
-  }
-
-  private final LabelTypes labelTypes;
+  private OutgoingEmailNew email;
+  private ChangeEmailNew changeEmail;
+  private LabelTypes labelTypes;
+  private final EmailArguments args;
   private final Optional<String> stickyApprovalDiff;
 
-  @Inject
-  public MergedSender(
-      EmailArguments args,
-      @Assisted Project.NameKey project,
-      @Assisted Change.Id changeId,
-      @Assisted Optional<String> stickyApprovalDiff) {
-    super(args, "merged", newChangeData(args, project, changeId));
-    labelTypes = getChangeData().getLabelTypes();
+  MergedChangeEmailDecorator(@Provided EmailArguments args, Optional<String> stickyApprovalDiff) {
+    this.args = args;
     this.stickyApprovalDiff = stickyApprovalDiff;
-    // We want to send the submit email even if the "send only when in attention set" is enabled.
-    setEmailOnlyAttentionSetIfEnabled(false);
   }
 
   @Override
-  protected void init() throws EmailException {
-    super.init();
+  public void init(OutgoingEmailNew email, ChangeEmailNew changeEmail) {
+    this.email = email;
+    this.changeEmail = changeEmail;
+    changeEmail.markAsReply();
+    labelTypes = changeEmail.getChangeData().getLabelTypes();
 
-    NotifyResolver.Result notify = getNotify();
+    // We want to send the submit email even if the "send only when in attention set" is enabled.
+    changeEmail.setEmailOnlyAttentionSetIfEnabled(false);
+
+    NotifyResolver.Result notify = email.getNotify();
     if (!stickyApprovalDiff.isEmpty() && !notify.handling().equals(NotifyHandling.ALL)) {
       logger.atFine().log(
           "Requested to notify %s, but for change submission with sticky approval diff,"
               + " Notify=ALL is enforced.",
           notify.handling().name());
-      setNotify(NotifyResolver.Result.create(NotifyHandling.ALL, notify.accounts()));
-    }
-  }
-
-  @Override
-  protected void formatChange() throws EmailException {
-    appendText(textTemplate("Merged"));
-
-    if (useHtml()) {
-      appendHtml(soyHtmlTemplate("MergedHtml"));
+      email.setNotify(NotifyResolver.Result.create(NotifyHandling.ALL, notify.accounts()));
     }
   }
 
@@ -86,7 +73,8 @@
       Table<Account.Id, String, PatchSetApproval> pos = HashBasedTable.create();
       Table<Account.Id, String, PatchSetApproval> neg = HashBasedTable.create();
       for (PatchSetApproval ca :
-          args.approvalsUtil.byPatchSet(getChangeData().notes(), getPatchSet().id())) {
+          args.approvalsUtil.byPatchSet(
+              changeEmail.getChangeData().notes(), changeEmail.getPatchSet().id())) {
         Optional<LabelType> lt = labelTypes.byLabel(ca.labelId());
         if (!lt.isPresent()) {
           continue;
@@ -113,7 +101,7 @@
     txt.append(type).append(":\n");
     for (Account.Id id : approvals.rowKeySet()) {
       txt.append("  ");
-      txt.append(getNameFor(id));
+      txt.append(email.getNameFor(id));
       txt.append(": ");
       boolean first = true;
       for (LabelType lt : labelTypes.getLabelTypes()) {
@@ -144,17 +132,24 @@
   }
 
   @Override
-  protected void populateEmailContent() throws EmailException {
-    super.populateEmailContent();
-    addSoyEmailDataParam("approvals", getApprovals());
+  public void populateEmailContent() {
+    email.addSoyEmailDataParam("approvals", getApprovals());
     if (stickyApprovalDiff.isPresent()) {
-      addSoyEmailDataParam("stickyApprovalDiff", stickyApprovalDiff.get());
-      addSoyEmailDataParam("stickyApprovalDiffHtml", getDiffTemplateData(stickyApprovalDiff.get()));
+      email.addSoyEmailDataParam("stickyApprovalDiff", stickyApprovalDiff.get());
+      email.addSoyEmailDataParam(
+          "stickyApprovalDiffHtml", ChangeEmailNew.getDiffTemplateData(stickyApprovalDiff.get()));
     }
 
-    ccAllApprovals();
-    bccStarredBy();
-    includeWatchers(NotifyType.ALL_COMMENTS);
-    includeWatchers(NotifyType.SUBMITTED_CHANGES);
+    changeEmail.addAuthors(RecipientType.TO);
+    changeEmail.ccAllApprovals();
+    changeEmail.bccStarredBy();
+    changeEmail.includeWatchers(NotifyType.ALL_COMMENTS);
+    changeEmail.includeWatchers(NotifyType.SUBMITTED_CHANGES);
+
+    email.appendText(email.textTemplate("Merged"));
+
+    if (email.useHtml()) {
+      email.appendHtml(email.soyHtmlTemplate("MergedHtml"));
+    }
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteVoteOp.java b/java/com/google/gerrit/server/restapi/change/DeleteVoteOp.java
index 3ac4d22..ef1b6f6 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteVoteOp.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteVoteOp.java
@@ -35,9 +35,10 @@
 import com.google.gerrit.server.approval.ApprovalsUtil;
 import com.google.gerrit.server.change.NotifyResolver;
 import com.google.gerrit.server.extensions.events.VoteDeleted;
-import com.google.gerrit.server.mail.send.DeleteVoteSender;
+import com.google.gerrit.server.mail.EmailModule.DeleteVoteChangeEmailFactories;
+import com.google.gerrit.server.mail.send.ChangeEmailNew;
 import com.google.gerrit.server.mail.send.MessageIdGenerator;
-import com.google.gerrit.server.mail.send.ReplyToChangeSender;
+import com.google.gerrit.server.mail.send.OutgoingEmailNew;
 import com.google.gerrit.server.permissions.LabelRemovalPermission;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.DeleteVoteControl;
@@ -76,7 +77,7 @@
   private final PatchSetUtil psUtil;
   private final ChangeMessagesUtil cmUtil;
   private final VoteDeleted voteDeleted;
-  private final DeleteVoteSender.Factory deleteVoteSenderFactory;
+  private final DeleteVoteChangeEmailFactories deleteVoteChangeEmailFactories;
 
   private final DeleteVoteControl deleteVoteControl;
   private final RemoveReviewerControl removeReviewerControl;
@@ -99,7 +100,7 @@
       PatchSetUtil psUtil,
       ChangeMessagesUtil cmUtil,
       VoteDeleted voteDeleted,
-      DeleteVoteSender.Factory deleteVoteSenderFactory,
+      DeleteVoteChangeEmailFactories deleteVoteChangeEmailFactories,
       DeleteVoteControl deleteVoteControl,
       MessageIdGenerator messageIdGenerator,
       RemoveReviewerControl removeReviewerControl,
@@ -113,7 +114,7 @@
     this.psUtil = psUtil;
     this.cmUtil = cmUtil;
     this.voteDeleted = voteDeleted;
-    this.deleteVoteSenderFactory = deleteVoteSenderFactory;
+    this.deleteVoteChangeEmailFactories = deleteVoteChangeEmailFactories;
     this.deleteVoteControl = deleteVoteControl;
     this.removeReviewerControl = removeReviewerControl;
     this.messageIdGenerator = messageIdGenerator;
@@ -187,17 +188,18 @@
 
     CurrentUser user = ctx.getUser();
     try {
+      ChangeEmailNew changeEmail =
+          deleteVoteChangeEmailFactories.createChangeEmail(ctx.getProject(), change.getId());
+      changeEmail.setChangeMessage(mailMessage, ctx.getWhen());
+      OutgoingEmailNew outgoingEmail = deleteVoteChangeEmailFactories.createEmail(changeEmail);
       NotifyResolver.Result notify = ctx.getNotify(change.getId());
-      ReplyToChangeSender emailSender =
-          deleteVoteSenderFactory.create(ctx.getProject(), change.getId());
       if (user.isIdentifiedUser()) {
-        emailSender.setFrom(user.getAccountId());
+        outgoingEmail.setFrom(user.getAccountId());
       }
-      emailSender.setChangeMessage(mailMessage, ctx.getWhen());
-      emailSender.setNotify(notify);
-      emailSender.setMessageId(
+      outgoingEmail.setNotify(notify);
+      outgoingEmail.setMessageId(
           messageIdGenerator.fromChangeUpdate(ctx.getRepoView(), change.currentPatchSetId()));
-      emailSender.send();
+      outgoingEmail.send();
     } catch (Exception e) {
       logger.atSevere().withCause(e).log("Cannot email update for change %s", change.getId());
     }
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index e586216..0116804 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -223,6 +223,8 @@
     info.mergeabilityComputationBehavior =
         MergeabilityComputationBehavior.fromConfig(config).name();
     info.enableRobotComments = toBoolean(config.getBoolean("change", "enableRobotComments", true));
+    info.conflictsPredicateEnabled =
+        toBoolean(config.getBoolean("change", "conflictsPredicateEnabled", true));
     return info;
   }
 
diff --git a/java/com/google/gerrit/server/submit/EmailMerge.java b/java/com/google/gerrit/server/submit/EmailMerge.java
index 7aa3716..1cac440 100644
--- a/java/com/google/gerrit/server/submit/EmailMerge.java
+++ b/java/com/google/gerrit/server/submit/EmailMerge.java
@@ -23,8 +23,10 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.NotifyResolver;
 import com.google.gerrit.server.config.SendEmailExecutor;
-import com.google.gerrit.server.mail.send.MergedSender;
+import com.google.gerrit.server.mail.EmailModule.MergedChangeEmailFactories;
+import com.google.gerrit.server.mail.send.ChangeEmailNew;
 import com.google.gerrit.server.mail.send.MessageIdGenerator;
+import com.google.gerrit.server.mail.send.OutgoingEmailNew;
 import com.google.gerrit.server.update.RepoView;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
@@ -49,7 +51,7 @@
   }
 
   private final ExecutorService sendEmailsExecutor;
-  private final MergedSender.Factory mergedSenderFactory;
+  private final MergedChangeEmailFactories mergedChangeEmailFactories;
   private final ThreadLocalRequestContext requestContext;
   private final MessageIdGenerator messageIdGenerator;
 
@@ -63,7 +65,7 @@
   @Inject
   EmailMerge(
       @SendEmailExecutor ExecutorService executor,
-      MergedSender.Factory mergedSenderFactory,
+      MergedChangeEmailFactories mergedChangeEmailFactories,
       ThreadLocalRequestContext requestContext,
       MessageIdGenerator messageIdGenerator,
       @Assisted Project.NameKey project,
@@ -73,7 +75,7 @@
       @Assisted RepoView repoView,
       @Assisted String stickyApprovalDiff) {
     this.sendEmailsExecutor = executor;
-    this.mergedSenderFactory = mergedSenderFactory;
+    this.mergedChangeEmailFactories = mergedChangeEmailFactories;
     this.requestContext = requestContext;
     this.messageIdGenerator = messageIdGenerator;
     this.project = project;
@@ -93,18 +95,19 @@
   public void run() {
     RequestContext old = requestContext.setContext(this);
     try {
-      MergedSender emailSender =
-          mergedSenderFactory.create(
+      ChangeEmailNew changeEmail =
+          mergedChangeEmailFactories.createChangeEmail(
               project,
               change.getId(),
               Optional.ofNullable(Strings.emptyToNull(stickyApprovalDiff)));
+      OutgoingEmailNew outgoingEmail = mergedChangeEmailFactories.createEmail(changeEmail);
       if (submitter != null) {
-        emailSender.setFrom(submitter.getAccountId());
+        outgoingEmail.setFrom(submitter.getAccountId());
       }
-      emailSender.setNotify(notify);
-      emailSender.setMessageId(
+      outgoingEmail.setNotify(notify);
+      outgoingEmail.setMessageId(
           messageIdGenerator.fromChangeUpdate(repoView, change.currentPatchSetId()));
-      emailSender.send();
+      outgoingEmail.send();
     } catch (Exception e) {
       logger.atSevere().withCause(e).log("Cannot email merged notification for %s", change.getId());
     } finally {
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor
index 1a7835b..e36ecdd 160000
--- a/plugins/codemirror-editor
+++ b/plugins/codemirror-editor
@@ -1 +1 @@
-Subproject commit 1a7835bdfa55d60c717bad20b943e34499da0016
+Subproject commit e36ecddbc99dc434f3259814d9d84fa96e509772
diff --git a/plugins/package.json b/plugins/package.json
index 612062b..5323c9c 100644
--- a/plugins/package.json
+++ b/plugins/package.json
@@ -9,28 +9,28 @@
     "@open-wc/testing": "^3.1.6",
     "@web/dev-server-esbuild": "^0.3.2",
     "@web/test-runner": "^0.14.0",
-    "@codemirror/autocomplete": "^6.4.2",
-    "@codemirror/commands": "^6.2.1",
-    "@codemirror/legacy-modes": "^6.3.1",
+    "@codemirror/autocomplete": "^6.5.1",
+    "@codemirror/commands": "^6.2.3",
+    "@codemirror/legacy-modes": "^6.3.2",
     "@codemirror/lang-cpp": "^6.0.2",
-    "@codemirror/lang-css": "^6.0.2",
-    "@codemirror/lang-html": "^6.4.2",
+    "@codemirror/lang-css": "^6.1.1",
+    "@codemirror/lang-html": "^6.4.3",
     "@codemirror/lang-java": "^6.0.1",
-    "@codemirror/lang-javascript": "^6.1.4",
+    "@codemirror/lang-javascript": "^6.1.7",
     "@codemirror/lang-json": "^6.0.1",
-    "@codemirror/lang-markdown": "^6.1.0",
+    "@codemirror/lang-markdown": "^6.1.1",
     "@codemirror/lang-php": "^6.0.1",
-    "@codemirror/lang-python": "^6.1.1",
+    "@codemirror/lang-python": "^6.1.2",
     "@codemirror/lang-rust": "^6.0.1",
-    "@codemirror/lang-sql": "^6.4.0",
+    "@codemirror/lang-sass": "^6.0.1",
+    "@codemirror/lang-sql": "^6.4.1",
     "@codemirror/lang-xml": "^6.0.2",
-    "@codemirror/language": "^6.2.0",
-    "@codemirror/language-data": "^6.1.0",
-    "@codemirror/lint": "^6.1.1",
-    "@codemirror/search": "^6.2.3",
+    "@codemirror/language": "^6.6.0",
+    "@codemirror/language-data": "^6.3.0",
+    "@codemirror/lint": "^6.2.1",
+    "@codemirror/search": "^6.3.0",
     "@codemirror/state": "^6.2.0",
-    "@codemirror/theme-one-dark": "^6.1.1",
-    "@codemirror/view": "^6.9.1",
+    "@codemirror/view": "^6.9.6",
     "lit": "^2.2.3",
     "rxjs": "^6.6.7",
     "sinon": "^13.0.0"
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index 158435d..9321303 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 158435d2aa9f8729e4e78835969e54701af9203b
+Subproject commit 9321303265fcab2ff7f764a444f8c23915747638
diff --git a/plugins/yarn.lock b/plugins/yarn.lock
index 3156f4c..cde18ce 100644
--- a/plugins/yarn.lock
+++ b/plugins/yarn.lock
@@ -23,26 +23,37 @@
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.3.2", "@codemirror/autocomplete@^6.4.2":
-  version "6.4.2"
-  resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.4.2.tgz#938b25223bd21f97b2a6d85474643355f98b505b"
-  integrity sha512-8WE2xp+D0MpWEv5lZ6zPW1/tf4AGb358T5GWYiKEuCP8MvFfT3tH2mIF9Y2yr2e3KbHuSvsVhosiEyqCpiJhZQ==
+"@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.3.2", "@codemirror/autocomplete@^6.5.1":
+  version "6.5.1"
+  resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.5.1.tgz#539cfff291dbffd3841cba078b222cea28ff7eda"
+  integrity sha512-/Sv9yJmqyILbZ26U4LBHnAtbikuVxWUp+rQ8BXuRGtxZfbfKOY/WPbsUtvSP2h0ZUZMlkxV/hqbKRFzowlA6xw==
   dependencies:
     "@codemirror/language" "^6.0.0"
     "@codemirror/state" "^6.0.0"
     "@codemirror/view" "^6.6.0"
     "@lezer/common" "^1.0.0"
 
-"@codemirror/commands@^6.2.1":
-  version "6.2.1"
-  resolved "https://registry.yarnpkg.com/@codemirror/commands/-/commands-6.2.1.tgz#ab5e979ad1458bbe395bf69ac601f461ac73cf08"
-  integrity sha512-FFiNKGuHA5O8uC6IJE5apI5rT9gyjlw4whqy4vlcX0wE/myxL6P1s0upwDhY4HtMWLOwzwsp0ap3bjdQhvfDOA==
+"@codemirror/commands@^6.2.3":
+  version "6.2.3"
+  resolved "https://registry.yarnpkg.com/@codemirror/commands/-/commands-6.2.3.tgz#ec476fd588f7a4333f54584d4783dd3862befe3b"
+  integrity sha512-9uf0g9m2wZyrIim1SavcxMdwsu8wc/y5uSw6JRUBYIGWrN+RY4vSru/BqB+MyNWqx4C2uRhQ/Kh7Pw8lAyT3qQ==
   dependencies:
     "@codemirror/language" "^6.0.0"
     "@codemirror/state" "^6.2.0"
     "@codemirror/view" "^6.0.0"
     "@lezer/common" "^1.0.0"
 
+"@codemirror/lang-angular@^0.1.0":
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/@codemirror/lang-angular/-/lang-angular-0.1.0.tgz#1054747c8196357a2aee2b9c36f0f6de9a6ffef9"
+  integrity sha512-vTjoHjzJmLrrMFmf/tojwp+O0P+R9mgWtjjaKDNDoY58PzOPg7ldMEBqIzABBc+/2mYPD85SG7O5byfBxc83eA==
+  dependencies:
+    "@codemirror/lang-html" "^6.0.0"
+    "@codemirror/lang-javascript" "^6.1.2"
+    "@codemirror/language" "^6.0.0"
+    "@lezer/common" "^1.0.0"
+    "@lezer/highlight" "^1.0.0"
+
 "@codemirror/lang-cpp@^6.0.0", "@codemirror/lang-cpp@^6.0.2":
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/@codemirror/lang-cpp/-/lang-cpp-6.0.2.tgz#076c98340c3beabde016d7d83e08eebe17254ef9"
@@ -51,20 +62,20 @@
     "@codemirror/language" "^6.0.0"
     "@lezer/cpp" "^1.0.0"
 
-"@codemirror/lang-css@^6.0.0", "@codemirror/lang-css@^6.0.2":
-  version "6.0.2"
-  resolved "https://registry.yarnpkg.com/@codemirror/lang-css/-/lang-css-6.0.2.tgz#b286d0226755a751f60599e1e2969d351aebbd4c"
-  integrity sha512-4V4zmUOl2Glx0GWw0HiO1oGD4zvMlIQ3zx5hXOE6ipCjhohig2bhWRAasrZylH9pRNTcl1VMa59Lsl8lZWlTzw==
+"@codemirror/lang-css@^6.0.0", "@codemirror/lang-css@^6.1.1":
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/@codemirror/lang-css/-/lang-css-6.1.1.tgz#8c4414d399df14e796f9891a8152e411264ef535"
+  integrity sha512-P6jdNEHyRcqqDgbvHYyC9Wxkek0rnG3a9aVSRi4a7WrjPbQtBTaOmvYpXmm13zZMAatO4Oqpac+0QZs7sy+LnQ==
   dependencies:
     "@codemirror/autocomplete" "^6.0.0"
     "@codemirror/language" "^6.0.0"
     "@codemirror/state" "^6.0.0"
     "@lezer/css" "^1.0.0"
 
-"@codemirror/lang-html@^6.0.0", "@codemirror/lang-html@^6.4.2":
-  version "6.4.2"
-  resolved "https://registry.yarnpkg.com/@codemirror/lang-html/-/lang-html-6.4.2.tgz#3c7117e45bae009bc7bc08eef8a79b5d05930d83"
-  integrity sha512-bqCBASkteKySwtIbiV/WCtGnn/khLRbbiV5TE+d9S9eQJD7BA4c5dTRm2b3bVmSpilff5EYxvB4PQaZzM/7cNw==
+"@codemirror/lang-html@^6.0.0", "@codemirror/lang-html@^6.4.3":
+  version "6.4.3"
+  resolved "https://registry.yarnpkg.com/@codemirror/lang-html/-/lang-html-6.4.3.tgz#dec78f76d9d0261cbe9f2a3a247a1b546327f700"
+  integrity sha512-VKzQXEC8nL69Jg2hvAFPBwOdZNvL8tMFOrdFwWpU+wc6a6KEkndJ/19R5xSaglNX6v2bttm8uIEFYxdQDcIZVQ==
   dependencies:
     "@codemirror/autocomplete" "^6.0.0"
     "@codemirror/lang-css" "^6.0.0"
@@ -84,10 +95,10 @@
     "@codemirror/language" "^6.0.0"
     "@lezer/java" "^1.0.0"
 
-"@codemirror/lang-javascript@^6.0.0", "@codemirror/lang-javascript@^6.1.4":
-  version "6.1.4"
-  resolved "https://registry.yarnpkg.com/@codemirror/lang-javascript/-/lang-javascript-6.1.4.tgz#8a41f4d213e1143b4eef6f65f8b77b349aaf894c"
-  integrity sha512-OxLf7OfOZBTMRMi6BO/F72MNGmgOd9B0vetOLvHsDACFXayBzW8fm8aWnDM0yuy68wTK03MBf4HbjSBNRG5q7A==
+"@codemirror/lang-javascript@^6.0.0", "@codemirror/lang-javascript@^6.1.2", "@codemirror/lang-javascript@^6.1.7":
+  version "6.1.7"
+  resolved "https://registry.yarnpkg.com/@codemirror/lang-javascript/-/lang-javascript-6.1.7.tgz#e39fb9757b1cf47de432e4244d18ca5284a73a58"
+  integrity sha512-KXKqxlZ4W6t5I7i2ScmITUD3f/F5Cllk3kj0De9P9mFeYVfhOVOWuDLgYiLpk357u7Xh4dhqjJAnsNPPoTLghQ==
   dependencies:
     "@codemirror/autocomplete" "^6.0.0"
     "@codemirror/language" "^6.6.0"
@@ -105,10 +116,10 @@
     "@codemirror/language" "^6.0.0"
     "@lezer/json" "^1.0.0"
 
-"@codemirror/lang-markdown@^6.0.0", "@codemirror/lang-markdown@^6.1.0":
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/@codemirror/lang-markdown/-/lang-markdown-6.1.0.tgz#218a6ddcd6ea25039e143058fbc95cfd82f003b4"
-  integrity sha512-HQDJg1Js19fPKKsI3Rp1X0J6mxyrRy2NX6+Evh0+/jGm6IZHL5ygMGKBYNWKXodoDQFvgdofNRG33gWOwV59Ag==
+"@codemirror/lang-markdown@^6.0.0", "@codemirror/lang-markdown@^6.1.1":
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/@codemirror/lang-markdown/-/lang-markdown-6.1.1.tgz#ff3cdd339c277f6a02d08eb12f1090977873e771"
+  integrity sha512-n87Ms6Y5UYb1UkFu8sRzTLfq/yyF1y2AYiWvaVdbBQi5WDj1tFk5N+AKA+WC0Jcjc1VxvrCCM0iizjdYYi9sFQ==
   dependencies:
     "@codemirror/lang-html" "^6.0.0"
     "@codemirror/language" "^6.3.0"
@@ -128,7 +139,7 @@
     "@lezer/common" "^1.0.0"
     "@lezer/php" "^1.0.0"
 
-"@codemirror/lang-python@^6.0.0", "@codemirror/lang-python@^6.1.1":
+"@codemirror/lang-python@^6.0.0", "@codemirror/lang-python@^6.1.2":
   version "6.1.2"
   resolved "https://registry.yarnpkg.com/@codemirror/lang-python/-/lang-python-6.1.2.tgz#cabb57529679981f170491833dbf798576e7ab18"
   integrity sha512-nbQfifLBZstpt6Oo4XxA2LOzlSp4b/7Bc5cmodG1R+Cs5PLLCTUvsMNWDnziiCfTOG/SW1rVzXq/GbIr6WXlcw==
@@ -145,10 +156,21 @@
     "@codemirror/language" "^6.0.0"
     "@lezer/rust" "^1.0.0"
 
-"@codemirror/lang-sql@^6.0.0", "@codemirror/lang-sql@^6.4.0":
-  version "6.4.0"
-  resolved "https://registry.yarnpkg.com/@codemirror/lang-sql/-/lang-sql-6.4.0.tgz#f9303e511fb9511884f90043e354d5df3bd4b032"
-  integrity sha512-UWGK1+zc9+JtkiT+XxHByp4N6VLgLvC2x0tIudrJG26gyNtn0hWOVoB0A8kh/NABPWkKl3tLWDYf2qOBJS9Zdw==
+"@codemirror/lang-sass@^6.0.0", "@codemirror/lang-sass@^6.0.1":
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/@codemirror/lang-sass/-/lang-sass-6.0.1.tgz#e390f427c8601175f155046e142371c3c4fb718c"
+  integrity sha512-USy9zqtdLYxSuqq0s4peMoQi+BDzyOyO7chUzli+X2xVCjmBhc3CsWQ4kkDU0NYtCHHFQRkcFO8770eaOwZqfw==
+  dependencies:
+    "@codemirror/lang-css" "^6.1.1"
+    "@codemirror/language" "^6.0.0"
+    "@codemirror/state" "^6.0.0"
+    "@lezer/common" "^1.0.2"
+    "@lezer/sass" "^1.0.0"
+
+"@codemirror/lang-sql@^6.0.0", "@codemirror/lang-sql@^6.4.1":
+  version "6.4.1"
+  resolved "https://registry.yarnpkg.com/@codemirror/lang-sql/-/lang-sql-6.4.1.tgz#e680fe8c12e5902a29fd952207bf454ae02b3bdc"
+  integrity sha512-PFB56L+A0WGY35uRya+Trt5g19V9k2V9X3c55xoFW4RgiATr/yLqWsbbnEsdxuMn5tLpuikp7Kmj9smRsqBXAg==
   dependencies:
     "@codemirror/autocomplete" "^6.0.0"
     "@codemirror/language" "^6.0.0"
@@ -156,6 +178,18 @@
     "@lezer/highlight" "^1.0.0"
     "@lezer/lr" "^1.0.0"
 
+"@codemirror/lang-vue@^0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@codemirror/lang-vue/-/lang-vue-0.1.1.tgz#79567fb3be3f411354cd135af59d67f956cdb042"
+  integrity sha512-GIfc/MemCFKUdNSYGTFZDN8XsD2z0DUY7DgrK34on0dzdZ/CawZbi+SADYfVzWoPPdxngHzLhqlR5pSOqyPCvA==
+  dependencies:
+    "@codemirror/lang-html" "^6.0.0"
+    "@codemirror/lang-javascript" "^6.1.2"
+    "@codemirror/language" "^6.0.0"
+    "@lezer/common" "^1.0.0"
+    "@lezer/highlight" "^1.0.0"
+    "@lezer/lr" "^1.3.1"
+
 "@codemirror/lang-wast@^6.0.0":
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/@codemirror/lang-wast/-/lang-wast-6.0.1.tgz#c15bec84548a5e9b0a43fa69fb63631d087d6047"
@@ -176,11 +210,12 @@
     "@lezer/common" "^1.0.0"
     "@lezer/xml" "^1.0.0"
 
-"@codemirror/language-data@^6.1.0":
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/@codemirror/language-data/-/language-data-6.1.0.tgz#479eff66289a6453493f7c8213d7b2ceb95c89f6"
-  integrity sha512-g9V23fuLRI9AEbpM6bDy1oquqgpFlIDHTihUhL21NPmxp+x67ZJbsKk+V71W7/Bj8SCqEO1PtqQA/tDGgt1nfw==
+"@codemirror/language-data@^6.3.0":
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/@codemirror/language-data/-/language-data-6.3.0.tgz#058365fc2e857eb48810ed92134ee469d9a9bba6"
+  integrity sha512-D9tOZS38mK59jDs1Flqe8GgCdUAYI339SqBdwHJZwxgyXHsBc8RIhAlz2oXWGpvZeP/kVHy9LVfoBFgO02mx7w==
   dependencies:
+    "@codemirror/lang-angular" "^0.1.0"
     "@codemirror/lang-cpp" "^6.0.0"
     "@codemirror/lang-css" "^6.0.0"
     "@codemirror/lang-html" "^6.0.0"
@@ -191,13 +226,15 @@
     "@codemirror/lang-php" "^6.0.0"
     "@codemirror/lang-python" "^6.0.0"
     "@codemirror/lang-rust" "^6.0.0"
+    "@codemirror/lang-sass" "^6.0.0"
     "@codemirror/lang-sql" "^6.0.0"
+    "@codemirror/lang-vue" "^0.1.1"
     "@codemirror/lang-wast" "^6.0.0"
     "@codemirror/lang-xml" "^6.0.0"
     "@codemirror/language" "^6.0.0"
     "@codemirror/legacy-modes" "^6.1.0"
 
-"@codemirror/language@^6.0.0", "@codemirror/language@^6.2.0", "@codemirror/language@^6.3.0", "@codemirror/language@^6.4.0", "@codemirror/language@^6.6.0":
+"@codemirror/language@^6.0.0", "@codemirror/language@^6.3.0", "@codemirror/language@^6.4.0", "@codemirror/language@^6.6.0":
   version "6.6.0"
   resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.6.0.tgz#2204407174a38a68053715c19e28ad61f491779f"
   integrity sha512-cwUd6lzt3MfNYOobdjf14ZkLbJcnv4WtndYaoBkbor/vF+rCNguMPK0IRtvZJG4dsWiaWPcK8x1VijhvSxnstg==
@@ -209,26 +246,26 @@
     "@lezer/lr" "^1.0.0"
     style-mod "^4.0.0"
 
-"@codemirror/legacy-modes@^6.1.0", "@codemirror/legacy-modes@^6.3.1":
-  version "6.3.1"
-  resolved "https://registry.yarnpkg.com/@codemirror/legacy-modes/-/legacy-modes-6.3.1.tgz#77ab3f3db1ce3e47aad7a5baac3a4b12844734a5"
-  integrity sha512-icXmCs4Mhst2F8mE0TNpmG6l7YTj1uxam3AbZaFaabINH5oWAdg2CfR/PVi+d/rqxJ+TuTnvkKK5GILHrNThtw==
+"@codemirror/legacy-modes@^6.1.0", "@codemirror/legacy-modes@^6.3.2":
+  version "6.3.2"
+  resolved "https://registry.yarnpkg.com/@codemirror/legacy-modes/-/legacy-modes-6.3.2.tgz#d5616b453f38866717437b51c16bde1ae3f011ec"
+  integrity sha512-ki5sqNKWzKi5AKvpVE6Cna4Q+SgxYuYVLAZFSsMjGBWx5qSVa+D+xipix65GS3f2syTfAD9pXKMX4i4p49eneQ==
   dependencies:
     "@codemirror/language" "^6.0.0"
 
-"@codemirror/lint@^6.0.0", "@codemirror/lint@^6.1.1":
-  version "6.2.0"
-  resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-6.2.0.tgz#25cdab7425fcda1b38a9d63f230f833c8b6b369f"
-  integrity sha512-KVCECmR2fFeYBr1ZXDVue7x3q5PMI0PzcIbA+zKufnkniMBo1325t0h1jM85AKp8l3tj67LRxVpZfgDxEXlQkg==
+"@codemirror/lint@^6.0.0", "@codemirror/lint@^6.2.1":
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-6.2.1.tgz#654581d8cc293c315ecfa5c9d61d78c52bbd9ccd"
+  integrity sha512-y1muai5U/uUPAGRyHMx9mHuHLypPcHWxzlZGknp/U5Mdb5Ol8Q5ZLp67UqyTbNFJJ3unVxZ8iX3g1fMN79S1JQ==
   dependencies:
     "@codemirror/state" "^6.0.0"
     "@codemirror/view" "^6.0.0"
     crelt "^1.0.5"
 
-"@codemirror/search@^6.2.3":
-  version "6.2.3"
-  resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.2.3.tgz#fab933fef1b1de8ef40cda275c73d9ac7a1ff40f"
-  integrity sha512-V9n9233lopQhB1dyjsBK2Wc1i+8hcCqxl1wQ46c5HWWLePoe4FluV3TGHoZ04rBRlGjNyz9DTmpJErig8UE4jw==
+"@codemirror/search@^6.3.0":
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.3.0.tgz#d48b2d636fc7474613e240ba8e56c4c5e6e51822"
+  integrity sha512-rBhZxzT34CarfhgCZGhaLBScABDN3iqJxixzNuINp9lrb3lzm0nTpR77G1VrxGO3HOGK7j62jcJftQM7eCOIuw==
   dependencies:
     "@codemirror/state" "^6.0.0"
     "@codemirror/view" "^6.0.0"
@@ -239,20 +276,10 @@
   resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.2.0.tgz#a0fb08403ced8c2a68d1d0acee926bd20be922f2"
   integrity sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==
 
-"@codemirror/theme-one-dark@^6.1.1":
-  version "6.1.1"
-  resolved "https://registry.yarnpkg.com/@codemirror/theme-one-dark/-/theme-one-dark-6.1.1.tgz#76600555cbb314c495216f018f75b0c28daff158"
-  integrity sha512-+CfzmScfJuD6uDF5bHJkAjWTQ2QAAHxODCPxUEgcImDYcJLT+4l5vLnBHmDVv46kCC5uUJGMrBJct2Z6JbvqyQ==
-  dependencies:
-    "@codemirror/language" "^6.0.0"
-    "@codemirror/state" "^6.0.0"
-    "@codemirror/view" "^6.0.0"
-    "@lezer/highlight" "^1.0.0"
-
-"@codemirror/view@^6.0.0", "@codemirror/view@^6.2.2", "@codemirror/view@^6.6.0", "@codemirror/view@^6.9.1":
-  version "6.9.1"
-  resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.9.1.tgz#2ce4c528974b6172a5a4a738b7b0a0f04a4c1140"
-  integrity sha512-bzfSjJn9dAADVpabLKWKNmMG4ibyTV2e3eOGowjElNPTdTkSbi6ixPYHm2u0ADcETfKsi2/R84Rkmi91dH9yEg==
+"@codemirror/view@^6.0.0", "@codemirror/view@^6.2.2", "@codemirror/view@^6.6.0", "@codemirror/view@^6.9.6":
+  version "6.9.6"
+  resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.9.6.tgz#6bf302608e03566688d5138c50b38d07356e11a1"
+  integrity sha512-g68PxS3RkHpxfYS6DTWCy1jeA5/oHzmdWjMVPkOzqQyxpElHEcPncUd4EeMVSa4okt0sS3hNXVaRnJqO/7MeJw==
   dependencies:
     "@codemirror/state" "^6.1.4"
     style-mod "^4.0.0"
@@ -275,7 +302,7 @@
   resolved "https://registry.yarnpkg.com/@gerritcodereview/typescript-api/-/typescript-api-3.8.0.tgz#2e418b814d7451c40365b2dc4f88e9965ece0769"
   integrity sha512-wUkIWUx99Rj1vxRYQISxyzN0nplqu7t5sRDyJ8R3yNNkvALQAMC6Whj63qzCsZsymVFzC5up3y+ZVxaeh7b+xA==
 
-"@lezer/common@^1.0.0":
+"@lezer/common@^1.0.0", "@lezer/common@^1.0.2":
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.0.2.tgz#8fb9b86bdaa2ece57e7d59e5ffbcb37d71815087"
   integrity sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng==
@@ -297,16 +324,16 @@
     "@lezer/lr" "^1.0.0"
 
 "@lezer/highlight@^1.0.0", "@lezer/highlight@^1.1.3":
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.1.3.tgz#bf5a36c2ee227f526d74997ac91f7777e29bd25d"
-  integrity sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.1.4.tgz#98ed821e89f72981b7ba590474e6ee86c8185619"
+  integrity sha512-IECkFmw2l7sFcYXrV8iT9GeY4W0fU4CxX0WMwhmhMIVjoDdD1Hr6q3G2NqVtLg/yVe5n7i4menG3tJ2r4eCrPQ==
   dependencies:
     "@lezer/common" "^1.0.0"
 
 "@lezer/html@^1.3.0":
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/@lezer/html/-/html-1.3.3.tgz#2eddae2ad000f9b184d9fc4686394d0fa0849993"
-  integrity sha512-04Fyvu66DjV2EjhDIG1kfDdktn5Pfw56SXPrzKNQH5B2m7BDfc6bDsz+ZJG8dLS3kIPEKbyyq1Sm2/kjeG0+AA==
+  version "1.3.4"
+  resolved "https://registry.yarnpkg.com/@lezer/html/-/html-1.3.4.tgz#7a5c5498dae6c93aee3de208bfb01aa3a0a932e3"
+  integrity sha512-HdJYMVZcT4YsMo7lW3ipL4NoyS2T67kMPuSVS5TgLGqmaCjEU/D6xv7zsa1ktvTK5lwk7zzF1e3eU6gBZIPm5g==
   dependencies:
     "@lezer/common" "^1.0.0"
     "@lezer/highlight" "^1.0.0"
@@ -321,9 +348,9 @@
     "@lezer/lr" "^1.0.0"
 
 "@lezer/javascript@^1.0.0":
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/@lezer/javascript/-/javascript-1.4.1.tgz#97a15042c76b5979af6a069fac83cf6485628cbf"
-  integrity sha512-Hqx36DJeYhKtdpc7wBYPR0XF56ZzIp0IkMO/zNNj80xcaFOV4Oj/P7TQc/8k2TxNhzl7tV5tXS8ZOCPbT4L3nA==
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/@lezer/javascript/-/javascript-1.4.2.tgz#aee4e18f573b496756294c558965d36320bfca47"
+  integrity sha512-77qdAD4zanmImPiAu4ibrMUzRc79UHoccdPa+Ey5iwS891TAkhnMAodUe17T7zV7tnF7e9HXM0pfmjoGEhrppg==
   dependencies:
     "@lezer/highlight" "^1.1.3"
     "@lezer/lr" "^1.3.0"
@@ -336,7 +363,7 @@
     "@lezer/highlight" "^1.0.0"
     "@lezer/lr" "^1.0.0"
 
-"@lezer/lr@^1.0.0", "@lezer/lr@^1.1.0", "@lezer/lr@^1.3.0":
+"@lezer/lr@^1.0.0", "@lezer/lr@^1.1.0", "@lezer/lr@^1.3.0", "@lezer/lr@^1.3.1":
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.3.3.tgz#0ac6c889f1235874f33c45a1b9785d7054f60708"
   integrity sha512-JPQe3mwJlzEVqy67iQiiGozhcngbO8QBgpqZM6oL1Wj/dXckrEexpBLeFkq0edtW5IqnPRFxA24BHJni8Js69w==
@@ -360,9 +387,9 @@
     "@lezer/lr" "^1.1.0"
 
 "@lezer/python@^1.0.0":
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/@lezer/python/-/python-1.1.2.tgz#1db9faf182ca04815b2c7e0f1ce37104b2564ec5"
-  integrity sha512-ukm4VhDasFX7/9BUYHTyUNXH0xQ5B7/QBlZD8P51+dh6GtXRSCQqNxloez5d+MxVb2Sg+31S8E/33qoFREfkpA==
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/@lezer/python/-/python-1.1.4.tgz#6ef58ff965286150fea9f2db776944a1d69cd9b9"
+  integrity sha512-x82XgYxqqX0Yiw7uIemQJ3z2QyQme5BYpectkPfNg99OQrakqfwqVolqEVIrsj4QO9rVDLFZZ49J0Vbne7UbAA==
   dependencies:
     "@lezer/highlight" "^1.0.0"
     "@lezer/lr" "^1.0.0"
@@ -375,6 +402,14 @@
     "@lezer/highlight" "^1.0.0"
     "@lezer/lr" "^1.0.0"
 
+"@lezer/sass@^1.0.0":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@lezer/sass/-/sass-1.0.1.tgz#c0ec3ece28b04e92437a75ac4a806367e5cb6fd4"
+  integrity sha512-S/aYAzABzMqWLfKKqV89pCWME4yjZYC6xzD02l44wbmb0sHxmN9/8aE4GULrKFzFaGazHdXcGEbPZ4zzB6yqwQ==
+  dependencies:
+    "@lezer/highlight" "^1.0.0"
+    "@lezer/lr" "^1.0.0"
+
 "@lezer/xml@^1.0.0":
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/@lezer/xml/-/xml-1.0.1.tgz#c4c738a407db610f0e9c59d0e9b16607cd029591"
@@ -2598,9 +2633,9 @@
     ansi-regex "^5.0.1"
 
 style-mod@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/style-mod/-/style-mod-4.0.0.tgz#97e7c2d68b592975f2ca7a63d0dd6fcacfe35a01"
-  integrity sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/style-mod/-/style-mod-4.0.3.tgz#136c4abc905f82a866a18b39df4dc08ec762b1ad"
+  integrity sha512-78Jv8kYJdjbvRwwijtCevYADfsI0lGzYJe4mMFdceO8l75DFFDoqBhR1jVDicDRRaX4//g1u9wKeo+ztc2h1Rw==
 
 supports-color@^5.3.0:
   version "5.5.0"
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group.ts
index 5a1ded2..3b2fa81 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group.ts
@@ -441,11 +441,17 @@
   }
 
   startLine(side: Side): LineNumber {
-    if (this.type === GrDiffGroupType.CONTEXT_CONTROL) {
+    // For both CONTEXT_CONTROL groups and SKIP groups the `lines` array will
+    // be empty. So we have to use `lineRange` instead of looking at the first
+    // line.
+    if (this.type === GrDiffGroupType.CONTEXT_CONTROL || this.skip) {
       return side === Side.LEFT
         ? this.lineRange.left.start_line
         : this.lineRange.right.start_line;
     }
+    // For "normal" groups we could also use the `lineRange`, but for FILE or
+    // LOST lines we want to return FILE or LOST. The `lineRange` contains
+    // numbers only.
     return this.lines[0].lineNumber(side);
   }
 
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group_test.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group_test.ts
index 4a19282..06bf92920 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group_test.ts
@@ -275,6 +275,17 @@
       assert.equal(group.startLine(Side.RIGHT), 4);
     });
 
+    test('SKIP', () => {
+      const group = new GrDiffGroup({
+        type: GrDiffGroupType.BOTH,
+        skip: 10,
+        offsetLeft: 3,
+        offsetRight: 6,
+      });
+      assert.equal(group.startLine(Side.LEFT), 3);
+      assert.equal(group.startLine(Side.RIGHT), 6);
+    });
+
     test('FILE', () => {
       const lines: GrDiffLine[] = [];
       lines.push(new GrDiffLine(GrDiffLineType.BOTH, 'FILE', 'FILE'));