Merge "Add css styles to the frontend plugin api"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index a3b6c4b..32867b6 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1413,8 +1413,24 @@
   query operator. Gerrit does not serve `mergeable` in
   link:rest-api-changes.html#change-info[ChangeInfo].
 
+NOTE: Gerrit would only render conflict changes section on change
+screen if `API_REF_UPDATED_AND_CHANGE_REINDEX` value is set.
+
 Default is `NEVER`.
 
+[[change.conflictsPredicateEnabled]]change.conflictsPredicateEnabled::
+
++
+This setting determines when Gerrit renders conflict changes section on change
+screen and also supports `conflicts` predicate. This computation is expensive,
+computing ConflictsPredicate has a runtime complexity of O(nˆ2) with n number
+of open changes on a branch. When set to false GUI will silently ignore the
+error message and leave the conflict changes section on change screen empty.
+See also implications on rendering of conflict changes section in configuration
+section:link:#change.mergeabilityComputationBehavior[change.mergeabilityComputationBehavior].
+
+Default is true.
+
 [[change.move]]change.move::
 +
 Whether the link:rest-api-changes.html#move-change[Move Change] REST
diff --git a/java/com/google/gerrit/server/notedb/CommitRewriter.java b/java/com/google/gerrit/server/notedb/CommitRewriter.java
index cefaa4d..549bd0f 100644
--- a/java/com/google/gerrit/server/notedb/CommitRewriter.java
+++ b/java/com/google/gerrit/server/notedb/CommitRewriter.java
@@ -875,7 +875,7 @@
   private Optional<Account.Id> parseIdent(PersonIdent ident) {
     Optional<Account.Id> account = NoteDbUtil.parseIdent(ident);
     if (!account.isPresent()) {
-      logger.atWarning().log("Failed to parse ident %", ident);
+      logger.atWarning().log("Failed to parse id %s", ident);
     }
     return account;
   }
diff --git a/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java b/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java
index f032f2a..62387ee 100644
--- a/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java
+++ b/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java
@@ -30,6 +30,7 @@
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.diff.DiffInfoCreator;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.LargeObjectException;
 import com.google.gerrit.server.git.validators.CommentCumulativeSizeValidator;
@@ -41,6 +42,7 @@
 import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -146,7 +148,7 @@
     StringBuilder diff =
         new StringBuilder(
             String.format(
-                "The name of the file: %s\nInsertions: %d, Deletions: %d.\n\n",
+                "```\nThe name of the file: %s\nInsertions: %d, Deletions: %d.\n\n",
                 fileDiffOutput.newPath().isPresent()
                     ? fileDiffOutput.newPath().get()
                     : fileDiffOutput.oldPath().get(),
@@ -176,15 +178,23 @@
               "The file %s was renamed to %s\n",
               fileDiffOutput.oldPath().get(), fileDiffOutput.newPath().get()));
     }
-    return diff.append(getUnifiedDiff(patchScript, notes)).toString();
+    diff.append(getUnifiedDiff(patchScript, notes));
+    // This line (and the ``` above) are useful for formatting in the web UI.
+    diff.append("\n```\n");
+    return diff.toString();
   }
 
-  /** Show patch set as unified difference for a specific file. */
+  /**
+   * Show patch set as unified difference for a specific file. We on purpose are not using {@link
+   * DiffInfoCreator} since we'd like to get the original git/JGit style diff.
+   */
   public String getUnifiedDiff(PatchScript patchScript, ChangeNotes changeNotes)
       throws IOException {
     TemporaryBuffer.Heap buf =
         new TemporaryBuffer.Heap(Math.min(HEAP_EST_SIZE, maxCumulativeSize), maxCumulativeSize);
     try (DiffFormatter fmt = new DiffFormatter(buf);
+        // TODO(paiking): ensure we open the repository only once by opening it in the calling
+        //  method.
         Repository git = repositoryManager.openRepository(changeNotes.getProjectName())) {
       fmt.setRepository(git);
       fmt.setDetectRenames(true);
@@ -194,16 +204,39 @@
       List<String> formatterResult =
           Arrays.stream(RawParseUtils.decode(buf.toByteArray()).split("\n"))
               .collect(Collectors.toList());
-
-      // remove non user friendly information.
-      while (formatterResult.size() > 0 && !formatterResult.get(0).startsWith("@@")) {
-        formatterResult.remove(0);
+      // only return information about the current file, and not about files that are not
+      // relevant. DiffFormatter returns other potential files because of rebases, which we can
+      // ignore.
+      List<String> modifiedFormatterResult = new ArrayList<>();
+      int indexOfFormatterResult = 0;
+      while (formatterResult.size() > indexOfFormatterResult
+          && !formatterResult
+              .get(indexOfFormatterResult)
+              .equals(
+                  String.format(
+                      "diff --git a/%s b/%s",
+                      patchScript.getOldName() != null
+                          ? patchScript.getOldName()
+                          : patchScript.getNewName(),
+                      patchScript.getNewName()))) {
+        indexOfFormatterResult++;
       }
-      if (formatterResult.size() == 0) {
+      // remove non user friendly information.
+      while (formatterResult.size() > indexOfFormatterResult
+          && !formatterResult.get(indexOfFormatterResult).startsWith("@@")) {
+        indexOfFormatterResult++;
+      }
+      for (; indexOfFormatterResult < formatterResult.size(); indexOfFormatterResult++) {
+        if (formatterResult.get(indexOfFormatterResult).startsWith("diff --git")) {
+          break;
+        }
+        modifiedFormatterResult.add(formatterResult.get(indexOfFormatterResult));
+      }
+      if (modifiedFormatterResult.size() == 0) {
         // This happens for diffs that are just renames, but we already account for renames.
         return "";
       }
-      return formatterResult.stream()
+      return modifiedFormatterResult.stream()
           .filter(s -> !s.equals("\\ No newline at end of file"))
           .collect(Collectors.joining("\n"));
     } catch (IOException e) {
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 131de74..383e385 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -247,6 +247,7 @@
     final ChangeIsVisibleToPredicate.Factory changeIsVisbleToPredicateFactory;
     final OperatorAliasConfig operatorAliasConfig;
     final boolean indexMergeable;
+    final boolean conflictsPredicateEnabled;
     final HasOperandAliasConfig hasOperandAliasConfig;
 
     private final Provider<CurrentUser> self;
@@ -311,6 +312,7 @@
           groupMembers,
           operatorAliasConfig,
           MergeabilityComputationBehavior.fromConfig(gerritConfig).includeInIndex(),
+          gerritConfig.getBoolean("change", null, "conflictsPredicateEnabled", true),
           hasOperandAliasConfig,
           changeIsVisbleToPredicateFactory);
     }
@@ -343,6 +345,7 @@
         GroupMembers groupMembers,
         OperatorAliasConfig operatorAliasConfig,
         boolean indexMergeable,
+        boolean conflictsPredicateEnabled,
         HasOperandAliasConfig hasOperandAliasConfig,
         ChangeIsVisibleToPredicate.Factory changeIsVisbleToPredicateFactory) {
       this.queryProvider = queryProvider;
@@ -373,6 +376,7 @@
       this.changeIsVisbleToPredicateFactory = changeIsVisbleToPredicateFactory;
       this.operatorAliasConfig = operatorAliasConfig;
       this.indexMergeable = indexMergeable;
+      this.conflictsPredicateEnabled = conflictsPredicateEnabled;
       this.hasOperandAliasConfig = hasOperandAliasConfig;
     }
 
@@ -405,6 +409,7 @@
           groupMembers,
           operatorAliasConfig,
           indexMergeable,
+          conflictsPredicateEnabled,
           hasOperandAliasConfig,
           changeIsVisbleToPredicateFactory);
     }
@@ -710,6 +715,9 @@
 
   @Operator
   public Predicate<ChangeData> conflicts(String value) throws QueryParseException {
+    if (!args.conflictsPredicateEnabled) {
+      throw new QueryParseException("'conflicts:' operator is not supported by server");
+    }
     List<Change> changes = parseChange(value);
     List<Predicate<ChangeData>> or = new ArrayList<>(changes.size());
     for (Change c : changes) {
diff --git a/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java b/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java
index 97c584d..ca54050 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java
@@ -19,6 +19,7 @@
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 import static com.google.gerrit.server.project.testing.TestLabels.labelBuilder;
 import static com.google.gerrit.server.project.testing.TestLabels.value;
+import static org.eclipse.jgit.lib.Constants.HEAD;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -31,10 +32,12 @@
 import com.google.gerrit.entities.LabelId;
 import com.google.gerrit.entities.LabelType;
 import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.api.changes.RebaseInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
 import com.google.gerrit.server.project.testing.TestLabels;
 import com.google.inject.Inject;
+import org.eclipse.jgit.lib.ObjectId;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -117,6 +120,182 @@
   }
 
   @Test
+  public void diffChangeMessageOnSubmitWithStickyVote_ignoreDiffFromRebaseAdditions()
+      throws Exception {
+    ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
+    Change.Id changeId =
+        changeOperations
+            .newChange()
+            .project(project)
+            .file("file")
+            .content("line1\nline2\nline3\nline4")
+            .create();
+
+    gApi.changes().id(changeId.get()).current().review(ReviewInput.approve());
+    changeOperations
+        .change(changeId)
+        .newPatchset()
+        .file("file")
+        .content("line012\nline1\nline2\nline3\nline4")
+        .create();
+
+    // add a reviewer to ensure an email is sent.
+    gApi.changes().id(changeId.get()).addReviewer(user.email());
+
+    testRepo.reset(initial);
+
+    // create 2 unrelated changes and rebase on top of them. Those rebases should be ignored.
+    // The changes add files.
+    Change.Id unrelated =
+        changeOperations.newChange().project(project).file("a").content("a").create();
+    gApi.changes().id(unrelated.get()).current().review(ReviewInput.approve());
+    gApi.changes().id(unrelated.get()).current().submit();
+    unrelated = changeOperations.newChange().project(project).file("z").content("z").create();
+    gApi.changes().id(unrelated.get()).current().review(ReviewInput.approve());
+    gApi.changes().id(unrelated.get()).current().submit();
+
+    RebaseInput rebaseInput = new RebaseInput();
+    rebaseInput.base = gApi.changes().id(unrelated.get()).current().commit(true).commit;
+    gApi.changes().id(changeId.get()).current().rebase(rebaseInput);
+
+    gApi.changes().id(changeId.get()).current().submit();
+
+    assertDiffChangeMessageAndEmailWithStickyApproval(
+        Iterables.getLast(gApi.changes().id(changeId.get()).messages()).message,
+        /* file= */ "file",
+        /* insertions= */ 1,
+        /* deletions= */ 0,
+        /* expectedFileDiff= */ "@@ -1,3 +1,4 @@\n"
+            + "+line012\n"
+            + " line1\n"
+            + " line2\n"
+            + " line3",
+        /* oldFileName= */ null);
+  }
+
+  @Test
+  public void diffChangeMessageOnSubmitWithStickyVote_ignoreDiffFromRebaseRenames()
+      throws Exception {
+    Change.Id setup = changeOperations.newChange().project(project).file("a").content("a").create();
+    gApi.changes().id(setup.get()).current().review(ReviewInput.approve());
+    gApi.changes().id(setup.get()).current().submit();
+
+    setup = changeOperations.newChange().project(project).file("z").content("z").create();
+    gApi.changes().id(setup.get()).current().review(ReviewInput.approve());
+    gApi.changes().id(setup.get()).current().submit();
+
+    ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
+    Change.Id changeId =
+        changeOperations
+            .newChange()
+            .project(project)
+            .file("file")
+            .content("line1\nline2\nline3\nline4")
+            .create();
+
+    gApi.changes().id(changeId.get()).current().review(ReviewInput.approve());
+    changeOperations
+        .change(changeId)
+        .newPatchset()
+        .file("file")
+        .content("line012\nline1\nline2\nline3\nline4")
+        .create();
+
+    // add a reviewer to ensure an email is sent.
+    gApi.changes().id(changeId.get()).addReviewer(user.email());
+
+    testRepo.reset(initial);
+
+    // create 2 unrelated changes and rebase on top of them. Those rebases should be ignored.
+    // The changes rename files.
+    Change.Id unrelated =
+        changeOperations.newChange().project(project).file("a").renameTo("aa").create();
+    gApi.changes().id(unrelated.get()).current().review(ReviewInput.approve());
+    gApi.changes().id(unrelated.get()).current().submit();
+    unrelated = changeOperations.newChange().project(project).file("z").renameTo("zz").create();
+    gApi.changes().id(unrelated.get()).current().review(ReviewInput.approve());
+    gApi.changes().id(unrelated.get()).current().submit();
+
+    RebaseInput rebaseInput = new RebaseInput();
+    rebaseInput.base = gApi.changes().id(unrelated.get()).current().commit(true).commit;
+    gApi.changes().id(changeId.get()).current().rebase(rebaseInput);
+
+    gApi.changes().id(changeId.get()).current().submit();
+
+    assertDiffChangeMessageAndEmailWithStickyApproval(
+        Iterables.getLast(gApi.changes().id(changeId.get()).messages()).message,
+        /* file= */ "file",
+        /* insertions= */ 1,
+        /* deletions= */ 0,
+        /* expectedFileDiff= */ "@@ -1,3 +1,4 @@\n"
+            + "+line012\n"
+            + " line1\n"
+            + " line2\n"
+            + " line3",
+        /* oldFileName= */ null);
+  }
+
+  @Test
+  public void diffChangeMessageOnSubmitWithStickyVote_ignoreDiffFromRebaseDeletions()
+      throws Exception {
+    Change.Id setup = changeOperations.newChange().project(project).file("a").content("a").create();
+    gApi.changes().id(setup.get()).current().review(ReviewInput.approve());
+    gApi.changes().id(setup.get()).current().submit();
+
+    setup = changeOperations.newChange().project(project).file("z").content("z").create();
+    gApi.changes().id(setup.get()).current().review(ReviewInput.approve());
+    gApi.changes().id(setup.get()).current().submit();
+
+    ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
+    Change.Id changeId =
+        changeOperations
+            .newChange()
+            .project(project)
+            .file("file")
+            .content("line1\nline2\nline3\nline4")
+            .create();
+
+    gApi.changes().id(changeId.get()).current().review(ReviewInput.approve());
+    changeOperations
+        .change(changeId)
+        .newPatchset()
+        .file("file")
+        .content("line012\nline1\nline2\nline3\nline4")
+        .create();
+
+    // add a reviewer to ensure an email is sent.
+    gApi.changes().id(changeId.get()).addReviewer(user.email());
+
+    testRepo.reset(initial);
+    // create 2 unrelated changes and rebase on top of them. Those rebases should be ignored.
+    // The changes delete files.
+    Change.Id unrelated = changeOperations.newChange().project(project).file("a").delete().create();
+    gApi.changes().id(unrelated.get()).current().review(ReviewInput.approve());
+    gApi.changes().id(unrelated.get()).current().submit();
+    unrelated = changeOperations.newChange().project(project).file("z").delete().create();
+    gApi.changes().id(unrelated.get()).current().review(ReviewInput.approve());
+    gApi.changes().id(unrelated.get()).current().submit();
+
+    RebaseInput rebaseInput = new RebaseInput();
+    rebaseInput.base = gApi.changes().id(unrelated.get()).current().commit(true).commit;
+    gApi.changes().id(changeId.get()).current().rebase(rebaseInput);
+
+    gApi.changes().id(changeId.get()).current().submit();
+
+    assertDiffChangeMessageAndEmailWithStickyApproval(
+        Iterables.getLast(gApi.changes().id(changeId.get()).messages()).message,
+        /* file= */ "file",
+        /* insertions= */ 1,
+        /* deletions= */ 0,
+        /* expectedFileDiff= */ "@@ -1,3 +1,4 @@\n"
+            + "+line012\n"
+            + " line1\n"
+            + " line2\n"
+            + " line3",
+        /* oldFileName= */ null);
+  }
+
+  @Test
   public void diffChangeMessageOnSubmitWithStickyVote_modifiedFileWithInsertionAndDeletion()
       throws Exception {
     Change.Id changeId =
@@ -208,9 +387,9 @@
     assertThat(Iterables.getLast(gApi.changes().id(changeId.get()).messages()).message)
         .isEqualTo(
             "Change has been successfully merged\n\n1 is the latest approved patch-set.\nThe "
-                + "change was submitted with unreviewed changes in the following files:\n\nThe "
-                + "name of the file: file\nInsertions: 1, Deletions: 1.\n\nThe diff is too "
-                + "large to show. Please review the diff.");
+                + "change was submitted with unreviewed changes in the following "
+                + "files:\n\n```\nThe name of the file: file\nInsertions: 1, Deletions: 1.\n\nThe"
+                + " diff is too large to show. Please review the diff.\n```\n");
   }
 
   @Test
