Merge "Fix gr-identities "Link Another Identity" button test"
diff --git a/Documentation/pg-plugin-endpoints.txt b/Documentation/pg-plugin-endpoints.txt
index c16d0d4..f2a72f1 100644
--- a/Documentation/pg-plugin-endpoints.txt
+++ b/Documentation/pg-plugin-endpoints.txt
@@ -228,3 +228,13 @@
 +
 current revision displayed, an instance of
 link:rest-api-changes.html#revision-info[RevisionInfo]
+
+=== account-status-icon
+The `account-status-icon` extension point adds an icon to all account chips and
+labels.
+
+In addition to default parameters, the following are available:
+
+* `accountId`
++
+the Id of the account that the status icon should correspond to.
\ No newline at end of file
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index eb77677..ec6c7f1 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -1226,7 +1226,7 @@
                 .submittedTogether(EnumSet.copyOf(submittedTogetherOptions))
                 .changes;
 
-    EnumSet enumSetIncludingNonVisibleChanges =
+    EnumSet<SubmittedTogetherOption> enumSetIncludingNonVisibleChanges =
         submittedTogetherOptions.isEmpty()
             ? EnumSet.of(NON_VISIBLE_CHANGES)
             : EnumSet.copyOf(submittedTogetherOptions);
diff --git a/java/com/google/gerrit/acceptance/SshSessionMina.java b/java/com/google/gerrit/acceptance/SshSessionMina.java
index debd9d8..89096e4 100644
--- a/java/com/google/gerrit/acceptance/SshSessionMina.java
+++ b/java/com/google/gerrit/acceptance/SshSessionMina.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE;
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.nio.file.Files.createTempDirectory;
 
 import com.google.common.io.CharSink;
 import com.google.common.io.Files;
@@ -140,7 +141,7 @@
                   + addr.getPort());
 
       // TODO(davido): Switch to memory only key resolving mode.
-      File userhome = Files.createTempDir();
+      File userhome = createTempDirectory("home-").toFile();
 
       FS fs = FS.DETECTED.setUserHome(userhome);
       File sshDir = new File(userhome, ".ssh");
diff --git a/java/com/google/gerrit/entities/Change.java b/java/com/google/gerrit/entities/Change.java
index 7ca235d..66e1a96 100644
--- a/java/com/google/gerrit/entities/Change.java
+++ b/java/com/google/gerrit/entities/Change.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.entities;
 
-import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.gerrit.entities.RefNames.REFS_CHANGES;
 
 import com.google.auto.value.AutoValue;
@@ -109,18 +108,6 @@
     /**
      * Parse a Change.Id out of a string representation.
      *
-     * @deprecated use {@link #tryParse(String)} instead.
-     */
-    @Deprecated
-    public static Id parse(String str) {
-      Integer id = Ints.tryParse(str);
-      checkArgument(id != null, "invalid change ID: %s", str);
-      return Change.id(id);
-    }
-
-    /**
-     * Parse a Change.Id out of a string representation.
-     *
      * @param str the string to parse
      * @return Optional containing the Change.Id, or {@code Optional.empty()} if str does not
      *     represent a valid Change.Id.
diff --git a/java/com/google/gerrit/httpd/raw/IndexServlet.java b/java/com/google/gerrit/httpd/raw/IndexServlet.java
index c7f239d..fcb821e 100644
--- a/java/com/google/gerrit/httpd/raw/IndexServlet.java
+++ b/java/com/google/gerrit/httpd/raw/IndexServlet.java
@@ -38,6 +38,8 @@
 
 public class IndexServlet extends HttpServlet {
   private static final long serialVersionUID = 1L;
+  private static final String POLY_GERRIT_INDEX_HTML_SOY =
+      "com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy";
 
   @Nullable private final String canonicalUrl;
   @Nullable private final String cdnPath;
@@ -60,7 +62,7 @@
     this.experimentFeatures = experimentFeatures;
     this.soySauce =
         SoyFileSet.builder()
-            .add(Resources.getResource("com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy"))
+            .add(Resources.getResource(POLY_GERRIT_INDEX_HTML_SOY), POLY_GERRIT_INDEX_HTML_SOY)
             .build()
             .compileTemplates();
     this.urlOrdainer =
diff --git a/java/com/google/gerrit/metrics/DisabledMetricMaker.java b/java/com/google/gerrit/metrics/DisabledMetricMaker.java
index 5b340fb..234378b 100644
--- a/java/com/google/gerrit/metrics/DisabledMetricMaker.java
+++ b/java/com/google/gerrit/metrics/DisabledMetricMaker.java
@@ -79,7 +79,7 @@
 
   @Override
   public <F1> Timer1<F1> newTimer(String name, Description desc, Field<F1> field1) {
-    return new Timer1<F1>(name, field1) {
+    return new Timer1<>(name, field1) {
       @Override
       protected void doRecord(F1 field1, long value, TimeUnit unit) {}
 
@@ -91,7 +91,7 @@
   @Override
   public <F1, F2> Timer2<F1, F2> newTimer(
       String name, Description desc, Field<F1> field1, Field<F2> field2) {
-    return new Timer2<F1, F2>(name, field1, field2) {
+    return new Timer2<>(name, field1, field2) {
       @Override
       protected void doRecord(F1 field1, F2 field2, long value, TimeUnit unit) {}
 
@@ -103,7 +103,7 @@
   @Override
   public <F1, F2, F3> Timer3<F1, F2, F3> newTimer(
       String name, Description desc, Field<F1> field1, Field<F2> field2, Field<F3> field3) {
-    return new Timer3<F1, F2, F3>(name, field1, field2, field3) {
+    return new Timer3<>(name, field1, field2, field3) {
       @Override
       protected void doRecord(F1 field1, F2 field2, F3 field3, long value, TimeUnit unit) {}
 
diff --git a/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java b/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java
index b7d535b..36b52e1 100644
--- a/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java
+++ b/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java
@@ -28,7 +28,7 @@
 
   @SuppressWarnings("unchecked")
   Timer1<F1> timer() {
-    return new Timer1<F1>(name, (Field<F1>) fields[0]) {
+    return new Timer1<>(name, (Field<F1>) fields[0]) {
       @Override
       protected void doRecord(F1 field1, long value, TimeUnit unit) {
         total.record(value, unit);
diff --git a/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java b/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java
index dee800e..77ce8cd 100644
--- a/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java
+++ b/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java
@@ -31,7 +31,7 @@
 
   @SuppressWarnings("unchecked")
   <F1, F2> Timer2<F1, F2> timer2() {
-    return new Timer2<F1, F2>(name, (Field<F1>) fields[0], (Field<F2>) fields[1]) {
+    return new Timer2<>(name, (Field<F1>) fields[0], (Field<F2>) fields[1]) {
       @Override
       protected void doRecord(F1 field1, F2 field2, long value, TimeUnit unit) {
         total.record(value, unit);
@@ -47,8 +47,7 @@
 
   @SuppressWarnings("unchecked")
   <F1, F2, F3> Timer3<F1, F2, F3> timer3() {
-    return new Timer3<F1, F2, F3>(
-        name, (Field<F1>) fields[0], (Field<F2>) fields[1], (Field<F3>) fields[2]) {
+    return new Timer3<>(name, (Field<F1>) fields[0], (Field<F2>) fields[1], (Field<F3>) fields[2]) {
       @Override
       protected void doRecord(F1 field1, F2 field2, F3 field3, long value, TimeUnit unit) {
         total.record(value, unit);
diff --git a/java/com/google/gerrit/server/account/AccountsUpdate.java b/java/com/google/gerrit/server/account/AccountsUpdate.java
index 0e643f8..3ee6365 100644
--- a/java/com/google/gerrit/server/account/AccountsUpdate.java
+++ b/java/com/google/gerrit/server/account/AccountsUpdate.java
@@ -583,7 +583,7 @@
     return metaDataUpdate;
   }
 
-  private static void doNothing() {};
+  private static void doNothing() {}
 
   @FunctionalInterface
   private interface ExecutableUpdate {
diff --git a/java/com/google/gerrit/server/account/Realm.java b/java/com/google/gerrit/server/account/Realm.java
index 51c5ecd..ffc95a3 100644
--- a/java/com/google/gerrit/server/account/Realm.java
+++ b/java/com/google/gerrit/server/account/Realm.java
@@ -56,7 +56,14 @@
    */
   Account.Id lookup(String accountName) throws IOException;
 
-  /** Returns true if the account is active. */
+  /**
+   * Returns true if the account is active.
+   *
+   * @throws LoginException thrown if login is required and fails
+   * @throws NamingException may be thrown if the name is invalid
+   * @throws AccountException may be thrown in case the username is ambiguous
+   * @throws IOException thrown in case of IO errors
+   */
   default boolean isActive(@SuppressWarnings("unused") String username)
       throws LoginException, NamingException, AccountException, IOException {
     return true;
@@ -64,7 +71,7 @@
 
   /** Returns true if the account is backed by the realm, false otherwise. */
   default boolean accountBelongsToRealm(
-      @SuppressWarnings("unused") Collection<ExternalId> externalIds) throws IOException {
+      @SuppressWarnings("unused") Collection<ExternalId> externalIds) {
     return false;
   }
 }