@@ -362,6 +541,7 @@
         "1 is the latest approved patch-set.\n"
             + "The change was submitted with unreviewed changes in the following files:\n"
             + "\n"
+            + "```\n"
             + String.format("The name of the file: %s\n", file)
             + String.format("Insertions: %d, Deletions: %d.\n\n", insertions, deletions);
 
@@ -369,6 +549,7 @@
       expectedMessage += String.format("The file %s was renamed to %s\n", oldFileName, file);
     }
     expectedMessage += expectedFileDiff;
+    expectedMessage += "\n```\n";
     String expectedChangeMessage = "Change has been successfully merged\n\n" + expectedMessage;
     assertThat(message.trim()).isEqualTo(expectedChangeMessage.trim());
     assertThat(Iterables.getLast(sender.getMessages()).body()).contains(expectedMessage);
diff --git a/package.json b/package.json
index ffca123..9df72da 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,7 @@
     "compile:local": "tsc --project ./polygerrit-ui/app/tsconfig.json",
     "compile:watch": "npm run compile:local -- --preserveWatchOutput --watch",
     "start": "polygerrit-ui/run-server.sh",
-    "test": "./polygerrit-ui/app/run_test.sh",
+    "test": "npm run safe_bazelisk test //polygerrit-ui:karma_test -- --test_verbose_timeout_warnings --test_output=all",
     "safe_bazelisk": "if which bazelisk >/dev/null; then bazel_bin=bazelisk; else bazel_bin=bazel; fi && $bazel_bin",
     "eslint": "npm run safe_bazelisk test polygerrit-ui/app:lint_test",
     "eslintfix": "npm run safe_bazelisk run polygerrit-ui/app:lint_bin -- -- --fix $(pwd)/polygerrit-ui/app",
diff --git a/plugins/gitiles b/plugins/gitiles
index 55380c6..6e78bae 160000
--- a/plugins/gitiles
+++ b/plugins/gitiles
@@ -1 +1 @@
-Subproject commit 55380c69680b1d6ae590939f60a08fe93d9b502b
+Subproject commit 6e78bae6502f693c509fa30c0c94ef2f1b1404c2
diff --git a/plugins/package.json b/plugins/package.json
index cd85e8c..f761be9 100644
--- a/plugins/package.json
+++ b/plugins/package.json
@@ -6,7 +6,7 @@
       "@polymer/decorators": "^3.0.0",
       "@polymer/polymer": "^3.4.1",
       "@gerritcodereview/typescript-api": "3.4.2",
-      "lit": "2.0.0-rc.2"
+      "lit": "2.0.0-rc.3"
     },
     "license": "Apache-2.0",
     "private": true
diff --git a/plugins/yarn.lock b/plugins/yarn.lock
index 4d7c38b..1faa71a 100644
--- a/plugins/yarn.lock
+++ b/plugins/yarn.lock
@@ -51,11 +51,18 @@
   dependencies:
     "@types/trusted-types" "^1.0.1"
 
-lit@2.0.0-rc.2:
-  version "2.0.0-rc.2"
-  resolved "https://registry.yarnpkg.com/lit/-/lit-2.0.0-rc.2.tgz#724a2d621aa098001d73bf7106f3a72b7b5948ef"
-  integrity sha512-BOCuoJR04WaTV8UqTKk09cNcQA10Aq2LCcBOiHuF7TzWH5RNDsbCBP5QM9sLBSotGTXbDug/gFO08jq6TbyEtw==
+lit-html@^2.0.0-rc.4:
+  version "2.0.0-rc.4"
+  resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.0.0-rc.4.tgz#1015fa8f1f7c8c5b79999ed0bc11c3b79ff1aab5"
+  integrity sha512-WSLGu3vxq7y8q/oOd9I3zxyBELNLLiDk6gAYoKK4PGctI5fbh6lhnO/jVBdy0PV/vTc+cLJCA/occzx3YoNPeg==
+  dependencies:
+    "@types/trusted-types" "^1.0.1"
+
+lit@2.0.0-rc.3:
+  version "2.0.0-rc.3"
+  resolved "https://registry.yarnpkg.com/lit/-/lit-2.0.0-rc.3.tgz#8b6a85268aba287c11125dfe57e88e0bc09beaff"
+  integrity sha512-UZDLWuspl7saA+WvS0e+TE3NdGGE05hOIwUPTWiibs34c5QupcEzpjB/aElt79V9bELQVNbUUwa0Ow7D1Wuszw==
   dependencies:
     "@lit/reactive-element" "^1.0.0-rc.2"
     lit-element "^3.0.0-rc.2"
-    lit-html "^2.0.0-rc.3"
+    lit-html "^2.0.0-rc.4"
diff --git a/polygerrit-ui/app/api/rest-api.ts b/polygerrit-ui/app/api/rest-api.ts
index 6474e85..f3bb39b 100644
--- a/polygerrit-ui/app/api/rest-api.ts
+++ b/polygerrit-ui/app/api/rest-api.ts
@@ -739,6 +739,20 @@
   | DetailedLabelInfo
   | (QuickLabelInfo & DetailedLabelInfo);
 
+export type LabelNameToLabelTypeInfoMap = {[labelName: string]: LabelTypeInfo};
+
+/**
+ * The LabelTypeInfo entity contains metadata about the labels that a project
+ * has.
+ * https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#label-type-info
+ */
+export interface LabelTypeInfo {
+  values: LabelTypeInfoValues;
+  default_value: number;
+}
+
+export type LabelTypeInfoValues = {[value: string]: string};
+
 // The map maps the values (“-2”, “-1”, " `0`", “+1”, “+2”) to the value descriptions.
 export type LabelValueToDescriptionMap = {[labelValue: string]: string};
 
@@ -807,6 +821,31 @@
 }
 
 /**
+ * The ProjectInfo entity contains information about a project
+ * https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#project-info
+ */
+export interface ProjectInfo {
+  id: UrlEncodedRepoName;
+  // name is not set if returned in a map where the project name is used as
+  // map key
+  name?: RepoName;
+  // ?-<n> if the parent project is not visible (<n> is a number which
+  // is increased for each non-visible project).
+  parent?: RepoName;
+  description?: string;
+  state?: ProjectState;
+  branches?: {[branchName: string]: CommitId};
+  // labels is filled for Create Project and Get Project calls.
+  labels?: LabelNameToLabelTypeInfoMap;
+  // Links to the project in external sites
+  web_links?: WebLinkInfo[];
+}
+
+export interface ProjectInfoWithName extends ProjectInfo {
+  name: RepoName;
+}
+
+/**
  * The PushCertificateInfo entity contains information about a pushcertificate
  * provided when the user pushed for review with git push
  * --signed HEAD:refs/for/<branch>. Only used when signed push is
@@ -1060,3 +1099,5 @@
   OVERRIDDEN = 'OVERRIDDEN',
   NOT_APPLICABLE = 'NOT_APPLICABLE',
 }
+
+export type UrlEncodedRepoName = BrandType<string, '_urlEncodedRepoName'>;
diff --git a/polygerrit-ui/app/api/rest.ts b/polygerrit-ui/app/api/rest.ts
index 4c93ef0..46dad3d 100644
--- a/polygerrit-ui/app/api/rest.ts
+++ b/polygerrit-ui/app/api/rest.ts
@@ -14,6 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import {ProjectInfoWithName, ServerInfo} from './rest-api';
+
 export type RequestPayload = string | object;
 
 export enum HttpMethod {
@@ -31,15 +33,16 @@
 
   getVersion(): Promise<string | undefined>;
 
-  /**
-   * Returns a ServerInfo object as defined here:
-   * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#server-info
-   * We neither want to repeat it nor add a dependency on it here.
-   */
-  getConfig(): Promise<unknown>;
+  getConfig(): Promise<ServerInfo | undefined>;
 
   invalidateReposCache(): void;
 