diff --git a/java/com/google/gerrit/server/approval/PatchSetApprovalUuidGenerator.java b/java/com/google/gerrit/server/approval/PatchSetApprovalUuidGenerator.java
index e752ec5..128bee4 100644
--- a/java/com/google/gerrit/server/approval/PatchSetApprovalUuidGenerator.java
+++ b/java/com/google/gerrit/server/approval/PatchSetApprovalUuidGenerator.java
@@ -22,17 +22,18 @@
 import java.time.Instant;
 
 /**
- * Generator for {@link PatchSetApproval.UUID}.
+ * Generator for {@link com.google.gerrit.entities.PatchSetApproval.UUID}.
  *
- * <p>Since {@link PatchSetApproval.UUID} must be unique for each granted {@link PatchSetApproval},
- * implementations must generate globally unique UUID for each {@link #get} invocation.
+ * <p>Since {@link com.google.gerrit.entities.PatchSetApproval.UUID} must be unique for each granted
+ * {@link PatchSetApproval}, implementations must generate globally unique UUID for each {@link
+ * #get} invocation.
  */
 @ImplementedBy(PatchSetApprovalUuidGeneratorImpl.class)
 public interface PatchSetApprovalUuidGenerator {
 
   /**
-   * Generates {@link PatchSetApproval.UUID} based on the properties of {@link PatchSetApproval}
-   * that is being granted.
+   * Generates {@link com.google.gerrit.entities.PatchSetApproval.UUID} based on the properties of
+   * {@link PatchSetApproval} that is being granted.
    */
   UUID get(
       PatchSet.Id patchSetId, Account.Id accountId, String label, short value, Instant granted);
diff --git a/java/com/google/gerrit/server/change/IncludedInRefs.java b/java/com/google/gerrit/server/change/IncludedInRefs.java
index f069251..17a0c9d 100644
--- a/java/com/google/gerrit/server/change/IncludedInRefs.java
+++ b/java/com/google/gerrit/server/change/IncludedInRefs.java
@@ -16,11 +16,8 @@
 
 import static java.util.stream.Collectors.toSet;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.entities.Project;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
@@ -28,7 +25,6 @@
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -55,8 +51,7 @@
 
   public Map<String, Set<String>> apply(
       Project.NameKey project, Set<String> commits, Set<String> refNames)
-      throws ResourceConflictException, BadRequestException, IOException,
-          PermissionBackendException, ResourceNotFoundException, AuthException {
+      throws IOException, PermissionBackendException {
     try (Repository repo = repoManager.openRepository(project)) {
       Set<Ref> visibleRefs = getVisibleRefs(repo, refNames, project);
 
@@ -72,7 +67,7 @@
         }
       }
     }
-    return Collections.EMPTY_MAP;
+    return ImmutableMap.of();
   }
 
   private Set<Ref> getVisibleRefs(Repository repo, Set<String> refNames, Project.NameKey project)
diff --git a/java/com/google/gerrit/server/change/RelatedChangesSorter.java b/java/com/google/gerrit/server/change/RelatedChangesSorter.java
index c993c42..719578f 100644
--- a/java/com/google/gerrit/server/change/RelatedChangesSorter.java
+++ b/java/com/google/gerrit/server/change/RelatedChangesSorter.java
@@ -29,7 +29,6 @@
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.server.change.RelatedChangesSorter.PatchSetData;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.permissions.ChangePermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
diff --git a/java/com/google/gerrit/server/git/CodeReviewCommit.java b/java/com/google/gerrit/server/git/CodeReviewCommit.java
index d7538ba..79df21a 100644
--- a/java/com/google/gerrit/server/git/CodeReviewCommit.java
+++ b/java/com/google/gerrit/server/git/CodeReviewCommit.java
@@ -24,6 +24,9 @@
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.submit.CommitMergeStatus;
 import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
 import java.util.Optional;
 import java.util.Set;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -35,7 +38,10 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 
 /** Extended commit entity with code review specific metadata. */
-public class CodeReviewCommit extends RevCommit {
+public class CodeReviewCommit extends RevCommit implements Serializable {
+
+  private static final long serialVersionUID = 1L;
+
   /**
    * Default ordering when merging multiple topologically-equivalent commits.
    *
@@ -126,7 +132,7 @@
    * Message for the status that is returned to the calling user if the status indicates a problem
    * that prevents submit.
    */
-  private Optional<String> statusMessage = Optional.empty();
+  private transient Optional<String> statusMessage = Optional.empty();
 
   /** List of files in this commit that contain Git conflict markers. */
   private ImmutableSet<String> filesWithGitConflicts;
@@ -191,4 +197,22 @@
   public void setNotes(ChangeNotes notes) {
     this.notes = notes;
   }
+
+  /** Custom serialization due to {@link #statusMessage} not being Serializable by default. */
+  private void writeObject(ObjectOutputStream oos) throws IOException {
+    oos.defaultWriteObject();
+    if (this.statusMessage.isPresent()) {
+      oos.writeUTF(this.statusMessage.get());
+    }
+  }
+
+  /** Custom deserialization due to {@link #statusMessage} not being Serializable by default. */
+  private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
+    ois.defaultReadObject();
+    String statusMessage = null;
+    if (ois.available() > 0) {
+      statusMessage = ois.readUTF();
+    }
+    this.statusMessage = Optional.ofNullable(statusMessage);
+  }
 }
diff --git a/java/com/google/gerrit/server/git/MultiProgressMonitor.java b/java/com/google/gerrit/server/git/MultiProgressMonitor.java
index a49260f..2a57d3d 100644
--- a/java/com/google/gerrit/server/git/MultiProgressMonitor.java
+++ b/java/com/google/gerrit/server/git/MultiProgressMonitor.java
@@ -309,7 +309,7 @@
    * calls {@link #end()}, the future has an additional {@code maxInterval} to finish before it is
    * forcefully cancelled and {@link ExecutionException} is thrown.
    *
-   * @see #waitForNonFinalTask(Future, long, TimeUnit)
+   * @see #waitForNonFinalTask(Future, long, TimeUnit, long, TimeUnit)
    * @param workerFuture a future that returns when worker threads are finished.
    * @param taskTimeoutTime overall timeout for the task; the future gets a cancellation signal
    *     after this timeout is exceeded; non-positive values indicate no timeout.
@@ -350,7 +350,7 @@
   /**
    * Wait for a non-final task managed by a {@link Future}, with no timeout.
    *
-   * @see #waitForNonFinalTask(Future, long, TimeUnit)
+   * @see #waitForNonFinalTask(Future, long, TimeUnit, long, TimeUnit)
    */
   public <T> T waitForNonFinalTask(Future<T> workerFuture) {
     try {
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 41f68c8..4e22933 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -1431,6 +1431,12 @@
   private void parseCreate(ReceiveCommand cmd)
       throws PermissionBackendException, NoSuchProjectException, IOException {
     try (TraceTimer traceTimer = newTimer("parseCreate")) {
+      if (repo.resolve(cmd.getRefName()) != null) {
+        reject(
+            cmd,
+            String.format("Cannot create ref '%s' because it already exists.", cmd.getRefName()));
+        return;
+      }
       RevObject obj;
       try {
         obj = receivePack.getRevWalk().parseAny(cmd.getNewId());
diff --git a/java/com/google/gerrit/server/git/receive/ReplaceOp.java b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
index e843dc8..197183f 100644
--- a/java/com/google/gerrit/server/git/receive/ReplaceOp.java
+++ b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
@@ -406,8 +406,7 @@
     return input;
   }
 
-  private String insertChangeMessage(ChangeUpdate update, ChangeContext ctx, String reviewMessage)
-      throws IOException {
+  private String insertChangeMessage(ChangeUpdate update, ChangeContext ctx, String reviewMessage) {
     String approvalMessage =
         ApprovalsUtil.renderMessageWithApprovals(
             patchSetId.get(), approvals, scanLabels(ctx, approvals));
@@ -451,8 +450,8 @@
     }
   }
 
-  private Map<String, PatchSetApproval> scanLabels(ChangeContext ctx, Map<String, Short> approvals)
-      throws IOException {
+  private Map<String, PatchSetApproval> scanLabels(
+      ChangeContext ctx, Map<String, Short> approvals) {
     Map<String, PatchSetApproval> current = new HashMap<>();
     // We optimize here and only retrieve current when approvals provided
     if (!approvals.isEmpty()) {
diff --git a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
index 32a8252..bfc1f5b 100644
--- a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
+++ b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
@@ -554,6 +554,8 @@
    * Returns whether this email is visible to the given account
    *
    * @param to account.
+   * @throws PermissionBackendException thrown if checking a permission fails due to an error in the
+   *     permission backend
    */
   protected boolean isVisibleTo(Account.Id to) throws PermissionBackendException {
     return true;
diff --git a/java/com/google/gerrit/server/notedb/ChangeNoteJson.java b/java/com/google/gerrit/server/notedb/ChangeNoteJson.java
index 0dfd494..44bb244 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNoteJson.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNoteJson.java
@@ -74,7 +74,7 @@
 
     @Override
     public Optional<Boolean> read(JsonReader in) throws IOException {
-      JsonElement parsed = new JsonParser().parse(in);
+      JsonElement parsed = JsonParser.parseReader(in);
       if (parsed == null) {
         return Optional.empty();
       }
@@ -101,7 +101,7 @@
 
     @Override
     public ObjectId read(JsonReader in) throws IOException {
-      JsonElement parsed = new JsonParser().parse(in);
+      JsonElement parsed = JsonParser.parseReader(in);
       if (parsed.isJsonObject() && isJGitFormat(parsed)) {
         // Some object IDs may have been serialized using the JGit format using the five integers
         // w1, w2, w3, w4, w5. Detect this case so that we can deserialize properly.
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index 8c876b4..0d59542 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -1040,7 +1040,7 @@
   }
 
   /**
-   * Identifies the {@link Account.Id} that issued the vote.
+   * Identifies the {@link com.google.gerrit.entities.Account.Id} that issued the vote.
    *
    * <p>There are potentially 3 accounts involved here: 1. The account from the commit, which is the
    * effective IdentifiedUser that produced the update. 2. The account in the label footer itself,
diff --git a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
index 866535b..94e11c8 100644
--- a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
+++ b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
@@ -469,7 +469,8 @@
    * <p>2. NoteDb rewriters.
    *
    * <p>3. If any of the receive commands is of type {@link
-   * ReceiveCommand.Type#UPDATE_NONFASTFORWARD} (for example due to a force push).
+   * org.eclipse.jgit.transport.ReceiveCommand.Type#UPDATE_NONFASTFORWARD} (for example due to a
+   * force push).
    *
    * <p>Note that we don't need to explicitly allow non fast-forward updates for DELETE commands
    * since JGit forces the update implicitly in this case.
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementsUtil.java b/java/com/google/gerrit/server/project/SubmitRequirementsUtil.java
index 4ea3bf5..e34ab1d 100644
--- a/java/com/google/gerrit/server/project/SubmitRequirementsUtil.java
+++ b/java/com/google/gerrit/server/project/SubmitRequirementsUtil.java
@@ -37,8 +37,8 @@
 
   @Singleton
   static class Metrics {
-    final Counter2 submitRequirementsMatchingWithLegacy;
-    final Counter2 submitRequirementsMismatchingWithLegacy;
+    final Counter2<String, String> submitRequirementsMatchingWithLegacy;
+    final Counter2<String, String> submitRequirementsMismatchingWithLegacy;
 
     @Inject
     Metrics(MetricMaker metricMaker) {
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 28c4da3..e5f6d32 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -935,37 +935,37 @@
   }
 
   @Operator
-  public Predicate<ChangeData> ext(String ext) throws QueryParseException {
+  public Predicate<ChangeData> ext(String ext) {
     return extension(ext);
   }
 
   @Operator
-  public Predicate<ChangeData> extension(String ext) throws QueryParseException {
+  public Predicate<ChangeData> extension(String ext) {
     return new FileExtensionPredicate(ext);
   }
 
   @Operator
-  public Predicate<ChangeData> onlyexts(String extList) throws QueryParseException {
+  public Predicate<ChangeData> onlyexts(String extList) {
     return onlyextensions(extList);
   }
 
   @Operator
-  public Predicate<ChangeData> onlyextensions(String extList) throws QueryParseException {
+  public Predicate<ChangeData> onlyextensions(String extList) {
     return new FileExtensionListPredicate(extList);
   }
 
   @Operator
-  public Predicate<ChangeData> footer(String footer) throws QueryParseException {
+  public Predicate<ChangeData> footer(String footer) {
     return ChangePredicates.footer(footer);
   }
 
   @Operator
-  public Predicate<ChangeData> dir(String directory) throws QueryParseException {
+  public Predicate<ChangeData> dir(String directory) {
     return directory(directory);
   }
 
   @Operator
-  public Predicate<ChangeData> directory(String directory) throws QueryParseException {
+  public Predicate<ChangeData> directory(String directory) {
     if (directory.startsWith("^")) {
       return new RegexDirectoryPredicate(directory);
     }
@@ -1525,7 +1525,7 @@
   }
 
   @Operator
-  public Predicate<ChangeData> submissionId(String value) throws QueryParseException {
+  public Predicate<ChangeData> submissionId(String value) {
     return ChangePredicates.submissionId(value);
   }
 
diff --git a/java/com/google/gerrit/server/query/change/LabelPredicate.java b/java/com/google/gerrit/server/query/change/LabelPredicate.java
index 2e09075..2a5a47d 100644
--- a/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -214,6 +214,8 @@
       case LESS_EQUAL:
         result.add(count);
         break;
+      case GREATER:
+      case LESS:
       default:
         break;
     }
@@ -226,6 +228,7 @@
       case LESS_EQUAL:
         IntStream.range(0, count).forEach(result::add);
         break;
+      case EQUAL:
       default:
         break;
     }
diff --git a/java/com/google/gerrit/server/query/change/PredicateArgs.java b/java/com/google/gerrit/server/query/change/PredicateArgs.java
index 9f0dffb..ebe4390 100644
--- a/java/com/google/gerrit/server/query/change/PredicateArgs.java
+++ b/java/com/google/gerrit/server/query/change/PredicateArgs.java
@@ -50,7 +50,7 @@
     Operator(String op) {
       this.op = op;
     }
-  };
+  }
 
   @AutoValue
   public abstract static class ValOp {
diff --git a/java/com/google/gerrit/server/restapi/change/PostReview.java b/java/com/google/gerrit/server/restapi/change/PostReview.java
index d4e6205..6a89247 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReview.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReview.java
@@ -1409,7 +1409,7 @@
     }
 
     private boolean updateLabels(ProjectState projectState, ChangeContext ctx)
-        throws ResourceConflictException, IOException {
+        throws ResourceConflictException {
       Map<String, Short> inLabels = firstNonNull(in.labels, Collections.emptyMap());
 
       // If no labels were modified and change is closed, abort early.
@@ -1576,8 +1576,7 @@
     }
 
     private Map<String, PatchSetApproval> scanLabels(
-        ProjectState projectState, ChangeContext ctx, List<PatchSetApproval> del)
-        throws IOException {
+        ProjectState projectState, ChangeContext ctx, List<PatchSetApproval> del) {
       LabelTypes labelTypes = projectState.getLabelTypes(ctx.getNotes());
       Map<String, PatchSetApproval> current = new HashMap<>();
 
diff --git a/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java b/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java
index e3c75e2..ddc3fca 100644
--- a/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java
+++ b/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java
@@ -401,7 +401,6 @@
   private SubmitTypeRecord typeError(String err, Exception e) {
     if (opts.logErrors()) {
       logger.atSevere().withCause(e).log("%s", err);
-      return typeError(DEFAULT_MSG);
     }
     return SubmitTypeRecord.error(err);
   }
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
index de10fe7..d06940c 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
@@ -327,7 +327,7 @@
         ctx.getRevWalk(), ctx.getUpdate(psId), psId, alreadyMergedCommit, groups, null, null);
   }
 
-  private void setApproval(ChangeContext ctx, IdentifiedUser user) throws IOException {
+  private void setApproval(ChangeContext ctx, IdentifiedUser user) {
     Change.Id id = ctx.getChange().getId();
     List<SubmitRecord> records = args.commitStatus.getSubmitRecords(id);
     PatchSet.Id oldPsId = toMerge.getPatchsetId();
@@ -518,19 +518,29 @@
     }
   }
 
-  /** See {@link #updateRepo(RepoContext)} */
+  /**
+   * See {@link #updateRepo(RepoContext)}
+   *
+   * @param ctx context for the repository update
+   */
   protected void updateRepoImpl(RepoContext ctx) throws Exception {}
 
   /**
    * Returns a new patch set if one was created by the submit strategy, or null if not
    *
    * <p>See {@link #updateChange(ChangeContext)}
+   *
+   * @param ctx context for the change update
    */
   protected PatchSet updateChangeImpl(ChangeContext ctx) throws Exception {
     return null;
   }
 
-  /** See {@link #postUpdate(PostUpdateContext)} */
+  /**
+   * See {@link #postUpdate(PostUpdateContext)}
+   *
+   * @param ctx context for the post update
+   */
   protected void postUpdateImpl(PostUpdateContext ctx) throws Exception {}
 
   /** Amend the commit with gitlink update */
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 2253202..ba1e1a7 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -1427,6 +1427,34 @@
   }
 
   @Test
+  public void pushToNonVisibleBranchIsRejected() throws Exception {
+    String master = "refs/heads/master";
+
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(block(Permission.READ).ref(master).group(REGISTERED_USERS))
+        .update();
+
+    testRepo.branch("HEAD").commit().message("New Commit 1").insertChangeId().create();
+    // Since the branch is not visible to the caller, the command tries to create the ref resulting
+    // in the command being rejected because the ref already exists.
+    assertPushRejected(
+        pushHead(testRepo, master),
+        master,
+        "Cannot create ref 'refs/heads/master' because it already exists.");
+
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(allow(Permission.READ).ref(master).group(REGISTERED_USERS))
+        .update();
+
+    testRepo.branch("HEAD").commit().message("New Commit 2").insertChangeId().create();
+    assertPushOk(pushHead(testRepo, master), master);
+  }
+
+  @Test
   public void pushSameCommitTwiceUsingMagicBranchBaseOption() throws Exception {
     projectOperations
         .project(project)
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java b/javatests/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java
index 531357a..793f256 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java
@@ -22,6 +22,7 @@
 import static com.google.gerrit.acceptance.rest.project.AbstractPushTag.TagType.ANNOTATED;
 import static com.google.gerrit.acceptance.rest.project.AbstractPushTag.TagType.LIGHTWEIGHT;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.permissionKey;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 
@@ -29,6 +30,7 @@
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.GitUtil;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Permission;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.testing.ConfigSuite;
@@ -191,33 +193,57 @@
     pushTagDeletion(tagName, Status.OK);
   }
 
+  @Test
+  public void pushToNonVisibleTagIsRejected() throws Exception {
+    allowTagCreation();
+    allowPushOnRefsTags();
+
+    String tagName = pushTagForExistingCommit(Status.OK);
+
+    removeReadFromRefsTags();
+    removeReadFromRefsHeads();
+
+    pushTag(
+        tagName,
+        /* newCommit= */ true,
+        /* force= */ false,
+        Status.REJECTED_OTHER_REASON,
+        /* expectedMessage= */ String.format(
+            "Cannot create ref '%s' because it already exists.", tagRef(tagName)));
+  }
+
   private String pushTagForExistingCommit(Status expectedStatus) throws Exception {
-    return pushTag(null, false, false, expectedStatus);
+    return pushTag(null, false, false, expectedStatus, /* expectedMessage= */ null);
   }
 
   private String pushTagForNewCommit(Status expectedStatus) throws Exception {
-    return pushTag(null, true, false, expectedStatus);
+    return pushTag(null, true, false, expectedStatus, /* expectedMessage= */ null);
   }
 
   private void fastForwardTagToExistingCommit(String tagName, Status expectedStatus)
       throws Exception {
-    pushTag(tagName, false, false, expectedStatus);
+    pushTag(tagName, false, false, expectedStatus, /* expectedMessage= */ null);
   }
 
   private void fastForwardTagToNewCommit(String tagName, Status expectedStatus) throws Exception {
-    pushTag(tagName, true, false, expectedStatus);
+    pushTag(tagName, true, false, expectedStatus, /* expectedMessage= */ null);
   }
 
   private void forceUpdateTagToExistingCommit(String tagName, Status expectedStatus)
       throws Exception {
-    pushTag(tagName, false, true, expectedStatus);
+    pushTag(tagName, false, true, expectedStatus, /* expectedMessage= */ null);
   }
 
   private void forceUpdateTagToNewCommit(String tagName, Status expectedStatus) throws Exception {
-    pushTag(tagName, true, true, expectedStatus);
+    pushTag(tagName, true, true, expectedStatus, /* expectedMessage= */ null);
   }
 
-  private String pushTag(String tagName, boolean newCommit, boolean force, Status expectedStatus)
+  private String pushTag(
+      String tagName,
+      boolean newCommit,
+      boolean force,
+      Status expectedStatus,
+      @Nullable String expectedMessage)
       throws Exception {
     if (force) {
       testRepo.reset(initialHead);
@@ -256,6 +282,9 @@
             : GitUtil.pushTag(testRepo, tagName, !createTag);
     RemoteRefUpdate refUpdate = r.getRemoteUpdate(tagRef);
     assertWithMessage(tagType.name()).that(refUpdate.getStatus()).isEqualTo(expectedStatus);
+    if (expectedMessage != null) {
+      assertWithMessage(tagType.name()).that(refUpdate.getMessage()).isEqualTo(expectedMessage);
+    }
     return tagName;
   }
 
@@ -352,6 +381,22 @@
         .update();
   }
 
+  private void removeReadFromRefsTags() throws Exception {
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(block(Permission.READ).ref("refs/tags/*").group(REGISTERED_USERS))
+        .update();
+  }
+
+  private void removeReadFromRefsHeads() throws Exception {
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(block(Permission.READ).ref("refs/heads/*").group(REGISTERED_USERS))
+        .update();
+  }
+
   private void commit(PersonIdent ident, String subject) throws Exception {
     commitBuilder().ident(ident).message(subject + " (" + System.nanoTime() + ")").create();
   }
diff --git a/javatests/com/google/gerrit/acceptance/server/git/CodeReviewCommitTest.java b/javatests/com/google/gerrit/acceptance/server/git/CodeReviewCommitTest.java
new file mode 100644
index 0000000..b5e1b07
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/git/CodeReviewCommitTest.java
@@ -0,0 +1,74 @@
+// Copyright (C) 2022 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.acceptance.server.git;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.Test;
+
+/** Tests for {@link CodeReviewCommit}. */
+public class CodeReviewCommitTest {
+
+  @Test
+  public void checkSerializable_withStatusMessage() throws Exception {
+    CodeReviewCommit commit = new CodeReviewCommit(createCommit());
+    commit.setStatusMessage("Status");
+    CodeReviewCommit deserializedCommit = serializeAndReadBack(commit);
+    assertThat(deserializedCommit).isEqualTo(commit);
+    assertThat(deserializedCommit.getStatusMessage().get()).isEqualTo("Status");
+  }
+
+  @Test
+  public void checkSerializable_emptyStatusMessage() throws Exception {
+    CodeReviewCommit commit = new CodeReviewCommit(createCommit());
+    CodeReviewCommit deserializedCommit = serializeAndReadBack(commit);
+    assertThat(deserializedCommit).isEqualTo(commit);
+    assertThat(deserializedCommit.getStatusMessage().isPresent()).isFalse();
+  }
+
+  private CodeReviewCommit serializeAndReadBack(CodeReviewCommit codeReviewCommit)
+      throws Exception {
+    try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        ObjectOutputStream out = new ObjectOutputStream(bos)) {
+      out.writeObject(codeReviewCommit);
+      out.flush();
+      try (ByteArrayInputStream fileIn = new ByteArrayInputStream(bos.toByteArray());
+          ObjectInputStream in = new ObjectInputStream(fileIn); ) {
+        return (CodeReviewCommit) in.readObject();
+      }
+    }
+  }
+
+  private ObjectId createCommit() throws Exception {
+    InMemoryRepositoryManager repoManager = new InMemoryRepositoryManager();
+    Project.NameKey project = Project.nameKey("test");
+    try (Repository repo = repoManager.createRepository(project);
+        TestRepository<Repository> tr = new TestRepository<>(repo)) {
+      PersonIdent ident = new PersonIdent(new PersonIdent("Test Ident", "test@test.com"));
+      return tr.commit().author(ident).committer(ident).message("Test commit").create();
+    }
+  }
+}
diff --git a/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java b/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java
index fb51ae0..61e204f 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java
@@ -354,7 +354,7 @@
     ChangeInfo changeInfo = gApi.changes().id(changeId).revert().get();
     String revertId = Integer.toString(changeInfo._number);
     ChangeData revertChangeData =
-        changeQueryProvider.get().byLegacyChangeId(Change.Id.parse(revertId)).get(0);
+        changeQueryProvider.get().byLegacyChangeId(Change.Id.tryParse(revertId).get()).get(0);
     result = evaluator.evaluateRequirement(sr, revertChangeData);
     assertThat(result.status()).isEqualTo(SubmitRequirementResult.Status.SATISFIED);
   }
diff --git a/javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java b/javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java
index 5414b7ee..3ba7829 100644
--- a/javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java
@@ -161,6 +161,12 @@
     assertThat(statusForRuleRenamedFile()).isEqualTo(SubmitRecord.Status.OK);
   }
 
+  @Test
+  public void typeError() throws Exception {
+    modifySubmitRules("user(1000000)."); // the trailing '.' triggers a type error
+    assertThat(statusForRuleAddFile("foo")).isEqualTo(SubmitRecord.Status.RULE_ERROR);
+  }
+
   private SubmitRecord.Status statusForRule() throws Exception {
     String oldHead = projectOperations.project(project).getHead("master").name();
     PushOneCommit.Result result =
diff --git a/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java b/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
index 3b38bad..18c4952 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
@@ -29,7 +29,6 @@
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.inject.Inject;
-import com.google.inject.Injector;
 import java.util.List;
 import org.junit.Test;
 
@@ -38,15 +37,12 @@
 public abstract class AbstractIndexTests extends AbstractDaemonTest {
   @Inject private ExtensionRegistry extensionRegistry;
 
-  public void configureIndex(Injector injector) {}
-
   @Test
   @GerritConfig(name = "index.autoReindexIfStale", value = "false")
   public void indexChange() throws Exception {
     ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter();
     try (Registration registration =
         extensionRegistry.newRegistration().add(changeIndexedCounter)) {
-      configureIndex(server.getTestInjector());
 
       PushOneCommit.Result change = createChange("first change", "test1.txt", "test1");
       String changeId = change.getChangeId();
@@ -76,7 +72,6 @@
     ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter();
     try (Registration registration =
         extensionRegistry.newRegistration().add(changeIndexedCounter)) {
-      configureIndex(server.getTestInjector());
 
       PushOneCommit.Result change = createChange("first change", "test1.txt", "test1");
       String changeId = change.getChangeId();
diff --git a/javatests/com/google/gerrit/server/patch/DiffOperationsTest.java b/javatests/com/google/gerrit/server/patch/DiffOperationsTest.java
index 9f0fc29..fa04cf8 100644
--- a/javatests/com/google/gerrit/server/patch/DiffOperationsTest.java
+++ b/javatests/com/google/gerrit/server/patch/DiffOperationsTest.java
@@ -228,7 +228,7 @@
     enum FileType {
       REGULAR,
       SYMLINK
-    };
+    }
 
     FileEntity(String name, String content) {
       this(name, content, FileType.REGULAR);
diff --git a/plugins/replication b/plugins/replication
index e19028c..98926b4 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit e19028c4f7e5d26bbc4be7762aa96434bf1d7781
+Subproject commit 98926b44a199b5a7049232f6c3b3758267368f8f
diff --git a/plugins/webhooks b/plugins/webhooks
index 7830135..d8815bf 160000
--- a/plugins/webhooks
+++ b/plugins/webhooks
@@ -1 +1 @@
-Subproject commit 7830135fd85ff9de10a8ca1f2e6112af59fca15f
+Subproject commit d8815bf9660b6655696db242b8ad2801e866c036
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 80e208d..14f1e95 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -268,17 +268,22 @@
     ],
 )
 
-nodejs_binary(
+nodejs_test(
     name = "lit_analysis",
     data = [
         ":lit_analysis_src_code",
         "@npm//lit-analyzer",
     ],
     entry_point = "@npm//:node_modules/lit-analyzer/cli.js",
+    tags = [
+        "local",
+        "manual",
+    ],
     templated_args = [
         "**/elements/**/*.ts",
         "--strict",
         "--rules.no-property-visibility-mismatch off",
         "--rules.no-incompatible-property-type off",
+        "--rules.no-incompatible-type-binding off",
     ],
 )
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts
index 762bffc..10ca808 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts
@@ -62,6 +62,7 @@
 import {sharedStyles} from '../../../styles/shared-styles';
 import {LitElement, PropertyValues, css, html} from 'lit';
 import {customElement, property, state} from 'lit/decorators';
+import {ifDefined} from 'lit/directives/if-defined';
 
 const INTERNAL_GROUP_REGEX = /^[\da-f]{40}$/;
 
@@ -252,8 +253,7 @@
         </span>
         <gr-dropdown-list
           id="pageSelect"
-          lowercase
-          value=${this.computeSelectValue()}
+          value=${ifDefined(this.computeSelectValue())}
           .items=${this.subsectionLinks}
           @value-change=${this.handleSubsectionChange}
         >
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
index bed1c54..119b905 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
@@ -142,8 +142,6 @@
           <span class="title">Provide base commit sha1 for change</span>
           <span class="value">
             <iron-input
-              maxlength="40"
-              placeholder="(optional)"
               .bindValue=${this.baseCommit}
               @bind-value-changed=${(e: BindValueChangeEvent) => {
                 this.baseCommit = e.detail.value;
@@ -161,8 +159,6 @@
           <span class="title">Enter topic for new change</span>
           <span class="value">
             <iron-input
-              maxlength="1024"
-              placeholder="(optional)"
               .bindValue=${this.topic}
               @bind-value-changed=${(e: BindValueChangeEvent) => {
                 this.topic = e.detail.value;
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
index b2e8ce6..ea9495c 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
@@ -138,7 +138,7 @@
                 id="rightsInheritFromInput"
                 .text=${convertToString(this.repoConfig.parent)}
                 .query=${this.query}
-                .placeholder="Optional, defaults to 'All-Projects'"
+                .placeholder=${"Optional, defaults to 'All-Projects'"}
                 @text-changed=${this.handleRightsTextChanged}
               >
               </gr-autocomplete>
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
index a4a910b..8a0068b 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
@@ -49,6 +49,7 @@
 import {tableStyles} from '../../../styles/gr-table-styles';
 import {LitElement, css, html} from 'lit';
 import {customElement, property, query, state} from 'lit/decorators';
+import {ifDefined} from 'lit/directives/if-defined';
 
 const SUGGESTIONS_LIMIT = 15;
 const SAVING_ERROR_TEXT =
@@ -305,7 +306,7 @@
   private renderIncludedGroupHref(group: GroupInfo) {
     if (group.url) {
       return html`
-        <a href=${this.computeGroupUrl(group.url)} rel="noopener">
+        <a href=${ifDefined(this.computeGroupUrl(group.url))} rel="noopener">
           ${group.name}
         </a>
       `;
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts b/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
index a8f3141..8faa5e7 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
@@ -182,7 +182,7 @@
             @text-changed=${this.handleNameTextChanged}
           ></gr-autocomplete>
         </span>
-        <span class="value" ?disabled=${this.computeGroupDisabled()}>
+        <span class="value">
           <gr-button
             id="inputUpdateNameBtn"
             ?disabled=${!groupNameEdited}
@@ -218,7 +218,7 @@
           >
           </gr-autocomplete>
         </span>
-        <span class="value" ?disabled=${this.computeGroupDisabled()}>
+        <span class="value">
           <gr-button
             id="inputUpdateOwnerBtn"
             ?disabled=${!groupOwnerNameEdited}
@@ -248,9 +248,9 @@
             ?disabled=${this.computeGroupDisabled()}
             .text=${this.groupConfig?.description}
             @text-changed=${this.handleDescriptionTextChanged}
-          >
+          ></gr-textarea>
         </div>
-        <span class="value" ?disabled=${this.computeGroupDisabled()}>
+        <span class="value">
           <gr-button
             ?disabled=${!groupDescriptionEdited}
             @click=${this.handleSaveDescription}
@@ -302,7 +302,7 @@
             </gr-select>
           </span>
         </section>
-        <span class="value" ?disabled=${this.computeGroupDisabled()}>
+        <span class="value">
           <gr-button
             ?disabled=${!groupOptionsEdited}
             @click=${this.handleSaveOptions}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
index b86c9b9..990b34d 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
@@ -137,7 +137,7 @@
             <gr-endpoint-decorator name="repo-command">
               <gr-endpoint-param name="config" .value=${this.repoConfig}>
               </gr-endpoint-param>
-              <gr-endpoint-param name="repoName" value="${this.repo}">
+              <gr-endpoint-param name="repoName" .value="${this.repo}">
               </gr-endpoint-param>
             </gr-endpoint-decorator>
           </div>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
index 02121f9..6186573 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
@@ -49,6 +49,7 @@
 import {customElement, query, property, state} from 'lit/decorators';
 import {BindValueChangeEvent} from '../../../types/events';
 import {assertIsDefined} from '../../../utils/common-util';
+import {ifDefined} from 'lit/directives/if-defined';
 
 const PGP_START = '-----BEGIN PGP SIGNATURE-----';
 
@@ -248,7 +249,7 @@
     return html`
       <tr class="table">
         <td class="${this.detailType} name">
-          <a href=${this.computeFirstWebLink(item)}>
+          <a href=${ifDefined(this.computeFirstWebLink(item))}>
             ${this.stripRefs(item.ref, this.detailType)}
           </a>
         </td>
@@ -467,7 +468,7 @@
 
   private computeFirstWebLink(repo: ProjectInfo | BranchInfo | TagInfo) {
     const webLinks = this.computeWeblink(repo);
-    return webLinks.length > 0 ? webLinks[0].url : null;
+    return webLinks.length > 0 ? webLinks[0].url : undefined;
   }
 
   // private but used in test
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
index 3292cd7..5fc7bc8 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
@@ -267,7 +267,7 @@
           ?disabled=${this.readOnly}
           .text=${this.repoConfig?.description}
           @text-changed=${this.handleDescriptionTextChanged}
-        >
+        ></gr-textarea>
       </fieldset>
     `;
   }
@@ -599,8 +599,6 @@
             id="maxGitObjSizeIronInput"
             .bindValue=${this.repoConfig?.max_object_size_limit
               ?.configured_value}
-            type="text"
-            ?disabled=${this.readOnly}
             @bind-value-changed=${this.handleMaxGitObjSizeBindValueChanged}
           >
             <input
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts
index 3b180df..4d0f2c4 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts
@@ -25,6 +25,7 @@
 import {LitElement, PropertyValues, html, css} from 'lit';
 import {customElement, property, state} from 'lit/decorators';
 import {BindValueChangeEvent} from '../../../types/events';
+import {ifDefined} from 'lit/directives/if-defined';
 
 /**
  * Fired when the rule has been modified or removed.
@@ -223,7 +224,10 @@
             </select>
           </gr-select>
           ${this.renderMinAndMaxLabel()} ${this.renderMinAndMaxInput()}
-          <a class="groupPath" href="${this.computeGroupPath(this.groupId)}">
+          <a
+            class="groupPath"
+            href="${ifDefined(this.computeGroupPath(this.groupId))}"
+          >
             ${this.groupName}
           </a>
           <gr-select
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 2ea22ef..12df6aa 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
@@ -54,6 +54,7 @@
 import {LitElement, css, html} from 'lit';
 import {customElement, property, state} from 'lit/decorators';
 import {submitRequirementsStyles} from '../../../styles/gr-submit-requirements-styles';
+import {ifDefined} from 'lit/directives/if-defined';
 
 enum ChangeSize {
   XS = 10,
@@ -81,7 +82,9 @@
     'gr-change-list-item': GrChangeListItem;
   }
 }
-
+/**
+ * @attr {Boolean} selected - change list item is selected by cursor
+ */
 @customElement('gr-change-list-item')
 export class GrChangeListItem extends LitElement {
   /** The logged-in user's account, or null if no user is logged in. */
@@ -307,7 +310,7 @@
     return html`
       <td class="cell subject">
         <a
-          title="${this.change?.subject}"
+          title="${ifDefined(this.change?.subject)}"
           href="${changeUrl}"
           @click=${() => this.handleChangeClick()}
         >
@@ -504,7 +507,7 @@
 
     return html`
       <td class="cell size">
-        <gr-tooltip-content hasTooltip title="${this.computeSizeTooltip()}">
+        <gr-tooltip-content has-tooltip title="${this.computeSizeTooltip()}">
           ${this.renderChangeSize()}
         </gr-tooltip-content>
       </td>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
index 1185fe8..dfe4720b 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
@@ -27,7 +27,6 @@
 } from '../../core/gr-navigation/gr-navigation';
 import {getPluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints';
 import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
-import {isOwner} from '../../../utils/change-util';
 import {GrCursorManager} from '../../shared/gr-cursor-manager/gr-cursor-manager';
 import {
   AccountInfo,
@@ -35,7 +34,6 @@
   ServerInfo,
   PreferencesInput,
 } from '../../../types/common';
-import {hasAttention} from '../../../utils/attention-set-util';
 import {fire, fireEvent, fireReload} from '../../../utils/event-util';
 import {ScrollMode} from '../../../constants/constants';
 import {
@@ -55,7 +53,6 @@
 import {ValueChangedEvent} from '../../../types/events';
 
 const NUMBER_FIXED_COLUMNS = 3;
-const CLOSED_STATUS = ['MERGED', 'ABANDONED'];
 const LABEL_PREFIX_INVALID_PROLOG = 'Invalid-Prolog-Rules-Label-Name--';
 const MAX_SHORTCUT_CHARS = 5;
 
@@ -292,7 +289,7 @@
   ) {
     return html`
       <tr class="noChanges">
-        <td class="leftPadding" ?aria-hidden="true"></td>
+        <td class="leftPadding" aria-hidden="true"></td>
         <td
           class="star"
           ?aria-hidden=${!this.showStar}
@@ -318,7 +315,7 @@
   ) {
     return html`
       <tr class="groupTitle">
-        <td class="leftPadding" ?aria-hidden="true"></td>
+        <td class="leftPadding" aria-hidden="true"></td>
         <td
           class="star"
           aria-label="Star status column"
@@ -364,11 +361,6 @@
     labelNames: string[]
   ) {
     const ariaLabel = this.computeAriaLabel(change, changeSection.name);
-    const highlight = this.computeItemHighlight(
-      this.account,
-      change,
-      changeSection.name
-    );
     const selected = this.computeItemSelected(
       sectionIndex,
       index,
@@ -385,7 +377,6 @@
       <gr-change-list-item
         .account=${this.account}
         ?selected=${selected}
-        .highlight=${highlight}
         .change=${change}
         .config=${this.config}
         .sectionName=${changeSection.name}
@@ -605,20 +596,6 @@
       : undefined;
   }
 
-  private computeItemHighlight(
-    account?: AccountInfo,
-    change?: ChangeInfo,
-    sectionName?: string
-  ) {
-    if (!change || !account) return false;
-    if (CLOSED_STATUS.indexOf(change.status) !== -1) return false;
-    return (
-      hasAttention(account, change) &&
-      !isOwner(change, account) &&
-      sectionName === YOUR_TURN.name
-    );
-  }
-
   private nextChange() {
     this.isCursorMoving = true;
     this.cursor.next();
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 3342f83..18eda12 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
@@ -1314,6 +1314,7 @@
       // need to reload anything and we render the change view component as is.
       document.documentElement.scrollTop = this.scrollPosition ?? 0;
       this.reporting.reportInteraction('change-view-re-rendered');
+      this.updateTitle(this._change);
       // We still need to check if post load tasks need to be done such as when
       // user wants to open the reply dialog when in the diff page, the change
       // page should open the reply dialog
@@ -1504,6 +1505,12 @@
     this.set('viewState.patchRange', this._patchRange);
   }
 
+  private updateTitle(change?: ChangeInfo | ParsedChangeInfo) {
+    if (!change) return;
+    const title = change.subject + ' (' + change.change_id.substr(0, 9) + ')';
+    fireTitleChange(this, title);
+  }
+
   _changeChanged(change?: ChangeInfo | ParsedChangeInfo) {
     if (!change || !this._patchRange || !this._allPatchSets) {
       return;
@@ -1519,9 +1526,7 @@
     );
 
     this.set('_patchRange.basePatchNum', parent);
-
-    const title = change.subject + ' (' + change.change_id.substr(0, 9) + ')';
-    fireTitleChange(this, title);
+    this.updateTitle(change);
   }
 
   /**
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-action.ts b/polygerrit-ui/app/elements/checks/gr-checks-action.ts
index 74d0e30..bec6e08 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-action.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-action.ts
@@ -72,7 +72,7 @@
   private renderTooltip() {
     if (!this.action.tooltip) return;
     return html`
-      <paper-tooltip offset="5" fit-to-visible-bounds>
+      <paper-tooltip offset="5" ?fitToVisibleBounds=${true}>
         ${this.action.tooltip}
       </paper-tooltip>
     `;
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index 2e95ce3..7f66423 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -544,12 +544,10 @@
     >
       <span>${tag.name}</span>
       <paper-tooltip offset="5" ?fitToVisibleBounds="${true}">
-        ${
-          tag.tooltip ??
-          'A category tag for this check result. Click to filter.'
-        }
+        ${tag.tooltip ??
+        'A category tag for this check result. Click to filter.'}
       </paper-tooltip>
-    </div>`;
+    </button>`;
   }
 }
 
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
index 8912551..7bbb93b 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
@@ -84,6 +84,7 @@
   'is:open',
   'is:owner',
   'is:private',
+  'is:pure-revert',
   'is:reviewed',
   'is:reviewer',
   'is:starred',
@@ -221,7 +222,7 @@
           multi
           .threshold=${this.threshold}
           tab-complete
-          verticalOffset="30"
+          .verticalOffset=${30}
           @commit=${(e: Event) => {
             this.handleInputCommit(e);
           }}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
index 32f5f39..290773b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
@@ -30,6 +30,7 @@
 
 import {css, html, LitElement, PropertyValues} from 'lit';
 import {customElement, property, query, state} from 'lit/decorators';
+import {ifDefined} from 'lit/directives/if-defined';
 import {classMap} from 'lit/directives/class-map';
 import {StyleInfo, styleMap} from 'lit/directives/style-map';
 
@@ -435,7 +436,7 @@
             // layer.
             'pointer-events': this.showHighlight ? 'auto' : 'none',
           })}"
-          src="${this.diffHighlightSrc}"
+          src="${ifDefined(this.diffHighlightSrc)}"
         />
       </div>
     `;
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
index c4ffad4..eeb636f 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
@@ -53,6 +53,7 @@
 import {subscribe} from '../../lit/subscription-controller';
 import {commentsModelToken} from '../../../models/comments/comments-model';
 import {resolve} from '../../../models/dependency';
+import {ifDefined} from 'lit/directives/if-defined';
 
 // Maximum length for patch set descriptions.
 const PATCH_DESC_MAX_LENGTH = 500;
@@ -205,7 +206,7 @@
     return html`<span class="filesWeblinks">
       ${fileLinks.map(
         weblink => html`
-          <a target="_blank" rel="noopener" href="${weblink.url}">
+          <a target="_blank" rel="noopener" href="${ifDefined(weblink.url)}">
             ${weblink.name}
           </a>
         `
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 dddd6a4..f892410 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
@@ -559,13 +559,19 @@
    */
   _notify(state: SyntaxLayerState) {
     if (state.lineNums.left - state.lastNotify.left) {
-      this._notifyRange(state.lastNotify.left, state.lineNums.left, Side.LEFT);
+      this._notifyRange(
+        state.lastNotify.left,
+        // We have to notify 1-based inclusive, so subtract 1.
+        state.lineNums.left - 1,
+        Side.LEFT
+      );
       state.lastNotify.left = state.lineNums.left;
     }
     if (state.lineNums.right - state.lastNotify.right) {
       this._notifyRange(
         state.lastNotify.right,
-        state.lineNums.right,
+        // We have to notify 1-based inclusive, so subtract 1.
+        state.lineNums.right - 1,
         Side.RIGHT
       );
       state.lastNotify.right = state.lineNums.right;
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
index c907a80..2b03bd3 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
@@ -171,6 +171,7 @@
     window.hljs = mockHLJS;
     const highlightSpy = sinon.spy(mockHLJS, 'highlight');
     const processNextSpy = sinon.spy(element, '_processNextLine');
+    const notifyRangeSpy = sinon.spy(element, '_notifyRange');
     await element.process();
 
     const linesA = diff.meta_a.lines;
@@ -182,6 +183,11 @@
 
     assert.equal(highlightSpy.callCount, linesA + linesB);
 
+    assert.isTrue(notifyRangeSpy.called);
+    assert.equal(notifyRangeSpy.lastCall.args[0], 44);
+    assert.equal(notifyRangeSpy.lastCall.args[1], 48);
+    assert.equal(notifyRangeSpy.lastCall.args[2], 'right');
+
     // The first line of both sides have a range.
     let ranges = [element.baseRanges[0], element.revisionRanges[0]];
     for (const range of ranges) {
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
index 3dba057..9ce62c1 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
@@ -96,6 +96,7 @@
           border: 1px solid var(--border-color);
           display: inline-flex;
           padding: 0 1px;
+          --account-label-padding-horizontal: 6px;
         }
         :host:focus {
           border-color: transparent;
@@ -131,9 +132,6 @@
     /* eslint-disable lit/prefer-static-styles */
     const customStyle = html`
       <style>
-        .container {
-          --account-label-padding-horizontal: 6px;
-        }
         gr-button.remove::part(paper-button),
         gr-button.remove:hover::part(paper-button),
         gr-button.remove:focus::part(paper-button) {
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
index bff4e20..99e10ae 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
@@ -17,6 +17,8 @@
 import '@polymer/iron-icon/iron-icon';
 import '../gr-avatar/gr-avatar';
 import '../gr-hovercard-account/gr-hovercard-account';
+import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
+import '../../plugins/gr-endpoint-param/gr-endpoint-param';
 import {getAppContext} from '../../../services/app-context';
 import {getDisplayName} from '../../../utils/display-name-util';
 import {isSelf, isServiceUser} from '../../../utils/account-util';
@@ -254,15 +256,16 @@
           ? html`<gr-avatar .account="${account}" imageSize="32"></gr-avatar>`
           : ''}
         <span class="text" part="gr-account-label-text">
-          <span class="name"
-            >${this._computeName(account, this.firstName, this._config)}</span
-          >
+          <span class="name">
+            ${this._computeName(account, this.firstName, this._config)}
+          </span>
           ${!this.hideStatus && account.status
             ? html`<iron-icon
                 class="status"
                 icon="gr-icons:unavailable"
               ></iron-icon>`
             : ''}
+          ${this.renderAccountStatusPlugins()}
         </span>
       </span>`;
   }
@@ -281,6 +284,22 @@
     });
   }
 
+  // Note: account statuses from plugins are shown regardless of
+  // hideStatus setting
+  private renderAccountStatusPlugins() {
+    if (!this.account?._account_id) {
+      return;
+    }
+    return html`
+      <gr-endpoint-decorator name="account-status-icon">
+        <gr-endpoint-param
+          name="accountId"
+          .value="${this.account._account_id}"
+        ></gr-endpoint-param>
+      </gr-endpoint-decorator>
+    `;
+  }
+
   handleKeyDown(e: KeyboardEvent) {
     if (modifierPressed(e)) return;
     // Only react to `return` and `space`.
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts
index 731b7c4..edeb399 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.ts
@@ -75,6 +75,9 @@
         <span class="name">
           kermit
         </span>
+        <gr-endpoint-decorator name="account-status-icon">
+          <gr-endpoint-param name="accountId"></gr-endpoint-param>
+        </gr-endpoint-decorator>
       </span>
     </span>
     `);
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
index bbb743a..f0c9106 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
@@ -99,12 +99,6 @@
           exportparts="gr-account-label-text: gr-account-link-text"
         >
         </gr-account-label>
-        <gr-endpoint-decorator name="account-status">
-          <gr-endpoint-param
-            name="accountId"
-            .value="${this.account._account_id}"
-          ></gr-endpoint-param>
-        </gr-endpoint-decorator>
       </a>
     </span>`;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.ts b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.ts
index 00990b8..a78f32f 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.ts
@@ -45,9 +45,6 @@
           exportparts="gr-account-label-text: gr-account-link-text"
         >
         </gr-account-label>
-        <gr-endpoint-decorator name="account-status">
-          <gr-endpoint-param name="accountId"></gr-endpoint-param>
-        </gr-endpoint-decorator>
       </a>
     </span>
   `);
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
index 6b8789e..2da1b19 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
@@ -27,7 +27,11 @@
     'gr-button': GrButton;
   }
 }
-
+/**
+ * @attr {Boolean} no-uppercase - text in button is not uppercased
+ * @attr {Boolean} primary - set primary button color
+ * @attr {Boolean} secondary - set secondary button color
+ */
 @customElement('gr-button')
 export class GrButton extends LitElement {
   // Private but used in tests.
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.ts b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.ts
index 00b8feb..a9dcab1 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.ts
@@ -55,6 +55,20 @@
     await element.updateComplete;
   });
 
+  test('renders', () => {
+    expect(element).shadowDom.to.equal(`<paper-button
+      animated=""
+      aria-disabled="false"
+      elevation="1"
+      part="paper-button"
+      raised=""
+      role="button"
+      tabindex="-1"
+    ><slot></slot><i class="downArrow"></i>
+    </paper-button>
+    `);
+  });
+
   test('disabled is set by disabled', async () => {
     const paperBtn = queryAndAssert<PaperButtonElement>(
       element,
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 b32938a..ee40ad5 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
@@ -473,7 +473,6 @@
           <gr-comment
             .comment="${comment}"
             .comments="${this.thread!.comments}"
-            .patchNum="${this.thread?.patchNum}"
             ?initially-collapsed="${initiallyCollapsed}"
             ?robot-button-disabled="${robotButtonDisabled}"
             ?show-patchset="${this.showPatchset}"
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts
index 7e6c17c..8564751 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts
@@ -213,6 +213,10 @@
   }
 
   _handleEnter(event: KeyboardEvent) {
+    const grAutocomplete = this.getGrAutocomplete();
+    if (event.composedPath().some(el => el === grAutocomplete)) {
+      return;
+    }
     const inputContainer = queryAndAssert(this, '.inputContainer');
     const isEventFromInput = event
       .composedPath()
@@ -233,7 +237,7 @@
   }
 
   _handleCommit() {
-    this._save();
+    this.getInput()?.focus();
   }
 
   _computeLabelClass(readOnly?: boolean, value?: string, placeholder?: string) {
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
index 70de130..a0b3274 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
@@ -311,7 +311,9 @@
         link
         aria-label="Remove vote"
         @click="${this.onDeleteVote}"
-        data-account-id="${ifDefined(reviewer._account_id)}"
+        data-account-id="${ifDefined(
+          reviewer._account_id as number | undefined
+        )}"
         class="deleteBtn ${this.computeDeleteClass(
           reviewer,
           this.mutable,
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts
index 7008db2..9bb112e 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts
@@ -16,6 +16,7 @@
  */
 import {customElement, property} from 'lit/decorators';
 import {html, LitElement} from 'lit';
+import '../gr-tooltip-content/gr-tooltip-content';
 
 declare global {
   interface HTMLElementTagNameMap {
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 0d3e86a..ab5f1ad 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
@@ -117,7 +117,6 @@
         <div class="filterContainer">
           <label>Filter:</label>
           <iron-input
-            type="text"
             .bindValue=${this.filter}
             @bind-value-changed=${this.handleFilterBindValueChanged}
           >
diff --git a/polygerrit-ui/app/types/types.ts b/polygerrit-ui/app/types/types.ts
index 42f3d45..c6137eb 100644
--- a/polygerrit-ui/app/types/types.ts
+++ b/polygerrit-ui/app/types/types.ts
@@ -171,7 +171,9 @@
 }
 
 export type DiffLayerListener = (
+  /** 1-based inclusive */
   start: number,
+  /** 1-based inclusive */
   end: number,
   side: Side
 ) => void;
diff --git a/tools/node_tools/launchpad.patch b/tools/node_tools/launchpad.patch
deleted file mode 100644
index 565494b..0000000
--- a/tools/node_tools/launchpad.patch
+++ /dev/null
@@ -1,240 +0,0 @@
-From d430b5d912bebe87529b887f408ee55c82a0e003 Mon Sep 17 00:00:00 2001
-From: Michele Romano <33063403+Mik317@users.noreply.github.com>
-Date: Fri, 26 Jun 2020 20:16:47 +0200
-Subject: [PATCH 1/7] Update version.js
-
----
- lib/local/version.js | 15 ++++++++++++---
- 1 file changed, 12 insertions(+), 3 deletions(-)
-
-diff --git a/tools/node_tools/node_modules/launchpad/lib/local/version.js b/tools/node_tools/node_modules/launchpad/lib/g/local/version.js
-index 0110a74..2c02bef 100644
---- a/tools/node_tools/node_modules/launchpad/lib/local/version.js
-+++ b/tools/node_tools/node_modules/launchpad/lib/local/version.js
-@@ -6,6 +6,15 @@ var plist = require('plist');
- var utils = require('./utils');
- var debug = require('debug')('launchpad:local:version');
- 
-+var validPath = function (filename){
-+  var filter = /[`!@#$%^&*()_+\-=\[\]{};':"\\|,<>\/?~]/;
-+  if (filter.test(filename)){
-+    console.log('\nInvalid characters inside the path to the browser\n');
-+    return
-+  }
-+  return filename;
-+}
-+
- module.exports = function(browser) {
-   if (!browser || !browser.path) {
-     return Q(null);
-@@ -18,7 +27,7 @@ module.exports = function(browser) {
- 
-     debug('Retrieving version for windows executable', command);
-     // Can't use Q.nfcall here unfortunately because of non 0 exit code
--    exec(command, function(error, stdout) {
-+    exec(command.split(' ')[0], command.split(' ').slice(1), function(error, stdout) {
-       var regex = /ProductVersion:\s*(.*)/;
-       // ShowVer.exe returns a non zero status code even if it works
-       if (typeof stdout === 'string' && regex.test(stdout)) {
-@@ -47,8 +56,8 @@ module.exports = function(browser) {
-   }
- 
-   // Try executing <browser> --version (everything else)
--  return Q.nfcall(exec, browser.path + ' --version').then(function(stdout) {
--    debug('Ran ' + browser.path + ' --version', stdout);
-+  return Q.nfcall(exec, validPath(browser.path) + ' --version').then(function(stdout) {
-+    debug('Ran ' + validPath(browser.path) + ' --version', stdout);
-     var version = utils.getStdout(stdout);
-     if (version) {
-       browser.version = version;
-
-From 09ce4fab2fd53cab893ceaa3b4d7f997af9b41d8 Mon Sep 17 00:00:00 2001
-From: Michele Romano <33063403+Mik317@users.noreply.github.com>
-Date: Fri, 26 Jun 2020 20:18:35 +0200
-Subject: [PATCH 2/7] Update instance.js
-
----
- lib/local/instance.js | 11 +++++++++--
- 1 file changed, 9 insertions(+), 2 deletions(-)
-
-diff --git a/tools/node_tools/node_modules/launchpad/lib/local/instance.js b/tools/node_tools/node_modules/launchpad/lib/g/local/instance.js
-index 484a866..b49990f 100644
---- a/tools/node_tools/node_modules/launchpad/lib/local/instance.js
-+++ b/tools/node_tools/node_modules/launchpad/lib/local/instance.js
-@@ -5,8 +5,15 @@ var EventEmitter = require('events').EventEmitter;
- var debug = require('debug')('launchpad:local:instance');
- var rimraf = require('rimraf');
- 
-+var safe = function (str) {
-+   // Avoid quotes makes impossible escape the `multi command` scenario
-+   return str.replace(/['"]+/g, '');
-+}
-+
- var getProcessId = function (name, callback) {
- 
-+  name = safe(name);
-+
-   var commands = {
-     darwin: "ps -clx | grep '" + name + "$' | awk '{print $2}' | head -1",
-     linux: "ps -ax | grep '" + name + "$' | awk '{print $2}' | head -1",
-@@ -90,11 +97,11 @@ Instance.prototype.stop = function (callback) {
-     } catch (error) {}
-   } else {
-     if (this.options.command.indexOf('open') === 0) {
--      command = 'osascript -e \'tell application "' + self.options.process + '" to quit\'';
-+      command = 'osascript -e \'tell application "' + safe(self.options.process) + '" to quit\'';
-       debug('Executing shutdown AppleScript', command);
-       exec(command);
-     } else if (process.platform === 'win32') {
--      command = 'taskkill /IM ' + (this.options.imageName || path.basename(this.cmd));
-+      command = 'taskkill /IM "' + safe(this.options.imageName || path.basename(this.cmd)) + '"';
-       debug('Executing shutdown taskkil', command);
-       exec(command).once('exit', function(data) {
-         self.emit('stop', data);
-
-From d3993fce090ed6ef378c1f0594eff18d125dad1e Mon Sep 17 00:00:00 2001
-From: Michele Romano <33063403+Mik317@users.noreply.github.com>
-Date: Fri, 26 Jun 2020 20:19:17 +0200
-Subject: [PATCH 3/7] Update version.js
-
----
- lib/local/version.js | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/tools/node_tools/node_modules/launchpad/lib/local/version.js b/tools/node_tools/node_modules/launchpad/lib/g/local/version.js
-index 2c02bef..5eac082 100644
---- a/tools/node_tools/node_modules/launchpad/lib/local/version.js
-+++ b/tools/node_tools/node_modules/launchpad/lib/local/version.js
-@@ -6,6 +6,7 @@ var plist = require('plist');
- var utils = require('./utils');
- var debug = require('debug')('launchpad:local:version');
- 
-+// Validate paths supplied by the user in order to avoid "arbitrary command execution"
- var validPath = function (filename){
-   var filter = /[`!@#$%^&*()_+\-=\[\]{};':"\\|,<>\/?~]/;
-   if (filter.test(filename)){
-
-From abf3dbcc79e6b338338594ab2dbef834550e8f65 Mon Sep 17 00:00:00 2001
-From: Michele Romano <33063403+Mik317@users.noreply.github.com>
-Date: Mon, 29 Jun 2020 13:32:50 +0200
-Subject: [PATCH 4/7] Update instance.js
-
----
- lib/local/instance.js | 10 +++++++---
- 1 file changed, 7 insertions(+), 3 deletions(-)
-
-diff --git a/tools/node_tools/node_modules/launchpad/lib/local/instance.js b/tools/node_tools/node_modules/launchpad/lib/g/local/instance.js
-index b49990f..9375d1f 100644
---- a/tools/node_tools/node_modules/launchpad/lib/local/instance.js
-+++ b/tools/node_tools/node_modules/launchpad/lib/local/instance.js
-@@ -1,6 +1,7 @@
- var path = require('path');
- var spawn = require("child_process").spawn;
- var exec = require("child_process").exec;
-+var execFile = require("child_process").execFile;
- var EventEmitter = require('events').EventEmitter;
- var debug = require('debug')('launchpad:local:instance');
- var rimraf = require('rimraf');
-@@ -99,11 +100,14 @@ Instance.prototype.stop = function (callback) {
-     if (this.options.command.indexOf('open') === 0) {
-       command = 'osascript -e \'tell application "' + safe(self.options.process) + '" to quit\'';
-       debug('Executing shutdown AppleScript', command);
--      exec(command);
-+      command = command.split(' ');
-+      execFile(command[0], command.slice(1));
-     } else if (process.platform === 'win32') {
--      command = 'taskkill /IM "' + safe(this.options.imageName || path.basename(this.cmd)) + '"';
-+      //Adding `"` wasn't safe/functional on Win systems
-+      command = 'taskkill /IM ' + (this.options.imageName || path.basename(this.cmd); 
-       debug('Executing shutdown taskkil', command);
--      exec(command).once('exit', function(data) {
-+      command = command.split(' ');
-+      execFile(command[0], command.slice(1)).once('exit', function(data) {
-         self.emit('stop', data);
-       });
-     } else {
-
-From 68518b274c9351f799d41ce85f23499ca4a785e9 Mon Sep 17 00:00:00 2001
-From: Michele Romano <33063403+Mik317@users.noreply.github.com>
-Date: Tue, 30 Jun 2020 00:01:31 +0200
-Subject: [PATCH 5/7] Update instance.js
-
----
- lib/local/instance.js | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/tools/node_tools/node_modules/launchpad/lib/local/instance.js b/tools/node_tools/node_modules/launchpad/lib/g/local/instance.js
-index 9375d1f..f157dd4 100644
---- a/tools/node_tools/node_modules/launchpad/lib/local/instance.js
-+++ b/tools/node_tools/node_modules/launchpad/lib/local/instance.js
-@@ -104,7 +104,7 @@ Instance.prototype.stop = function (callback) {
-       execFile(command[0], command.slice(1));
-     } else if (process.platform === 'win32') {
-       //Adding `"` wasn't safe/functional on Win systems
--      command = 'taskkill /IM ' + (this.options.imageName || path.basename(this.cmd); 
-+      command = 'taskkill /IM ' + (this.options.imageName || path.basename(this.cmd)); 
-       debug('Executing shutdown taskkil', command);
-       command = command.split(' ');
-       execFile(command[0], command.slice(1)).once('exit', function(data) {
-
-From e711d07d40d39162ea4bdb1ed344c58f92bfa10b Mon Sep 17 00:00:00 2001
-From: Michele Romano <33063403+Mik317@users.noreply.github.com>
-Date: Fri, 3 Jul 2020 12:30:31 +0200
-Subject: [PATCH 6/7] Update version.js
-
----
- lib/local/version.js | 5 +++--
- 1 file changed, 3 insertions(+), 2 deletions(-)
-
-diff --git a/tools/node_tools/node_modules/launchpad/lib/local/version.js b/tools/node_tools/node_modules/launchpad/lib/g/local/version.js
-index 5eac082..d1403a0 100644
---- a/tools/node_tools/node_modules/launchpad/lib/local/version.js
-+++ b/tools/node_tools/node_modules/launchpad/lib/local/version.js
-@@ -1,5 +1,6 @@
- var fs = require('fs');
- var exec = require('child_process').exec;
-+var execFile = require('child_process').execFile;
- var Q = require('q');
- var path = require('path');
- var plist = require('plist');
-@@ -8,7 +9,7 @@ var debug = require('debug')('launchpad:local:version');
- 
- // Validate paths supplied by the user in order to avoid "arbitrary command execution"
- var validPath = function (filename){
--  var filter = /[`!@#$%^&*()_+\-=\[\]{};':"\\|,<>\/?~]/;
-+  var filter = /[`!@#$%^&*()_+\-=\[\]{};':"|,<>?~]/;
-   if (filter.test(filename)){
-     console.log('\nInvalid characters inside the path to the browser\n');
-     return
-@@ -28,7 +29,7 @@ module.exports = function(browser) {
- 
-     debug('Retrieving version for windows executable', command);
-     // Can't use Q.nfcall here unfortunately because of non 0 exit code
--    exec(command.split(' ')[0], command.split(' ').slice(1), function(error, stdout) {
-+    execFile(command.split(' ')[0], command.split(' ').slice(1), function(error, stdout) {
-       var regex = /ProductVersion:\s*(.*)/;
-       // ShowVer.exe returns a non zero status code even if it works
-       if (typeof stdout === 'string' && regex.test(stdout)) {
-
-From a3ff1804f0aacfb4fa20dad1312427b81280bb3e Mon Sep 17 00:00:00 2001
-From: Michele Romano <33063403+Mik317@users.noreply.github.com>
-Date: Fri, 3 Jul 2020 12:31:31 +0200
-Subject: [PATCH 7/7] Update version.js
-
----
- lib/local/version.js | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/tools/node_tools/node_modules/launchpad/lib/local/version.js b/tools/node_tools/node_modules/launchpad/lib/g/local/version.js
-index d1403a0..d937be4 100644
---- a/tools/node_tools/node_modules/launchpad/lib/local/version.js
-+++ b/tools/node_tools/node_modules/launchpad/lib/local/version.js
-@@ -9,7 +9,7 @@ var debug = require('debug')('launchpad:local:version');
- 
- // Validate paths supplied by the user in order to avoid "arbitrary command execution"
- var validPath = function (filename){
--  var filter = /[`!@#$%^&*()_+\-=\[\]{};':"|,<>?~]/;
-+  var filter = /[`!@#$%^&*()_+\-=\[\]{};'"|,<>?~]/;
-   if (filter.test(filename)){
-     console.log('\nInvalid characters inside the path to the browser\n');
-     return
diff --git a/tools/node_tools/package.json b/tools/node_tools/package.json
index 7ee64df..33f9234 100644
--- a/tools/node_tools/package.json
+++ b/tools/node_tools/package.json
@@ -19,12 +19,11 @@
     "typescript": "4.3.2"
   },
   "devDependencies": {},
-  "scripts": {
-    "postinstall": "(git apply --reverse --ignore-whitespace launchpad.patch || true) && git apply --ignore-whitespace launchpad.patch"
-  },
   "license": "Apache-2.0",
   "private": true,
   "resolutions": {
-    "lodash": "4.17.21"
+    "lodash": "4.17.21",
+    "wct-local": "2.1.6",
+    "launchpad": "git+https://github.com/418sec/launchpad.git#de5aca11dc16a8e530195281c77614bdbb08e7be"
   }
-}
+}
\ No newline at end of file
diff --git a/tools/node_tools/yarn.lock b/tools/node_tools/yarn.lock
index 6525c41..6e146ea 100644
--- a/tools/node_tools/yarn.lock
+++ b/tools/node_tools/yarn.lock
@@ -5341,10 +5341,9 @@
   dependencies:
     package-json "^4.0.0"
 
-launchpad@^0.7.0:
+"launchpad@git+https://github.com/418sec/launchpad.git#de5aca11dc16a8e530195281c77614bdbb08e7be", "launchpad@git://github.com/418sec/launchpad.git#de5aca11dc16a8e530195281c77614bdbb08e7be":
   version "0.7.5"
-  resolved "https://registry.yarnpkg.com/launchpad/-/launchpad-0.7.5.tgz#a16950c937572f10ef01c9be945a96f7aef8e427"
-  integrity sha512-gsYFgT8XKL3X2XZHPPPrgwM0JqeQwGpSWnzg7EYadBY3MirbQrTVq6L4fm6l7UE2T+7gnfuhiGkKr/xxuU/fdw==
+  resolved "git+https://github.com/418sec/launchpad.git#de5aca11dc16a8e530195281c77614bdbb08e7be"
   dependencies:
     async "^2.0.1"
     browserstack "^1.2.0"
@@ -8910,10 +8909,10 @@
   dependencies:
     minimalistic-assert "^1.0.0"
 
-wct-local@^2.1.1:
-  version "2.1.5"
-  resolved "https://registry.yarnpkg.com/wct-local/-/wct-local-2.1.5.tgz#f7986753e3ad9a35d39178a9989350523561fff1"
-  integrity sha512-eqoZhjGy4Xq2tY0uB46Grkw/ztq+/rC0ImbYKl62unFHXtOgal+kkvnxR3SLRFNM8ty9+ItgycPeH0IpTqVL+w==
+wct-local@2.1.6, wct-local@^2.1.1:
+  version "2.1.6"
+  resolved "https://registry.yarnpkg.com/wct-local/-/wct-local-2.1.6.tgz#2d099c52996e77265d16e03a5d6d897b77ea9967"
+  integrity sha512-jvTzgOIIfJ43H3DXUfruHPTQ/TJ269SDk4R2CfCpU13EYbwxn3U1B6L5NHYRFu/cgdJmOHraGrn/wREHH6xeXQ==
   dependencies:
     "@types/express" "^4.0.30"
     "@types/freeport" "^1.0.19"
@@ -8922,7 +8921,7 @@
     chalk "^2.3.0"
     cleankill "^2.0.0"
     freeport "^1.0.4"
-    launchpad "^0.7.0"
+    launchpad "git://github.com/418sec/launchpad.git#de5aca11dc16a8e530195281c77614bdbb08e7be"
     selenium-standalone "^6.7.0"
     which "^1.0.8"