+  getRepos(
+    filter: string,
+    reposPerPage: number,
+    offset?: number
+  ): Promise<ProjectInfoWithName[] | undefined>;
+
   fetch(
     method: HttpMethod,
     url: string,
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
index e0887ce..3e52165 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
@@ -93,8 +93,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._getCreateGroupCapability();
     fireTitleChange(this, 'Groups');
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 90f0699..1c9c6f3 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
@@ -173,8 +173,7 @@
 
   private readonly jsAPI = appContext.jsApiService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.reload();
   }
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 1aa04ac..63b10ec 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
@@ -101,8 +101,7 @@
     this._query = (input: string) => this._getRepoBranchesSuggestions(input);
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     if (!this.repoName) return;
 
@@ -116,8 +115,7 @@
     });
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.disconnected$.next();
     super.disconnectedCallback();
   }
diff --git a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts
index c0ff9ba..180e60a 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts
@@ -51,7 +51,7 @@
     this.hasNewGroupName = !!name;
   }
 
-  focus() {
+  override focus() {
     this.shadowRoot?.querySelector('input')?.focus();
   }
 
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 94a4b0a..a493747 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
@@ -87,7 +87,7 @@
     return getBaseUrl() + '/admin/repos/' + encodeURL(repoName, true);
   }
 
-  focus() {
+  override focus() {
     this.shadowRoot?.querySelector('input')?.focus();
   }
 
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts
index 87696f1..cdc2fb3 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts
@@ -53,14 +53,12 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     fireTitleChange(this, 'Audit Log');
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this._getAuditLogs();
   }
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 0a862eb..70242d8 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
@@ -127,8 +127,7 @@
     this._queryIncludedGroup = input => this._getGroupSuggestions(input);
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._loadGroupDetails();
 
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 0bb13ba..18862b5 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
@@ -129,8 +129,7 @@
     this._query = (input: string) => this._getGroupSuggestions(input);
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._loadGroup();
   }
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
index eb535fa..0c34a84 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
@@ -144,8 +144,7 @@
     this.addEventListener('access-saved', () => this._handleAccessSaved());
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this._setupValues();
   }
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts
index 96cae0e..f275dfd 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts
@@ -74,8 +74,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     fireTitleChange(this, 'Plugins');
   }
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 df78df3..3cef13f 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
@@ -89,8 +89,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._loadRepo();
 
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
index 3bc3bef..982fd8d 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
@@ -86,8 +86,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._getCreateRepoCapability();
     fireTitleChange(this, 'Repos');
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
index 3d916d1..42060ee 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
@@ -67,7 +67,7 @@
   @property({type: Object})
   pluginData?: PluginData;
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       formStyles,
@@ -88,7 +88,7 @@
     ];
   }
 
-  render() {
+  override render() {
     // Render can be called prior to pluginData being updated.
     const pluginConfigOptions = this.pluginData
       ? this._computePluginConfigOptions(this.pluginData)
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 6763308..d132267 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
@@ -139,8 +139,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._loadRepo();
 
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 37e0596..172f807 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
@@ -141,8 +141,7 @@
     this.addEventListener('access-saved', () => this._handleAccessSaved());
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     // Called on ready rather than the observer because when new rules are
     // added, the observer is triggered prior to being ready.
@@ -152,8 +151,7 @@
     this._setupValues(this.rule);
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     // Check needed for test purposes.
     if (!this._originalRuleValues && this.rule) {
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 fa3c9c6..50bbda9 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
@@ -122,8 +122,7 @@
 
   reporting: ReportingService = appContext.reportingService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     getPluginLoader()
       .awaitPluginsLoaded()
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
index 84bc1e2..9970dd5 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
@@ -118,8 +118,7 @@
     this.addEventListener('previous-page', () => this._handlePreviousPage());
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._loadPreferences();
   }
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 133dfa0..5c0bab8 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
@@ -142,7 +142,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  keyboardShortcuts() {
+  override keyboardShortcuts() {
     return {
       [Shortcut.CURSOR_NEXT_CHANGE]: '_nextChange',
       [Shortcut.CURSOR_PREV_CHANGE]: '_prevChange',
@@ -166,16 +166,14 @@
     );
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this.restApiService.getConfig().then(config => {
       this._config = config;
     });
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     getPluginLoader()
       .awaitPluginsLoaded()
@@ -185,8 +183,7 @@
       });
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.cursor.unsetCursor();
     super.disconnectedCallback();
   }
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts
index e7c7e0c..055996ee 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.ts
@@ -30,7 +30,7 @@
 
 @customElement('gr-create-change-help')
 export class GrCreateChangeHelp extends GrLitElement {
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       css`
@@ -79,7 +79,7 @@
     ];
   }
 
-  render() {
+  override render() {
     return html` <div id="graphic">
         <div id="circle">
           <iron-icon id="icon" icon="gr-icons:zeroState"></iron-icon>
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.ts b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.ts
index 3656991..171c1c3 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.ts
@@ -43,7 +43,7 @@
   @property({type: String})
   branch?: string;
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       css`
@@ -61,7 +61,7 @@
     ];
   }
 
-  render() {
+  override render() {
     return html` <gr-overlay id="commandsOverlay" with-backdrop="">
       <gr-dialog
         id="commandsDialog"
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
index 41a7598..c341dea 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
@@ -124,8 +124,7 @@
     super();
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._loadPreferences();
     this.addEventListener('reload', () => this._reload(this.params));
diff --git a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts
index 2d3e129..7b60715 100644
--- a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts
@@ -38,7 +38,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       dashboardHeaderStyles,
@@ -66,7 +66,7 @@
     </div> `;
   }
 
-  render() {
+  override render() {
     return html` <div class="info">
       <h1 class="heading-1">${this.repo}</h1>
       <hr />
@@ -77,7 +77,7 @@
     </div>`;
   }
 
-  updated(changedProperties: PropertyValues) {
+  override updated(changedProperties: PropertyValues) {
     if (changedProperties.has('repo')) {
       this._repoChanged();
     }
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index db5b64e..081df40 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -575,8 +575,7 @@
     );
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this.jsAPI.addElement(TargetElement.CHANGE_ACTIONS, this);
     this.restApiService.getConfig().then(config => {
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
index 40e8685..e06f664 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
@@ -226,8 +226,7 @@
 
   private readonly flagsService = appContext.flagsService;
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this.queryTopic = (input: string) => this._getTopicSuggestions(input);
     this._isSubmitRequirementsUiEnabled = this.flagsService.isEnabled(
diff --git a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
index e596901..97d8a37 100644
--- a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
@@ -90,7 +90,7 @@
 
   private readonly reporting = appContext.reportingService;
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       css`
@@ -142,7 +142,7 @@
     ];
   }
 
-  render() {
+  override render() {
     const chipClass = `summaryChip font-small ${this.styleType}`;
     const grIcon = this.icon ? `gr-icons:${this.icon}` : '';
     return html`<button class="${chipClass}" @click="${this.handleClick}">
@@ -171,7 +171,7 @@
   @property()
   text = '';
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       css`
@@ -279,7 +279,7 @@
     ];
   }
 
-  render() {
+  override render() {
     if (!this.text) return;
     if (!this.statusOrCategory) return;
     const icon = iconFor(this.statusOrCategory);
@@ -351,7 +351,7 @@
     this.subscribe('loginCallback', loginCallbackLatest$);
   }
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       spinnerStyles,
@@ -596,7 +596,7 @@
     e.stopPropagation();
   }
 
-  render() {
+  override render() {
     const commentThreads =
       this.commentThreads?.filter(t => !isRobotThread(t) || hasHumanReply(t)) ??
       [];
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 d2f4485..5ec35c4 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
@@ -556,7 +556,7 @@
 
   private replyDialogResizeObserver?: ResizeObserver;
 
-  keyboardShortcuts() {
+  override keyboardShortcuts() {
     return {
       [Shortcut.SEND_REPLY]: null, // DOC_ONLY binding
       [Shortcut.EMOJI_DROPDOWN]: null, // DOC_ONLY binding
@@ -586,8 +586,7 @@
 
   private lastStarredTimestamp?: number;
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     aPluginHasRegistered$.pipe(takeUntil(this.disconnected$)).subscribe(b => {
       this._showChecksTab = b;
@@ -625,8 +624,7 @@
     this.addEventListener('open-reply-dialog', () => this._openReplyDialog());
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._throttledToggleChangeStar = throttleWrap(e =>
       this._handleToggleChangeStar(e as CustomKeyboardEvent)
@@ -689,8 +687,7 @@
     });
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.disconnected$.next();
     document.removeEventListener(
       'visibilitychange',
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
index 5aef4bb..968800d 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
@@ -108,13 +108,12 @@
     });
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this._ensureAttribute('role', 'dialog');
   }
 
-  focus() {
+  override focus() {
     if (this._schemes.length) {
       this.$.downloadCommands.focusOnCopy();
     } else {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index c1da09f..0427e9e 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -324,7 +324,7 @@
     };
   }
 
-  keyboardShortcuts() {
+  override keyboardShortcuts() {
     return {
       [Shortcut.LEFT_PANE]: '_handleLeftPane',
       [Shortcut.RIGHT_PANE]: '_handleRightPane',
@@ -365,8 +365,7 @@
     );
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     changeComments$
       .pipe(takeUntil(this.disconnected$))
@@ -417,8 +416,7 @@
       });
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.disconnected$.next();
     this.diffCursor.dispose();
     this.fileCursor.unsetCursor();
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
index 178029a..94295d0 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
@@ -135,7 +135,7 @@
     reflectToAttribute: true,
     computed: '_computeIsHidden(hideAutomated, isAutomated)',
   })
-  hidden = false;
+  override hidden = false;
 
   @computed('message')
   get isAutomated() {
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-change.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-change.ts
index 921c45c..b3730fd 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-change.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-change.ts
@@ -47,7 +47,7 @@
   @property()
   connectedRevisions?: CommitId[];
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       css`
@@ -104,7 +104,7 @@
     ];
   }
 
-  render() {
+  override render() {
     const change = this.change;
     if (!change) throw new Error('Missing change');
     const linkClass = this._computeLinkClass(change);
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
index a68796c..dd86ea1 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
@@ -91,7 +91,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       css`
@@ -138,7 +138,7 @@
     ];
   }
 
-  render() {
+  override render() {
     const sectionSize = this.sectionSizeFactory(
       this.relatedChanges.length,
       this.submittedTogether?.changes.length || 0,
@@ -668,7 +668,7 @@
 @customElement('gr-related-collapse')
 export class GrRelatedCollapse extends GrLitElement {
   @property()
-  title = '';
+  override title = '';
 
   @property({type: Boolean})
   showAll = false;
@@ -684,7 +684,7 @@
 
   private readonly reporting = appContext.reportingService;
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       css`
@@ -720,7 +720,7 @@
     ];
   }
 
-  render() {
+  override render() {
     const title = html`<h4 class="title">${this.title}</h4>`;
 
     const collapsible = this.length > this.numChangesWhenCollapsed;
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index 0d2f68d..89df2cb 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -379,8 +379,7 @@
     this.filterCCSuggestion = this._filterReviewerSuggestionGenerator(true);
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     (
       IronA11yAnnouncer as unknown as FixIronA11yAnnouncer
@@ -409,14 +408,12 @@
     });
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this.jsAPI.addElement(TargetElement.REPLY_DIALOG, this);
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.storeTask?.cancel();
     super.disconnectedCallback();
   }
@@ -459,7 +456,7 @@
     return draft.length > 0 || draftCommentThreads.base.length > 0;
   }
 
-  focus() {
+  override focus() {
     this._focusOn(FocusTarget.ANY);
   }
 
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
index 320c588..47f6e85 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
@@ -37,7 +37,7 @@
   @property({type: Boolean})
   mutable?: boolean;
 
-  static get styles() {
+  static override get styles() {
     return [
       css`
         :host {
@@ -81,11 +81,25 @@
         iron-icon.unsatisfied {
           color: var(--warning-foreground);
         }
+        .testing {
+          margin-top: var(--spacing-xxl);
+          padding-left: var(--metadata-horizontal-padding);
+          color: var(--deemphasized-text-color);
+        }
+        .testing gr-button {
+          min-width: 25px;
+        }
+        .testing * {
+          visibility: hidden;
+        }
+        .testing:hover * {
+          visibility: visible;
+        }
       `,
     ];
   }
 
-  render() {
+  override render() {
     const submit_requirements = (this.change?.submit_requirements ?? []).filter(
       req => req.status !== SubmitRequirementStatus.NOT_APPLICABLE
     );
@@ -102,7 +116,7 @@
           </div>
           <div class="value">${this.renderLabels(requirement)}</div>
         </section>`
-      )}`;
+      )}${this.renderFakeControls()}`;
   }
 
   renderStatus(status: SubmitRequirementStatus) {
@@ -155,6 +169,49 @@
       ></gr-label-info>`
     );
   }
+
+  renderFakeControls() {
+    return html`
+      <div class="testing">
+        <div>Toggle fake data:</div>
+        <gr-button link @click="${() => this.renderFakeSubmitRequirements()}"
+          >G</gr-button
+        >
+      </div>
+    `;
+  }
+
+  renderFakeSubmitRequirements() {
+    if (!this.change) return;
+    this.change = {
+      ...this.change,
+      submit_requirements: [
+        {
+          name: 'Code-Review',
+          status: SubmitRequirementStatus.SATISFIED,
+          description:
+            "At least one maximum vote for label 'Code-Review' is required",
+          submittability_expression_result: {
+            expression: 'label:Code-Review=MAX -label:Code-Review=MIN',
+            fulfilled: true,
+            passing_atoms: [],
+            failing_atoms: [],
+          },
+        },
+        {
+          name: 'Verified',
+          status: SubmitRequirementStatus.UNSATISFIED,
+          description: 'CI build and tests results are verified',
+          submittability_expression_result: {
+            expression: 'label:Verified=MAX -label:Verified=MIN',
+            fulfilled: false,
+            passing_atoms: [],
+            failing_atoms: [],
+          },
+        },
+      ],
+    };
+  }
 }
 
 declare global {
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-action.ts b/polygerrit-ui/app/elements/checks/gr-checks-action.ts
index f967ea5..4633c0e 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-action.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-action.ts
@@ -29,12 +29,12 @@
   @property()
   eventTarget?: EventTarget;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     checkRequiredProperty(this.action, 'action');
   }
 
-  static get styles() {
+  static override get styles() {
     return [
       css`
         :host {
@@ -61,7 +61,7 @@
     ];
   }
 
-  render() {
+  override render() {
     return html`
       <gr-button
         link
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-attempt.ts b/polygerrit-ui/app/elements/checks/gr-checks-attempt.ts
index 098f5b4..6de92e1 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-attempt.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-attempt.ts
@@ -25,7 +25,7 @@
   @property()
   run?: CheckRun;
 
-  static get styles() {
+  static override get styles() {
     return [
       css`
         .attempt {
@@ -62,7 +62,7 @@
     ];
   }
 
-  render() {
+  override render() {
     if (!this.run) return undefined;
     if (this.run.isSingleAttempt) return undefined;
     if (!this.run.attempt) return undefined;
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index 936ba5a..08f3907 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -107,7 +107,7 @@
     this.subscribe('labels', labels$);
   }
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       css`
@@ -284,24 +284,24 @@
     ];
   }
 
-  update(changedProperties: PropertyValues) {
+  override update(changedProperties: PropertyValues) {
     if (changedProperties.has('result')) {
       this.isExpandable = !!this.result?.summary && !!this.result?.message;
     }
     super.update(changedProperties);
   }
 
-  focus() {
+  override focus() {
     if (this.nameEl) this.nameEl.focus();
   }
 
-  firstUpdated() {
+  override firstUpdated() {
     const loading = this.shadowRoot?.querySelector('.container');
     assertIsDefined(loading, '"Loading" element');
     whenVisible(loading, () => this.setAttribute('shouldRender', 'true'), 200);
   }
 
-  render() {
+  override render() {
     if (!this.result) return '';
     if (!this.shouldRender) {
       return html`
@@ -547,7 +547,7 @@
 
   private changeService = appContext.changeService;
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       css`
@@ -573,7 +573,7 @@
     this.subscribe('repoConfig', repoConfig$);
   }
 
-  render() {
+  override render() {
     if (!this.result) return '';
     return html`
       ${this.renderFirstPrimaryLink()} ${this.renderOtherPrimaryLinks()}
@@ -750,7 +750,7 @@
     this.subscribe('someProvidersAreLoading', someProvidersAreLoadingSelected$);
   }
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       spinnerStyles,
@@ -935,7 +935,7 @@
     ];
   }
 
-  protected updated(changedProperties: PropertyValues) {
+  protected override updated(changedProperties: PropertyValues) {
     super.updated(changedProperties);
     if (changedProperties.has('tabState') && this.tabState) {
       const {statusOrCategory, checkName} = this.tabState;
@@ -971,7 +971,7 @@
     });
   }
 
-  render() {
+  override render() {
     // To pass CSS mixins for @apply to Polymer components, they need to appear
     // in <style> inside the template.
     const style = html`<style>
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
index d29d483..250b035 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
@@ -71,7 +71,7 @@
 
 @customElement('gr-checks-run')
 export class GrChecksRun extends GrLitElement {
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       css`
@@ -195,7 +195,7 @@
   @property()
   shouldRender = false;
 
-  firstUpdated() {
+  override firstUpdated() {
     assertIsDefined(this.chipElement, 'chip element');
     whenVisible(
       this.chipElement,
@@ -204,7 +204,7 @@
     );
   }
 
-  protected updated(changedProperties: PropertyValues) {
+  protected override updated(changedProperties: PropertyValues) {
     super.updated(changedProperties);
 
     // For some reason the browser does not pick up the correct `checked` state
@@ -219,7 +219,7 @@
     }
   }
 
-  render() {
+  override render() {
     if (!this.shouldRender) return html`<div class="chip">Loading ...</div>`;
 
     const icon = iconForRun(this.run);
@@ -396,7 +396,7 @@
     this.subscribe('loginCallback', loginCallbackLatest$);
   }
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       css`
@@ -495,7 +495,7 @@
     ];
   }
 
-  protected updated(changedProperties: PropertyValues) {
+  protected override updated(changedProperties: PropertyValues) {
     super.updated(changedProperties);
     if (changedProperties.has('tabState') && this.tabState) {
       const {statusOrCategory} = this.tabState;
@@ -512,7 +512,7 @@
     }
   }
 
-  render() {
+  override render() {
     if (this.collapsed) {
       return html`${this.renderCollapseButton()}`;
     }
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
index d60bded..1e98dfa 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
@@ -86,7 +86,7 @@
     );
   }
 
-  static get styles() {
+  static override get styles() {
     return css`
       :host {
         display: block;
@@ -104,7 +104,7 @@
     `;
   }
 
-  render() {
+  override render() {
     return html`
       <div class="container">
         <gr-checks-runs
@@ -129,7 +129,7 @@
     `;
   }
 
-  protected updated(changedProperties: PropertyValues) {
+  protected override updated(changedProperties: PropertyValues) {
     super.updated(changedProperties);
     if (changedProperties.has('tabState')) {
       if (this.tabState) {
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts
index 658959b..881eb28 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts
@@ -55,8 +55,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.handleLocationChange();
     window.addEventListener('location-change', this.handleLocationChange);
@@ -72,8 +71,7 @@
     });
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     window.removeEventListener('location-change', this.handleLocationChange);
     super.disconnectedCallback();
   }
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts
index f250b76..3b09d8c 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.ts
@@ -153,8 +153,7 @@
 
   private checkLoggedInTask?: DelayedTask;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     document.addEventListener(EventType.SERVER_ERROR, this.handleServerError);
     document.addEventListener(EventType.NETWORK_ERROR, this.handleNetworkError);
@@ -176,8 +175,7 @@
     ).requestAvailability();
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this._clearHideAlertHandle();
     document.removeEventListener(
       EventType.SERVER_ERROR,
diff --git a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.ts b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.ts
index 657d7cc..c820ec4 100644
--- a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.ts
+++ b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.ts
@@ -26,7 +26,7 @@
 
 @customElement('gr-key-binding-display')
 export class GrKeyBindingDisplay extends GrLitElement {
-  static get styles() {
+  static override get styles() {
     return [
       css`
         .key {
@@ -43,7 +43,7 @@
     ];
   }
 
-  render() {
+  override render() {
     const items = this.binding.map((binding, index) => [
       index > 0 ? html` or ` : html``,
       this._computeModifiers(binding).map(
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
index 0ec9b39..68b696e 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
@@ -67,22 +67,19 @@
     ) => this._onDirectoryUpdated(d);
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this._ensureAttribute('role', 'dialog');
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.addKeyboardShortcutDirectoryListener(
       this.keyboardShortcutDirectoryListener
     );
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.removeKeyboardShortcutDirectoryListener(
       this.keyboardShortcutDirectoryListener
     );
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
index fd7e25e..9d34929 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
@@ -161,14 +161,12 @@
 
   private readonly disconnected$ = new Subject();
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this._ensureAttribute('role', 'banner');
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     // TODO(brohlfs): This just ensures that the userService is instantiated at
     // all. We need the service to manage the model, but we are not making any
     // direct calls. Will need to find a better solution to this problem ...
@@ -191,8 +189,7 @@
     });
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.disconnected$.next();
     super.disconnectedCallback();
   }
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 eea95e1..7c65c3f 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
@@ -239,7 +239,7 @@
     }
   }
 
-  keyboardShortcuts() {
+  override keyboardShortcuts() {
     return {
       [Shortcut.SEARCH]: '_handleSearch',
     };
diff --git a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.ts b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.ts
index 7419713..6a73d8d 100644
--- a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.ts
+++ b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.ts
@@ -67,8 +67,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.restApiService.getConfig().then(cfg => {
       this._config = cfg;
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index e60c2bb..aa01c5f 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -150,7 +150,7 @@
     this.addEventListener('diff-context-expanded', this.refitOverlay);
   }
 
-  disconnectedCallback() {
+  override disconnectedCallback() {
     if (this.refitOverlay) {
       this.removeEventListener('diff-context-expanded', this.refitOverlay);
     }
diff --git a/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts b/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
index 4b32f5e..ff275ad 100644
--- a/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
+++ b/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
@@ -106,7 +106,7 @@
 
   private disconnected$ = new Subject();
 
-  static styles = css`
+  static override styles = css`
     :host {
       display: flex;
       justify-content: center;
@@ -209,12 +209,12 @@
     }
   `;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.setupButtonHoverHandler();
   }
 
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.disconnected$.next();
   }
 
@@ -529,7 +529,7 @@
     return !!(this.diff && this.section && this.contextGroups?.length);
   }
 
-  render() {
+  override render() {
     if (!this.hasValidProperties()) {
       console.error('Invalid properties for gr-context-controls!');
       return html`<p>invalid properties</p>`;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.ts
index 51fc207..4186a10 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.ts
@@ -30,7 +30,7 @@
     super(diff, prefs, outputEl);
   }
 
-  buildSectionElement(): HTMLElement {
+  override buildSectionElement(): HTMLElement {
     const section = this._createElement('tbody', 'binary-diff');
     const line = new GrDiffLine(GrDiffLineType.BOTH, 'FILE', 'FILE');
     const fileRow = this._createRow(line);
@@ -40,6 +40,5 @@
     return section;
   }
 
-  /** @override */
-  updateRenderPrefs(_renderPrefs: RenderPreferences) {}
+  override updateRenderPrefs(_renderPrefs: RenderPreferences) {}
 }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
index 412a7b0..67f5a58 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
@@ -166,8 +166,7 @@
   @property({type: Object})
   _cancelableRenderPromise: CancelablePromise<unknown> | null = null;
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     if (this._builder) {
       this._builder.clear();
     }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts
index b42e90a..5629aa4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts
@@ -216,8 +216,7 @@
     section.appendChild(tr);
   }
 
-  /** @override */
-  updateRenderPrefs(renderPrefs: RenderPreferences) {
+  override updateRenderPrefs(renderPrefs: RenderPreferences) {
     const imageViewer = this._outputEl.querySelector(
       'gr-image-viewer'
     ) as GrImageViewer;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
index 51360b9..057b926 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
@@ -28,7 +28,7 @@
     diff: DiffInfo,
     prefs: DiffPreferencesInfo,
     outputEl: HTMLElement,
-    readonly layers: DiffLayer[] = [],
+    layers: DiffLayer[] = [],
     renderPrefs?: RenderPreferences
   ) {
     super(diff, prefs, outputEl, layers, renderPrefs);
@@ -137,6 +137,5 @@
     return null;
   }
 
-  /** @override */
-  updateRenderPrefs(_renderPrefs: RenderPreferences) {}
+  override updateRenderPrefs(_renderPrefs: RenderPreferences) {}
 }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
index a16aa07..b2dfa61 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
@@ -27,7 +27,7 @@
     diff: DiffInfo,
     prefs: DiffPreferencesInfo,
     outputEl: HTMLElement,
-    readonly layers: DiffLayer[] = [],
+    layers: DiffLayer[] = [],
     renderPrefs?: RenderPreferences
   ) {
     super(diff, prefs, outputEl, layers, renderPrefs);
@@ -146,6 +146,5 @@
     return null;
   }
 
-  /** @override */
-  updateRenderPrefs(_renderPrefs: RenderPreferences) {}
+  override updateRenderPrefs(_renderPrefs: RenderPreferences) {}
 }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
index 8a87791..647f701 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
@@ -92,10 +92,14 @@
 
   private tokenToLinesRight = new Map<string, Set<number>>();
 
+  private hoveredElement?: Element;
+
   private updateTokenTask?: DelayedTask;
 
   constructor() {
-    window.addEventListener('click', _ => this.handleWindowClick());
+    window.addEventListener('click', _ => {
+      this.handleWindowClick();
+    });
   }
 
   annotate(
@@ -132,7 +136,12 @@
       // These listeners do not have to be cleaned, because listeners are
       // garbage collected along with the element itself once it is not attached
       // to the DOM anymore and no references exist anymore.
-      el.addEventListener('mouseover', e => this.handleMouseOver(e));
+      el.addEventListener('mouseover', e => {
+        this.handleMouseOver(e);
+      });
+      el.addEventListener('mouseout', e => {
+        this.handleMouseOut(e);
+      });
     }
   }
 
@@ -153,17 +162,35 @@
     numbers.add(Number(lineNumber));
   }
 
+  private handleMouseOut(e: MouseEvent) {
+    // If there's no ongoing hover-task, terminate early.
+    if (!this.updateTokenTask?.isActive()) return;
+    if (this.interferesWithSelection(e)) return;
+    const {element} = this.findTokenAncestor(e?.target);
+    if (!element) return;
+    if (element === this.hoveredElement) {
+      // If we are moving out of the currently hovered element, cancel the
+      // update task.
+      this.hoveredElement = undefined;
+      this.updateTokenTask?.cancel();
+    }
+  }
+
   private handleMouseOver(e: MouseEvent) {
     if (this.interferesWithSelection(e)) return;
-    const {line, token} = this.findTokenAncestor(e?.target);
-    if (!token) return;
-    const oldHighlight = this.currentHighlight;
-    const newHighlight = token;
-    if (!newHighlight || newHighlight === oldHighlight) return;
+    const {
+      line,
+      token: newHighlight,
+      element,
+    } = this.findTokenAncestor(e?.target);
+    if (!newHighlight || newHighlight === this.currentHighlight) return;
     if (this.countOccurrences(newHighlight) <= 1) return;
+    this.hoveredElement = element;
     this.updateTokenTask = debounce(
       this.updateTokenTask,
-      () => this._updateTokenHighlight(line, newHighlight),
+      () => {
+        this.updateTokenHighlight(newHighlight, line);
+      },
       HOVER_DELAY_MS
     );
   }
@@ -175,37 +202,44 @@
   }
 
   private handleWindowClick() {
+    this.hoveredElement = undefined;
     this.updateTokenTask?.cancel();
-    this._updateTokenHighlight(undefined, undefined);
+    this.updateTokenHighlight(undefined, 0);
   }
 
-  private _updateTokenHighlight(
-    newLineNumber: number | undefined,
-    newHighlight: string | undefined
+  private updateTokenHighlight(
+    newHighlight: string | undefined,
+    newLineNumber: number
   ) {
     const oldHighlight = this.currentHighlight;
     const oldLineNumber = this.currentHighlightLineNumber;
     this.currentHighlight = newHighlight;
-    this.currentHighlightLineNumber = newLineNumber ?? 0;
+    this.currentHighlightLineNumber = newLineNumber;
     this.notifyForToken(oldHighlight, oldLineNumber);
-    this.notifyForToken(newHighlight, newLineNumber ?? 0);
+    this.notifyForToken(newHighlight, newLineNumber);
+    // Reset the hovered element.
+    this.hoveredElement = undefined;
   }
 
   findTokenAncestor(el?: EventTarget | Element | null): {
     token?: string;
     line: number;
+    element?: Element;
   } {
-    if (!(el instanceof Element)) return {line: 0, token: undefined};
+    if (!(el instanceof Element))
+      return {line: 0, token: undefined, element: undefined};
     if (
       el.classList.contains(CSS_TOKEN) ||
       el.classList.contains(CSS_HIGHLIGHT)
     ) {
       const tkClass = [...el.classList].find(c => c.startsWith('tk-'));
       const line = lineNumberToNumber(getLineNumberByChild(el));
-      if (!line || !tkClass) return {line: 0, token: undefined};
-      return {line, token: tkClass.substring(3)};
+      if (!line || !tkClass)
+        return {line: 0, token: undefined, element: undefined};
+      return {line, token: tkClass.substring(3), element: el};
     }
-    if (el.tagName === 'TD') return {line: 0, token: undefined};
+    if (el.tagName === 'TD')
+      return {line: 0, token: undefined, element: undefined};
     return this.findTokenAncestor(el.parentElement);
   }
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts
index cbd5047..3e292fe 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts
@@ -93,8 +93,7 @@
     );
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.selectionChangeTask?.cancel();
     super.disconnectedCallback();
   }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
index 4b35931..06d31b3 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
@@ -194,7 +194,7 @@
   filesWeblinks: FilesWebLinks | {} = {};
 
   @property({type: Boolean, reflectToAttribute: true})
-  hidden = false;
+  override hidden = false;
 
   @property({type: Boolean})
   noRenderOnPrefsChange = false;
@@ -303,16 +303,14 @@
     );
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     if (this._canReload()) {
       this.reload();
     }
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._getLoggedIn().then(loggedIn => {
       this._loggedIn = loggedIn;
@@ -324,8 +322,7 @@
       });
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.disconnected$.next();
     this.clear();
     super.disconnectedCallback();
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 b0c7097..cf825ff 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
@@ -171,7 +171,7 @@
   // TODO(hermannloose): Make GrLibLoader a singleton.
   private static readonly libLoader = new GrLibLoader();
 
-  static styles = css`
+  static override styles = css`
     :host {
       display: grid;
       grid-template-rows: 1fr auto;
@@ -413,7 +413,7 @@
     `;
   }
 
-  render() {
+  override render() {
     const src = this.baseSelected ? this.baseUrl : this.revisionUrl;
 
     const sourceImage = html`
@@ -640,7 +640,7 @@
     `;
   }
 
-  firstUpdated() {
+  override firstUpdated() {
     this.resizeObserver.observe(this.imageArea, {box: 'content-box'});
     GrImageViewer.libLoader.getLibrary(RESEMBLEJS_LIBRARY_CONFIG).then(() => {
       this.canHighlightDiffs = true;
@@ -650,14 +650,14 @@
 
   // We don't want property changes in updateSizes() to trigger infinite update
   // loops, so we perform this in update() instead of updated().
-  update(changedProperties: PropertyValues) {
+  override update(changedProperties: PropertyValues) {
     if (!this.baseUrl) this.baseSelected = false;
     if (!this.revisionUrl) this.baseSelected = true;
     this.updateSizes();
     super.update(changedProperties);
   }
 
-  updated(changedProperties: PropertyValues) {
+  override updated(changedProperties: PropertyValues) {
     if (
       (changedProperties.has('baseUrl') && this.baseSelected) ||
       (changedProperties.has('revisionUrl') && !this.baseSelected)
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-overview-image.ts b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-overview-image.ts
index 9439dca..b1ea72f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-overview-image.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-overview-image.ts
@@ -93,7 +93,7 @@
     }
   );
 
-  static styles = css`
+  static override styles = css`
     :host {
       --background-color: var(--overview-image-background-color, #000);
       --frame-color: var(--overview-image-frame-color, #f00);
@@ -127,7 +127,7 @@
     }
   `;
 
-  render() {
+  override render() {
     return html`
       <div class="content-box">
         <div
@@ -158,7 +158,7 @@
     `;
   }
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     if (this.isConnected) {
       this.overlay = document.createElement('div');
@@ -194,7 +194,7 @@
     }
   }
 
-  disconnectedCallback() {
+  override disconnectedCallback() {
     if (this.overlay) {
       document.body.removeChild(this.overlay);
       this.overlay = undefined;
@@ -202,12 +202,12 @@
     super.disconnectedCallback();
   }
 
-  firstUpdated() {
+  override firstUpdated() {
     this.resizeObserver.observe(this.contentBox);
     this.resizeObserver.observe(this.contentTransform);
   }
 
-  updated(changedProperties: PropertyValues) {
+  override updated(changedProperties: PropertyValues) {
     if (changedProperties.has('frameRect')) {
       this.updateFrameStyle();
     }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-zoomed-image.ts b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-zoomed-image.ts
index 4558dda..671b858 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-zoomed-image.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-zoomed-image.ts
@@ -43,7 +43,7 @@
 
   @state() protected imageStyles: StyleInfo = {};
 
-  static styles = css`
+  static override styles = css`
     :host {
       display: block;
     }
@@ -63,7 +63,7 @@
     }
   `;
 
-  render() {
+  override render() {
     return html`
       <div id="clip">
         <div id="transform" style="${styleMap(this.imageStyles)}">
@@ -73,7 +73,7 @@
     `;
   }
 
-  updated(changedProperties: PropertyValues) {
+  override updated(changedProperties: PropertyValues) {
     if (changedProperties.has('scale') || changedProperties.has('frameRect')) {
       this.updateImageStyles();
     }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
index 4eb33c7..a7702d1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
@@ -48,8 +48,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     (
       IronA11yAnnouncer as unknown as FixIronA11yAnnouncer
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
index 33d6f74..b1e15a8 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
@@ -117,14 +117,12 @@
 
   private resetIsScrollingTask?: DelayedTask;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     window.addEventListener('scroll', this.handleWindowScroll);
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.resetIsScrollingTask?.cancel();
     this.cancel();
     window.removeEventListener('scroll', this.handleWindowScroll);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts
index 961ad45..0051b8b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts
@@ -76,8 +76,7 @@
     addListener(this, 'down', e => this._handleDown(e));
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.classList.add(SelectionClass.RIGHT);
   }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index 1b09e87..61259d1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -289,7 +289,7 @@
     };
   }
 
-  keyboardShortcuts() {
+  override keyboardShortcuts() {
     return {
       [Shortcut.LEFT_PANE]: '_handleLeftPane',
       [Shortcut.RIGHT_PANE]: '_handleRightPane',
@@ -345,8 +345,7 @@
 
   disconnected$ = new Subject();
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._throttledToggleFileReviewed = throttleWrap(e =>
       this._handleToggleFileReviewed(e as CustomKeyboardEvent)
@@ -367,8 +366,7 @@
     this.$.diffHost.addEventListener('render', this._onRenderHandler);
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.disconnected$.next();
     this.cursor.dispose();
     if (this._onRenderHandler) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
index 61718ba..9fb2a19 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
@@ -181,7 +181,7 @@
   isImageDiff?: boolean;
 
   @property({type: Boolean, reflectToAttribute: true})
-  hidden = false;
+  override hidden = false;
 
   @property({type: Boolean})
   noRenderOnPrefsChange?: boolean;
@@ -242,7 +242,7 @@
    * obtained by making the content of the diff table "contentEditable".
    */
   @property({type: Boolean})
-  isContentEditable = isSafari();
+  override isContentEditable = isSafari();
 
   /**
    * Whether the safety check for large diffs when whole-file is set has
@@ -318,15 +318,13 @@
     this.addEventListener('moved-link-clicked', e => this._movedLinkClicked(e));
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._observeNodes();
     this.isAttached = true;
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.isAttached = false;
     this.renderDiffTableTask?.cancel();
     this._unobserveIncrementalNodes();
diff --git a/polygerrit-ui/app/elements/diff/gr-range-header/gr-range-header.ts b/polygerrit-ui/app/elements/diff/gr-range-header/gr-range-header.ts
index 4ae9e7e..776b954 100644
--- a/polygerrit-ui/app/elements/diff/gr-range-header/gr-range-header.ts
+++ b/polygerrit-ui/app/elements/diff/gr-range-header/gr-range-header.ts
@@ -29,7 +29,7 @@
   @property({type: String})
   icon?: string;
 
-  static get styles() {
+  static override get styles() {
     return [
       css`
         .row {
@@ -52,7 +52,7 @@
     ];
   }
 
-  render() {
+  override render() {
     const icon = this.icon ?? '';
     return html` <div class="row">
       <iron-icon class="icon" .icon=${icon}></iron-icon>
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts b/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts
index 909100f..f82290b 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts
@@ -27,7 +27,7 @@
   @property({type: Object})
   range?: CommentRange;
 
-  static get styles() {
+  static override get styles() {
     return [
       grRangedCommentTheme,
       sharedStyles,
@@ -42,7 +42,7 @@
     ];
   }
 
-  render() {
+  override render() {
     // To pass CSS mixins for @apply to Polymer components, they need to appear
     // in <style> inside the template.
     const customStyle = html`
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts
index f125bfa..90c8383 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts
@@ -52,8 +52,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     fireTitleChange(this, 'Documentation Search');
   }
diff --git a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts
index d0fc408..b04e6cf 100644
--- a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts
+++ b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.ts
@@ -36,7 +36,7 @@
   @property({type: String})
   fileContent = '';
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       css`
@@ -59,7 +59,7 @@
     ];
   }
 
-  render() {
+  override render() {
     return html` <textarea
       id="textarea"
       value="${this.fileContent}"
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
index 08b69ee..ce4c313 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
@@ -40,7 +40,7 @@
   @property({type: Array})
   _allFileActions = Object.values(GrEditConstants.Actions);
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       css`
@@ -56,7 +56,7 @@
     ];
   }
 
-  render() {
+  override render() {
     // To pass CSS mixins for @apply to Polymer components, they need to appear
     // in <style> inside the template.
     const customStyle = html`
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
index 16d54dd..f5dc610 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
@@ -150,16 +150,14 @@
     });
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._getEditPrefs().then(prefs => {
       this._prefs = prefs;
     });
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.storeTask?.cancel();
     super.disconnectedCallback();
   }
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index a5c48da..88ed0dc 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -212,7 +212,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  keyboardShortcuts() {
+  override keyboardShortcuts() {
     return {
       [Shortcut.OPEN_SHORTCUT_HELP_DIALOG]: '_showKeyboardShortcuts',
       [Shortcut.GO_TO_USER_DASHBOARD]: '_goToUserDashboard',
@@ -251,8 +251,7 @@
     document.addEventListener('gr-rpc-log', e => this._handleRpcLog(e));
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this._updateLoginUrl();
     this.reporting.appStarted();
diff --git a/polygerrit-ui/app/elements/lit/gr-lit-element.ts b/polygerrit-ui/app/elements/lit/gr-lit-element.ts
index 9ebadd5..d0b68e3 100644
--- a/polygerrit-ui/app/elements/lit/gr-lit-element.ts
+++ b/polygerrit-ui/app/elements/lit/gr-lit-element.ts
@@ -45,7 +45,7 @@
     });
   }
 
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.disconnected$.next();
     super.disconnectedCallback();
   }
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts
index 9a1414e..35f8243 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts
@@ -58,8 +58,7 @@
    */
   _endpointCallBack: (info: ModuleInfo) => void = () => {};
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     for (const [el, domHook] of this._domHooks) {
       domHook.handleInstanceDetached(el);
     }
@@ -196,8 +195,7 @@
     });
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this._endpointCallBack = (info: ModuleInfo) => this._initModule(info);
     getPluginEndpoints().onNewEndpoint(this.name, this._endpointCallBack);
diff --git a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.ts b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.ts
index f8d77ba..88964bf 100644
--- a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.ts
@@ -58,14 +58,12 @@
     }
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._importAndApply();
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     getPluginLoader()
       .awaitPluginsLoaded()
diff --git a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.ts b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.ts
index 7d82ff4..f913cf6 100644
--- a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.ts
@@ -30,7 +30,7 @@
   logoUrl = '';
 
   @property({type: String})
-  title = '';
+  override title = '';
 
   static get template() {
     return html`
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts
index 6373c17..41fc71a 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts
@@ -29,8 +29,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.loadData();
   }
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
index b724e72..76047f4 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
@@ -66,8 +66,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.loadData();
 
diff --git a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.ts b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.ts
index 0fb6715..2757454 100644
--- a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.ts
+++ b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.ts
@@ -44,7 +44,7 @@
     });
   }
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       formStyles,
@@ -64,7 +64,7 @@
     ];
   }
 
-  render() {
+  override render() {
     return html` <div class="gr-form-styles">
       <table id="groups">
         <thead>
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.ts b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.ts
index d7e16db..ccf8a01 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.ts
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.ts
@@ -46,8 +46,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.loadData();
   }
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts
index 3763fbf..aa8f62b 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts
@@ -85,8 +85,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this._ensureAttribute('role', 'dialog');
   }
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.ts
index 2441f6f..ecb02eb 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.ts
@@ -30,9 +30,9 @@
   anchor?: string;
 
   @property({type: String})
-  title = '';
+  override title = '';
 
-  static get styles() {
+  static override get styles() {
     return [
       css`
         :host {
@@ -43,7 +43,7 @@
     ];
   }
 
-  render() {
+  override render() {
     const anchor = this.anchor ?? '';
     return html`<h2 id="${anchor}" class="heading-2">${this.title}</h2>`;
   }
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.ts
index 0f6000e..0748379 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.ts
@@ -31,13 +31,13 @@
   href?: string;
 
   @property({type: String})
-  title = '';
+  override title = '';
 
-  static get styles() {
+  static override get styles() {
     return [sharedStyles, pageNavStyles];
   }
 
-  render() {
+  override render() {
     const href = this.href ?? '';
     return html` <div class="navStyles">
       <li><a href="${href}">${this.title}</a></li>
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
index 5630610..91b2ed6 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
@@ -227,8 +227,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     // Polymer 2: anchor tag won't work on shadow DOM
     // we need to manually calling scrollIntoView when hash changed
@@ -316,7 +315,7 @@
     });
   }
 
-  disconnectedCallback() {
+  override disconnectedCallback() {
     window.removeEventListener('location-change', this.handleLocationChange);
     super.disconnectedCallback();
   }
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 670302f..a3650b1 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
@@ -81,7 +81,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  static get styles() {
+  static override get styles() {
     return [
       css`
         :host {
@@ -125,7 +125,7 @@
     ];
   }
 
-  render() {
+  override render() {
     // To pass CSS mixins for @apply to Polymer components, they need to appear
     // in <style> inside the template.
     const customStyle = html`
@@ -133,33 +133,21 @@
         .container {
           --account-label-padding-horizontal: 6px;
         }
-        gr-button.remove {
-          --gr-remove-button-style: {
-            border-top-width: 0;
-            border-right-width: 0;
-            border-bottom-width: 0;
-            border-left-width: 0;
-            color: var(--deemphasized-text-color);
-            font-weight: var(--font-weight-normal);
-            height: 0.6em;
-            line-height: 10px;
-            /* This cancels most of the --account-label-padding-horizontal. */
-            margin-left: -4px;
-            padding: 0 2px 0 0;
-            text-decoration: none;
-          }
-        }
-
-        gr-button.remove:hover,
-        gr-button.remove:focus {
-          --gr-button: {
-            @apply --gr-remove-button-style;
-          }
-        }
-        gr-button.remove {
-          --gr-button: {
-            @apply --gr-remove-button-style;
-          }
+        gr-button.remove::part(paper-button),
+        gr-button.remove:hover::part(paper-button),
+        gr-button.remove:focus::part(paper-button) {
+          border-top-width: 0;
+          border-right-width: 0;
+          border-bottom-width: 0;
+          border-left-width: 0;
+          color: var(--deemphasized-text-color);
+          font-weight: var(--font-weight-normal);
+          height: 0.6em;
+          line-height: 10px;
+          /* This cancels most of the --account-label-padding-horizontal. */
+          margin-left: -4px;
+          padding: 0 2px 0 0;
+          text-decoration: none;
         }
       </style>
     `;
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
index c250428..df5f441 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
@@ -75,7 +75,7 @@
     return this.$.input.focusStart;
   }
 
-  focus() {
+  override focus() {
     this.$.input.focus();
   }
 
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 07d757c..dc36c78 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
@@ -105,7 +105,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  static get styles() {
+  static override get styles() {
     return [
       css`
         :host {
@@ -184,7 +184,7 @@
     ];
   }
 
-  render() {
+  override render() {
     const {account, change, highlightAttention, forceAttention, _config} = this;
     if (!account) return;
     const hasAttention =
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 a610ffa..97b3b34 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
@@ -64,7 +64,7 @@
   @property({type: Boolean})
   firstName = false;
 
-  static get styles() {
+  static override get styles() {
     return [
       css`
         :host {
@@ -82,7 +82,7 @@
     ];
   }
 
-  render() {
+  override render() {
     if (!this.account) return;
     return html`<span>
       <a href="${this._computeOwnerLink(this.account)}">
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts
index d2bac72..220f5fd 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts
@@ -154,15 +154,13 @@
   @property()
   _actionCallback?: () => void;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._boundTransitionEndHandler = () => this._handleTransitionEnd();
     this.addEventListener('transitionend', this._boundTransitionEndHandler);
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     if (this._boundTransitionEndHandler) {
       this.removeEventListener(
         'transitionend',
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
index 4d2aee7..ac6c99e 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
@@ -80,10 +80,10 @@
   isHidden = true;
 
   @property({type: Number})
-  verticalOffset: number | null = null;
+  override verticalOffset: number | null = null;
 
   @property({type: Number})
-  horizontalOffset: number | null = null;
+  override horizontalOffset: number | null = null;
 
   @property({type: Array})
   suggestions: Item[] = [];
@@ -107,8 +107,7 @@
     this.cursor.focusOnMove = true;
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.cursor.unsetCursor();
     super.disconnectedCallback();
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
index ba3c26d..7595a17 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
@@ -203,14 +203,12 @@
       this.$.input.inputElement) as HTMLInputElement;
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     document.addEventListener('click', this.handleBodyClick);
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     document.removeEventListener('click', this.handleBodyClick);
     this.updateSuggestionsTask?.cancel();
     super.disconnectedCallback();
@@ -220,7 +218,7 @@
     return this.$.input;
   }
 
-  focus() {
+  override focus() {
     this._nativeInput.focus();
   }
 
diff --git a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.ts b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.ts
index 1d2ccbb..6dd3a00 100644
--- a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.ts
+++ b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.ts
@@ -34,7 +34,7 @@
 
   private readonly restApiService = appContext.restApiService;
 
-  static get styles() {
+  static override get styles() {
     return [
       css`
         :host([hidden]) {
@@ -53,13 +53,12 @@
     ];
   }
 
-  render() {
+  override render() {
     this._updateAvatarURL();
     return html``;
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     Promise.all([
       this._getConfig(),
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 76938e9..aef55fa 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
@@ -95,8 +95,7 @@
     );
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this._ensureAttribute('role', 'button');
     this._ensureAttribute('tabindex', '0');
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button_html.ts b/polygerrit-ui/app/elements/shared/gr-button/gr-button_html.ts
index a174627..a15b560 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button_html.ts
@@ -169,7 +169,12 @@
       border-top-color: var(--deemphasized-text-color);
     }
   </style>
-  <paper-button raised="[[!link]]" disabled="[[_disabled]]" tabindex="-1">
+  <paper-button
+    raised="[[!link]]"
+    disabled="[[_disabled]]"
+    tabindex="-1"
+    part="paper-button"
+  >
     <template is="dom-if" if="[[loading]]">
       <span class="loadingSpin"></span>
     </template>
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 bba6f49..0b43879 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
@@ -222,8 +222,7 @@
     );
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._getLoggedIn().then(loggedIn => {
       this._showActions = loggedIn;
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index 97f36de..2c2d454 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -285,8 +285,7 @@
 
   private draftToastTask?: DelayedTask;
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.restApiService.getAccount().then(account => {
       this._selfAccount = account;
@@ -301,8 +300,7 @@
     });
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.fireUpdateTask?.cancel();
     this.storeTask?.cancel();
     this.draftToastTask?.cancel();
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
index 36cffb7..b85703a 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
@@ -46,7 +46,7 @@
   @property({type: Boolean})
   hideInput = false;
 
-  static get styles() {
+  static override get styles() {
     return [
       css`
         .text {
@@ -86,7 +86,7 @@
     ];
   }
 
-  render() {
+  override render() {
     // To pass CSS mixins for @apply to Polymer components, they need to appear
     // in <style> inside the template.
     const customStyle = html`
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts
index 489b489..39ec9e4 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts
@@ -92,7 +92,7 @@
    * native browser tooltip.
    */
   @property({type: Boolean})
-  hasTooltip = false;
+  override hasTooltip = false;
 
   @property({type: Boolean})
   showYesterday = false;
@@ -105,7 +105,7 @@
     reflectToAttribute: true,
     computed: '_computeFullDateStr(dateStr, _timeFormat, _dateFormat)',
   })
-  title = '';
+  override title = '';
 
   /** @type {?{short: string, full: string}} */
   @property({type: Object})
@@ -129,8 +129,7 @@
     super();
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._loadPreferences();
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts
index a0e5e5e..f39e5b8 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts
@@ -68,8 +68,7 @@
   @property({type: String})
   confirmTooltip?: string;
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this._ensureAttribute('role', 'dialog');
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
index 2bc5a17..f808e38 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
@@ -75,8 +75,7 @@
 
   disconnected$ = new Subject();
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this._getLoggedIn().then(loggedIn => {
       this._loggedIn = loggedIn;
@@ -89,8 +88,7 @@
     });
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.disconnected$.next();
     super.disconnectedCallback();
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
index 279e2e1..078e2ac 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
@@ -136,8 +136,7 @@
     this.cursor.focusOnMove = true;
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.cursor.unsetCursor();
     super.disconnectedCallback();
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
index 592efba..866ee5a 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
@@ -115,13 +115,7 @@
   // Tests use this so needs to be non private
   storeTask?: DelayedTask;
 
-  /** @override */
-  ready() {
-    super.ready();
-  }
-
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.storeTask?.cancel();
     super.disconnectedCallback();
   }
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 b133472..b2bd925 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
@@ -99,8 +99,7 @@
   @property({type: Object})
   query: AutocompleteQuery = () => Promise.resolve([]);
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this._ensureAttribute('tabindex', '0');
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
index ddca5c5..487f288 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
@@ -22,7 +22,7 @@
 
 const CODE_MARKER_PATTERN = /^(`{1,3})([^`]+?)\1$/;
 
-interface Block {
+export interface Block {
   type: string;
   text?: string;
   blocks?: Block[];
@@ -47,7 +47,7 @@
 
   private readonly reporting = appContext.reportingService;
 
-  static get styles() {
+  static override get styles() {
     return [
       css`
         :host {
@@ -98,7 +98,7 @@
     ];
   }
 
-  render() {
+  override render() {
     const nodes = this._computeNodes(this._computeBlocks(this.content));
     return html`<div id="container">${nodes}</div>`;
   }
@@ -126,7 +126,7 @@
   _computeBlocks(content?: string): Block[] {
     if (!content) return [];
 
-    const result = [];
+    const result: Block[] = [];
     const lines = content.replace(/[\s\n\r\t]+$/g, '').split('\n');
 
     for (let i = 0; i < lines.length; i++) {
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.js b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
similarity index 82%
rename from polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.js
rename to polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
index 8464af7..cf12032 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
@@ -15,22 +15,34 @@
  * limitations under the License.
  */
 
-import '../../../test/common-test-setup-karma.js';
-import './gr-formatted-text.js';
+import '../../../test/common-test-setup-karma';
+import './gr-formatted-text';
+import {GrFormattedText, Block} from './gr-formatted-text';
 
 const basicFixture = fixtureFromElement('gr-formatted-text');
 
 suite('gr-formatted-text tests', () => {
-  let element;
+  let element: GrFormattedText;
 
-  function assertBlock(result, index, type, text) {
+  function assertBlock(
+    result: Block[],
+    index: number,
+    type: string,
+    text?: string
+  ) {
     assert.equal(result[index].type, type);
     assert.equal(result[index].text, text);
   }
 
-  function assertListBlock(result, resultIndex, itemIndex, text) {
+  function assertListBlock(
+    result: Block[],
+    resultIndex: number,
+    itemIndex: number,
+    text: string
+  ) {
     assert.equal(result[resultIndex].type, 'list');
-    assert.equal(result[resultIndex].items[itemIndex], text);
+    const item = result[resultIndex].items?.[itemIndex];
+    assert.equal(item, text);
   }
 
   setup(() => {
@@ -38,7 +50,6 @@
   });
 
   test('parse null undefined and empty', () => {
-    assert.lengthOf(element._computeBlocks(null), 0);
     assert.lengthOf(element._computeBlocks(undefined), 0);
     assert.lengthOf(element._computeBlocks(''), 0);
   });
@@ -69,8 +80,9 @@
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
     assert.equal(result[0].type, 'quote');
-    assert.lengthOf(result[0].blocks, 1);
-    assertBlock(result[0].blocks, 0, 'paragraph', 'Quote text');
+    const blocks = result[0].blocks!;
+    assert.lengthOf(blocks, 1);
+    assertBlock(blocks, 0, 'paragraph', 'Quote text');
   });
 
   test('parse quote lead space', () => {
@@ -78,8 +90,9 @@
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
     assert.equal(result[0].type, 'quote');
-    assert.lengthOf(result[0].blocks, 1);
-    assertBlock(result[0].blocks, 0, 'paragraph', 'Quote text');
+    const blocks = result[0].blocks!;
+    assert.lengthOf(blocks, 1);
+    assertBlock(blocks, 0, 'paragraph', 'Quote text');
   });
 
   test('parse multiline quote', () => {
@@ -87,9 +100,14 @@
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
     assert.equal(result[0].type, 'quote');
-    assert.lengthOf(result[0].blocks, 1);
-    assertBlock(result[0].blocks, 0, 'paragraph',
-        'Quote line 1\nQuote line 2\nQuote line 3');
+    const blocks = result[0].blocks!;
+    assert.lengthOf(blocks, 1);
+    assertBlock(
+      blocks,
+      0,
+      'paragraph',
+      'Quote line 1\nQuote line 2\nQuote line 3'
+    );
   });
 
   test('parse pre', () => {
@@ -142,27 +160,28 @@
   });
 
   test('parse mixed block types', () => {
-    const comment = 'Paragraph\nacross\na\nfew\nlines.' +
-        '\n\n' +
-        '> Quote\n> across\n> not many lines.' +
-        '\n\n' +
-        'Another paragraph' +
-        '\n\n' +
-        '* Series\n* of\n* list\n* items' +
-        '\n\n' +
-        'Yet another paragraph' +
-        '\n\n' +
-        '\tPreformatted text.' +
-        '\n\n' +
-        'Parting words.';
+    const comment =
+      'Paragraph\nacross\na\nfew\nlines.' +
+      '\n\n' +
+      '> Quote\n> across\n> not many lines.' +
+      '\n\n' +
+      'Another paragraph' +
+      '\n\n' +
+      '* Series\n* of\n* list\n* items' +
+      '\n\n' +
+      'Yet another paragraph' +
+      '\n\n' +
+      '\tPreformatted text.' +
+      '\n\n' +
+      'Parting words.';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 7);
     assertBlock(result, 0, 'paragraph', 'Paragraph\nacross\na\nfew\nlines.\n');
 
     assert.equal(result[1].type, 'quote');
-    assert.lengthOf(result[1].blocks, 1);
-    assertBlock(result[1].blocks, 0, 'paragraph',
-        'Quote\nacross\nnot many lines.');
+    const secondBlocks = result[1].blocks!;
+    assert.lengthOf(secondBlocks, 1);
+    assertBlock(secondBlocks, 0, 'paragraph', 'Quote\nacross\nnot many lines.');
 
     assertBlock(result, 2, 'paragraph', 'Another paragraph\n');
     assertListBlock(result, 3, 0, 'Series');
@@ -203,9 +222,10 @@
   });
 
   test('bullet list 4', () => {
-    const comment = 'To see this bug, you have to:\n' +
-        '* Be on IMAP or EAS (not on POP)\n' +
-        '* Be very unlucky\n';
+    const comment =
+      'To see this bug, you have to:\n' +
+      '* Be on IMAP or EAS (not on POP)\n' +
+      '* Be very unlucky\n';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 2);
     assertBlock(result, 0, 'paragraph', 'To see this bug, you have to:');
@@ -214,10 +234,11 @@
   });
 
   test('bullet list 5', () => {
-    const comment = 'To see this bug,\n' +
-        'you have to:\n' +
-        '* Be on IMAP or EAS (not on POP)\n' +
-        '* Be very unlucky\n';
+    const comment =
+      'To see this bug,\n' +
+      'you have to:\n' +
+      '* Be on IMAP or EAS (not on POP)\n' +
+      '* Be very unlucky\n';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 2);
     assertBlock(result, 0, 'paragraph', 'To see this bug,\nyou have to:');
@@ -306,12 +327,13 @@
   });
 
   test('quote 1', () => {
-    const comment = '> I\'m happy\n > with quotes!\n\nSee above.';
+    const comment = "> I'm happy\n > with quotes!\n\nSee above.";
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 2);
     assert.equal(result[0].type, 'quote');
-    assert.lengthOf(result[0].blocks, 1);
-    assertBlock(result[0].blocks, 0, 'paragraph', 'I\'m happy\nwith quotes!');
+    const blocks = result[0].blocks!;
+    assert.lengthOf(blocks, 1);
+    assertBlock(blocks, 0, 'paragraph', "I'm happy\nwith quotes!");
     assertBlock(result, 1, 'paragraph', 'See above.');
   });
 
@@ -321,8 +343,9 @@
     assert.lengthOf(result, 3);
     assertBlock(result, 0, 'paragraph', 'See this said:');
     assert.equal(result[1].type, 'quote');
-    assert.lengthOf(result[1].blocks, 1);
-    assertBlock(result[1].blocks, 0, 'paragraph', 'a quoted\nstring block');
+    const secondBlocks = result[1].blocks!;
+    assert.lengthOf(secondBlocks, 1);
+    assertBlock(secondBlocks, 0, 'paragraph', 'a quoted\nstring block');
     assertBlock(result, 2, 'paragraph', 'OK?');
   });
 
@@ -331,11 +354,13 @@
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 1);
     assert.equal(result[0].type, 'quote');
-    assert.lengthOf(result[0].blocks, 2);
-    assert.equal(result[0].blocks[0].type, 'quote');
-    assert.lengthOf(result[0].blocks[0].blocks, 1);
-    assertBlock(result[0].blocks[0].blocks, 0, 'paragraph', 'prior');
-    assertBlock(result[0].blocks, 1, 'paragraph', 'next');
+    const blocks = result[0].blocks!;
+    assert.lengthOf(blocks, 2);
+    assert.equal(blocks[0].type, 'quote');
+    const blocksBlocks = blocks[0].blocks!;
+    assert.lengthOf(blocksBlocks, 1);
+    assertBlock(blocksBlocks, 0, 'paragraph', 'prior');
+    assertBlock(blocks, 1, 'paragraph', 'next');
   });
 
   test('code 1', () => {
@@ -385,7 +410,8 @@
   });
 
   test('mix all 1', () => {
-    const comment = ' bullets:\n- bullet 1\n- bullet 2\n\ncode example:\n' +
+    const comment =
+      ' bullets:\n- bullet 1\n- bullet 2\n\ncode example:\n' +
       '```// test code```\n\n> reference is here';
     const result = element._computeBlocks(comment);
     assert.lengthOf(result, 5);
@@ -396,4 +422,3 @@
     assert.equal(result[4].type, 'quote');
   });
 });
-
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
index 48de78e..76c13e3 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
@@ -127,8 +127,7 @@
 
       private isScheduledToHide?: boolean;
 
-      /** @override */
-      connectedCallback() {
+      override connectedCallback() {
         super.connectedCallback();
         if (!this._target) {
           this._target = this.target;
@@ -150,15 +149,14 @@
         this.addEventListener('mouseleave', this.unlock);
       }
 
-      disconnectedCallback() {
+      override disconnectedCallback() {
         this.cancelShowTask();
         this.cancelHideTask();
         this.unlock();
         super.disconnectedCallback();
       }
 
-      /** @override */
-      ready() {
+      override ready() {
         super.ready();
         // First, check to see if the container has already been created.
         this.container = getHovercardContainer({createIfNotExists: true});
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 e3d3077..4a2ba86 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
@@ -50,7 +50,7 @@
 
   /** Boolean property used by TooltipMixin. */
   @property({type: Boolean})
-  hasTooltip = false;
+  override hasTooltip = false;
 
   /** Boolean property used by TooltipMixin. */
   @property({type: Boolean})
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts
index 3971767..b74d643 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts
@@ -89,32 +89,20 @@
     // in <style> inside the template.
     const customStyle = html`
       <style>
-        gr-button.remove {
-          --gr-remove-button-style: {
-            border-top-width: 0;
-            border-right-width: 0;
-            border-bottom-width: 0;
-            border-left-width: 0;
-            color: var(--deemphasized-text-color);
-            font-weight: var(--font-weight-normal);
-            height: 0.6em;
-            line-height: 10px;
-            margin-left: var(--spacing-xs);
-            padding: 0;
-            text-decoration: none;
-          }
-        }
-
-        gr-button.remove:hover,
-        gr-button.remove:focus {
-          --gr-button: {
-            @apply --gr-remove-button-style;
-          }
-        }
-        gr-button.remove {
-          --gr-button: {
-            @apply --gr-remove-button-style;
-          }
+        gr-button::part(paper-button),
+        gr-button.remove:hover::part(paper-button),
+        gr-button.remove:focus::part(paper-button) {
+          border-top-width: 0;
+          border-right-width: 0;
+          border-bottom-width: 0;
+          border-left-width: 0;
+          color: var(--deemphasized-text-color);
+          font-weight: var(--font-weight-normal);
+          height: 0.6em;
+          line-height: 10px;
+          margin-left: var(--spacing-xs);
+          padding: 0;
+          text-decoration: none;
         }
       </style>
     `;
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
index 287ed1b..5d72925 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
@@ -46,7 +46,7 @@
   @property({type: Object})
   config?: LinkTextParserConfig;
 
-  static get styles() {
+  static override get styles() {
     return [
       css`
         :host {
@@ -68,14 +68,14 @@
     ];
   }
 
-  render() {
+  override render() {
     if (!this.config) {
       return;
     }
     return html`<span id="output">${this.content}</span>`;
   }
 
-  updated() {
+  override updated() {
     if (!this.outputElement || !this.config) return;
     this.outputElement.textContent = '';
     // TODO(TS): mapCommentlinks always has value, remove
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 bdce2c9..055c45f 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
@@ -63,8 +63,7 @@
 
   private reloadTask?: DelayedTask;
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     this.reloadTask?.cancel();
     super.disconnectedCallback();
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
index 2c48ec0f..b6cb8f9 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
@@ -63,7 +63,7 @@
 
   private returnFocusTo?: HTMLElement;
 
-  get _focusableNodes() {
+  override get _focusableNodes() {
     if (this.focusableNodes) {
       return this.focusableNodes;
     }
@@ -89,7 +89,7 @@
     );
   }
 
-  open() {
+  override open() {
     this.returnFocusTo = findActiveElement(document, true) ?? undefined;
     window.addEventListener('popstate', this._boundHandleClose);
     return new Promise<void>((resolve, reject) => {
@@ -119,7 +119,7 @@
     }
   }
 
-  _onCaptureFocus(e: Event) {
+  override _onCaptureFocus(e: Event) {
     const hovercardContainer = getHovercardContainer();
     if (hovercardContainer) {
       // Hovercard container is not a child of an overlay.
diff --git a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.ts b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.ts
index 221af37..b2477dd 100644
--- a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.ts
+++ b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.ts
@@ -58,7 +58,7 @@
     window.addEventListener('scroll', this.bodyScrollHandler);
   }
 
-  disconnectedCallback() {
+  override disconnectedCallback() {
     window.removeEventListener('scroll', this.bodyScrollHandler);
     super.disconnectedCallback();
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.ts b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.ts
index 08beae3..bc60520 100644
--- a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.ts
+++ b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.ts
@@ -70,16 +70,14 @@
     this._repoQuery = input => this._getRepoSuggestions(input);
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     if (this.repo) {
       this.$.repoInput.setText(this.repo);
     }
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this._branchDisabled = !this.repo;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-select/gr-select.ts b/polygerrit-ui/app/elements/shared/gr-select/gr-select.ts
index 861381f..e71add9 100644
--- a/polygerrit-ui/app/elements/shared/gr-select/gr-select.ts
+++ b/polygerrit-ui/app/elements/shared/gr-select/gr-select.ts
@@ -65,7 +65,7 @@
     this.bindValue = this.nativeSelect.value;
   }
 
-  focus() {
+  override focus() {
     this.nativeSelect.focus();
   }
 
@@ -75,8 +75,7 @@
     this.addEventListener('dom-change', () => this._updateValue());
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     // If not set via the property, set bind-value to the element value.
     if (this.bindValue === undefined && this.nativeSelect.options.length > 0) {
diff --git a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.ts b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.ts
index cd2fa67..5b42318 100644
--- a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.ts
+++ b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.ts
@@ -38,7 +38,7 @@
   @property({type: String})
   tooltip = '';
 
-  static get styles() {
+  static override get styles() {
     return [
       sharedStyles,
       css`
@@ -76,7 +76,7 @@
     ];
   }
 
-  render() {
+  override render() {
     const label = this.label ?? '';
     return html` <label>${label}</label>
       <div class="commandContainer">
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
index 17c46c6..3d5cb3d 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
@@ -162,8 +162,7 @@
     this.reporting = appContext.reportingService;
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     if (this.monospace) {
       this.classList.add('monospace');
diff --git a/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin.ts b/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin.ts
index a5b25d9..4d119eb 100644
--- a/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin.ts
+++ b/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin.ts
@@ -86,8 +86,7 @@
         this.hideHandler = (e: Event | undefined) => this._handleHideTooltip(e);
       }
 
-      /** @override */
-      disconnectedCallback() {
+      override disconnectedCallback() {
         // NOTE: if you define your own `detached` in your component
         // then this won't take affect (as its not a class yet)
         this._handleHideTooltip(undefined);
diff --git a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
index 4d2bbf2..c48b179 100644
--- a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
+++ b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
@@ -884,8 +884,7 @@
         }
       }
 
-      /** @override */
-      connectedCallback() {
+      override connectedCallback() {
         super.connectedCallback();
         this.restApiService.getPreferences().then(prefs => {
           if (prefs?.disable_keyboard_shortcuts) {
@@ -896,8 +895,7 @@
         this.enableBindings();
       }
 
-      /** @override */
-      disconnectedCallback() {
+      override disconnectedCallback() {
         this.destroyVisibilityObserver();
         this.disableBindings();
         super.disconnectedCallback();
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 96c05ee..696ab63 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -83,7 +83,10 @@
   InheritedBooleanInfo,
   LabelInfo,
   LabelNameToInfoMap,
+  LabelNameToLabelTypeInfoMap,
   LabelNameToValueMap,
+  LabelTypeInfo,
+  LabelTypeInfoValues,
   LabelValueToDescriptionMap,
   MaxObjectSizeLimitInfo,
   NumericChangeId,
@@ -92,6 +95,8 @@
   PluginConfigInfo,
   PluginNameToPluginParametersMap,
   PluginParameterToConfigParameterInfoMap,
+  ProjectInfo,
+  ProjectInfoWithName,
   QuickLabelInfo,
   ReceiveInfo,
   RepoName,
@@ -109,6 +114,7 @@
   Timestamp,
   TimezoneOffset,
   TopicName,
+  UrlEncodedRepoName,
   UserConfigInfo,
   VotingRangeInfo,
   WebLinkInfo,
@@ -165,7 +171,10 @@
   InheritedBooleanInfo,
   LabelInfo,
   LabelNameToInfoMap,
+  LabelNameToLabelTypeInfoMap,
   LabelNameToValueMap,
+  LabelTypeInfo,
+  LabelTypeInfoValues,
   LabelValueToDescriptionMap,
   MaxObjectSizeLimitInfo,
   NumericChangeId,
@@ -174,6 +183,8 @@
   PluginConfigInfo,
   PluginNameToPluginParametersMap,
   PluginParameterToConfigParameterInfoMap,
+  ProjectInfo,
+  ProjectInfoWithName,
   QuickLabelInfo,
   ReceiveInfo,
   RepoName,
@@ -190,6 +201,7 @@
   Timestamp,
   TimezoneOffset,
   TopicName,
+  UrlEncodedRepoName,
   UserConfigInfo,
   VotingRangeInfo,
   isDetailedLabelInfo,
@@ -224,8 +236,6 @@
 // without 'parent'.
 export const ParentPatchSetNum = 'PARENT' as BasePatchSetNum;
 
-export type UrlEncodedRepoName = BrandType<string, '_urlEncodedRepoName'>;
-
 export type RobotId = BrandType<string, '_robotId'>;
 
 export type RobotRunId = BrandType<string, '_robotRunId'>;
@@ -718,45 +728,7 @@
   context_line: string;
 }
 
-/**
- * The ProjectInfo entity contains information about a project
- * https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#project-info
- */
-export interface ProjectInfo {
-  id: UrlEncodedRepoName;
-  // name is not set if returned in a map where the project name is used as
-  // map key
-  name?: RepoName;
-  // ?-<n> if the parent project is not visible (<n> is a number which
-  // is increased for each non-visible project).
-  parent?: RepoName;
-  description?: string;
-  state?: ProjectState;
-  branches?: {[branchName: string]: CommitId};
-  // labels is filled for Create Project and Get Project calls.
-  labels?: LabelNameToLabelTypeInfoMap;
-  // Links to the project in external sites
-  web_links?: WebLinkInfo[];
-}
-
-export interface ProjectInfoWithName extends ProjectInfo {
-  name: RepoName;
-}
-
 export type NameToProjectInfoMap = {[projectName: string]: ProjectInfo};
-export type LabelNameToLabelTypeInfoMap = {[labelName: string]: LabelTypeInfo};
-
-/**
- * The LabelTypeInfo entity contains metadata about the labels that a project
- * has.
- * https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#label-type-info
- */
-export interface LabelTypeInfo {
-  values: LabelTypeInfoValues;
-  default_value: number;
-}
-
-export type LabelTypeInfoValues = {[value: string]: string};
 
 export type FilePathToDiffInfoMap = {[path: string]: DiffInfo};
 
diff --git a/resources/com/google/gerrit/server/mail/Merged.soy b/resources/com/google/gerrit/server/mail/Merged.soy
index dac4290..e268a31 100644
--- a/resources/com/google/gerrit/server/mail/Merged.soy
+++ b/resources/com/google/gerrit/server/mail/Merged.soy
@@ -26,16 +26,18 @@
   {@param email: ?}
   {@param fromName: ?}
   {$fromName} has submitted this change.
+
   {if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n}
   {\n}
+
+  {$email.stickyApprovalDiff}
+
   Change subject: {$change.subject}{\n}
   ......................................................................{\n}
   {\n}
   {$email.changeDetail}
   {$email.approvals}
   {\n}
-  {$email.stickyApprovalDiff}
-  {\n}
   {if $email.includeDiff}
     {\n}
     {\n}
diff --git a/resources/com/google/gerrit/server/mail/MergedHtml.soy b/resources/com/google/gerrit/server/mail/MergedHtml.soy
index 19c4ba0..d2f7bfd 100644
--- a/resources/com/google/gerrit/server/mail/MergedHtml.soy
+++ b/resources/com/google/gerrit/server/mail/MergedHtml.soy
@@ -32,15 +32,16 @@
     </p>
   {/if}
 
+  {call mailTemplate.UnifiedDiff}
+    {param diffLines: $email.stickyApprovalDiffHtml /}
+  {/call}
+
   <div style="white-space:pre-wrap">{$email.approvals}</div>
 
   {call mailTemplate.Pre}
     {param content: $email.changeDetail /}
   {/call}
 
-  {call mailTemplate.UnifiedDiff}
-    {param diffLines: $email.stickyApprovalDiffHtml /}
-  {/call}
   {\n}
 
   {if $email.includeDiff}