Merge "Restore keyboard shortcut for expand all diff context"
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index a4e27b3..723b45a 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -3586,7 +3586,7 @@
 |`plugin_config`                           |optional|
 Plugin configuration as map which maps the plugin name to a map of
 parameter names to link:#config-parameter-info[ConfigParameterInfo]
-entities.
+entities. Only filled for users who have read access to `refs/meta/config`.
 |`actions`                                 |optional|
 Actions the caller might be able to perform on this project. The
 information is a map of view names to
diff --git a/WORKSPACE b/WORKSPACE
index 47c3e9c..41d6ef8 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -49,49 +49,25 @@
 # otherwise refer to RBE docs.
 rbe_autoconfig(name = "rbe_default")
 
-# TODO(davido): Switch to upstream again, when this PR is merged:
-# https://github.com/bazelbuild/rules_closure/pull/478
 http_archive(
-    name = "io_bazel_rules_closure",
-    sha256 = "b9c2bc6ba377aa497eb7c31681d34404febf9d4e3c9c7d98ce0d78238a0af20f",
-    strip_prefix = "rules_closure-0.31",
+    name = "com_google_protobuf",
+    sha256 = "71030a04aedf9f612d2991c1c552317038c3c5a2b578ac4745267a45e7037c29",
+    strip_prefix = "protobuf-3.12.3",
     urls = [
-        "https://github.com/davido/rules_closure/archive/V0.31.tar.gz",
-        "https://gerrit-ci.gerritforge.com/lib/V0.31.tar.gz",
+        "https://github.com/protocolbuffers/protobuf/archive/v3.12.3.tar.gz",
     ],
 )
 
+load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
+
+protobuf_deps()
+
 http_archive(
     name = "build_bazel_rules_nodejs",
     sha256 = "84abf7ac4234a70924628baa9a73a5a5cbad944c4358cf9abdb4aab29c9a5b77",
     urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.7.0/rules_nodejs-1.7.0.tar.gz"],
 )
 
-# File is specific to Polymer and copied from the Closure Github -- should be
-# synced any time there are major changes to Polymer.
-# https://github.com/google/closure-compiler/blob/master/contrib/externs/polymer-1.0.js
-http_file(
-    name = "polymer_closure",
-    downloaded_file_path = "polymer_closure.js",
-    sha256 = "4d63a36dcca040475bd6deb815b9a600bd686e1413ac1ebd4b04516edd675020",
-    urls = ["https://raw.githubusercontent.com/google/closure-compiler/35d2b3340ff23a69441f10fa3bc820691c2942f2/contrib/externs/polymer-1.0.js"],
-)
-
-load("@io_bazel_rules_closure//closure:repositories.bzl", "rules_closure_dependencies", "rules_closure_toolchains")
-
-# Prevent redundant loading of dependencies.
-# TODO(davido): Omit re-fetching ancient args4j version when these PRs are merged:
-# https://github.com/bazelbuild/rules_closure/pull/262
-# https://github.com/google/closure-templates/pull/155
-rules_closure_dependencies(
-    omit_aopalliance = True,
-    omit_bazel_skylib = True,
-    omit_javax_inject = True,
-    omit_rules_cc = True,
-)
-
-rules_closure_toolchains()
-
 # Golang support for PolyGerrit local dev server.
 http_archive(
     name = "io_bazel_rules_go",
@@ -328,7 +304,7 @@
 )
 
 maven_jar(
-    name = "args4j-intern",
+    name = "args4j",
     artifact = "args4j:args4j:2.33",
     sha1 = "bd87a75374a6d6523de82fef51fc3cfe9baf9fc9",
 )
@@ -993,6 +969,45 @@
     sha1 = "639033469776fd37c08358c6b92a4761feb2af4b",
 )
 
+load("@build_bazel_rules_nodejs//:index.bzl", "yarn_install")
+
+yarn_install(
+    name = "npm",
+    package_json = "//:package.json",
+    yarn_lock = "//:yarn.lock",
+)
+
+yarn_install(
+    name = "ui_npm",
+    args = ["--prod"],
+    package_json = "//:polygerrit-ui/app/package.json",
+    yarn_lock = "//:polygerrit-ui/app/yarn.lock",
+)
+
+yarn_install(
+    name = "ui_dev_npm",
+    package_json = "//:polygerrit-ui/package.json",
+    yarn_lock = "//:polygerrit-ui/yarn.lock",
+)
+
+yarn_install(
+    name = "tools_npm",
+    package_json = "//:tools/node_tools/package.json",
+    yarn_lock = "//:tools/node_tools/yarn.lock",
+)
+
+yarn_install(
+    name = "plugins_npm",
+    args = ["--prod"],
+    package_json = "//:plugins/package.json",
+    yarn_lock = "//:plugins/yarn.lock",
+)
+
+# Install all Bazel dependencies needed for npm packages that supply Bazel rules
+load("@npm//:install_bazel_dependencies.bzl", "install_bazel_dependencies")
+
+install_bazel_dependencies()
+
 load("//tools/bzl:js.bzl", "bower_archive", "npm_binary")
 
 # NPM binaries bundled along with their dependencies.
@@ -1184,45 +1199,6 @@
     version = "6.5.1",
 )
 
-load("@build_bazel_rules_nodejs//:index.bzl", "yarn_install")
-
-yarn_install(
-    name = "npm",
-    package_json = "//:package.json",
-    yarn_lock = "//:yarn.lock",
-)
-
-yarn_install(
-    name = "ui_npm",
-    args = ["--prod"],
-    package_json = "//:polygerrit-ui/app/package.json",
-    yarn_lock = "//:polygerrit-ui/app/yarn.lock",
-)
-
-yarn_install(
-    name = "ui_dev_npm",
-    package_json = "//:polygerrit-ui/package.json",
-    yarn_lock = "//:polygerrit-ui/yarn.lock",
-)
-
-yarn_install(
-    name = "tools_npm",
-    package_json = "//:tools/node_tools/package.json",
-    yarn_lock = "//:tools/node_tools/yarn.lock",
-)
-
-yarn_install(
-    name = "plugins_npm",
-    args = ["--prod"],
-    package_json = "//:plugins/package.json",
-    yarn_lock = "//:plugins/yarn.lock",
-)
-
-# Install all Bazel dependencies needed for npm packages that supply Bazel rules
-load("@npm//:install_bazel_dependencies.bzl", "install_bazel_dependencies")
-
-install_bazel_dependencies()
-
 load("@npm_bazel_typescript//:index.bzl", "ts_setup_workspace")
 
 ts_setup_workspace()
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 10a8852..dfb7a55 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -983,7 +983,7 @@
   protected void setUseSignedOffBy(InheritableBoolean value) throws Exception {
     try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
       ProjectConfig config = projectConfigFactory.read(md);
-      config.getProject().setBooleanConfig(BooleanProjectConfig.USE_SIGNED_OFF_BY, value);
+      config.updateProject(p -> p.setBooleanConfig(BooleanProjectConfig.USE_SIGNED_OFF_BY, value));
       config.commit(md);
       projectCache.evict(config.getProject());
     }
@@ -992,7 +992,7 @@
   protected void setRequireChangeId(InheritableBoolean value) throws Exception {
     try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
       ProjectConfig config = projectConfigFactory.read(md);
-      config.getProject().setBooleanConfig(BooleanProjectConfig.REQUIRE_CHANGE_ID, value);
+      config.updateProject(p -> p.setBooleanConfig(BooleanProjectConfig.REQUIRE_CHANGE_ID, value));
       config.commit(md);
       projectCache.evict(config.getProject());
     }
@@ -1442,10 +1442,10 @@
       LabelValue... value)
       throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = label(label, value);
+      LabelType.Builder labelType = label(label, value).toBuilder();
       labelType.setFunction(func);
-      labelType.setRefPatterns(refPatterns);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      labelType.setRefPatterns(ImmutableList.copyOf(refPatterns));
+      u.getConfig().upsertLabelType(labelType.build());
       u.save();
     }
   }
@@ -1453,10 +1453,11 @@
   protected void enableCreateNewChangeForAllNotInTarget() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
       u.getConfig()
-          .getProject()
-          .setBooleanConfig(
-              BooleanProjectConfig.CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET,
-              InheritableBoolean.TRUE);
+          .updateProject(
+              p ->
+                  p.setBooleanConfig(
+                      BooleanProjectConfig.CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET,
+                      InheritableBoolean.TRUE));
       u.save();
     }
   }
diff --git a/java/com/google/gerrit/acceptance/ExtensionRegistry.java b/java/com/google/gerrit/acceptance/ExtensionRegistry.java
index cfe7964..a5d8d19 100644
--- a/java/com/google/gerrit/acceptance/ExtensionRegistry.java
+++ b/java/com/google/gerrit/acceptance/ExtensionRegistry.java
@@ -36,6 +36,7 @@
 import com.google.gerrit.server.ExceptionHook;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.change.ChangeETagComputation;
+import com.google.gerrit.server.config.ProjectConfigEntry;
 import com.google.gerrit.server.git.ChangeMessageModifier;
 import com.google.gerrit.server.git.validators.CommitValidationListener;
 import com.google.gerrit.server.git.validators.OnSubmitValidationListener;
@@ -79,6 +80,7 @@
   private final DynamicSet<WorkInProgressStateChangedListener> workInProgressStateChangedListeners;
   private final DynamicMap<CapabilityDefinition> capabilityDefinitions;
   private final DynamicMap<PluginProjectPermissionDefinition> pluginProjectPermissionDefinitions;
+  private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
 
   @Inject
   ExtensionRegistry(
@@ -107,7 +109,8 @@
       DynamicSet<OnSubmitValidationListener> onSubmitValidationListeners,
       DynamicSet<WorkInProgressStateChangedListener> workInProgressStateChangedListeners,
       DynamicMap<CapabilityDefinition> capabilityDefinitions,
-      DynamicMap<PluginProjectPermissionDefinition> pluginProjectPermissionDefinitions) {
+      DynamicMap<PluginProjectPermissionDefinition> pluginProjectPermissionDefinitions,
+      DynamicMap<ProjectConfigEntry> pluginConfigEntries) {
     this.accountIndexedListeners = accountIndexedListeners;
     this.changeIndexedListeners = changeIndexedListeners;
     this.groupIndexedListeners = groupIndexedListeners;
@@ -134,6 +137,7 @@
     this.workInProgressStateChangedListeners = workInProgressStateChangedListeners;
     this.capabilityDefinitions = capabilityDefinitions;
     this.pluginProjectPermissionDefinitions = pluginProjectPermissionDefinitions;
+    this.pluginConfigEntries = pluginConfigEntries;
   }
 
   public Registration newRegistration() {
@@ -254,6 +258,10 @@
       return add(pluginProjectPermissionDefinitions, pluginProjectPermissionDefinition, exportName);
     }
 
+    public Registration add(ProjectConfigEntry pluginConfigEntry, String exportName) {
+      return add(pluginConfigEntries, pluginConfigEntry, exportName);
+    }
+
     private <T> Registration add(DynamicSet<T> dynamicSet, T extension) {
       return add(dynamicSet, extension, "gerrit");
     }
diff --git a/java/com/google/gerrit/common/data/LabelType.java b/java/com/google/gerrit/common/data/LabelType.java
index 3a68414..9c1423d 100644
--- a/java/com/google/gerrit/common/data/LabelType.java
+++ b/java/com/google/gerrit/common/data/LabelType.java
@@ -14,23 +14,21 @@
 
 package com.google.gerrit.common.data;
 
-import static com.google.common.collect.ImmutableList.toImmutableList;
 import static java.util.Comparator.comparing;
-import static java.util.stream.Collectors.collectingAndThen;
 import static java.util.stream.Collectors.toList;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.LabelId;
 import com.google.gerrit.entities.PatchSetApproval;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
-public class LabelType {
+@AutoValue
+public abstract class LabelType {
   public static final boolean DEF_ALLOW_POST_SUBMIT = true;
   public static final boolean DEF_CAN_OVERRIDE = true;
   public static final boolean DEF_COPY_ALL_SCORES_IF_NO_CHANGE = true;
@@ -46,12 +44,12 @@
   public static LabelType withDefaultValues(String name) {
     checkName(name);
     List<LabelValue> values = new ArrayList<>(2);
-    values.add(new LabelValue((short) 0, "Rejected"));
-    values.add(new LabelValue((short) 1, "Approved"));
-    return new LabelType(name, values);
+    values.add(LabelValue.create((short) 0, "Rejected"));
+    values.add(LabelValue.create((short) 1, "Approved"));
+    return create(name, values);
   }
 
-  public static String checkName(String name) {
+  public static String checkName(String name) throws IllegalArgumentException {
     checkNameInternal(name);
     if ("SUBM".equals(name)) {
       throw new IllegalArgumentException("Reserved label name \"" + name + "\"");
@@ -59,7 +57,7 @@
     return name;
   }
 
-  public static String checkNameInternal(String name) {
+  public static String checkNameInternal(String name) throws IllegalArgumentException {
     if (name == null || name.isEmpty()) {
       throw new IllegalArgumentException("Empty label name");
     }
@@ -76,270 +74,135 @@
     return name;
   }
 
-  private static List<LabelValue> sortValues(List<LabelValue> values) {
-    values = new ArrayList<>(values);
+  private static ImmutableList<LabelValue> sortValues(List<LabelValue> values) {
     if (values.isEmpty()) {
-      return Collections.emptyList();
+      return ImmutableList.of();
     }
     values = values.stream().sorted(comparing(LabelValue::getValue)).collect(toList());
     short v = values.get(0).getValue();
     short i = 0;
-    ArrayList<LabelValue> result = new ArrayList<>();
+    ImmutableList.Builder<LabelValue> result = ImmutableList.builder();
     // Fill in any missing values with empty text.
     while (i < values.size()) {
       while (v < values.get(i).getValue()) {
-        result.add(new LabelValue(v++, ""));
+        result.add(LabelValue.create(v++, ""));
       }
       v++;
       result.add(values.get(i++));
     }
-    result.trimToSize();
-    return Collections.unmodifiableList(result);
+    return result.build();
   }
 
-  protected String name;
+  public abstract String getName();
 
-  protected LabelFunction function;
+  public abstract LabelFunction getFunction();
 
-  protected boolean copyAnyScore;
-  protected boolean copyMinScore;
-  protected boolean copyMaxScore;
-  protected boolean copyAllScoresOnMergeFirstParentUpdate;
-  protected boolean copyAllScoresOnTrivialRebase;
-  protected boolean copyAllScoresIfNoCodeChange;
-  protected boolean copyAllScoresIfNoChange;
-  protected ImmutableList<Short> copyValues;
-  protected boolean allowPostSubmit;
-  protected boolean ignoreSelfApproval;
-  protected short defaultValue;
+  public abstract boolean isCopyAnyScore();
 
-  protected List<LabelValue> values;
-  protected short maxNegative;
-  protected short maxPositive;
+  public abstract boolean isCopyMinScore();
 
-  private transient boolean canOverride;
-  private transient List<String> refPatterns;
-  private transient Map<Short, LabelValue> byValue;
+  public abstract boolean isCopyMaxScore();
 
-  protected LabelType() {}
+  public abstract boolean isCopyAllScoresOnMergeFirstParentUpdate();
 
-  public LabelType(String name, List<LabelValue> valueList) {
-    this.name = checkName(name);
-    canOverride = true;
-    values = sortValues(valueList);
-    defaultValue = 0;
+  public abstract boolean isCopyAllScoresOnTrivialRebase();
 
-    function = LabelFunction.MAX_WITH_BLOCK;
+  public abstract boolean isCopyAllScoresIfNoCodeChange();
 
-    maxNegative = Short.MIN_VALUE;
-    maxPositive = Short.MAX_VALUE;
-    if (!values.isEmpty()) {
-      if (values.get(0).getValue() < 0) {
-        maxNegative = values.get(0).getValue();
-      }
-      if (values.get(values.size() - 1).getValue() > 0) {
-        maxPositive = values.get(values.size() - 1).getValue();
-      }
-    }
-    setCanOverride(DEF_CAN_OVERRIDE);
-    setCopyAllScoresIfNoChange(DEF_COPY_ALL_SCORES_IF_NO_CHANGE);
-    setCopyAllScoresIfNoCodeChange(DEF_COPY_ALL_SCORES_IF_NO_CODE_CHANGE);
-    setCopyAllScoresOnTrivialRebase(DEF_COPY_ALL_SCORES_ON_TRIVIAL_REBASE);
-    setCopyAllScoresOnMergeFirstParentUpdate(DEF_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE);
-    setCopyAnyScore(DEF_COPY_ANY_SCORE);
-    setCopyMaxScore(DEF_COPY_MAX_SCORE);
-    setCopyMinScore(DEF_COPY_MIN_SCORE);
-    setCopyValues(DEF_COPY_VALUES);
-    setAllowPostSubmit(DEF_ALLOW_POST_SUBMIT);
-    setIgnoreSelfApproval(DEF_IGNORE_SELF_APPROVAL);
+  public abstract boolean isCopyAllScoresIfNoChange();
 
-    byValue = new HashMap<>();
-    for (LabelValue v : values) {
-      byValue.put(v.getValue(), v);
-    }
+  public abstract ImmutableList<Short> getCopyValues();
+
+  public abstract boolean isAllowPostSubmit();
+
+  public abstract boolean isIgnoreSelfApproval();
+
+  public abstract short getDefaultValue();
+
+  public abstract ImmutableList<LabelValue> getValues();
+
+  public abstract short getMaxNegative();
+
+  public abstract short getMaxPositive();
+
+  public abstract boolean isCanOverride();
+
+  @Nullable
+  public abstract ImmutableList<String> getRefPatterns();
+
+  public abstract ImmutableMap<Short, LabelValue> getByValue();
+
+  public static LabelType create(String name, List<LabelValue> valueList) {
+    return LabelType.builder(name, valueList).build();
   }
 
-  public String getName() {
-    return name;
-  }
-
-  public void setName(String name) {
-    this.name = checkName(name);
+  public static LabelType.Builder builder(String name, List<LabelValue> valueList) {
+    return (new AutoValue_LabelType.Builder())
+        .setName(name)
+        .setValues(valueList)
+        .setDefaultValue((short) 0)
+        .setFunction(LabelFunction.MAX_WITH_BLOCK)
+        .setMaxNegative(Short.MIN_VALUE)
+        .setMaxPositive(Short.MAX_VALUE)
+        .setCanOverride(DEF_CAN_OVERRIDE)
+        .setCopyAllScoresIfNoChange(DEF_COPY_ALL_SCORES_IF_NO_CHANGE)
+        .setCopyAllScoresIfNoCodeChange(DEF_COPY_ALL_SCORES_IF_NO_CODE_CHANGE)
+        .setCopyAllScoresOnTrivialRebase(DEF_COPY_ALL_SCORES_ON_TRIVIAL_REBASE)
+        .setCopyAllScoresOnMergeFirstParentUpdate(DEF_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE)
+        .setCopyAnyScore(DEF_COPY_ANY_SCORE)
+        .setCopyMaxScore(DEF_COPY_MAX_SCORE)
+        .setCopyMinScore(DEF_COPY_MIN_SCORE)
+        .setCopyValues(DEF_COPY_VALUES)
+        .setAllowPostSubmit(DEF_ALLOW_POST_SUBMIT)
+        .setIgnoreSelfApproval(DEF_IGNORE_SELF_APPROVAL);
   }
 
   public boolean matches(PatchSetApproval psa) {
-    return psa.labelId().get().equalsIgnoreCase(name);
-  }
-
-  public LabelFunction getFunction() {
-    return function;
-  }
-
-  public void setFunction(@Nullable LabelFunction function) {
-    this.function = function;
-  }
-
-  public boolean canOverride() {
-    return canOverride;
-  }
-
-  @Nullable
-  public List<String> getRefPatterns() {
-    return refPatterns;
-  }
-
-  public void setCanOverride(boolean canOverride) {
-    this.canOverride = canOverride;
-  }
-
-  public boolean allowPostSubmit() {
-    return allowPostSubmit;
-  }
-
-  public void setAllowPostSubmit(boolean allowPostSubmit) {
-    this.allowPostSubmit = allowPostSubmit;
-  }
-
-  public boolean ignoreSelfApproval() {
-    return ignoreSelfApproval;
-  }
-
-  public void setIgnoreSelfApproval(boolean ignoreSelfApproval) {
-    this.ignoreSelfApproval = ignoreSelfApproval;
-  }
-
-  public void setRefPatterns(List<String> refPatterns) {
-    if (refPatterns != null && !refPatterns.isEmpty()) {
-      this.refPatterns =
-          refPatterns.stream().collect(collectingAndThen(toList(), Collections::unmodifiableList));
-    } else {
-      this.refPatterns = null;
-    }
-  }
-
-  public List<LabelValue> getValues() {
-    return values;
-  }
-
-  public void setValues(List<LabelValue> values) {
-    this.values = sortValues(values);
+    return psa.labelId().get().equalsIgnoreCase(getName());
   }
 
   public LabelValue getMin() {
-    if (values.isEmpty()) {
+    if (getValues().isEmpty()) {
       return null;
     }
-    return values.get(0);
+    return getValues().get(0);
   }
 
   public LabelValue getMax() {
-    if (values.isEmpty()) {
+    if (getValues().isEmpty()) {
       return null;
     }
-    return values.get(values.size() - 1);
-  }
-
-  public short getDefaultValue() {
-    return defaultValue;
-  }
-
-  public void setDefaultValue(short defaultValue) {
-    this.defaultValue = defaultValue;
-  }
-
-  public boolean isCopyAnyScore() {
-    return copyAnyScore;
-  }
-
-  public void setCopyAnyScore(boolean copyAnyScore) {
-    this.copyAnyScore = copyAnyScore;
-  }
-
-  public boolean isCopyMinScore() {
-    return copyMinScore;
-  }
-
-  public void setCopyMinScore(boolean copyMinScore) {
-    this.copyMinScore = copyMinScore;
-  }
-
-  public boolean isCopyMaxScore() {
-    return copyMaxScore;
-  }
-
-  public void setCopyMaxScore(boolean copyMaxScore) {
-    this.copyMaxScore = copyMaxScore;
-  }
-
-  public boolean isCopyAllScoresOnMergeFirstParentUpdate() {
-    return copyAllScoresOnMergeFirstParentUpdate;
-  }
-
-  public void setCopyAllScoresOnMergeFirstParentUpdate(
-      boolean copyAllScoresOnMergeFirstParentUpdate) {
-    this.copyAllScoresOnMergeFirstParentUpdate = copyAllScoresOnMergeFirstParentUpdate;
-  }
-
-  public boolean isCopyAllScoresOnTrivialRebase() {
-    return copyAllScoresOnTrivialRebase;
-  }
-
-  public void setCopyAllScoresOnTrivialRebase(boolean copyAllScoresOnTrivialRebase) {
-    this.copyAllScoresOnTrivialRebase = copyAllScoresOnTrivialRebase;
-  }
-
-  public boolean isCopyAllScoresIfNoCodeChange() {
-    return copyAllScoresIfNoCodeChange;
-  }
-
-  public void setCopyAllScoresIfNoCodeChange(boolean copyAllScoresIfNoCodeChange) {
-    this.copyAllScoresIfNoCodeChange = copyAllScoresIfNoCodeChange;
-  }
-
-  public boolean isCopyAllScoresIfNoChange() {
-    return copyAllScoresIfNoChange;
-  }
-
-  public void setCopyAllScoresIfNoChange(boolean copyAllScoresIfNoChange) {
-    this.copyAllScoresIfNoChange = copyAllScoresIfNoChange;
-  }
-
-  public ImmutableList<Short> getCopyValues() {
-    return copyValues;
-  }
-
-  public void setCopyValues(Collection<Short> copyValues) {
-    this.copyValues = copyValues.stream().sorted().collect(toImmutableList());
+    return getValues().get(getValues().size() - 1);
   }
 
   public boolean isMaxNegative(PatchSetApproval ca) {
-    return maxNegative == ca.value();
+    return getMaxNegative() == ca.value();
   }
 
   public boolean isMaxPositive(PatchSetApproval ca) {
-    return maxPositive == ca.value();
+    return getMaxPositive() == ca.value();
   }
 
   public LabelValue getValue(short value) {
-    return byValue.get(value);
+    return getByValue().get(value);
   }
 
   public LabelValue getValue(PatchSetApproval ca) {
-    return byValue.get(ca.value());
+    return getByValue().get(ca.value());
   }
 
   public LabelId getLabelId() {
-    return LabelId.create(name);
+    return LabelId.create(getName());
   }
 
   @Override
-  public String toString() {
-    StringBuilder sb = new StringBuilder(name).append('[');
+  public final String toString() {
+    StringBuilder sb = new StringBuilder(getName()).append('[');
     LabelValue min = getMin();
     LabelValue max = getMax();
     if (min != null && max != null) {
       sb.append(
-          new PermissionRange(Permission.forLabel(name), min.getValue(), max.getValue())
+          new PermissionRange(Permission.forLabel(getName()), min.getValue(), max.getValue())
               .toString()
               .trim());
     } else if (min != null) {
@@ -350,4 +213,84 @@
     sb.append(']');
     return sb.toString();
   }
+
+  public abstract Builder toBuilder();
+
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder setName(String name);
+
+    public abstract Builder setFunction(LabelFunction function);
+
+    public abstract Builder setCanOverride(boolean canOverride);
+
+    public abstract Builder setAllowPostSubmit(boolean allowPostSubmit);
+
+    public abstract Builder setIgnoreSelfApproval(boolean ignoreSelfApproval);
+
+    public abstract Builder setRefPatterns(@Nullable ImmutableList<String> refPatterns);
+
+    public abstract Builder setValues(List<LabelValue> values);
+
+    public abstract Builder setDefaultValue(short defaultValue);
+
+    public abstract Builder setCopyAnyScore(boolean copyAnyScore);
+
+    public abstract Builder setCopyMinScore(boolean copyMinScore);
+
+    public abstract Builder setCopyMaxScore(boolean copyMaxScore);
+
+    public abstract Builder setCopyAllScoresOnMergeFirstParentUpdate(
+        boolean copyAllScoresOnMergeFirstParentUpdate);
+
+    public abstract Builder setCopyAllScoresOnTrivialRebase(boolean copyAllScoresOnTrivialRebase);
+
+    public abstract Builder setCopyAllScoresIfNoCodeChange(boolean copyAllScoresIfNoCodeChange);
+
+    public abstract Builder setCopyAllScoresIfNoChange(boolean copyAllScoresIfNoChange);
+
+    public abstract Builder setCopyValues(Collection<Short> copyValues);
+
+    public abstract Builder setMaxNegative(short maxNegative);
+
+    public abstract Builder setMaxPositive(short maxPositive);
+
+    public abstract ImmutableList<LabelValue> getValues();
+
+    protected abstract String getName();
+
+    protected abstract Builder setByValue(ImmutableMap<Short, LabelValue> byValue);
+
+    @Nullable
+    protected abstract ImmutableList<String> getRefPatterns();
+
+    protected abstract LabelType autoBuild();
+
+    public LabelType build() throws IllegalArgumentException {
+      setName(checkName(getName()));
+      if (getRefPatterns() == null || getRefPatterns().isEmpty()) {
+        // Empty to null
+        setRefPatterns(null);
+      }
+
+      List<LabelValue> valueList = sortValues(getValues());
+      setValues(valueList);
+      if (!valueList.isEmpty()) {
+        if (valueList.get(0).getValue() < 0) {
+          setMaxNegative(valueList.get(0).getValue());
+        }
+        if (valueList.get(valueList.size() - 1).getValue() > 0) {
+          setMaxPositive(valueList.get(valueList.size() - 1).getValue());
+        }
+      }
+
+      ImmutableMap.Builder<Short, LabelValue> byValue = ImmutableMap.builder();
+      for (LabelValue v : valueList) {
+        byValue.put(v.getValue(), v);
+      }
+      setByValue(byValue.build());
+
+      return autoBuild();
+    }
+  }
 }
diff --git a/java/com/google/gerrit/common/data/LabelValue.java b/java/com/google/gerrit/common/data/LabelValue.java
index c0ba781..ec16fb2 100644
--- a/java/com/google/gerrit/common/data/LabelValue.java
+++ b/java/com/google/gerrit/common/data/LabelValue.java
@@ -14,65 +14,42 @@
 
 package com.google.gerrit.common.data;
 
-import java.util.Objects;
+import com.google.auto.value.AutoValue;
 
-public class LabelValue {
+@AutoValue
+public abstract class LabelValue {
   public static String formatValue(short value) {
     if (value < 0) {
       return Short.toString(value);
     } else if (value == 0) {
       return " 0";
     } else {
-      return "+" + Short.toString(value);
+      return "+" + value;
     }
   }
 
-  protected short value;
-  protected String text;
+  public abstract short getValue();
 
-  public LabelValue(short value, String text) {
-    this.value = value;
-    this.text = text;
-  }
+  public abstract String getText();
 
-  protected LabelValue() {}
-
-  public short getValue() {
-    return value;
-  }
-
-  public String getText() {
-    return text;
+  public static LabelValue create(short value, String text) {
+    return new AutoValue_LabelValue(value, text);
   }
 
   public String formatValue() {
-    return formatValue(value);
+    return formatValue(getValue());
   }
 
   public String format() {
     StringBuilder sb = new StringBuilder(formatValue());
-    if (!text.isEmpty()) {
-      sb.append(' ').append(text);
+    if (!getText().isEmpty()) {
+      sb.append(' ').append(getText());
     }
     return sb.toString();
   }
 
   @Override
-  public boolean equals(Object o) {
-    if (!(o instanceof LabelValue)) {
-      return false;
-    }
-    LabelValue v = (LabelValue) o;
-    return value == v.value && Objects.equals(text, v.text);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(value, text);
-  }
-
-  @Override
-  public String toString() {
+  public final String toString() {
     return format();
   }
 }
diff --git a/java/com/google/gerrit/entities/Project.java b/java/com/google/gerrit/entities/Project.java
index 1300b9d..759d50a 100644
--- a/java/com/google/gerrit/entities/Project.java
+++ b/java/com/google/gerrit/entities/Project.java
@@ -16,7 +16,10 @@
 
 import static java.util.Objects.requireNonNull;
 
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
 import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.client.InheritableBoolean;
 import com.google.gerrit.extensions.client.ProjectState;
 import com.google.gerrit.extensions.client.SubmitType;
@@ -27,7 +30,8 @@
 import java.util.Optional;
 
 /** Projects match a source code repository managed by Gerrit */
-public final class Project {
+@AutoValue
+public abstract class Project {
   /** Default submit type for new projects. */
   public static final SubmitType DEFAULT_SUBMIT_TYPE = SubmitType.MERGE_IF_NECESSARY;
 
@@ -60,10 +64,6 @@
       return nameKey(KeyUtil.decode(str));
     }
 
-    public static String asStringOrNull(NameKey key) {
-      return key == null ? null : key.get();
-    }
-
     private final String name;
 
     protected NameKey(String name) {
@@ -98,118 +98,64 @@
     }
   }
 
-  protected NameKey name;
+  public abstract NameKey getNameKey();
 
-  protected String description;
+  @Nullable
+  public abstract String getDescription();
 
-  protected Map<BooleanProjectConfig, InheritableBoolean> booleanConfigs;
-
-  protected SubmitType submitType;
-
-  protected ProjectState state;
-
-  protected NameKey parent;
-
-  protected String maxObjectSizeLimit;
-
-  protected String defaultDashboardId;
-
-  protected String localDefaultDashboardId;
-
-  protected String configRefState;
-
-  protected Project() {}
-
-  public Project(Project.NameKey nameKey) {
-    name = nameKey;
-    submitType = SubmitType.MERGE_IF_NECESSARY;
-    state = ProjectState.ACTIVE;
-
-    booleanConfigs = new HashMap<>();
-    Arrays.stream(BooleanProjectConfig.values())
-        .forEach(c -> booleanConfigs.put(c, InheritableBoolean.INHERIT));
-  }
-
-  public Project.NameKey getNameKey() {
-    return name;
-  }
-
-  public String getName() {
-    return name != null ? name.get() : null;
-  }
-
-  public String getDescription() {
-    return description;
-  }
-
-  public void setDescription(String d) {
-    description = d;
-  }
-
-  public String getMaxObjectSizeLimit() {
-    return maxObjectSizeLimit;
-  }
-
-  public InheritableBoolean getBooleanConfig(BooleanProjectConfig config) {
-    return booleanConfigs.get(config);
-  }
-
-  public void setBooleanConfig(BooleanProjectConfig config, InheritableBoolean val) {
-    booleanConfigs.replace(config, val);
-  }
-
-  public void setMaxObjectSizeLimit(String limit) {
-    maxObjectSizeLimit = limit;
-  }
+  protected abstract ImmutableMap<BooleanProjectConfig, InheritableBoolean> getBooleanConfigs();
 
   /**
    * Submit type as configured in {@code project.config}.
    *
    * <p>Does not take inheritance into account, i.e. may return {@link SubmitType#INHERIT}.
-   *
-   * @return submit type.
    */
-  public SubmitType getConfiguredSubmitType() {
-    return submitType;
-  }
+  public abstract SubmitType getSubmitType();
 
-  public void setSubmitType(SubmitType type) {
-    submitType = type;
-  }
-
-  public ProjectState getState() {
-    return state;
-  }
-
-  public void setState(ProjectState newState) {
-    state = newState;
-  }
-
-  public String getDefaultDashboard() {
-    return defaultDashboardId;
-  }
-
-  public void setDefaultDashboard(String defaultDashboardId) {
-    this.defaultDashboardId = defaultDashboardId;
-  }
-
-  public String getLocalDefaultDashboard() {
-    return localDefaultDashboardId;
-  }
-
-  public void setLocalDefaultDashboard(String localDefaultDashboardId) {
-    this.localDefaultDashboardId = localDefaultDashboardId;
-  }
+  public abstract ProjectState getState();
 
   /**
-   * Returns the name key of the parent project.
+   * Name key of the parent project.
    *
-   * @return name key of the parent project, {@code null} if this project is the wild project,
-   *     {@code null} or the name key of the wild project if this project is a direct child of the
-   *     wild project
+   * <p>{@code null} if this project is the wild project, {@code null} or the name key of the wild
+   * project if this project is a direct child of the wild project.
    */
-  public Project.NameKey getParent() {
-    return parent;
+  @Nullable
+  public abstract NameKey getParent();
+
+  @Nullable
+  public abstract String getMaxObjectSizeLimit();
+
+  @Nullable
+  public abstract String getDefaultDashboard();
+
+  @Nullable
+  public abstract String getLocalDefaultDashboard();
+
+  /** The {@code ObjectId} as 40 digit hex of {@code refs/meta/config}'s HEAD. */
+  @Nullable
+  public abstract String getConfigRefState();
+
+  public static Builder builder(Project.NameKey nameKey) {
+    Builder builder =
+        new AutoValue_Project.Builder()
+            .setNameKey(nameKey)
+            .setSubmitType(SubmitType.MERGE_IF_NECESSARY)
+            .setState(ProjectState.ACTIVE);
+    ImmutableMap.Builder<BooleanProjectConfig, InheritableBoolean> booleans =
+        ImmutableMap.builder();
+    Arrays.stream(BooleanProjectConfig.values())
+        .forEach(b -> booleans.put(b, InheritableBoolean.INHERIT));
+    builder.setBooleanConfigs(booleans.build());
+    return builder;
+  }
+
+  public String getName() {
+    return getNameKey() != null ? getNameKey().get() : null;
+  }
+
+  public InheritableBoolean getBooleanConfig(BooleanProjectConfig config) {
+    return getBooleanConfigs().get(config);
   }
 
   /**
@@ -220,11 +166,11 @@
    *     project
    */
   public Project.NameKey getParent(Project.NameKey allProjectsName) {
-    if (parent != null) {
-      return parent;
+    if (getParent() != null) {
+      return getParent();
     }
 
-    if (name.equals(allProjectsName)) {
+    if (getNameKey().equals(allProjectsName)) {
       return null;
     }
 
@@ -232,29 +178,53 @@
   }
 
   public String getParentName() {
-    return parent != null ? parent.get() : null;
-  }
-
-  public void setParentName(String n) {
-    parent = n != null ? nameKey(n) : null;
-  }
-
-  public void setParentName(NameKey n) {
-    parent = n;
-  }
-
-  /** Returns the {@code ObjectId} as 40 digit hex of {@code refs/meta/config}'s HEAD. */
-  public String getConfigRefState() {
-    return configRefState;
-  }
-
-  /** Sets the {@code ObjectId} as 40 digit hex of {@code refs/meta/config}'s HEAD. */
-  public void setConfigRefState(String state) {
-    configRefState = state;
+    return getParent() != null ? getParent().get() : null;
   }
 
   @Override
-  public String toString() {
+  public final String toString() {
     return Optional.of(getName()).orElse("<null>");
   }
+
+  public abstract Builder toBuilder();
+
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder setDescription(String description);
+
+    public Builder setBooleanConfig(BooleanProjectConfig config, InheritableBoolean val) {
+      Map<BooleanProjectConfig, InheritableBoolean> map = new HashMap<>(getBooleanConfigs());
+      map.replace(config, val);
+      setBooleanConfigs(ImmutableMap.copyOf(map));
+      return this;
+    }
+
+    public abstract Builder setMaxObjectSizeLimit(String limit);
+
+    public abstract Builder setSubmitType(SubmitType type);
+
+    public abstract Builder setState(ProjectState newState);
+
+    public abstract Builder setDefaultDashboard(String defaultDashboardId);
+
+    public abstract Builder setLocalDefaultDashboard(String localDefaultDashboard);
+
+    public abstract Builder setParent(NameKey n);
+
+    public Builder setParent(String n) {
+      return setParent(n != null ? nameKey(n) : null);
+    }
+
+    /** Sets the {@code ObjectId} as 40 digit hex of {@code refs/meta/config}'s HEAD. */
+    public abstract Builder setConfigRefState(String state);
+
+    public abstract Project build();
+
+    protected abstract Builder setNameKey(Project.NameKey nameKey);
+
+    protected abstract ImmutableMap<BooleanProjectConfig, InheritableBoolean> getBooleanConfigs();
+
+    protected abstract Builder setBooleanConfigs(
+        ImmutableMap<BooleanProjectConfig, InheritableBoolean> booleanConfigs);
+  }
 }
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index 0c3b7b0..c5f97a3 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -96,6 +96,7 @@
 import com.google.gerrit.server.ssh.NoSshModule;
 import com.google.gerrit.server.ssh.SshAddressesModule;
 import com.google.gerrit.server.submit.LocalMergeSuperSetComputation;
+import com.google.gerrit.server.submit.SubscriptionGraph;
 import com.google.gerrit.sshd.SshHostKeyModule;
 import com.google.gerrit.sshd.SshKeyCacheImpl;
 import com.google.gerrit.sshd.SshModule;
@@ -322,6 +323,7 @@
     }
 
     modules.add(new RestApiModule());
+    modules.add(new SubscriptionGraph.Module());
     modules.add(new WorkQueue.Module());
     modules.add(new GerritInstanceNameModule());
     modules.add(
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 57bec71..63278c1 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -107,6 +107,7 @@
 import com.google.gerrit.server.ssh.NoSshModule;
 import com.google.gerrit.server.ssh.SshAddressesModule;
 import com.google.gerrit.server.submit.LocalMergeSuperSetComputation;
+import com.google.gerrit.server.submit.SubscriptionGraph;
 import com.google.gerrit.sshd.SshHostKeyModule;
 import com.google.gerrit.sshd.SshKeyCacheImpl;
 import com.google.gerrit.sshd.SshModule;
@@ -411,6 +412,7 @@
     // work queue can get stuck waiting on index futures that will never return.
     modules.add(createIndexModule());
 
+    modules.add(new SubscriptionGraph.Module());
     modules.add(new WorkQueue.Module());
     modules.add(new StreamEventsApiListener.Module());
     modules.add(new EventBroker.Module());
diff --git a/java/com/google/gerrit/server/change/LabelsJson.java b/java/com/google/gerrit/server/change/LabelsJson.java
index 2db17d6..739e263 100644
--- a/java/com/google/gerrit/server/change/LabelsJson.java
+++ b/java/com/google/gerrit/server/change/LabelsJson.java
@@ -122,7 +122,7 @@
       if (rec.labels != null) {
         for (SubmitRecord.Label r : rec.labels) {
           LabelType type = labelTypes.byLabel(r.label);
-          if (type != null && (!isMerged || type.allowPostSubmit())) {
+          if (type != null && (!isMerged || type.isAllowPostSubmit())) {
             toCheck.put(type.getName(), type);
           }
         }
@@ -139,7 +139,7 @@
       }
       for (SubmitRecord.Label r : rec.labels) {
         LabelType type = labelTypes.byLabel(r.label);
-        if (type == null || (isMerged && !type.allowPostSubmit())) {
+        if (type == null || (isMerged && !type.isAllowPostSubmit())) {
           continue;
         }
 
diff --git a/java/com/google/gerrit/server/git/NotifyConfig.java b/java/com/google/gerrit/server/git/NotifyConfig.java
index 429f15a..1a1bbb6 100644
--- a/java/com/google/gerrit/server/git/NotifyConfig.java
+++ b/java/com/google/gerrit/server/git/NotifyConfig.java
@@ -14,113 +14,101 @@
 
 package com.google.gerrit.server.git;
 
-import com.google.common.base.MoreObjects;
+import com.google.auto.value.AutoValue;
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.mail.Address;
 import com.google.gerrit.server.account.ProjectWatches.NotifyType;
 import java.util.EnumSet;
-import java.util.HashSet;
 import java.util.Set;
+import org.eclipse.jgit.annotations.Nullable;
 
-public class NotifyConfig implements Comparable<NotifyConfig> {
+@AutoValue
+public abstract class NotifyConfig implements Comparable<NotifyConfig> {
   public enum Header {
     TO,
     CC,
     BCC
   }
 
-  private String name;
-  private EnumSet<NotifyType> types = EnumSet.of(NotifyType.ALL);
-  private String filter;
+  @Nullable
+  public abstract String getName();
 
-  private Header header;
-  private Set<GroupReference> groups = new HashSet<>();
-  private Set<Address> addresses = new HashSet<>();
+  public abstract ImmutableSet<NotifyType> getNotify();
 
-  public String getName() {
-    return name;
-  }
+  @Nullable
+  public abstract String getFilter();
 
-  public void setName(String name) {
-    this.name = name;
-  }
+  @Nullable
+  public abstract Header getHeader();
+
+  public abstract ImmutableSet<GroupReference> getGroups();
+
+  public abstract ImmutableSet<Address> getAddresses();
 
   public boolean isNotify(NotifyType type) {
-    return types.contains(type) || types.contains(NotifyType.ALL);
+    return getNotify().contains(type) || getNotify().contains(NotifyType.ALL);
   }
 
-  public Set<NotifyType> getNotify() {
-    return types;
+  public static Builder builder() {
+    return new AutoValue_NotifyConfig.Builder()
+        .setNotify(ImmutableSet.copyOf(EnumSet.of(NotifyType.ALL)));
   }
 
-  public void setTypes(Set<NotifyType> newTypes) {
-    types = EnumSet.copyOf(newTypes);
-  }
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder setName(String name);
 
-  public String getFilter() {
-    return filter;
-  }
+    public abstract Builder setNotify(Set<NotifyType> newTypes);
 
-  public void setFilter(String filter) {
-    if ("*".equals(filter)) {
-      this.filter = null;
-    } else {
-      this.filter = Strings.emptyToNull(filter);
+    public abstract Builder setFilter(@Nullable String filter);
+
+    public abstract Builder setHeader(Header hdr);
+
+    public Builder addGroup(GroupReference group) {
+      groupsBuilder().add(group);
+      return this;
+    }
+
+    public Builder addAddress(Address address) {
+      addressesBuilder().add(address);
+      return this;
+    }
+
+    protected abstract ImmutableSet.Builder<GroupReference> groupsBuilder();
+
+    protected abstract ImmutableSet.Builder<Address> addressesBuilder();
+
+    protected abstract NotifyConfig autoBuild();
+
+    protected abstract String getFilter();
+
+    public NotifyConfig build() {
+      if ("*".equals(getFilter())) {
+        setFilter(null);
+      } else {
+        setFilter(Strings.emptyToNull(getFilter()));
+      }
+      return autoBuild();
     }
   }
 
-  public Header getHeader() {
-    return header;
-  }
-
-  public void setHeader(Header hdr) {
-    header = hdr;
-  }
-
-  public Set<GroupReference> getGroups() {
-    return groups;
-  }
-
-  public Set<Address> getAddresses() {
-    return addresses;
-  }
-
-  public void addEmail(GroupReference group) {
-    groups.add(group);
-  }
-
-  public void addEmail(Address address) {
-    addresses.add(address);
+  @Override
+  public final int compareTo(NotifyConfig o) {
+    return getName().compareTo(o.getName());
   }
 
   @Override
-  public int compareTo(NotifyConfig o) {
-    return name.compareTo(o.name);
+  public final int hashCode() {
+    return getName().hashCode();
   }
 
   @Override
-  public int hashCode() {
-    return name.hashCode();
-  }
-
-  @Override
-  public boolean equals(Object obj) {
+  public final boolean equals(Object obj) {
     if (obj instanceof NotifyConfig) {
       return compareTo((NotifyConfig) obj) == 0;
     }
     return false;
   }
-
-  @Override
-  public String toString() {
-    return MoreObjects.toStringHelper(this)
-        .add("name", name)
-        .add("addresses", addresses)
-        .add("groups", groups)
-        .add("header", header)
-        .add("types", types)
-        .add("filter", filter)
-        .toString();
-  }
 }
diff --git a/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 348ab7d..1c81694 100644
--- a/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -659,7 +659,9 @@
       addFooter(msg, e.getValue().getFooterKey());
       noteUtil.appendAccountIdIdentString(msg, e.getKey()).append('\n');
     }
+
     applyReviewerUpdatesToAttentionSet();
+
     for (Map.Entry<Address, ReviewerStateInternal> e : reviewersByEmail.entrySet()) {
       addFooter(msg, e.getValue().getByEmailFooterKey(), e.getKey().toString());
     }
@@ -766,8 +768,10 @@
 
   private void applyReviewerUpdatesToAttentionSet() {
     if ((workInProgress != null && workInProgress == true)
-        || getNotes().getChange().isWorkInProgress()) {
-      // Users shouldn't be added to the attention set if the change is work in progress.
+        || getNotes().getChange().isWorkInProgress()
+        || status == Change.Status.MERGED) {
+      // Attention set shouldn't change here for changes that are work in progress or are about to
+      // be submitted.
       return;
     }
     Set<Account.Id> currentReviewers =
diff --git a/java/com/google/gerrit/server/project/LabelDefinitionJson.java b/java/com/google/gerrit/server/project/LabelDefinitionJson.java
index 0452d0b..9ff079f 100644
--- a/java/com/google/gerrit/server/project/LabelDefinitionJson.java
+++ b/java/com/google/gerrit/server/project/LabelDefinitionJson.java
@@ -31,7 +31,7 @@
         labelType.getValues().stream().collect(toMap(LabelValue::formatValue, LabelValue::getText));
     label.defaultValue = labelType.getDefaultValue();
     label.branches = labelType.getRefPatterns() != null ? labelType.getRefPatterns() : null;
-    label.canOverride = toBoolean(labelType.canOverride());
+    label.canOverride = toBoolean(labelType.isCanOverride());
     label.copyAnyScore = toBoolean(labelType.isCopyAnyScore());
     label.copyMinScore = toBoolean(labelType.isCopyMinScore());
     label.copyMaxScore = toBoolean(labelType.isCopyMaxScore());
@@ -41,8 +41,8 @@
     label.copyAllScoresOnMergeFirstParentUpdate =
         toBoolean(labelType.isCopyAllScoresOnMergeFirstParentUpdate());
     label.copyValues = labelType.getCopyValues().isEmpty() ? null : labelType.getCopyValues();
-    label.allowPostSubmit = toBoolean(labelType.allowPostSubmit());
-    label.ignoreSelfApproval = toBoolean(labelType.ignoreSelfApproval());
+    label.allowPostSubmit = toBoolean(labelType.isAllowPostSubmit());
+    label.ignoreSelfApproval = toBoolean(labelType.isIgnoreSelfApproval());
     return label;
   }
 
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 2d4928a..35257ef 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.project;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.gerrit.common.data.Permission.isPermission;
 import static com.google.gerrit.entities.Project.DEFAULT_SUBMIT_TYPE;
@@ -81,6 +82,7 @@
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -329,6 +331,16 @@
     return project;
   }
 
+  public void setProject(Project.Builder project) {
+    this.project = project.build();
+  }
+
+  public void updateProject(Consumer<Project.Builder> update) {
+    Project.Builder builder = project.toBuilder();
+    update.accept(builder);
+    project = builder.build();
+  }
+
   public AccountsSection getAccountsSection() {
     return accountsSection;
   }
@@ -484,6 +496,20 @@
     return labelSections;
   }
 
+  /** Adds or replaces the given {@link LabelType} in this config. */
+  public void upsertLabelType(LabelType labelType) {
+    labelSections.put(labelType.getName(), labelType);
+  }
+
+  /** Allows a mutation of an existing {@link LabelType}. */
+  public void updateLabelType(String name, Consumer<LabelType.Builder> update) {
+    LabelType labelType = labelSections.get(name);
+    checkState(labelType != null, "labelType must not be null");
+    LabelType.Builder builder = labelSections.get(name).toBuilder();
+    update.accept(builder);
+    upsertLabelType(builder.build());
+  }
+
   public Collection<StoredCommentLinkInfo> getCommentLinkSections() {
     return commentLinkSections.values();
   }
@@ -579,13 +605,8 @@
 
     rulesId = getObjectId("rules.pl");
     Config rc = readConfig(PROJECT_CONFIG, baseConfig);
-    project = new Project(projectName);
-
-    Project p = project;
-    p.setDescription(rc.getString(PROJECT, null, KEY_DESCRIPTION));
-    if (p.getDescription() == null) {
-      p.setDescription("");
-    }
+    Project.Builder p = Project.builder(projectName);
+    p.setDescription(Strings.nullToEmpty(rc.getString(PROJECT, null, KEY_DESCRIPTION)));
     if (revision != null) {
       p.setConfigRefState(revision.toObjectId().name());
     }
@@ -595,7 +616,7 @@
       // as there is no guarantee which of the parents would be used then.
       error(ValidationError.create(PROJECT_CONFIG, "Cannot inherit from multiple projects"));
     }
-    p.setParentName(rc.getString(ACCESS, null, KEY_INHERIT_FROM));
+    p.setParent(rc.getString(ACCESS, null, KEY_INHERIT_FROM));
 
     for (BooleanProjectConfig config : BooleanProjectConfig.values()) {
       p.setBooleanConfig(
@@ -615,6 +636,7 @@
 
     p.setDefaultDashboard(rc.getString(DASHBOARD, null, KEY_DEFAULT));
     p.setLocalDefaultDashboard(rc.getString(DASHBOARD, null, KEY_LOCAL_DEFAULT));
+    this.project = p.build();
 
     loadAccountsSection(rc);
     loadContributorAgreements(rc);
@@ -719,13 +741,13 @@
   private void loadNotifySections(Config rc) {
     notifySections = new HashMap<>();
     for (String sectionName : rc.getSubsections(NOTIFY)) {
-      NotifyConfig n = new NotifyConfig();
+      NotifyConfig.Builder n = NotifyConfig.builder();
       n.setName(sectionName);
       n.setFilter(rc.getString(NOTIFY, sectionName, KEY_FILTER));
 
       EnumSet<NotifyType> types = EnumSet.noneOf(NotifyType.class);
       types.addAll(ConfigUtil.getEnumList(rc, NOTIFY, sectionName, KEY_TYPE, NotifyType.ALL));
-      n.setTypes(types);
+      n.setNotify(types);
       n.setHeader(rc.getEnum(NOTIFY, sectionName, KEY_HEADER, NotifyConfig.Header.BCC));
 
       for (String dst : rc.getStringList(NOTIFY, sectionName, KEY_EMAIL)) {
@@ -736,7 +758,7 @@
             ref = groupList.resolve(GroupReference.create(groupName));
           }
           if (ref.getUUID() != null) {
-            n.addEmail(ref);
+            n.addGroup(ref);
           } else {
             error(
                 ValidationError.create(
@@ -747,7 +769,7 @@
           error(ValidationError.create(PROJECT_CONFIG, dst + " not supported"));
         } else {
           try {
-            n.addEmail(Address.parse(dst));
+            n.addAddress(Address.parse(dst));
           } catch (IllegalArgumentException err) {
             error(
                 ValidationError.create(
@@ -756,7 +778,7 @@
           }
         }
       }
-      notifySections.put(sectionName, n);
+      notifySections.put(sectionName, n.build());
     }
   }
 
@@ -904,7 +926,7 @@
       throw new IllegalArgumentException("empty value");
     }
     String valueText = parts.size() > 1 ? parts.get(1) : "";
-    return new LabelValue(Shorts.checkedCast(PermissionRule.parseInt(parts.get(0))), valueText);
+    return LabelValue.create(Shorts.checkedCast(PermissionRule.parseInt(parts.get(0))), valueText);
   }
 
   private void loadLabelSections(Config rc) {
@@ -943,9 +965,9 @@
         }
       }
 
-      LabelType label;
+      LabelType.Builder label;
       try {
-        label = new LabelType(name, values);
+        label = LabelType.builder(name, values);
       } catch (IllegalArgumentException badName) {
         error(ValidationError.create(PROJECT_CONFIG, String.format("Invalid label \"%s\"", name)));
         continue;
@@ -1035,8 +1057,9 @@
       label.setCopyValues(copyValues);
       label.setCanOverride(
           rc.getBoolean(LABEL, name, KEY_CAN_OVERRIDE, LabelType.DEF_CAN_OVERRIDE));
-      label.setRefPatterns(getStringListOrNull(rc, LABEL, name, KEY_BRANCH));
-      labelSections.put(name, label);
+      List<String> refPatterns = getStringListOrNull(rc, LABEL, name, KEY_BRANCH);
+      label.setRefPatterns(refPatterns == null ? null : ImmutableList.copyOf(refPatterns));
+      labelSections.put(name, label.build());
     }
   }
 
@@ -1174,7 +1197,7 @@
         KEY_MAX_OBJECT_SIZE_LIMIT,
         validMaxObjectSizeLimit(p.getMaxObjectSizeLimit()));
 
-    set(rc, SUBMIT, null, KEY_ACTION, p.getConfiguredSubmitType(), DEFAULT_SUBMIT_TYPE);
+    set(rc, SUBMIT, null, KEY_ACTION, p.getSubmitType(), DEFAULT_SUBMIT_TYPE);
 
     set(rc, PROJECT, null, KEY_STATE, p.getState(), DEFAULT_STATE_VALUE);
 
@@ -1444,14 +1467,14 @@
           LABEL,
           name,
           KEY_ALLOW_POST_SUBMIT,
-          label.allowPostSubmit(),
+          label.isAllowPostSubmit(),
           LabelType.DEF_ALLOW_POST_SUBMIT);
       setBooleanConfigKey(
           rc,
           LABEL,
           name,
           KEY_IGNORE_SELF_APPROVAL,
-          label.ignoreSelfApproval(),
+          label.isIgnoreSelfApproval(),
           LabelType.DEF_IGNORE_SELF_APPROVAL);
       setBooleanConfigKey(
           rc,
@@ -1508,7 +1531,7 @@
           KEY_COPY_VALUE,
           label.getCopyValues().stream().map(LabelValue::formatValue).collect(toList()));
       setBooleanConfigKey(
-          rc, LABEL, name, KEY_CAN_OVERRIDE, label.canOverride(), LabelType.DEF_CAN_OVERRIDE);
+          rc, LABEL, name, KEY_CAN_OVERRIDE, label.isCanOverride(), LabelType.DEF_CAN_OVERRIDE);
       List<String> values = new ArrayList<>(label.getValues().size());
       for (LabelValue value : label.getValues()) {
         values.add(value.format().trim());
diff --git a/java/com/google/gerrit/server/project/ProjectCreator.java b/java/com/google/gerrit/server/project/ProjectCreator.java
index cc10f27..6ffbdef 100644
--- a/java/com/google/gerrit/server/project/ProjectCreator.java
+++ b/java/com/google/gerrit/server/project/ProjectCreator.java
@@ -17,6 +17,7 @@
 import static com.google.gerrit.server.project.ProjectCache.illegalState;
 
 import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.GroupDescription;
@@ -150,26 +151,32 @@
     try (MetaDataUpdate md = metaDataUpdateFactory.create(args.getProject())) {
       ProjectConfig config = projectConfigFactory.read(md);
 
-      Project newProject = config.getProject();
-      newProject.setDescription(args.projectDescription);
-      newProject.setSubmitType(
-          MoreObjects.firstNonNull(
-              args.submitType, repositoryCfg.getDefaultSubmitType(args.getProject())));
-      newProject.setBooleanConfig(
-          BooleanProjectConfig.USE_CONTRIBUTOR_AGREEMENTS, args.contributorAgreements);
-      newProject.setBooleanConfig(BooleanProjectConfig.USE_SIGNED_OFF_BY, args.signedOffBy);
-      newProject.setBooleanConfig(BooleanProjectConfig.USE_CONTENT_MERGE, args.contentMerge);
-      newProject.setBooleanConfig(
-          BooleanProjectConfig.CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET,
-          args.newChangeForAllNotInTarget);
-      newProject.setBooleanConfig(BooleanProjectConfig.REQUIRE_CHANGE_ID, args.changeIdRequired);
-      newProject.setBooleanConfig(BooleanProjectConfig.REJECT_EMPTY_COMMIT, args.rejectEmptyCommit);
-      newProject.setMaxObjectSizeLimit(args.maxObjectSizeLimit);
-      newProject.setBooleanConfig(BooleanProjectConfig.ENABLE_SIGNED_PUSH, args.enableSignedPush);
-      newProject.setBooleanConfig(BooleanProjectConfig.REQUIRE_SIGNED_PUSH, args.requireSignedPush);
-      if (args.newParent != null) {
-        newProject.setParentName(args.newParent);
-      }
+      config.updateProject(
+          newProject -> {
+            newProject.setDescription(Strings.nullToEmpty(args.projectDescription));
+            newProject.setSubmitType(
+                MoreObjects.firstNonNull(
+                    args.submitType, repositoryCfg.getDefaultSubmitType(args.getProject())));
+            newProject.setBooleanConfig(
+                BooleanProjectConfig.USE_CONTRIBUTOR_AGREEMENTS, args.contributorAgreements);
+            newProject.setBooleanConfig(BooleanProjectConfig.USE_SIGNED_OFF_BY, args.signedOffBy);
+            newProject.setBooleanConfig(BooleanProjectConfig.USE_CONTENT_MERGE, args.contentMerge);
+            newProject.setBooleanConfig(
+                BooleanProjectConfig.CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET,
+                args.newChangeForAllNotInTarget);
+            newProject.setBooleanConfig(
+                BooleanProjectConfig.REQUIRE_CHANGE_ID, args.changeIdRequired);
+            newProject.setBooleanConfig(
+                BooleanProjectConfig.REJECT_EMPTY_COMMIT, args.rejectEmptyCommit);
+            newProject.setMaxObjectSizeLimit(args.maxObjectSizeLimit);
+            newProject.setBooleanConfig(
+                BooleanProjectConfig.ENABLE_SIGNED_PUSH, args.enableSignedPush);
+            newProject.setBooleanConfig(
+                BooleanProjectConfig.REQUIRE_SIGNED_PUSH, args.requireSignedPush);
+            if (args.newParent != null) {
+              newProject.setParent(args.newParent);
+            }
+          });
 
       if (!args.ownerIds.isEmpty()) {
         AccessSection all = config.getAccessSection(AccessSection.ALL, true);
diff --git a/java/com/google/gerrit/server/project/ProjectState.java b/java/com/google/gerrit/server/project/ProjectState.java
index 1c7c7d6..6353103 100644
--- a/java/com/google/gerrit/server/project/ProjectState.java
+++ b/java/com/google/gerrit/server/project/ProjectState.java
@@ -402,7 +402,7 @@
       for (LabelType type : s.getConfig().getLabelSections().values()) {
         String lower = type.getName().toLowerCase();
         LabelType old = types.get(lower);
-        if (old == null || old.canOverride()) {
+        if (old == null || old.isCanOverride()) {
           types.put(lower, type);
         }
       }
@@ -501,7 +501,7 @@
 
   public SubmitType getSubmitType() {
     for (ProjectState s : tree()) {
-      SubmitType t = s.getProject().getConfiguredSubmitType();
+      SubmitType t = s.getProject().getSubmitType();
       if (t != SubmitType.INHERIT) {
         return t;
       }
diff --git a/java/com/google/gerrit/server/project/RefValidationHelper.java b/java/com/google/gerrit/server/project/RefValidationHelper.java
index 9b297f9..1912660 100644
--- a/java/com/google/gerrit/server/project/RefValidationHelper.java
+++ b/java/com/google/gerrit/server/project/RefValidationHelper.java
@@ -43,7 +43,7 @@
       throws ResourceConflictException {
     RefOperationValidators refValidators =
         refValidatorsFactory.create(
-            new Project(Project.nameKey(projectName)),
+            Project.builder(Project.nameKey(projectName)).build(),
             user,
             RefOperationValidators.getCommand(update, operationType));
     try {
diff --git a/java/com/google/gerrit/server/project/testing/TestLabels.java b/java/com/google/gerrit/server/project/testing/TestLabels.java
index 6c2ddde..2c0b23c 100644
--- a/java/com/google/gerrit/server/project/testing/TestLabels.java
+++ b/java/com/google/gerrit/server/project/testing/TestLabels.java
@@ -35,18 +35,23 @@
   }
 
   public static LabelType patchSetLock() {
-    LabelType label =
-        label("Patch-Set-Lock", value(1, "Patch Set Locked"), value(0, "Patch Set Unlocked"));
+    LabelType.Builder label =
+        labelBuilder(
+            "Patch-Set-Lock", value(1, "Patch Set Locked"), value(0, "Patch Set Unlocked"));
     label.setFunction(LabelFunction.PATCH_SET_LOCK);
-    return label;
+    return label.build();
   }
 
   public static LabelValue value(int value, String text) {
-    return new LabelValue((short) value, text);
+    return LabelValue.create((short) value, text);
   }
 
   public static LabelType label(String name, LabelValue... values) {
-    return new LabelType(name, Arrays.asList(values));
+    return labelBuilder(name, values).build();
+  }
+
+  public static LabelType.Builder labelBuilder(String name, LabelValue... values) {
+    return LabelType.builder(name, Arrays.asList(values));
   }
 
   private TestLabels() {}
diff --git a/java/com/google/gerrit/server/restapi/change/PostReview.java b/java/com/google/gerrit/server/restapi/change/PostReview.java
index 6edbdb0..85079e2 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReview.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReview.java
@@ -1319,7 +1319,7 @@
       for (PatchSetApproval psa : del) {
         LabelType lt = requireNonNull(labelTypes.byLabel(psa.label()));
         String normName = lt.getName();
-        if (!lt.allowPostSubmit()) {
+        if (!lt.isAllowPostSubmit()) {
           disallowed.add(normName);
         }
         Short prev = previous.get(normName);
@@ -1331,7 +1331,7 @@
       for (PatchSetApproval psa : ups) {
         LabelType lt = requireNonNull(labelTypes.byLabel(psa.label()));
         String normName = lt.getName();
-        if (!lt.allowPostSubmit()) {
+        if (!lt.isAllowPostSubmit()) {
           disallowed.add(normName);
         }
         Short prev = previous.get(normName);
diff --git a/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java b/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
index 5deace9..783b39b 100644
--- a/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
+++ b/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
@@ -77,8 +77,7 @@
     this.defaultSubmitType.value = projectState.getSubmitType();
     this.defaultSubmitType.configuredValue =
         MoreObjects.firstNonNull(
-            projectState.getConfig().getProject().getConfiguredSubmitType(),
-            Project.DEFAULT_SUBMIT_TYPE);
+            projectState.getConfig().getProject().getSubmitType(), Project.DEFAULT_SUBMIT_TYPE);
     ProjectState parent =
         projectState.isAllProjects() ? projectState : projectState.parents().get(0);
     this.defaultSubmitType.inheritedValue = parent.getSubmitType();
diff --git a/java/com/google/gerrit/server/restapi/project/CreateLabel.java b/java/com/google/gerrit/server/restapi/project/CreateLabel.java
index a85ad39..1c19eb0 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateLabel.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateLabel.java
@@ -134,15 +134,15 @@
       throw new BadRequestException("values are required");
     }
 
-    List<LabelValue> values = LabelDefinitionInputParser.parseValues(input.values);
-
-    LabelType labelType;
     try {
-      labelType = new LabelType(label, values);
+      LabelType.checkName(label);
     } catch (IllegalArgumentException e) {
       throw new BadRequestException("invalid name: " + label, e);
     }
 
+    List<LabelValue> values = LabelDefinitionInputParser.parseValues(input.values);
+    LabelType.Builder labelType = LabelType.builder(LabelType.checkName(label), values);
+
     if (input.function != null && !input.function.trim().isEmpty()) {
       labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
     } else {
@@ -203,8 +203,9 @@
       labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
     }
 
-    config.getLabelSections().put(labelType.getName(), labelType);
+    LabelType lt = labelType.build();
+    config.upsertLabelType(lt);
 
-    return labelType;
+    return lt;
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/project/GetConfig.java b/java/com/google/gerrit/server/restapi/project/GetConfig.java
index ce45e7d..ad66587 100644
--- a/java/com/google/gerrit/server/restapi/project/GetConfig.java
+++ b/java/com/google/gerrit/server/restapi/project/GetConfig.java
@@ -24,6 +24,9 @@
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.config.ProjectConfigEntry;
 import com.google.gerrit.server.extensions.webui.UiActions;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
 import com.google.gerrit.server.project.ProjectResource;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -34,6 +37,7 @@
   private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
   private final PluginConfigFactory cfgFactory;
   private final AllProjectsName allProjects;
+  private final PermissionBackend permissionBackend;
   private final UiActions uiActions;
   private final DynamicMap<RestView<ProjectResource>> views;
 
@@ -43,24 +47,31 @@
       DynamicMap<ProjectConfigEntry> pluginConfigEntries,
       PluginConfigFactory cfgFactory,
       AllProjectsName allProjects,
+      PermissionBackend permissionBackend,
       UiActions uiActions,
       DynamicMap<RestView<ProjectResource>> views) {
     this.serverEnableSignedPush = serverEnableSignedPush;
     this.pluginConfigEntries = pluginConfigEntries;
     this.allProjects = allProjects;
     this.cfgFactory = cfgFactory;
+    this.permissionBackend = permissionBackend;
     this.uiActions = uiActions;
     this.views = views;
   }
 
   @Override
-  public Response<ConfigInfo> apply(ProjectResource resource) {
+  public Response<ConfigInfo> apply(ProjectResource resource) throws PermissionBackendException {
+    boolean readConfigAllowed =
+        permissionBackend
+            .currentUser()
+            .project(resource.getNameKey())
+            .test(ProjectPermission.READ_CONFIG);
     return Response.ok(
         new ConfigInfoImpl(
             serverEnableSignedPush,
             resource.getProjectState(),
             resource.getUser(),
-            pluginConfigEntries,
+            readConfigAllowed ? pluginConfigEntries : DynamicMap.emptyMap(),
             cfgFactory,
             allProjects,
             uiActions,
diff --git a/java/com/google/gerrit/server/restapi/project/LabelDefinitionInputParser.java b/java/com/google/gerrit/server/restapi/project/LabelDefinitionInputParser.java
index 1e288f4..ccc216d 100644
--- a/java/com/google/gerrit/server/restapi/project/LabelDefinitionInputParser.java
+++ b/java/com/google/gerrit/server/restapi/project/LabelDefinitionInputParser.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.restapi.project;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.primitives.Shorts;
 import com.google.gerrit.common.data.LabelFunction;
 import com.google.gerrit.common.data.LabelType;
@@ -56,21 +57,22 @@
       if (valueDescription.isEmpty()) {
         throw new BadRequestException("description for value '" + e.getKey() + "' cannot be empty");
       }
-      valueList.add(new LabelValue(value, valueDescription));
+      valueList.add(LabelValue.create(value, valueDescription));
     }
     return valueList;
   }
 
-  public static short parseDefaultValue(LabelType labelType, short defaultValue)
+  public static short parseDefaultValue(LabelType.Builder labelType, short defaultValue)
       throws BadRequestException {
-    if (labelType.getValue(defaultValue) == null) {
+    if (!labelType.getValues().stream().anyMatch(v -> v.getValue() == defaultValue)) {
       throw new BadRequestException("invalid default value: " + defaultValue);
     }
     return defaultValue;
   }
 
-  public static List<String> parseBranches(List<String> branches) throws BadRequestException {
-    List<String> validBranches = new ArrayList<>();
+  public static ImmutableList<String> parseBranches(List<String> branches)
+      throws BadRequestException {
+    ImmutableList.Builder<String> validBranches = ImmutableList.builder();
     for (String branch : branches) {
       String newBranch = branch.trim();
       if (newBranch.isEmpty()) {
@@ -86,7 +88,7 @@
       }
       validBranches.add(newBranch);
     }
-    return validBranches;
+    return validBranches.build();
   }
 
   private LabelDefinitionInputParser() {}
diff --git a/java/com/google/gerrit/server/restapi/project/PutConfig.java b/java/com/google/gerrit/server/restapi/project/PutConfig.java
index 9f9433b..658f57e 100644
--- a/java/com/google/gerrit/server/restapi/project/PutConfig.java
+++ b/java/com/google/gerrit/server/restapi/project/PutConfig.java
@@ -134,28 +134,25 @@
 
     try (MetaDataUpdate md = metaDataUpdateFactory.get().create(projectName)) {
       ProjectConfig projectConfig = projectConfigFactory.read(md);
-      Project p = projectConfig.getProject();
-
-      p.setDescription(Strings.emptyToNull(input.description));
-
-      for (BooleanProjectConfig cfg : BooleanProjectConfig.values()) {
-        InheritableBoolean val = BooleanProjectConfigTransformations.get(cfg, input);
-        if (val != null) {
-          p.setBooleanConfig(cfg, val);
-        }
-      }
-
-      if (input.maxObjectSizeLimit != null) {
-        p.setMaxObjectSizeLimit(input.maxObjectSizeLimit);
-      }
-
-      if (input.submitType != null) {
-        p.setSubmitType(input.submitType);
-      }
-
-      if (input.state != null) {
-        p.setState(input.state);
-      }
+      projectConfig.updateProject(
+          p -> {
+            p.setDescription(Strings.emptyToNull(input.description));
+            for (BooleanProjectConfig cfg : BooleanProjectConfig.values()) {
+              InheritableBoolean val = BooleanProjectConfigTransformations.get(cfg, input);
+              if (val != null) {
+                p.setBooleanConfig(cfg, val);
+              }
+            }
+            if (input.maxObjectSizeLimit != null) {
+              p.setMaxObjectSizeLimit(input.maxObjectSizeLimit);
+            }
+            if (input.submitType != null) {
+              p.setSubmitType(input.submitType);
+            }
+            if (input.state != null) {
+              p.setState(input.state);
+            }
+          });
 
       if (input.pluginConfigValues != null) {
         setPluginConfigValues(projectState, projectConfig, input.pluginConfigValues);
@@ -169,7 +166,7 @@
       try {
         projectConfig.commit(md);
         projectCache.evict(projectConfig.getProject());
-        md.getRepository().setGitwebDescription(p.getDescription());
+        md.getRepository().setGitwebDescription(projectConfig.getProject().getDescription());
       } catch (IOException e) {
         if (e.getCause() instanceof ConfigInvalidException) {
           throw new ResourceConflictException(
diff --git a/java/com/google/gerrit/server/restapi/project/PutDescription.java b/java/com/google/gerrit/server/restapi/project/PutDescription.java
index a0b9feb..a65c626 100644
--- a/java/com/google/gerrit/server/restapi/project/PutDescription.java
+++ b/java/com/google/gerrit/server/restapi/project/PutDescription.java
@@ -16,7 +16,6 @@
 
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Strings;
-import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.api.projects.DescriptionInput;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -73,8 +72,8 @@
 
     try (MetaDataUpdate md = updateFactory.get().create(resource.getNameKey())) {
       ProjectConfig config = projectConfigFactory.read(md);
-      Project project = config.getProject();
-      project.setDescription(Strings.emptyToNull(input.description));
+      String desc = input.description;
+      config.updateProject(p -> p.setDescription(Strings.emptyToNull(desc)));
 
       String msg =
           MoreObjects.firstNonNull(
@@ -86,11 +85,11 @@
       md.setMessage(msg);
       config.commit(md);
       cache.evict(resource.getProjectState().getProject());
-      md.getRepository().setGitwebDescription(project.getDescription());
+      md.getRepository().setGitwebDescription(config.getProject().getDescription());
 
-      return Strings.isNullOrEmpty(project.getDescription())
+      return Strings.isNullOrEmpty(config.getProject().getDescription())
           ? Response.none()
-          : Response.ok(project.getDescription());
+          : Response.ok(config.getProject().getDescription());
     } catch (RepositoryNotFoundException notFound) {
       throw new ResourceNotFoundException(resource.getName(), notFound);
     } catch (ConfigInvalidException e) {
diff --git a/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java b/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java
index 5d5e779..390dea9 100644
--- a/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java
+++ b/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java
@@ -243,7 +243,7 @@
       } catch (UnprocessableEntityException e) {
         throw new ResourceConflictException(e.getMessage(), e);
       }
-      config.getProject().setParentName(newParentProjectName);
+      config.updateProject(p -> p.setParent(newParentProjectName));
     }
   }
 
diff --git a/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java b/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java
index 9920be0..5aef76a 100644
--- a/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java
+++ b/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java
@@ -16,7 +16,6 @@
 
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Strings;
-import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.api.projects.DashboardInfo;
 import com.google.gerrit.extensions.api.projects.SetDashboardInput;
 import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -97,11 +96,11 @@
 
     try (MetaDataUpdate md = updateFactory.create(rsrc.getProjectState().getNameKey())) {
       ProjectConfig config = projectConfigFactory.read(md);
-      Project project = config.getProject();
+      String id = input.id;
       if (inherited) {
-        project.setDefaultDashboard(input.id);
+        config.updateProject(p -> p.setDefaultDashboard(id));
       } else {
-        project.setLocalDefaultDashboard(input.id);
+        config.updateProject(p -> p.setLocalDefaultDashboard(id));
       }
 
       String msg =
diff --git a/java/com/google/gerrit/server/restapi/project/SetLabel.java b/java/com/google/gerrit/server/restapi/project/SetLabel.java
index 0a35865..ade274a 100644
--- a/java/com/google/gerrit/server/restapi/project/SetLabel.java
+++ b/java/com/google/gerrit/server/restapi/project/SetLabel.java
@@ -88,6 +88,9 @@
         } else {
           md.setMessage("Update label");
         }
+        String newName = Strings.nullToEmpty(input.name).trim();
+        labelType =
+            config.getLabelSections().get(newName.isEmpty() ? labelType.getName() : newName);
 
         config.commit(md);
         projectCache.evict(rsrc.getProject().getProjectState().getProject());
@@ -109,8 +112,7 @@
   public boolean updateLabel(ProjectConfig config, LabelType labelType, LabelDefinitionInput input)
       throws BadRequestException, ResourceConflictException {
     boolean dirty = false;
-
-    config.getLabelSections().remove(labelType.getName());
+    LabelType.Builder labelTypeBuilder = labelType.toBuilder();
 
     if (input.name != null) {
       String newName = input.name.trim();
@@ -130,10 +132,12 @@
         }
 
         try {
-          labelType.setName(newName);
+          LabelType.checkName(newName);
         } catch (IllegalArgumentException e) {
           throw new BadRequestException("invalid name: " + input.name, e);
         }
+
+        labelTypeBuilder.setName(newName);
         dirty = true;
       }
     }
@@ -142,7 +146,7 @@
       if (input.function.trim().isEmpty()) {
         throw new BadRequestException("function cannot be empty");
       }
-      labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
+      labelTypeBuilder.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
       dirty = true;
     }
 
@@ -150,77 +154,79 @@
       if (input.values.isEmpty()) {
         throw new BadRequestException("values cannot be empty");
       }
-      labelType.setValues(LabelDefinitionInputParser.parseValues(input.values));
+      labelTypeBuilder.setValues(LabelDefinitionInputParser.parseValues(input.values));
       dirty = true;
     }
 
     if (input.defaultValue != null) {
-      labelType.setDefaultValue(
-          LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
+      labelTypeBuilder.setDefaultValue(
+          LabelDefinitionInputParser.parseDefaultValue(labelTypeBuilder, input.defaultValue));
       dirty = true;
     }
 
     if (input.branches != null) {
-      labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
+      labelTypeBuilder.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
       dirty = true;
     }
 
     if (input.canOverride != null) {
-      labelType.setCanOverride(input.canOverride);
+      labelTypeBuilder.setCanOverride(input.canOverride);
       dirty = true;
     }
 
     if (input.copyAnyScore != null) {
-      labelType.setCopyAnyScore(input.copyAnyScore);
+      labelTypeBuilder.setCopyAnyScore(input.copyAnyScore);
       dirty = true;
     }
 
     if (input.copyMinScore != null) {
-      labelType.setCopyMinScore(input.copyMinScore);
+      labelTypeBuilder.setCopyMinScore(input.copyMinScore);
       dirty = true;
     }
 
     if (input.copyMaxScore != null) {
-      labelType.setCopyMaxScore(input.copyMaxScore);
+      labelTypeBuilder.setCopyMaxScore(input.copyMaxScore);
       dirty = true;
     }
 
     if (input.copyAllScoresIfNoChange != null) {
-      labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
+      labelTypeBuilder.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
+      dirty = true;
     }
 
     if (input.copyAllScoresIfNoCodeChange != null) {
-      labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
+      labelTypeBuilder.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
       dirty = true;
     }
 
     if (input.copyAllScoresOnTrivialRebase != null) {
-      labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
+      labelTypeBuilder.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
       dirty = true;
     }
 
     if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
-      labelType.setCopyAllScoresOnMergeFirstParentUpdate(
+      labelTypeBuilder.setCopyAllScoresOnMergeFirstParentUpdate(
           input.copyAllScoresOnMergeFirstParentUpdate);
       dirty = true;
     }
 
     if (input.copyValues != null) {
-      labelType.setCopyValues(input.copyValues);
+      labelTypeBuilder.setCopyValues(input.copyValues);
       dirty = true;
     }
 
     if (input.allowPostSubmit != null) {
-      labelType.setAllowPostSubmit(input.allowPostSubmit);
+      labelTypeBuilder.setAllowPostSubmit(input.allowPostSubmit);
       dirty = true;
     }
 
     if (input.ignoreSelfApproval != null) {
-      labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
+      labelTypeBuilder.setIgnoreSelfApproval(input.ignoreSelfApproval);
       dirty = true;
     }
 
-    config.getLabelSections().put(labelType.getName(), labelType);
+    config.getLabelSections().remove(labelType.getName());
+    config.upsertLabelType(labelTypeBuilder.build());
 
     return dirty;
   }
diff --git a/java/com/google/gerrit/server/restapi/project/SetParent.java b/java/com/google/gerrit/server/restapi/project/SetParent.java
index 42790aa..91c29f5 100644
--- a/java/com/google/gerrit/server/restapi/project/SetParent.java
+++ b/java/com/google/gerrit/server/restapi/project/SetParent.java
@@ -103,8 +103,7 @@
     validateParentUpdate(rsrc.getProjectState().getNameKey(), user, parentName, checkIfAdmin);
     try (MetaDataUpdate md = updateFactory.get().create(rsrc.getNameKey())) {
       ProjectConfig config = projectConfigFactory.read(md);
-      Project project = config.getProject();
-      project.setParentName(parentName);
+      config.updateProject(p -> p.setParent(parentName));
 
       String msg = Strings.emptyToNull(input.commitMessage);
       if (msg == null) {
@@ -117,7 +116,7 @@
       config.commit(md);
       cache.evict(rsrc.getProjectState().getProject());
 
-      Project.NameKey parent = project.getParent(allProjects);
+      Project.NameKey parent = config.getProject().getParent(allProjects);
       requireNonNull(parent);
       return parent.get();
     } catch (RepositoryNotFoundException notFound) {
diff --git a/java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java b/java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java
index 54915fb..132747d 100644
--- a/java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java
+++ b/java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java
@@ -66,7 +66,8 @@
       return ruleError(E_UNABLE_TO_FETCH_LABELS);
     }
 
-    boolean shouldIgnoreSelfApproval = labelTypes.stream().anyMatch(LabelType::ignoreSelfApproval);
+    boolean shouldIgnoreSelfApproval =
+        labelTypes.stream().anyMatch(LabelType::isIgnoreSelfApproval);
     if (!shouldIgnoreSelfApproval) {
       // Shortcut to avoid further processing if no label should ignore uploader approvals
       return Optional.empty();
@@ -86,7 +87,7 @@
     submitRecord.requirements = new ArrayList<>();
 
     for (LabelType t : labelTypes) {
-      if (!t.ignoreSelfApproval()) {
+      if (!t.isIgnoreSelfApproval()) {
         // The default rules are enough in this case.
         continue;
       }
diff --git a/java/com/google/gerrit/server/schema/AllProjectsCreator.java b/java/com/google/gerrit/server/schema/AllProjectsCreator.java
index cfa5825..018a96a 100644
--- a/java/com/google/gerrit/server/schema/AllProjectsCreator.java
+++ b/java/com/google/gerrit/server/schema/AllProjectsCreator.java
@@ -30,7 +30,6 @@
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.PermissionRule.Action;
-import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.config.AllProjectsName;
@@ -116,19 +115,16 @@
 
       // init basic project configs.
       ProjectConfig config = projectConfigFactory.read(md);
-      Project p = config.getProject();
-      p.setDescription(
-          input.projectDescription().orElse("Access inherited by all other projects."));
-
-      // init boolean project configs.
-      input.booleanProjectConfigs().forEach(p::setBooleanConfig);
+      config.updateProject(
+          p -> {
+            p.setDescription(
+                input.projectDescription().orElse("Access inherited by all other projects."));
+            // init boolean project configs.
+            input.booleanProjectConfigs().forEach(p::setBooleanConfig);
+          });
 
       // init labels.
-      input
-          .codeReviewLabel()
-          .ifPresent(
-              codeReviewLabel ->
-                  config.getLabelSections().put(codeReviewLabel.getName(), codeReviewLabel));
+      input.codeReviewLabel().ifPresent(codeReviewLabel -> config.upsertLabelType(codeReviewLabel));
 
       if (input.initDefaultAcls()) {
         // init access sections.
diff --git a/java/com/google/gerrit/server/schema/AllProjectsInput.java b/java/com/google/gerrit/server/schema/AllProjectsInput.java
index 6e11a5d..c91695f 100644
--- a/java/com/google/gerrit/server/schema/AllProjectsInput.java
+++ b/java/com/google/gerrit/server/schema/AllProjectsInput.java
@@ -46,18 +46,17 @@
 
   @UsedAt(UsedAt.Project.GOOGLE)
   public static LabelType getDefaultCodeReviewLabel() {
-    LabelType type =
-        new LabelType(
+    return LabelType.builder(
             "Code-Review",
             ImmutableList.of(
-                new LabelValue((short) 2, "Looks good to me, approved"),
-                new LabelValue((short) 1, "Looks good to me, but someone else must approve"),
-                new LabelValue((short) 0, "No score"),
-                new LabelValue((short) -1, "I would prefer this is not merged as is"),
-                new LabelValue((short) -2, "This shall not be merged")));
-    type.setCopyMinScore(true);
-    type.setCopyAllScoresOnTrivialRebase(true);
-    return type;
+                LabelValue.create((short) 2, "Looks good to me, approved"),
+                LabelValue.create((short) 1, "Looks good to me, but someone else must approve"),
+                LabelValue.create((short) 0, "No score"),
+                LabelValue.create((short) -1, "I would prefer this is not merged as is"),
+                LabelValue.create((short) -2, "This shall not be merged")))
+        .setCopyMinScore(true)
+        .setCopyAllScoresOnTrivialRebase(true)
+        .build();
   }
 
   /** The administrator group which gets default permissions granted. */
diff --git a/java/com/google/gerrit/server/schema/AllUsersCreator.java b/java/com/google/gerrit/server/schema/AllUsersCreator.java
index 4904028..10d7070 100644
--- a/java/com/google/gerrit/server/schema/AllUsersCreator.java
+++ b/java/com/google/gerrit/server/schema/AllUsersCreator.java
@@ -26,7 +26,6 @@
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.config.AllUsersName;
@@ -112,15 +111,14 @@
       md.setMessage("Initialized Gerrit Code Review " + Version.getVersion());
 
       ProjectConfig config = projectConfigFactory.read(md);
-      Project project = config.getProject();
-      project.setDescription("Individual user settings and preferences.");
+      config.updateProject(p -> p.setDescription("Individual user settings and preferences."));
 
       AccessSection users =
           config.getAccessSection(
               RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}", true);
 
       // Initialize "Code-Review" label.
-      config.getLabelSections().put(codeReviewLabel.getName(), codeReviewLabel);
+      config.upsertLabelType(codeReviewLabel);
 
       grant(config, users, Permission.READ, false, true, registered);
       grant(config, users, Permission.PUSH, false, true, registered);
diff --git a/java/com/google/gerrit/server/submit/SubmoduleOp.java b/java/com/google/gerrit/server/submit/SubmoduleOp.java
index 5adda2c..a1ed373 100644
--- a/java/com/google/gerrit/server/submit/SubmoduleOp.java
+++ b/java/com/google/gerrit/server/submit/SubmoduleOp.java
@@ -30,7 +30,6 @@
 import com.google.gerrit.server.config.VerboseSuperprojectUpdate;
 import com.google.gerrit.server.git.CodeReviewCommit;
 import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.submit.MergeOpRepoManager.OpenRepo;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.BatchUpdateListener;
@@ -89,27 +88,31 @@
 
   @Singleton
   public static class Factory {
-    private final GitModules.Factory gitmodulesFactory;
+    private final SubscriptionGraph.Factory subscriptionGraphFactory;
     private final Provider<PersonIdent> serverIdent;
     private final Config cfg;
-    private final ProjectCache projectCache;
 
     @Inject
     Factory(
-        GitModules.Factory gitmodulesFactory,
+        SubscriptionGraph.Factory subscriptionGraphFactory,
         @GerritPersonIdent Provider<PersonIdent> serverIdent,
-        @GerritServerConfig Config cfg,
-        ProjectCache projectCache) {
-      this.gitmodulesFactory = gitmodulesFactory;
+        @GerritServerConfig Config cfg) {
+      this.subscriptionGraphFactory = subscriptionGraphFactory;
       this.serverIdent = serverIdent;
       this.cfg = cfg;
-      this.projectCache = projectCache;
     }
 
     public SubmoduleOp create(Set<BranchNameKey> updatedBranches, MergeOpRepoManager orm)
         throws SubmoduleConflictException {
-      return new SubmoduleOp(
-          gitmodulesFactory, serverIdent.get(), cfg, projectCache, updatedBranches, orm);
+      SubscriptionGraph subscriptionGraph;
+      if (cfg.getBoolean("submodule", "enableSuperProjectSubscriptions", true)) {
+        subscriptionGraph = subscriptionGraphFactory.compute(updatedBranches, orm);
+      } else {
+        logger.atFine().log("Updating superprojects disabled");
+        subscriptionGraph =
+            SubscriptionGraph.createEmptyGraph(ImmutableSet.copyOf(updatedBranches));
+      }
+      return new SubmoduleOp(serverIdent.get(), cfg, orm, subscriptionGraph);
     }
   }
 
@@ -118,19 +121,15 @@
   private final long maxCombinedCommitMessageSize;
   private final long maxCommitMessages;
   private final MergeOpRepoManager orm;
-  private final SubscriptionGraph.Factory subscriptionGraphFactory;
   private final SubscriptionGraph subscriptionGraph;
 
   private final BranchTips branchTips = new BranchTips();
 
   private SubmoduleOp(
-      GitModules.Factory gitmodulesFactory,
       PersonIdent myIdent,
       Config cfg,
-      ProjectCache projectCache,
-      Set<BranchNameKey> updatedBranches,
-      MergeOpRepoManager orm)
-      throws SubmoduleConflictException {
+      MergeOpRepoManager orm,
+      SubscriptionGraph subscriptionGraph) {
     this.myIdent = myIdent;
     this.verboseSuperProject =
         cfg.getEnum("submodule", null, "verboseSuperprojectUpdate", VerboseSuperprojectUpdate.TRUE);
@@ -138,15 +137,7 @@
         cfg.getLong("submodule", "maxCombinedCommitMessageSize", 256 << 10);
     this.maxCommitMessages = cfg.getLong("submodule", "maxCommitMessages", 1000);
     this.orm = orm;
-    this.subscriptionGraphFactory =
-        new SubscriptionGraph.DefaultFactory(gitmodulesFactory, projectCache, orm);
-    if (cfg.getBoolean("submodule", "enableSuperProjectSubscriptions", true)) {
-      this.subscriptionGraph = subscriptionGraphFactory.compute(updatedBranches);
-    } else {
-      logger.atFine().log("Updating superprojects disabled");
-      this.subscriptionGraph =
-          SubscriptionGraph.createEmptyGraph(ImmutableSet.copyOf(updatedBranches));
-    }
+    this.subscriptionGraph = subscriptionGraph;
   }
 
   @UsedAt(UsedAt.Project.PLUGIN_DELETE_PROJECT)
diff --git a/java/com/google/gerrit/server/submit/SubscriptionGraph.java b/java/com/google/gerrit/server/submit/SubscriptionGraph.java
index eac6d2c..f037261 100644
--- a/java/com/google/gerrit/server/submit/SubscriptionGraph.java
+++ b/java/com/google/gerrit/server/submit/SubscriptionGraph.java
@@ -31,6 +31,8 @@
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.submit.MergeOpRepoManager.OpenRepo;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
 import java.io.IOException;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -102,12 +104,12 @@
   }
 
   /** Get branches updated as part of the enclosing submit or push batch. */
-  ImmutableSet<BranchNameKey> getUpdatedBranches() {
+  public ImmutableSet<BranchNameKey> getUpdatedBranches() {
     return updatedBranches;
   }
 
   /** Get all superprojects affected. */
-  ImmutableSet<Project.NameKey> getAffectedSuperProjects() {
+  public ImmutableSet<Project.NameKey> getAffectedSuperProjects() {
     return branchesByProject.keySet();
   }
 
@@ -120,7 +122,7 @@
    * Returns all branches within the superproject {@code project} which have submodule
    * subscriptions.
    */
-  ImmutableSet<BranchNameKey> getAffectedSuperBranches(Project.NameKey project) {
+  public ImmutableSet<BranchNameKey> getAffectedSuperBranches(Project.NameKey project) {
     return branchesByProject.get(project);
   }
 
@@ -130,71 +132,78 @@
    *
    * @see SubscriptionGraph#sortedBranches
    */
-  ImmutableSet<BranchNameKey> getSortedSuperprojectAndSubmoduleBranches() {
+  public ImmutableSet<BranchNameKey> getSortedSuperprojectAndSubmoduleBranches() {
     return sortedBranches;
   }
 
   /** Check if a {@code branch} is a submodule of a superproject. */
-  boolean hasSuperproject(BranchNameKey branch) {
+  public boolean hasSuperproject(BranchNameKey branch) {
     return subscribedBranches.contains(branch);
   }
 
   /** See if a {@code branch} is a superproject branch affected. */
-  boolean hasSubscription(BranchNameKey branch) {
+  public boolean hasSubscription(BranchNameKey branch) {
     return targets.containsKey(branch);
   }
 
   /** Get all related {@code SubmoduleSubscription}s whose super branch is {@code branch}. */
-  ImmutableSet<SubmoduleSubscription> getSubscriptions(BranchNameKey branch) {
+  public ImmutableSet<SubmoduleSubscription> getSubscriptions(BranchNameKey branch) {
     return targets.get(branch);
   }
 
   public interface Factory {
-    SubscriptionGraph compute(Set<BranchNameKey> updatedBranches) throws SubmoduleConflictException;
+    SubscriptionGraph compute(Set<BranchNameKey> updatedBranches, MergeOpRepoManager orm)
+        throws SubmoduleConflictException;
+  }
+
+  public static class Module extends AbstractModule {
+    @Override
+    protected void configure() {
+      bind(Factory.class).to(DefaultFactory.class);
+    }
   }
 
   static class DefaultFactory implements Factory {
     private static final FluentLogger logger = FluentLogger.forEnclosingClass();
     private final ProjectCache projectCache;
     private final GitModules.Factory gitmodulesFactory;
-    private final Map<BranchNameKey, GitModules> branchGitModules;
-    private final MergeOpRepoManager orm;
 
-    // Fields required to the constructor of SubscriptionGraph.
-    /** All affected branches, including those in superprojects and submodules. */
-    private final Set<BranchNameKey> affectedBranches;
-
-    /** @see SubscriptionGraph#targets */
-    private final SetMultimap<BranchNameKey, SubmoduleSubscription> targets;
-
-    /** @see SubscriptionGraph#branchesByProject */
-    private final SetMultimap<Project.NameKey, BranchNameKey> branchesByProject;
-
-    /** @see SubscriptionGraph#subscribedBranches */
-    private final Set<BranchNameKey> subscribedBranches;
-
-    DefaultFactory(
-        GitModules.Factory gitmodulesFactory, ProjectCache projectCache, MergeOpRepoManager orm) {
+    @Inject
+    DefaultFactory(GitModules.Factory gitmodulesFactory, ProjectCache projectCache) {
       this.gitmodulesFactory = gitmodulesFactory;
       this.projectCache = projectCache;
-      this.orm = orm;
-      this.branchGitModules = new HashMap<>();
-
-      this.affectedBranches = new HashSet<>();
-      this.targets = MultimapBuilder.hashKeys().hashSetValues().build();
-      this.branchesByProject = MultimapBuilder.hashKeys().hashSetValues().build();
-      this.subscribedBranches = new HashSet<>();
     }
 
     @Override
-    public SubscriptionGraph compute(Set<BranchNameKey> updatedBranches)
+    public SubscriptionGraph compute(Set<BranchNameKey> updatedBranches, MergeOpRepoManager orm)
         throws SubmoduleConflictException {
+      Map<BranchNameKey, GitModules> branchGitModules = new HashMap<>();
+      // All affected branches, including those in superprojects and submodules.
+      Set<BranchNameKey> affectedBranches = new HashSet<>();
+
+      // See SubscriptionGraph#targets.
+      SetMultimap<BranchNameKey, SubmoduleSubscription> targets =
+          MultimapBuilder.hashKeys().hashSetValues().build();
+
+      // See SubscriptionGraph#branchesByProject.
+      SetMultimap<Project.NameKey, BranchNameKey> branchesByProject =
+          MultimapBuilder.hashKeys().hashSetValues().build();
+
+      // See SubscriptionGraph#subscribedBranches.
+      Set<BranchNameKey> subscribedBranches = new HashSet<>();
+
+      Set<BranchNameKey> sortedBranches =
+          calculateSubscriptionMaps(
+              updatedBranches,
+              affectedBranches,
+              targets,
+              branchesByProject,
+              subscribedBranches,
+              branchGitModules,
+              orm);
+
       return new SubscriptionGraph(
-          updatedBranches,
-          targets,
-          branchesByProject,
-          subscribedBranches,
-          calculateSubscriptionMaps(updatedBranches));
+          updatedBranches, targets, branchesByProject, subscribedBranches, sortedBranches);
     }
 
     /**
@@ -203,15 +212,22 @@
      * <p>In addition to the return value, the following fields are populated as a side effect:
      *
      * <ul>
-     *   <li>{@link #affectedBranches}
-     *   <li>{@link #targets}
-     *   <li>{@link #branchesByProject}
-     *   <li>{@link #subscribedBranches}
+     *   <li>{@code affectedBranches}
+     *   <li>{@code targets}
+     *   <li>{@code branchesByProject}
+     *   <li>{@code subscribedBranches}
      * </ul>
      *
      * @return the ordered set to be stored in {@link #sortedBranches}.
      */
-    private Set<BranchNameKey> calculateSubscriptionMaps(Set<BranchNameKey> updatedBranches)
+    private Set<BranchNameKey> calculateSubscriptionMaps(
+        Set<BranchNameKey> updatedBranches,
+        Set<BranchNameKey> affectedBranches,
+        SetMultimap<BranchNameKey, SubmoduleSubscription> targets,
+        SetMultimap<Project.NameKey, BranchNameKey> branchesByProject,
+        Set<BranchNameKey> subscribedBranches,
+        Map<BranchNameKey, GitModules> branchGitModules,
+        MergeOpRepoManager orm)
         throws SubmoduleConflictException {
       logger.atFine().log("Calculating superprojects - submodules map");
       LinkedHashSet<BranchNameKey> allVisited = new LinkedHashSet<>();
@@ -220,7 +236,16 @@
           continue;
         }
 
-        searchForSuperprojects(updatedBranch, new LinkedHashSet<>(), allVisited);
+        searchForSuperprojects(
+            updatedBranch,
+            new LinkedHashSet<>(),
+            allVisited,
+            affectedBranches,
+            targets,
+            branchesByProject,
+            subscribedBranches,
+            branchGitModules,
+            orm);
       }
 
       // Since the searchForSuperprojects will add all branches (related or
@@ -235,7 +260,13 @@
     private void searchForSuperprojects(
         BranchNameKey current,
         LinkedHashSet<BranchNameKey> currentVisited,
-        LinkedHashSet<BranchNameKey> allVisited)
+        LinkedHashSet<BranchNameKey> allVisited,
+        Set<BranchNameKey> affectedBranches,
+        SetMultimap<BranchNameKey, SubmoduleSubscription> targets,
+        SetMultimap<Project.NameKey, BranchNameKey> branchesByProject,
+        Set<BranchNameKey> subscribedBranches,
+        Map<BranchNameKey, GitModules> branchGitModules,
+        MergeOpRepoManager orm)
         throws SubmoduleConflictException {
       logger.atFine().log("Now processing %s", current);
 
@@ -252,10 +283,19 @@
       currentVisited.add(current);
       try {
         Collection<SubmoduleSubscription> subscriptions =
-            superProjectSubscriptionsForSubmoduleBranch(current);
+            superProjectSubscriptionsForSubmoduleBranch(current, branchGitModules, orm);
         for (SubmoduleSubscription sub : subscriptions) {
           BranchNameKey superBranch = sub.getSuperProject();
-          searchForSuperprojects(superBranch, currentVisited, allVisited);
+          searchForSuperprojects(
+              superBranch,
+              currentVisited,
+              allVisited,
+              affectedBranches,
+              targets,
+              branchesByProject,
+              subscribedBranches,
+              branchGitModules,
+              orm);
           targets.put(superBranch, sub);
           branchesByProject.put(superBranch.project(), superBranch);
           affectedBranches.add(superBranch);
@@ -269,8 +309,8 @@
       allVisited.add(current);
     }
 
-    private Collection<BranchNameKey> getDestinationBranches(BranchNameKey src, SubscribeSection s)
-        throws IOException {
+    private Collection<BranchNameKey> getDestinationBranches(
+        BranchNameKey src, SubscribeSection s, MergeOpRepoManager orm) throws IOException {
       OpenRepo or;
       try {
         or = orm.getRepo(s.project());
@@ -286,7 +326,10 @@
     }
 
     private Collection<SubmoduleSubscription> superProjectSubscriptionsForSubmoduleBranch(
-        BranchNameKey srcBranch) throws IOException {
+        BranchNameKey srcBranch,
+        Map<BranchNameKey, GitModules> branchGitModules,
+        MergeOpRepoManager orm)
+        throws IOException {
       logger.atFine().log("Calculating possible superprojects for %s", srcBranch);
       Collection<SubmoduleSubscription> ret = new ArrayList<>();
       Project.NameKey srcProject = srcBranch.project();
@@ -296,7 +339,7 @@
               .orElseThrow(illegalState(srcProject))
               .getSubscribeSections(srcBranch)) {
         logger.atFine().log("Checking subscribe section %s", s);
-        Collection<BranchNameKey> branches = getDestinationBranches(srcBranch, s);
+        Collection<BranchNameKey> branches = getDestinationBranches(srcBranch, s, orm);
         for (BranchNameKey targetBranch : branches) {
           Project.NameKey targetProject = targetBranch.project();
           try {
diff --git a/java/com/google/gerrit/testing/InMemoryModule.java b/java/com/google/gerrit/testing/InMemoryModule.java
index a682d33..6c9fbed 100644
--- a/java/com/google/gerrit/testing/InMemoryModule.java
+++ b/java/com/google/gerrit/testing/InMemoryModule.java
@@ -87,6 +87,7 @@
 import com.google.gerrit.server.securestore.SecureStore;
 import com.google.gerrit.server.ssh.NoSshKeyCache;
 import com.google.gerrit.server.submit.LocalMergeSuperSetComputation;
+import com.google.gerrit.server.submit.SubscriptionGraph;
 import com.google.gerrit.server.util.ReplicaUtil;
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
@@ -175,6 +176,7 @@
     install(new SearchingChangeCacheImpl.Module());
     factory(GarbageCollection.Factory.class);
     install(new AuditModule());
+    install(new SubscriptionGraph.Module());
 
     bindScope(RequestScoped.class, PerThreadRequestScope.REQUEST);
 
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java
index 11cc82f..c0a9da6 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java
@@ -67,7 +67,8 @@
   protected void setUseContributorAgreements(InheritableBoolean value) throws Exception {
     try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
       ProjectConfig config = projectConfigFactory.read(md);
-      config.getProject().setBooleanConfig(BooleanProjectConfig.USE_CONTRIBUTOR_AGREEMENTS, value);
+      config.updateProject(
+          p -> p.setBooleanConfig(BooleanProjectConfig.USE_CONTRIBUTOR_AGREEMENTS, value));
       config.commit(md);
       projectCache.evict(config.getProject());
     }
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 39a860e..42c09c7 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -2231,7 +2231,7 @@
     LabelType verified =
         label("Verified", value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().put(verified.getName(), verified);
+      u.getConfig().upsertLabelType(verified);
       u.save();
     }
     projectOperations
@@ -2518,7 +2518,7 @@
         label("Verified", value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
     String heads = "refs/heads/*";
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().put(verified.getName(), verified);
+      u.getConfig().upsertLabelType(verified);
       u.save();
     }
     projectOperations
@@ -2863,9 +2863,9 @@
     LabelType custom2 =
         label("Custom2", value(1, "Positive"), value(0, "No score"), value(-1, "Negative"));
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().put(verified.getName(), verified);
-      u.getConfig().getLabelSections().put(custom1.getName(), custom1);
-      u.getConfig().getLabelSections().put(custom2.getName(), custom2);
+      u.getConfig().upsertLabelType(verified);
+      u.getConfig().upsertLabelType(custom1);
+      u.getConfig().upsertLabelType(custom2);
       u.save();
     }
     projectOperations
@@ -3603,7 +3603,7 @@
     String heads = RefNames.REFS_HEADS + "*";
 
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().put(verified.getName(), verified);
+      u.getConfig().upsertLabelType(verified);
       u.save();
     }
     projectOperations
@@ -3671,7 +3671,7 @@
 
     // add new label and assert that it's returned for existing changes
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().put(verified.getName(), verified);
+      u.getConfig().upsertLabelType(verified);
       u.save();
     }
     projectOperations
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java b/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java
index 0ac7e20..b855e72 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java
@@ -398,7 +398,7 @@
         .review(ReviewInput.approve());
     gApi.changes().id(result.getChangeId()).revision(result.getCommit().name()).submit();
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getProject().setState(ProjectState.READ_ONLY);
+      u.getConfig().updateProject(p -> p.setState(ProjectState.READ_ONLY));
       u.save();
     }
 
@@ -481,7 +481,7 @@
 
     // revoke write permissions for the first repository.
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getProject().setState(ProjectState.READ_ONLY);
+      u.getConfig().updateProject(p -> p.setState(ProjectState.READ_ONLY));
       u.save();
     }
 
diff --git a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
index 923b66f..3d8a034 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
@@ -26,7 +26,7 @@
 import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION;
 import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.project.testing.TestLabels.label;
+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;
 
@@ -78,8 +78,8 @@
     try (ProjectConfigUpdate u = updateProject(project)) {
       // Overwrite "Code-Review" label that is inherited from All-Projects.
       // This way changes to the "Code Review" label don't affect other tests.
-      LabelType codeReview =
-          label(
+      LabelType.Builder codeReview =
+          labelBuilder(
               "Code-Review",
               value(2, "Looks good to me, approved"),
               value(1, "Looks good to me, but someone else must approve"),
@@ -87,12 +87,12 @@
               value(-1, "I would prefer that you didn't submit this"),
               value(-2, "Do not submit"));
       codeReview.setCopyAllScoresIfNoChange(false);
-      u.getConfig().getLabelSections().put(codeReview.getName(), codeReview);
+      u.getConfig().upsertLabelType(codeReview.build());
 
-      LabelType verified =
-          label("Verified", value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
+      LabelType.Builder verified =
+          labelBuilder("Verified", value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
       verified.setCopyAllScoresIfNoChange(false);
-      u.getConfig().getLabelSections().put(verified.getName(), verified);
+      u.getConfig().upsertLabelType(verified.build());
 
       u.save();
     }
@@ -121,7 +121,7 @@
   @Test
   public void stickyOnAnyScore() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().get("Code-Review").setCopyAnyScore(true);
+      u.getConfig().updateLabelType("Code-Review", b -> b.setCopyAnyScore(true));
       u.save();
     }
 
@@ -143,7 +143,7 @@
   @Test
   public void stickyOnMinScore() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().get("Code-Review").setCopyMinScore(true);
+      u.getConfig().updateLabelType("Code-Review", b -> b.setCopyMinScore(true));
       u.save();
     }
 
@@ -165,7 +165,7 @@
   @Test
   public void stickyOnMaxScore() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().get("Code-Review").setCopyMaxScore(true);
+      u.getConfig().updateLabelType("Code-Review", b -> b.setCopyMaxScore(true));
       u.save();
     }
 
@@ -190,9 +190,8 @@
 
     try (ProjectConfigUpdate u = updateProject(project)) {
       u.getConfig()
-          .getLabelSections()
-          .get("Code-Review")
-          .setCopyValues(ImmutableList.of((short) -1, (short) 1));
+          .updateLabelType(
+              "Code-Review", b -> b.setCopyValues(ImmutableList.of((short) -1, (short) 1)));
       u.save();
     }
 
@@ -216,7 +215,7 @@
   @Test
   public void stickyOnTrivialRebase() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().get("Code-Review").setCopyAllScoresOnTrivialRebase(true);
+      u.getConfig().updateLabelType("Code-Review", b -> b.setCopyAllScoresOnTrivialRebase(true));
       u.save();
     }
 
@@ -262,7 +261,7 @@
   @Test
   public void stickyOnNoCodeChange() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().get("Verified").setCopyAllScoresIfNoCodeChange(true);
+      u.getConfig().updateLabelType("Verified", b -> b.setCopyAllScoresIfNoCodeChange(true));
       u.save();
     }
 
@@ -287,9 +286,7 @@
   public void stickyOnMergeFirstParentUpdate() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
       u.getConfig()
-          .getLabelSections()
-          .get("Code-Review")
-          .setCopyAllScoresOnMergeFirstParentUpdate(true);
+          .updateLabelType("Code-Review", b -> b.setCopyAllScoresOnMergeFirstParentUpdate(true));
       u.save();
     }
 
@@ -313,7 +310,7 @@
   @Test
   public void notStickyWithCopyOnNoChangeWhenSecondParentIsUpdated() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().get("Code-Review").setCopyAllScoresIfNoChange(true);
+      u.getConfig().updateLabelType("Code-Review", b -> b.setCopyAllScoresIfNoChange(true));
       u.save();
     }
 
@@ -330,8 +327,8 @@
   @Test
   public void removedVotesNotSticky() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().get("Code-Review").setCopyAllScoresOnTrivialRebase(true);
-      u.getConfig().getLabelSections().get("Verified").setCopyAllScoresIfNoCodeChange(true);
+      u.getConfig().updateLabelType("Code-Review", b -> b.setCopyAllScoresOnTrivialRebase(true));
+      u.getConfig().updateLabelType("Verified", b -> b.setCopyAllScoresIfNoCodeChange(true));
       u.save();
     }
 
@@ -360,8 +357,8 @@
   @Test
   public void stickyAcrossMultiplePatchSets() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().get("Code-Review").setCopyMaxScore(true);
-      u.getConfig().getLabelSections().get("Verified").setCopyAllScoresIfNoCodeChange(true);
+      u.getConfig().updateLabelType("Code-Review", b -> b.setCopyMaxScore(true));
+      u.getConfig().updateLabelType("Verified", b -> b.setCopyAllScoresIfNoCodeChange(true));
       u.save();
     }
 
@@ -386,8 +383,8 @@
     // change kind against all prior patch sets. This is a regression that made Gerrit do expensive
     // work in O(num-patch-sets). This test ensures that we aren't regressing.
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().get("Code-Review").setCopyMaxScore(true);
-      u.getConfig().getLabelSections().get("Verified").setCopyAllScoresIfNoCodeChange(true);
+      u.getConfig().updateLabelType("Code-Review", b -> b.setCopyMaxScore(true));
+      u.getConfig().updateLabelType("Verified", b -> b.setCopyAllScoresIfNoCodeChange(true));
       u.save();
     }
 
@@ -418,8 +415,8 @@
   @Test
   public void copyMinMaxAcrossMultiplePatchSets() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().get("Code-Review").setCopyMaxScore(true);
-      u.getConfig().getLabelSections().get("Code-Review").setCopyMinScore(true);
+      u.getConfig().updateLabelType("Code-Review", b -> b.setCopyMaxScore(true));
+      u.getConfig().updateLabelType("Code-Review", b -> b.setCopyMinScore(true));
       u.save();
     }
 
@@ -459,7 +456,7 @@
   public void deleteStickyVote() throws Exception {
     String label = "Code-Review";
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().get(label).setCopyMaxScore(true);
+      u.getConfig().updateLabelType(label, b -> b.setCopyMaxScore(true));
       u.save();
     }
 
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
index cb22849..840d3e0 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -696,6 +696,31 @@
   }
 
   @Test
+  public void pluginConfigsReturnedWhenRefsMetaConfigReadable() throws Exception {
+    ProjectConfigEntry entry = new ProjectConfigEntry("enabled", "true");
+    try (Registration ignored =
+        extensionRegistry.newRegistration().add(entry, "test-config-entry")) {
+      // The admin can see refs/meta/config and hence has the READ_CONFIG permission.
+      requestScopeOperations.setApiUser(admin.id());
+      ConfigInfo configInfo = getConfig();
+      assertThat(configInfo.pluginConfig).isNotNull();
+      assertThat(configInfo.pluginConfig).isNotEmpty();
+    }
+  }
+
+  @Test
+  public void pluginConfigsNotReturnedWhenRefsMetaConfigNotReadable() throws Exception {
+    ProjectConfigEntry entry = new ProjectConfigEntry("enabled", "true");
+    try (Registration ignored =
+        extensionRegistry.newRegistration().add(entry, "test-config-entry")) {
+      // This user cannot see refs/meta/config and hence does not have the READ_CONFIG permission.
+      requestScopeOperations.setApiUser(user.id());
+      ConfigInfo configInfo = getConfig();
+      assertThat(configInfo.pluginConfig).isNull();
+    }
+  }
+
+  @Test
   public void noCommentlinksByDefault() throws Exception {
     assertThat(getConfig().commentlinks).isEmpty();
   }
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
index dad09f9..e45d95c 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
@@ -104,18 +104,18 @@
     Project.NameKey p1 = projectOperations.newProject().create();
     Project.NameKey p2 = projectOperations.newProject().create();
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getProject().setParentName(p1);
+      u.getConfig().updateProject(p -> p.setParent(p1));
       u.save();
     }
     assertThat(stalenessChecker.check(project).isStale()).isFalse();
 
-    updateProjectConfigWithoutIndexUpdate(p1, c -> c.getProject().setParentName(p2));
+    updateProjectConfigWithoutIndexUpdate(p1, c -> c.updateProject(p -> p.setParent(p2)));
     assertThat(stalenessChecker.check(project).isStale()).isTrue();
   }
 
   private void updateProjectConfigWithoutIndexUpdate(Project.NameKey project) throws Exception {
     updateProjectConfigWithoutIndexUpdate(
-        project, c -> c.getProject().setDescription("making it stale"));
+        project, c -> c.updateProject(p -> p.setDescription("making it stale")));
   }
 
   private void updateProjectConfigWithoutIndexUpdate(
diff --git a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index f0bb201..d361247 100644
--- a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -772,8 +772,9 @@
     String cr = "Code-Review";
     try (ProjectConfigUpdate u = updateProject(project)) {
       LabelType codeReview = TestLabels.codeReview();
-      codeReview.setCopyAllScoresIfNoCodeChange(true);
-      u.getConfig().getLabelSections().put(cr, codeReview);
+      u.getConfig().upsertLabelType(codeReview);
+      u.getConfig()
+          .updateLabelType(codeReview.getName(), lt -> lt.setCopyAllScoresIfNoCodeChange(true));
       u.save();
     }
 
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 7213a9f..b01b195 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -161,7 +161,7 @@
   public void setUpPatchSetLock() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
       patchSetLock = TestLabels.patchSetLock();
-      u.getConfig().getLabelSections().put(patchSetLock.getName(), patchSetLock);
+      u.getConfig().upsertLabelType(patchSetLock);
       u.save();
     }
     projectOperations
@@ -1200,7 +1200,7 @@
         label("Custom-Label", value(1, "Positive"), value(0, "No score"), value(-1, "Negative"));
     String heads = "refs/heads/*";
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().put(Q.getName(), Q);
+      u.getConfig().upsertLabelType(Q);
       u.save();
     }
     projectOperations
@@ -1686,8 +1686,10 @@
 
     try (ProjectConfigUpdate u = updateProject(project)) {
       u.getConfig()
-          .getProject()
-          .setBooleanConfig(BooleanProjectConfig.REQUIRE_CHANGE_ID, InheritableBoolean.FALSE);
+          .updateProject(
+              p ->
+                  p.setBooleanConfig(
+                      BooleanProjectConfig.REQUIRE_CHANGE_ID, InheritableBoolean.FALSE));
       u.save();
     }
 
@@ -1712,8 +1714,10 @@
 
     try (ProjectConfigUpdate u = updateProject(project)) {
       u.getConfig()
-          .getProject()
-          .setBooleanConfig(BooleanProjectConfig.REQUIRE_CHANGE_ID, InheritableBoolean.FALSE);
+          .updateProject(
+              p ->
+                  p.setBooleanConfig(
+                      BooleanProjectConfig.REQUIRE_CHANGE_ID, InheritableBoolean.FALSE));
       u.save();
     }
 
@@ -1863,9 +1867,8 @@
   @Test
   public void pushNewPatchsetOverridingStickyLabel() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType codeReview = TestLabels.codeReview();
-      codeReview.setCopyMaxScore(true);
-      u.getConfig().getLabelSections().put(codeReview.getName(), codeReview);
+      LabelType codeReview = TestLabels.codeReview().toBuilder().setCopyMaxScore(true).build();
+      u.getConfig().upsertLabelType(codeReview);
       u.save();
     }
 
diff --git a/javatests/com/google/gerrit/acceptance/git/ImplicitMergeCheckIT.java b/javatests/com/google/gerrit/acceptance/git/ImplicitMergeCheckIT.java
index b51263e..80cc508 100644
--- a/javatests/com/google/gerrit/acceptance/git/ImplicitMergeCheckIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/ImplicitMergeCheckIT.java
@@ -85,8 +85,10 @@
   private void setRejectImplicitMerges() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
       u.getConfig()
-          .getProject()
-          .setBooleanConfig(BooleanProjectConfig.REJECT_IMPLICIT_MERGES, InheritableBoolean.TRUE);
+          .updateProject(
+              p ->
+                  p.setBooleanConfig(
+                      BooleanProjectConfig.REJECT_IMPLICIT_MERGES, InheritableBoolean.TRUE));
       u.save();
     }
   }
diff --git a/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java b/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java
index f9c751f..98b93a8 100644
--- a/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java
@@ -175,7 +175,7 @@
   public void readOnlyProjectRejectedBeforeTestingPermissions() throws Exception {
     try (Repository repo = repoManager.openRepository(project)) {
       try (ProjectConfigUpdate u = updateProject(project)) {
-        u.getConfig().getProject().setState(ProjectState.READ_ONLY);
+        u.getConfig().updateProject(p -> p.setState(ProjectState.READ_ONLY));
         u.save();
       }
     }
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java b/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
index 840853c..00c7fb8 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
@@ -174,7 +174,7 @@
   public void voteOnBehalfOfLabelNotPermitted() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
       LabelType verified = TestLabels.verified();
-      u.getConfig().getLabelSections().put(verified.getName(), verified);
+      u.getConfig().upsertLabelType(verified);
       u.save();
     }
 
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
index 55eeaf4..1027938 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
@@ -277,7 +277,7 @@
     }
 
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getProject().setLocalDefaultDashboard(dashboardRef + ":overview");
+      u.getConfig().updateProject(p -> p.setLocalDefaultDashboard(dashboardRef + ":overview"));
       u.save();
     }
   }
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
index a8149ce..1532b33 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
@@ -240,6 +240,50 @@
     assertThat(attentionSet.reason()).isEqualTo("Change was submitted");
   }
 
+  /**
+   * There is currently a bug that adds the person who submitted the change as reviewer, which in
+   * turn adds them to the attention set. This test ensures this doesn't happen.
+   */
+  @Test
+  public void submitDoesNotAddReviewersToAttentionSet() throws Exception {
+    PushOneCommit.Result r = createChange("refs/heads/master", "file1", "content");
+
+    // Someone else approves, because if admin reviews, they will be added to the reviewers (and the
+    // bug won't be reproduced).
+    requestScopeOperations.setApiUser(accountCreator.admin2().id());
+    change(r).current().review(ReviewInput.approve().addUserToAttentionSet(user.email(), "reason"));
+
+    requestScopeOperations.setApiUser(admin.id());
+
+    change(r).attention(admin.email()).remove(new AttentionSetInput("remove"));
+    change(r).current().submit();
+
+    // Attention set updates that relate to the admin (the person who replied) are filtered out.
+    AttentionSetUpdate attentionSet =
+        Iterables.getOnlyElement(getAttentionSetUpdatesForUser(r, admin));
+
+    assertThat(attentionSet.account()).isEqualTo(admin.id());
+    assertThat(attentionSet.operation()).isEqualTo(AttentionSetUpdate.Operation.REMOVE);
+    assertThat(attentionSet.reason()).isEqualTo("remove");
+
+    change(r).addReviewer(user.email());
+  }
+
+  @Test
+  public void addedReviewersAreAddedToAttentionSetOnMergedChanges() throws Exception {
+    PushOneCommit.Result r = createChange();
+    change(r).current().review(ReviewInput.approve());
+    change(r).current().submit();
+
+    change(r).addReviewer(user.email());
+    AttentionSetUpdate attentionSet =
+        Iterables.getOnlyElement(getAttentionSetUpdatesForUser(r, user));
+
+    assertThat(attentionSet.account()).isEqualTo(user.id());
+    assertThat(attentionSet.operation()).isEqualTo(AttentionSetUpdate.Operation.ADD);
+    assertThat(attentionSet.reason()).isEqualTo("Reviewer was added");
+  }
+
   @Test
   public void reviewersAddedAndRemovedFromAttentionSet() throws Exception {
     PushOneCommit.Result r = createChange();
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java
index 37b1713..542085c 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java
@@ -245,7 +245,7 @@
 
     LabelType patchSetLock = TestLabels.patchSetLock();
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().put(patchSetLock.getName(), patchSetLock);
+      u.getConfig().upsertLabelType(patchSetLock);
       u.save();
     }
     projectOperations
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java b/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
index 874f07a..10fd65f 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
@@ -224,7 +224,7 @@
     Project project = projectCache.get(Project.nameKey(newProjectName)).get().getProject();
     assertProjectInfo(project, p);
     assertThat(project.getDescription()).isEqualTo(in.description);
-    assertThat(project.getConfiguredSubmitType()).isEqualTo(in.submitType);
+    assertThat(project.getSubmitType()).isEqualTo(in.submitType);
     assertThat(project.getBooleanConfig(BooleanProjectConfig.USE_CONTRIBUTOR_AGREEMENTS))
         .isEqualTo(in.useContributorAgreements);
     assertThat(project.getBooleanConfig(BooleanProjectConfig.USE_SIGNED_OFF_BY))
@@ -368,8 +368,11 @@
 
   @Test
   public void createProjectWithCreateProjectCapabilityAndParentNotVisible() throws Exception {
-    Project parent = projectCache.get(allProjects).get().getProject();
-    parent.setState(com.google.gerrit.extensions.client.ProjectState.HIDDEN);
+    try (ProjectConfigUpdate u = updateProject(allProjects)) {
+      u.getConfig()
+          .updateProject(p -> p.setState(com.google.gerrit.extensions.client.ProjectState.HIDDEN));
+      u.save();
+    }
     projectOperations
         .allProjectsForUpdate()
         .add(
@@ -383,7 +386,12 @@
       ProjectInfo p = gApi.projects().create(in).get();
       assertThat(p.name).isEqualTo(in.name);
     } finally {
-      parent.setState(com.google.gerrit.extensions.client.ProjectState.ACTIVE);
+      try (ProjectConfigUpdate u = updateProject(allProjects)) {
+        u.getConfig()
+            .updateProject(
+                p -> p.setState(com.google.gerrit.extensions.client.ProjectState.ACTIVE));
+        u.save();
+      }
       projectOperations
           .allProjectsForUpdate()
           .remove(
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/GetLabelIT.java b/javatests/com/google/gerrit/acceptance/rest/project/GetLabelIT.java
index 940fae5..3e35f04 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/GetLabelIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/GetLabelIT.java
@@ -22,7 +22,6 @@
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
 import com.google.gerrit.common.data.LabelFunction;
-import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.extensions.common.LabelDefinitionInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -75,9 +74,7 @@
 
     // set default value
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setDefaultValue((short) 1);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig().updateLabelType("foo", labelType -> labelType.setDefaultValue((short) 1));
       u.save();
     }
 
@@ -100,11 +97,14 @@
 
     // unset rules which are enabled by default
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setCanOverride(false);
-      labelType.setCopyAllScoresIfNoChange(false);
-      labelType.setAllowPostSubmit(false);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig()
+          .updateLabelType(
+              "foo",
+              labelType -> {
+                labelType.setCanOverride(false);
+                labelType.setCopyAllScoresIfNoChange(false);
+                labelType.setAllowPostSubmit(false);
+              });
       u.save();
     }
 
@@ -128,16 +128,19 @@
 
     // set rules which are not enabled by default
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setCopyAnyScore(true);
-      labelType.setCopyMinScore(true);
-      labelType.setCopyMaxScore(true);
-      labelType.setCopyAllScoresIfNoCodeChange(true);
-      labelType.setCopyAllScoresOnTrivialRebase(true);
-      labelType.setCopyAllScoresOnMergeFirstParentUpdate(true);
-      labelType.setCopyValues(ImmutableList.of((short) -1, (short) 1));
-      labelType.setIgnoreSelfApproval(true);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig()
+          .updateLabelType(
+              "foo",
+              labelType -> {
+                labelType.setCopyAnyScore(true);
+                labelType.setCopyMinScore(true);
+                labelType.setCopyMaxScore(true);
+                labelType.setCopyAllScoresIfNoCodeChange(true);
+                labelType.setCopyAllScoresOnTrivialRebase(true);
+                labelType.setCopyAllScoresOnMergeFirstParentUpdate(true);
+                labelType.setCopyValues(ImmutableList.of((short) -1, (short) 1));
+                labelType.setIgnoreSelfApproval(true);
+              });
       u.save();
     }
 
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ListLabelsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/ListLabelsIT.java
index ef08079..33a0654 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/ListLabelsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ListLabelsIT.java
@@ -27,7 +27,6 @@
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
 import com.google.gerrit.common.data.LabelFunction;
-import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
@@ -88,9 +87,7 @@
 
     // set default value
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setDefaultValue((short) 1);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig().updateLabelType("foo", labelType -> labelType.setDefaultValue((short) 1));
       u.save();
     }
 
@@ -119,11 +116,14 @@
 
     // unset rules which are enabled by default
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setCanOverride(false);
-      labelType.setCopyAllScoresIfNoChange(false);
-      labelType.setAllowPostSubmit(false);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig()
+          .updateLabelType(
+              "foo",
+              labelType -> {
+                labelType.setCanOverride(false);
+                labelType.setCopyAllScoresIfNoChange(false);
+                labelType.setAllowPostSubmit(false);
+              });
       u.save();
     }
 
@@ -150,16 +150,19 @@
 
     // set rules which are not enabled by default
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setCopyAnyScore(true);
-      labelType.setCopyMinScore(true);
-      labelType.setCopyMaxScore(true);
-      labelType.setCopyAllScoresIfNoCodeChange(true);
-      labelType.setCopyAllScoresOnTrivialRebase(true);
-      labelType.setCopyAllScoresOnMergeFirstParentUpdate(true);
-      labelType.setCopyValues(ImmutableList.of((short) -1, (short) 1));
-      labelType.setIgnoreSelfApproval(true);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig()
+          .updateLabelType(
+              "foo",
+              labelType -> {
+                labelType.setCopyAnyScore(true);
+                labelType.setCopyMinScore(true);
+                labelType.setCopyMaxScore(true);
+                labelType.setCopyAllScoresIfNoCodeChange(true);
+                labelType.setCopyAllScoresOnTrivialRebase(true);
+                labelType.setCopyAllScoresOnMergeFirstParentUpdate(true);
+                labelType.setCopyValues(ImmutableList.of((short) -1, (short) 1));
+                labelType.setIgnoreSelfApproval(true);
+              });
       u.save();
     }
 
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/SetLabelIT.java b/javatests/com/google/gerrit/acceptance/rest/project/SetLabelIT.java
index b08c72b..a1817d9 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/SetLabelIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/SetLabelIT.java
@@ -26,7 +26,6 @@
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
 import com.google.gerrit.common.data.LabelFunction;
-import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.common.LabelDefinitionInfo;
@@ -450,9 +449,7 @@
   public void setCanOverride() throws Exception {
     configLabel("foo", LabelFunction.NO_OP);
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setCanOverride(false);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig().updateLabelType("foo", lt -> lt.setCanOverride(false));
       u.save();
     }
     assertThat(gApi.projects().name(project.get()).label("foo").get().canOverride).isNull();
@@ -501,9 +498,7 @@
   public void unsetCopyAnyScore() throws Exception {
     configLabel("foo", LabelFunction.NO_OP);
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setCopyAnyScore(true);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig().updateLabelType("foo", lt -> lt.setCopyAnyScore(true));
       u.save();
     }
     assertThat(gApi.projects().name(project.get()).label("foo").get().copyAnyScore).isTrue();
@@ -537,9 +532,7 @@
   public void unsetCopyMinScore() throws Exception {
     configLabel("foo", LabelFunction.NO_OP);
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setCopyMinScore(true);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig().updateLabelType("foo", lt -> lt.setCopyMinScore(true));
       u.save();
     }
     assertThat(gApi.projects().name(project.get()).label("foo").get().copyMinScore).isTrue();
@@ -573,9 +566,7 @@
   public void unsetCopyMaxScore() throws Exception {
     configLabel("foo", LabelFunction.NO_OP);
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setCopyMaxScore(true);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig().updateLabelType("foo", lt -> lt.setCopyMaxScore(true));
       u.save();
     }
     assertThat(gApi.projects().name(project.get()).label("foo").get().copyMaxScore).isTrue();
@@ -594,9 +585,7 @@
   public void setCopyAllScoresIfNoChange() throws Exception {
     configLabel("foo", LabelFunction.NO_OP);
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setCopyAllScoresIfNoChange(false);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig().updateLabelType("foo", lt -> lt.setCopyAllScoresIfNoChange(false));
       u.save();
     }
     assertThat(gApi.projects().name(project.get()).label("foo").get().copyAllScoresIfNoChange)
@@ -651,9 +640,7 @@
   public void unsetCopyAllScoresIfNoCodeChange() throws Exception {
     configLabel("foo", LabelFunction.NO_OP);
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setCopyAllScoresIfNoCodeChange(true);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig().updateLabelType("foo", lt -> lt.setCopyAllScoresIfNoCodeChange(true));
       u.save();
     }
     assertThat(gApi.projects().name(project.get()).label("foo").get().copyAllScoresIfNoCodeChange)
@@ -691,9 +678,7 @@
   public void unsetCopyAllScoresOnTrivialRebase() throws Exception {
     configLabel("foo", LabelFunction.NO_OP);
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setCopyAllScoresOnTrivialRebase(true);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig().updateLabelType("foo", lt -> lt.setCopyAllScoresOnTrivialRebase(true));
       u.save();
     }
     assertThat(gApi.projects().name(project.get()).label("foo").get().copyAllScoresOnTrivialRebase)
@@ -741,9 +726,7 @@
   public void unsetCopyAllScoresOnMergeFirstParentUpdate() throws Exception {
     configLabel("foo", LabelFunction.NO_OP);
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setCopyAllScoresOnMergeFirstParentUpdate(true);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig().updateLabelType("foo", lt -> lt.setCopyAllScoresOnMergeFirstParentUpdate(true));
       u.save();
     }
     assertThat(
@@ -791,9 +774,8 @@
   public void unsetCopyValues() throws Exception {
     configLabel("foo", LabelFunction.NO_OP);
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setCopyValues(ImmutableList.of((short) -1, (short) 1));
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig()
+          .updateLabelType("foo", lt -> lt.setCopyValues(ImmutableList.of((short) -1, (short) 1)));
       u.save();
     }
     assertThat(gApi.projects().name(project.get()).label("foo").get().copyValues).isNotEmpty();
@@ -812,9 +794,7 @@
   public void setAllowPostSubmit() throws Exception {
     configLabel("foo", LabelFunction.NO_OP);
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setAllowPostSubmit(false);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig().updateLabelType("foo", lt -> lt.setAllowPostSubmit(false));
       u.save();
     }
     assertThat(gApi.projects().name(project.get()).label("foo").get().allowPostSubmit).isNull();
@@ -863,9 +843,7 @@
   public void unsetIgnoreSelfApproval() throws Exception {
     configLabel("foo", LabelFunction.NO_OP);
     try (ProjectConfigUpdate u = updateProject(project)) {
-      LabelType labelType = u.getConfig().getLabelSections().get("foo");
-      labelType.setIgnoreSelfApproval(true);
-      u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+      u.getConfig().updateLabelType("foo", lt -> lt.setIgnoreSelfApproval(true));
       u.save();
     }
     assertThat(gApi.projects().name(project.get()).label("foo").get().ignoreSelfApproval).isTrue();
diff --git a/javatests/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java b/javatests/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java
index 8469fff..17eb534 100644
--- a/javatests/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java
@@ -61,8 +61,8 @@
 
   private void saveLabelConfig() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().put(label.getName(), label);
-      u.getConfig().getLabelSections().put(pLabel.getName(), pLabel);
+      u.getConfig().upsertLabelType(label);
+      u.getConfig().upsertLabelType(pLabel);
       u.save();
     }
   }
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java b/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
index d74cd71..76514ec 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
@@ -1500,7 +1500,7 @@
       throws Exception {
     for (SubmitType submitType : SubmitType.values()) {
       try (ProjectConfigUpdate u = updateProject(project)) {
-        u.getConfig().getProject().setSubmitType(submitType);
+        u.getConfig().updateProject(p -> p.setSubmitType(submitType));
         u.save();
       }
 
diff --git a/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java b/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
index 1d5204b..813a715 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
@@ -32,6 +32,7 @@
 import static com.google.gerrit.server.project.testing.TestLabels.value;
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 
+import com.google.common.collect.ImmutableList;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.ExtensionRegistry;
 import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
@@ -48,39 +49,38 @@
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.server.project.ProjectConfig;
 import com.google.inject.Inject;
-import java.util.Arrays;
 import org.junit.Before;
 import org.junit.Test;
 
 @NoHttpd
 public class CustomLabelIT extends AbstractDaemonTest {
+  private static final String LABEL_NAME = "CustomLabel";
+  private static final LabelType LABEL =
+      label("CustomLabel", value(1, "Positive"), value(0, "No score"), value(-1, "Negative"));
+  private static final String P_LABEL_NAME = "CustomLabel2";
+  private static final LabelType P =
+      label("CustomLabel2", value(1, "Positive"), value(0, "No score"));
 
   @Inject private ProjectOperations projectOperations;
   @Inject private ExtensionRegistry extensionRegistry;
 
-  private final LabelType label =
-      label("CustomLabel", value(1, "Positive"), value(0, "No score"), value(-1, "Negative"));
-
-  private final LabelType P = label("CustomLabel2", value(1, "Positive"), value(0, "No score"));
-
   @Before
   public void setUp() throws Exception {
     projectOperations
         .project(project)
         .forUpdate()
-        .add(allowLabel(label.getName()).ref("refs/heads/*").group(ANONYMOUS_USERS).range(-1, 1))
-        .add(allowLabel(P.getName()).ref("refs/heads/*").group(ANONYMOUS_USERS).range(0, 1))
+        .add(allowLabel(LABEL_NAME).ref("refs/heads/*").group(ANONYMOUS_USERS).range(-1, 1))
+        .add(allowLabel(P_LABEL_NAME).ref("refs/heads/*").group(ANONYMOUS_USERS).range(0, 1))
         .update();
   }
 
   @Test
   public void customLabelNoOp_NegativeVoteNotBlock() throws Exception {
-    label.setFunction(NO_OP);
-    saveLabelConfig();
+    saveLabelConfig(LABEL.toBuilder().setFunction(NO_OP));
     PushOneCommit.Result r = createChange();
-    revision(r).review(new ReviewInput().label(label.getName(), -1));
+    revision(r).review(new ReviewInput().label(LABEL_NAME, -1));
     ChangeInfo c = getWithLabels(r);
-    LabelInfo q = c.labels.get(label.getName());
+    LabelInfo q = c.labels.get(LABEL_NAME);
     assertThat(q.all).hasSize(1);
     assertThat(q.approved).isNull();
     assertThat(q.recommended).isNull();
@@ -91,12 +91,11 @@
 
   @Test
   public void customLabelNoBlock_NegativeVoteNotBlock() throws Exception {
-    label.setFunction(NO_BLOCK);
-    saveLabelConfig();
+    saveLabelConfig(LABEL.toBuilder().setFunction(NO_BLOCK));
     PushOneCommit.Result r = createChange();
-    revision(r).review(new ReviewInput().label(label.getName(), -1));
+    revision(r).review(new ReviewInput().label(LABEL_NAME, -1));
     ChangeInfo c = getWithLabels(r);
-    LabelInfo q = c.labels.get(label.getName());
+    LabelInfo q = c.labels.get(LABEL_NAME);
     assertThat(q.all).hasSize(1);
     assertThat(q.approved).isNull();
     assertThat(q.recommended).isNull();
@@ -107,12 +106,11 @@
 
   @Test
   public void customLabelMaxNoBlock_NegativeVoteNotBlock() throws Exception {
-    label.setFunction(MAX_NO_BLOCK);
-    saveLabelConfig();
+    saveLabelConfig(LABEL.toBuilder().setFunction(MAX_NO_BLOCK));
     PushOneCommit.Result r = createChange();
-    revision(r).review(new ReviewInput().label(label.getName(), -1));
+    revision(r).review(new ReviewInput().label(LABEL_NAME, -1));
     ChangeInfo c = getWithLabels(r);
-    LabelInfo q = c.labels.get(label.getName());
+    LabelInfo q = c.labels.get(LABEL_NAME);
     assertThat(q.all).hasSize(1);
     assertThat(q.approved).isNull();
     assertThat(q.recommended).isNull();
@@ -123,16 +121,14 @@
 
   @Test
   public void customLabelMaxNoBlock_MaxVoteSubmittable() throws Exception {
-    label.setFunction(MAX_NO_BLOCK);
-    P.setFunction(NO_OP);
-    saveLabelConfig();
+    saveLabelConfig(LABEL.toBuilder().setFunction(MAX_NO_BLOCK), P.toBuilder().setFunction(NO_OP));
     PushOneCommit.Result r = createChange();
     assertThat(info(r.getChangeId()).submittable).isNull();
-    revision(r).review(ReviewInput.approve().label(label.getName(), 1));
+    revision(r).review(ReviewInput.approve().label(LABEL_NAME, 1));
 
     ChangeInfo c = getWithLabels(r);
     assertThat(c.submittable).isTrue();
-    LabelInfo q = c.labels.get(label.getName());
+    LabelInfo q = c.labels.get(LABEL_NAME);
     assertThat(q.all).hasSize(1);
     assertThat(q.approved).isNotNull();
     assertThat(q.recommended).isNull();
@@ -143,12 +139,11 @@
 
   @Test
   public void customLabelAnyWithBlock_NegativeVoteBlock() throws Exception {
-    label.setFunction(ANY_WITH_BLOCK);
-    saveLabelConfig();
+    saveLabelConfig(LABEL.toBuilder().setFunction(ANY_WITH_BLOCK));
     PushOneCommit.Result r = createChange();
-    revision(r).review(new ReviewInput().label(label.getName(), -1));
+    revision(r).review(new ReviewInput().label(LABEL_NAME, -1));
     ChangeInfo c = getWithLabels(r);
-    LabelInfo q = c.labels.get(label.getName());
+    LabelInfo q = c.labels.get(LABEL_NAME);
     assertThat(q.all).hasSize(1);
     assertThat(q.approved).isNull();
     assertThat(q.recommended).isNull();
@@ -170,19 +165,18 @@
   public void customLabelAnyWithBlock_Addreviewer_ZeroVote() throws Exception {
     TestListener testListener = new TestListener();
     try (Registration registration = extensionRegistry.newRegistration().add(testListener)) {
-      P.setFunction(ANY_WITH_BLOCK);
-      saveLabelConfig();
+      saveLabelConfig(P.toBuilder().setFunction(ANY_WITH_BLOCK));
       PushOneCommit.Result r = createChange();
       AddReviewerInput in = new AddReviewerInput();
       in.reviewer = user.email();
       gApi.changes().id(r.getChangeId()).addReviewer(in);
 
-      ReviewInput input = new ReviewInput().label(P.getName(), 0);
+      ReviewInput input = new ReviewInput().label(P_LABEL_NAME, 0);
       input.message = "foo";
 
       revision(r).review(input);
       ChangeInfo c = getWithLabels(r);
-      LabelInfo q = c.labels.get(P.getName());
+      LabelInfo q = c.labels.get(P_LABEL_NAME);
       assertThat(q.all).hasSize(1);
       assertThat(q.approved).isNull();
       assertThat(q.recommended).isNull();
@@ -196,12 +190,11 @@
 
   @Test
   public void customLabelMaxWithBlock_NegativeVoteBlock() throws Exception {
-    label.setFunction(MAX_WITH_BLOCK);
-    saveLabelConfig();
+    saveLabelConfig(LABEL.toBuilder().setFunction(MAX_WITH_BLOCK));
     PushOneCommit.Result r = createChange();
-    revision(r).review(new ReviewInput().label(label.getName(), -1));
+    revision(r).review(new ReviewInput().label(LABEL_NAME, -1));
     ChangeInfo c = getWithLabels(r);
-    LabelInfo q = c.labels.get(label.getName());
+    LabelInfo q = c.labels.get(LABEL_NAME);
     assertThat(q.all).hasSize(1);
     assertThat(q.approved).isNull();
     assertThat(q.recommended).isNull();
@@ -212,16 +205,15 @@
 
   @Test
   public void customLabelMaxWithBlock_MaxVoteSubmittable() throws Exception {
-    label.setFunction(MAX_WITH_BLOCK);
-    P.setFunction(NO_OP);
-    saveLabelConfig();
+    saveLabelConfig(
+        LABEL.toBuilder().setFunction(MAX_WITH_BLOCK), P.toBuilder().setFunction(NO_OP));
     PushOneCommit.Result r = createChange();
     assertThat(info(r.getChangeId()).submittable).isNull();
-    revision(r).review(ReviewInput.approve().label(label.getName(), 1));
+    revision(r).review(ReviewInput.approve().label(LABEL_NAME, 1));
 
     ChangeInfo c = getWithLabels(r);
     assertThat(c.submittable).isTrue();
-    LabelInfo q = c.labels.get(label.getName());
+    LabelInfo q = c.labels.get(LABEL_NAME);
     assertThat(q.all).hasSize(1);
     assertThat(q.approved).isNotNull();
     assertThat(q.recommended).isNull();
@@ -232,13 +224,12 @@
 
   @Test
   public void customLabelMaxWithBlock_MaxVoteNegativeVoteBlock() throws Exception {
-    label.setFunction(MAX_WITH_BLOCK);
-    saveLabelConfig();
+    saveLabelConfig(LABEL.toBuilder().setFunction(MAX_WITH_BLOCK));
     PushOneCommit.Result r = createChange();
-    revision(r).review(new ReviewInput().label(label.getName(), 1));
-    revision(r).review(new ReviewInput().label(label.getName(), -1));
+    revision(r).review(new ReviewInput().label(LABEL_NAME, 1));
+    revision(r).review(new ReviewInput().label(LABEL_NAME, -1));
     ChangeInfo c = getWithLabels(r);
-    LabelInfo q = c.labels.get(label.getName());
+    LabelInfo q = c.labels.get(LABEL_NAME);
     assertThat(q.all).hasSize(1);
     assertThat(q.approved).isNull();
     assertThat(q.recommended).isNull();
@@ -249,10 +240,9 @@
 
   @Test
   public void customLabel_DisallowPostSubmit() throws Exception {
-    label.setFunction(NO_OP);
-    label.setAllowPostSubmit(false);
-    P.setFunction(NO_OP);
-    saveLabelConfig();
+    saveLabelConfig(
+        LABEL.toBuilder().setFunction(NO_OP).setAllowPostSubmit(false),
+        P.toBuilder().setFunction(NO_OP));
 
     PushOneCommit.Result r = createChange();
     revision(r).review(ReviewInput.approve());
@@ -260,20 +250,20 @@
 
     ChangeInfo info = getWithLabels(r);
     assertPermitted(info, "Code-Review", 2);
-    assertPermitted(info, P.getName(), 0, 1);
-    assertPermitted(info, label.getName());
+    assertPermitted(info, P_LABEL_NAME, 0, 1);
+    assertPermitted(info, LABEL_NAME);
 
     ReviewInput postSubmitReview1 = new ReviewInput();
     postSubmitReview1.label(P.getName(), P.getMax().getValue());
     revision(r).review(postSubmitReview1);
 
     ReviewInput postSubmitReview2 = new ReviewInput();
-    postSubmitReview2.label(label.getName(), label.getMax().getValue());
+    postSubmitReview2.label(LABEL.getName(), LABEL.getMax().getValue());
     ResourceConflictException thrown =
         assertThrows(ResourceConflictException.class, () -> revision(r).review(postSubmitReview2));
     assertThat(thrown)
         .hasMessageThat()
-        .contains("Voting on labels disallowed after submit: " + label.getName());
+        .contains("Voting on labels disallowed after submit: " + LABEL_NAME);
   }
 
   @Test
@@ -331,10 +321,9 @@
 
   @Test
   public void customLabel_withBranch() throws Exception {
-    label.setRefPatterns(Arrays.asList("master"));
-    saveLabelConfig();
+    saveLabelConfig(LABEL.toBuilder().setRefPatterns(ImmutableList.of("master")));
     ProjectConfig cfg = projectCache.get(project).orElseThrow(illegalState(project)).getConfig();
-    assertThat(cfg.getLabelSections().get(label.getName()).getRefPatterns()).contains("master");
+    assertThat(cfg.getLabelSections().get(LABEL_NAME).getRefPatterns()).contains("master");
   }
 
   private void assertLabelStatus(String changeId, String testLabel) throws Exception {
@@ -348,10 +337,11 @@
     assertThat(labelInfo.blocking).isNull();
   }
 
-  private void saveLabelConfig() throws Exception {
+  private void saveLabelConfig(LabelType.Builder... builders) throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().getLabelSections().put(label.getName(), label);
-      u.getConfig().getLabelSections().put(P.getName(), P);
+      for (LabelType.Builder b : builders) {
+        u.getConfig().upsertLabelType(b.build());
+      }
       u.save();
     }
   }
diff --git a/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java b/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
index a9114b9..ff26fec 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
@@ -50,15 +50,15 @@
   @Test
   public void newPatchSetsNotifyConfig() throws Exception {
     Address addr = Address.create("Watcher", "watcher@example.com");
-    NotifyConfig nc = new NotifyConfig();
-    nc.addEmail(addr);
+    NotifyConfig.Builder nc = NotifyConfig.builder();
+    nc.addAddress(addr);
     nc.setName("new-patch-set");
     nc.setHeader(NotifyConfig.Header.CC);
-    nc.setTypes(EnumSet.of(NotifyType.NEW_PATCHSETS));
+    nc.setNotify(EnumSet.of(NotifyType.NEW_PATCHSETS));
     nc.setFilter("message:sekret");
 
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().putNotifyConfig("watch", nc);
+      u.getConfig().putNotifyConfig("watch", nc.build());
       u.save();
     }
 
@@ -91,14 +91,14 @@
   @Test
   public void noNotificationForPrivateChangesForWatchersInNotifyConfig() throws Exception {
     Address addr = Address.create("Watcher", "watcher@example.com");
-    NotifyConfig nc = new NotifyConfig();
-    nc.addEmail(addr);
+    NotifyConfig.Builder nc = NotifyConfig.builder();
+    nc.addAddress(addr);
     nc.setName("team");
     nc.setHeader(NotifyConfig.Header.TO);
-    nc.setTypes(EnumSet.of(NotifyType.NEW_CHANGES, NotifyType.ALL_COMMENTS));
+    nc.setNotify(EnumSet.of(NotifyType.NEW_CHANGES, NotifyType.ALL_COMMENTS));
 
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().putNotifyConfig("team", nc);
+      u.getConfig().putNotifyConfig("team", nc.build());
       u.save();
     }
 
@@ -123,14 +123,14 @@
   public void noNotificationForChangeThatIsTurnedPrivateForWatchersInNotifyConfig()
       throws Exception {
     Address addr = Address.create("Watcher", "watcher@example.com");
-    NotifyConfig nc = new NotifyConfig();
-    nc.addEmail(addr);
+    NotifyConfig.Builder nc = NotifyConfig.builder();
+    nc.addAddress(addr);
     nc.setName("team");
     nc.setHeader(NotifyConfig.Header.TO);
-    nc.setTypes(EnumSet.of(NotifyType.NEW_PATCHSETS));
+    nc.setNotify(EnumSet.of(NotifyType.NEW_PATCHSETS));
 
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().putNotifyConfig("team", nc);
+      u.getConfig().putNotifyConfig("team", nc.build());
       u.save();
     }
 
@@ -152,14 +152,14 @@
   @Test
   public void noNotificationForWipChangesForWatchersInNotifyConfig() throws Exception {
     Address addr = Address.create("Watcher", "watcher@example.com");
-    NotifyConfig nc = new NotifyConfig();
-    nc.addEmail(addr);
+    NotifyConfig.Builder nc = NotifyConfig.builder();
+    nc.addAddress(addr);
     nc.setName("team");
     nc.setHeader(NotifyConfig.Header.TO);
-    nc.setTypes(EnumSet.of(NotifyType.NEW_CHANGES, NotifyType.ALL_COMMENTS));
+    nc.setNotify(EnumSet.of(NotifyType.NEW_CHANGES, NotifyType.ALL_COMMENTS));
 
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().putNotifyConfig("team", nc);
+      u.getConfig().putNotifyConfig("team", nc.build());
       u.save();
     }
 
@@ -183,14 +183,14 @@
   @Test
   public void noNotificationForChangeThatIsTurnedWipForWatchersInNotifyConfig() throws Exception {
     Address addr = Address.create("Watcher", "watcher@example.com");
-    NotifyConfig nc = new NotifyConfig();
-    nc.addEmail(addr);
+    NotifyConfig.Builder nc = NotifyConfig.builder();
+    nc.addAddress(addr);
     nc.setName("team");
     nc.setHeader(NotifyConfig.Header.TO);
-    nc.setTypes(EnumSet.of(NotifyType.NEW_PATCHSETS));
+    nc.setNotify(EnumSet.of(NotifyType.NEW_PATCHSETS));
 
     try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig().putNotifyConfig("team", nc);
+      u.getConfig().putNotifyConfig("team", nc.build());
       u.save();
     }
 
diff --git a/javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java b/javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java
index 1c820af..90d4e09 100644
--- a/javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java
@@ -89,7 +89,7 @@
       if (localLabelSections.isEmpty()) {
         localLabelSections.putAll(projectCache.getAllProjects().getConfig().getLabelSections());
       }
-      localLabelSections.get(labelName).setIgnoreSelfApproval(newState);
+      u.getConfig().updateLabelType(labelName, lt -> lt.setIgnoreSelfApproval(newState));
       u.save();
     }
   }
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java b/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
index 65c7b5c..c8899b9 100644
--- a/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
+++ b/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
@@ -106,7 +106,7 @@
     assertThat(cachedProjectConfig1).isNotSameInstanceAs(projectConfig);
     assertThat(cachedProjectConfig1.getProject().getDescription()).isEmpty();
     assertThat(projectConfig.getProject().getDescription()).isEmpty();
-    projectConfig.getProject().setDescription("my fancy project");
+    projectConfig.updateProject(p -> p.setDescription("my fancy project"));
 
     ProjectConfig cachedProjectConfig2 =
         projectCache.get(key).orElseThrow(illegalState(project)).getConfig();
diff --git a/javatests/com/google/gerrit/common/data/LabelFunctionTest.java b/javatests/com/google/gerrit/common/data/LabelFunctionTest.java
index 6f5232b..8fea072 100644
--- a/javatests/com/google/gerrit/common/data/LabelFunctionTest.java
+++ b/javatests/com/google/gerrit/common/data/LabelFunctionTest.java
@@ -87,12 +87,12 @@
   private static LabelType makeLabel() {
     List<LabelValue> values = new ArrayList<>();
     // The label text is irrelevant here, only the numerical value is used
-    values.add(new LabelValue((short) -2, "Great job, please fix compilation."));
-    values.add(new LabelValue((short) -1, "Really good, please make some minor changes."));
-    values.add(new LabelValue((short) 0, "No vote."));
-    values.add(new LabelValue((short) 1, "Closest thing perfection."));
-    values.add(new LabelValue((short) 2, "Perfect!"));
-    return new LabelType(LABEL_NAME, values);
+    values.add(LabelValue.create((short) -2, "Great job, please fix compilation."));
+    values.add(LabelValue.create((short) -1, "Really good, please make some minor changes."));
+    values.add(LabelValue.create((short) 0, "No vote."));
+    values.add(LabelValue.create((short) 1, "Closest thing perfection."));
+    values.add(LabelValue.create((short) 2, "Perfect!"));
+    return LabelType.create(LABEL_NAME, values);
   }
 
   private static PatchSetApproval makeApproval(int value) {
diff --git a/javatests/com/google/gerrit/common/data/LabelTypeTest.java b/javatests/com/google/gerrit/common/data/LabelTypeTest.java
index 6c3befb..76ea6e1 100644
--- a/javatests/com/google/gerrit/common/data/LabelTypeTest.java
+++ b/javatests/com/google/gerrit/common/data/LabelTypeTest.java
@@ -22,26 +22,26 @@
 public class LabelTypeTest {
   @Test
   public void sortLabelValues() {
-    LabelValue v0 = new LabelValue((short) 0, "Zero");
-    LabelValue v1 = new LabelValue((short) 1, "One");
-    LabelValue v2 = new LabelValue((short) 2, "Two");
-    LabelType types = new LabelType("Label", ImmutableList.of(v2, v0, v1));
+    LabelValue v0 = LabelValue.create((short) 0, "Zero");
+    LabelValue v1 = LabelValue.create((short) 1, "One");
+    LabelValue v2 = LabelValue.create((short) 2, "Two");
+    LabelType types = LabelType.create("Label", ImmutableList.of(v2, v0, v1));
     assertThat(types.getValues()).containsExactly(v0, v1, v2).inOrder();
   }
 
   @Test
   public void insertMissingLabelValues() {
-    LabelValue v0 = new LabelValue((short) 0, "Zero");
-    LabelValue v2 = new LabelValue((short) 2, "Two");
-    LabelValue v5 = new LabelValue((short) 5, "Five");
-    LabelType types = new LabelType("Label", ImmutableList.of(v2, v5, v0));
+    LabelValue v0 = LabelValue.create((short) 0, "Zero");
+    LabelValue v2 = LabelValue.create((short) 2, "Two");
+    LabelValue v5 = LabelValue.create((short) 5, "Five");
+    LabelType types = LabelType.create("Label", ImmutableList.of(v2, v5, v0));
     assertThat(types.getValues())
         .containsExactly(
             v0,
-            new LabelValue((short) 1, ""),
+            LabelValue.create((short) 1, ""),
             v2,
-            new LabelValue((short) 3, ""),
-            new LabelValue((short) 4, ""),
+            LabelValue.create((short) 3, ""),
+            LabelValue.create((short) 4, ""),
             v5)
         .inOrder();
   }
diff --git a/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java b/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java
index c259e60..ba8485b 100644
--- a/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java
+++ b/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java
@@ -105,7 +105,7 @@
     }
     LabelType lt =
         label("Verified", value(1, "Verified"), value(0, "No score"), value(-1, "Fails"));
-    pc.getLabelSections().put(lt.getName(), lt);
+    pc.upsertLabelType(lt);
     save(pc);
   }
 
diff --git a/javatests/com/google/gerrit/server/permissions/RefControlTest.java b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
index 33446e4..9029301 100644
--- a/javatests/com/google/gerrit/server/permissions/RefControlTest.java
+++ b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
@@ -207,7 +207,7 @@
         ProjectConfig allProjectsConfig = projectConfigFactory.create(allProjectsName);
         allProjectsConfig.load(md);
         LabelType cr = TestLabels.codeReview();
-        allProjectsConfig.getLabelSections().put(cr.getName(), cr);
+        allProjectsConfig.upsertLabelType(cr);
         allProjectsConfig.commit(md);
       }
     }
@@ -217,7 +217,7 @@
     try (MetaDataUpdate md = metaDataUpdateFactory.create(localKey)) {
       ProjectConfig newLocal = projectConfigFactory.create(localKey);
       newLocal.load(md);
-      newLocal.getProject().setParentName(parentKey);
+      newLocal.updateProject(p -> p.setParent(parentKey));
       newLocal.commit(md);
     }
 
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 331bb4c..4104017 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -1026,7 +1026,7 @@
     try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
       ProjectConfig cfg = projectConfigFactory.create(project);
       cfg.load(md);
-      cfg.getLabelSections().put(verified.getName(), verified);
+      cfg.upsertLabelType(verified);
       cfg.commit(md);
     }
     projectCache.evict(project);
diff --git a/javatests/com/google/gerrit/server/rules/IgnoreSelfApprovalRuleTest.java b/javatests/com/google/gerrit/server/rules/IgnoreSelfApprovalRuleTest.java
index d8af0e5..85a3207 100644
--- a/javatests/com/google/gerrit/server/rules/IgnoreSelfApprovalRuleTest.java
+++ b/javatests/com/google/gerrit/server/rules/IgnoreSelfApprovalRuleTest.java
@@ -72,12 +72,12 @@
   private static LabelType makeLabel(String labelName) {
     List<LabelValue> values = new ArrayList<>();
     // The label text is irrelevant here, only the numerical value is used
-    values.add(new LabelValue((short) -2, "-2"));
-    values.add(new LabelValue((short) -1, "-1"));
-    values.add(new LabelValue((short) 0, "No vote."));
-    values.add(new LabelValue((short) 1, "+1"));
-    values.add(new LabelValue((short) 2, "+2"));
-    return new LabelType(labelName, values);
+    values.add(LabelValue.create((short) -2, "-2"));
+    values.add(LabelValue.create((short) -1, "-1"));
+    values.add(LabelValue.create((short) 0, "No vote."));
+    values.add(LabelValue.create((short) 1, "+1"));
+    values.add(LabelValue.create((short) 2, "+2"));
+    return LabelType.create(labelName, values);
   }
 
   private static PatchSetApproval makeApproval(LabelId labelId, Account.Id accountId, int value) {
diff --git a/javatests/com/google/gerrit/server/schema/AllProjectsCreatorTest.java b/javatests/com/google/gerrit/server/schema/AllProjectsCreatorTest.java
index eceec8b..9cf4896 100644
--- a/javatests/com/google/gerrit/server/schema/AllProjectsCreatorTest.java
+++ b/javatests/com/google/gerrit/server/schema/AllProjectsCreatorTest.java
@@ -44,12 +44,12 @@
 
 public class AllProjectsCreatorTest {
   private static final LabelType TEST_LABEL =
-      new LabelType(
+      LabelType.create(
           "Test-Label",
           ImmutableList.of(
-              new LabelValue((short) 2, "Two"),
-              new LabelValue((short) 0, "Zero"),
-              new LabelValue((short) 1, "One")));
+              LabelValue.create((short) 2, "Two"),
+              LabelValue.create((short) 0, "Zero"),
+              LabelValue.create((short) 1, "One")));
 
   private static final String TEST_LABEL_STRING =
       String.join(
diff --git a/javatests/com/google/gerrit/server/submit/SubscriptionGraphTest.java b/javatests/com/google/gerrit/server/submit/SubscriptionGraphTest.java
index 489e1b6..5f71544 100644
--- a/javatests/com/google/gerrit/server/submit/SubscriptionGraphTest.java
+++ b/javatests/com/google/gerrit/server/submit/SubscriptionGraphTest.java
@@ -81,9 +81,9 @@
 
   @Test
   public void oneSuperprojectOneSubmodule() throws Exception {
-    SubscriptionGraph.Factory factory =
-        new DefaultFactory(mockGitModulesFactory, mockProjectCache, mergeOpRepoManager);
-    SubscriptionGraph subscriptionGraph = factory.compute(ImmutableSet.of(SUB_BRANCH));
+    SubscriptionGraph.Factory factory = new DefaultFactory(mockGitModulesFactory, mockProjectCache);
+    SubscriptionGraph subscriptionGraph =
+        factory.compute(ImmutableSet.of(SUB_BRANCH), mergeOpRepoManager);
 
     assertThat(subscriptionGraph.getAffectedSuperProjects()).containsExactly(SUPER_PROJECT);
     assertThat(subscriptionGraph.getAffectedSuperBranches(SUPER_PROJECT))
@@ -98,12 +98,12 @@
 
   @Test
   public void circularSubscription() throws Exception {
-    SubscriptionGraph.Factory factory =
-        new DefaultFactory(mockGitModulesFactory, mockProjectCache, mergeOpRepoManager);
+    SubscriptionGraph.Factory factory = new DefaultFactory(mockGitModulesFactory, mockProjectCache);
     setSubscription(SUPER_BRANCH, ImmutableList.of(SUB_BRANCH));
     SubmoduleConflictException e =
         assertThrows(
-            SubmoduleConflictException.class, () -> factory.compute(ImmutableSet.of(SUB_BRANCH)));
+            SubmoduleConflictException.class,
+            () -> factory.compute(ImmutableSet.of(SUB_BRANCH), mergeOpRepoManager));
 
     String expectedErrorMessage =
         "Subproject,refs/heads/one->Superproject,refs/heads/one->Subproject,refs/heads/one";
@@ -154,10 +154,9 @@
     setSubscription(submoduleBranch2, ImmutableList.of(superBranch1));
     setSubscription(submoduleBranch3, ImmutableList.of(superBranch1, superBranch2));
 
-    SubscriptionGraph.Factory factory =
-        new DefaultFactory(mockGitModulesFactory, mockProjectCache, mergeOpRepoManager);
+    SubscriptionGraph.Factory factory = new DefaultFactory(mockGitModulesFactory, mockProjectCache);
     SubscriptionGraph subscriptionGraph =
-        factory.compute(ImmutableSet.of(submoduleBranch1, submoduleBranch2));
+        factory.compute(ImmutableSet.of(submoduleBranch1, submoduleBranch2), mergeOpRepoManager);
 
     assertThat(subscriptionGraph.getAffectedSuperProjects())
         .containsExactly(superProject1, superProject2);
diff --git a/lib/BUILD b/lib/BUILD
index d3ef4b9..0110047 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -160,7 +160,7 @@
     name = "args4j",
     data = ["//lib:LICENSE-args4j"],
     visibility = ["//visibility:public"],
-    exports = ["@args4j-intern//jar"],
+    exports = ["@args4j//jar"],
 )
 
 java_library(
diff --git a/lib/polymer_externs/BUILD b/lib/polymer_externs/BUILD
deleted file mode 100644
index f07aa2f..0000000
--- a/lib/polymer_externs/BUILD
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_library")
-
-package(default_visibility = ["//visibility:public"])
-
-closure_js_library(
-    name = "polymer_closure",
-    srcs = ["@polymer_closure//file"],
-    data = ["//lib:LICENSE-Apache2.0"],
-    no_closure_library = True,
-)
diff --git a/package.json b/package.json
index 0331079..d051c41 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,7 @@
   "dependencies": {},
   "devDependencies": {
     "@bazel/rollup": "^1.6.1",
+    "@bazel/terser": "^1.7.0",
     "@bazel/typescript": "^1.6.1",
     "eslint": "^6.6.0",
     "eslint-config-google": "^0.13.0",
@@ -16,6 +17,7 @@
     "gts": "^2.0.2",
     "polymer-cli": "^1.9.11",
     "prettier": "2.0.5",
+    "terser": "^4.8.0",
     "typescript": "3.8.2"
   },
   "scripts": {
diff --git a/plugins/BUILD b/plugins/BUILD
index a071bde..943471a 100644
--- a/plugins/BUILD
+++ b/plugins/BUILD
@@ -49,6 +49,7 @@
     "//java/com/google/gerrit/server/audit",
     "//java/com/google/gerrit/server/cache/mem",
     "//java/com/google/gerrit/server/cache/serialize",
+    "//java/com/google/gerrit/server/data",
     "//java/com/google/gerrit/server/logging",
     "//java/com/google/gerrit/server/schema",
     "//java/com/google/gerrit/server/util/time",
diff --git a/plugins/delete-project b/plugins/delete-project
index 7671def..f420d06 160000
--- a/plugins/delete-project
+++ b/plugins/delete-project
@@ -1 +1 @@
-Subproject commit 7671def07882aab89b19eb7496418588ea7375d9
+Subproject commit f420d06562b97eab26a627baa7722c7f84d95763
diff --git a/plugins/replication b/plugins/replication
index b0ecbd3..ced7fc3 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit b0ecbd3c88fd0d8b19112e33049564e25ac5fc39
+Subproject commit ced7fc318feb76e2fc6d549669c5f5d8d905add5
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.js
index 50c2355..87e100c 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.js
@@ -21,7 +21,6 @@
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
 import {resetPlugins} from '../../../test/test-utils.js';
 import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
-import {pluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js';
 import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
 
 const testHtmlPlugin = document.createElement('dom-module');
@@ -119,8 +118,6 @@
         plugin.registerStyleModule('change-metadata', 'my-plugin-style');
       }, undefined, 'http://test.com/plugins/style.js');
       element = createElement();
-      sinon.stub(pluginEndpoints, 'importUrl')
-          .callsFake( url => Promise.resolve());
       pluginLoader.loadPlugins([]);
       pluginLoader.awaitPluginsLoaded().then(() => {
         flush(done);
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.js
index 26baf3c..8ec3121 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.js
@@ -20,7 +20,6 @@
 import './gr-change-metadata.js';
 import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
 import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
-import {pluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js';
 import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
 
 const basicFixture = fixtureFromElement('gr-change-metadata');
@@ -37,8 +36,6 @@
     });
 
     element = basicFixture.instantiate();
-    sinon.stub(pluginEndpoints, 'importUrl')
-        .callsFake( url => Promise.resolve());
   });
 
   test('computed fields', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index ae64c3a..af542e0 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -40,7 +40,6 @@
 import '../gr-file-list/gr-file-list.js';
 import '../gr-included-in-dialog/gr-included-in-dialog.js';
 import '../gr-messages-list/gr-messages-list.js';
-import '../gr-messages-list/gr-messages-list-experimental.js';
 import '../gr-related-changes-list/gr-related-changes-list.js';
 import '../../diff/gr-apply-fix-dialog/gr-apply-fix-dialog.js';
 import '../gr-reply-dialog/gr-reply-dialog.js';
@@ -66,7 +65,6 @@
 import {PrimaryTab, SecondaryTab} from '../../../constants/constants.js';
 import {NO_ROBOT_COMMENTS_THREADS_MSG} from '../../../constants/messages.js';
 import {appContext} from '../../../services/app-context.js';
-import {ExperimentIds} from '../../../services/flags.js';
 import {ChangeStatus} from '../../../constants/constants.js';
 
 const CHANGE_ID_ERROR = {
@@ -454,7 +452,6 @@
 
   constructor() {
     super();
-    this.flagsService = appContext.flagsService;
     this.reporting = appContext.reportingService;
   }
 
@@ -545,14 +542,8 @@
     }
   }
 
-  _isChangeLogExperimentEnabled() {
-    return this.flagsService.isEnabled(ExperimentIds.CLEANER_CHANGELOG);
-  }
-
   get messagesList() {
-    const tagName = this._isChangeLogExperimentEnabled()
-      ? 'gr-messages-list-experimental' : 'gr-messages-list';
-    return this.shadowRoot.querySelector(tagName);
+    return this.shadowRoot.querySelector('gr-messages-list');
   }
 
   get threadList() {
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.js
index c9fdb78..5b89c15 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.js
@@ -213,8 +213,7 @@
       --paper-tab-ink: var(--link-color);
     }
     gr-thread-list,
-    gr-messages-list,
-    gr-messages-list-experimental {
+    gr-messages-list {
       display: block;
     }
     gr-thread-list {
@@ -701,35 +700,19 @@
         is="dom-if"
         if="[[_isTabActive(_constants.SecondaryTab.CHANGE_LOG, _activeTabs)]]"
       >
-        <template is="dom-if" if="[[!_isChangeLogExperimentEnabled()]]">
-          <gr-messages-list
-            class="hideOnMobileOverlay"
-            change-num="[[_changeNum]]"
-            labels="[[_change.labels]]"
-            messages="[[_change.messages]]"
-            reviewer-updates="[[_change.reviewer_updates]]"
-            change-comments="[[_changeComments]]"
-            project-name="[[_change.project]]"
-            show-reply-buttons="[[_loggedIn]]"
-            on-message-anchor-tap="_handleMessageAnchorTap"
-            on-reply="_handleMessageReply"
-          ></gr-messages-list>
-        </template>
-        <template is="dom-if" if="[[_isChangeLogExperimentEnabled()]]">
-          <gr-messages-list-experimental
-            class="hideOnMobileOverlay"
-            change="[[_change]]"
-            change-num="[[_changeNum]]"
-            labels="[[_change.labels]]"
-            messages="[[_change.messages]]"
-            reviewer-updates="[[_change.reviewer_updates]]"
-            change-comments="[[_changeComments]]"
-            project-name="[[_change.project]]"
-            show-reply-buttons="[[_loggedIn]]"
-            on-message-anchor-tap="_handleMessageAnchorTap"
-            on-reply="_handleMessageReply"
-          ></gr-messages-list-experimental>
-        </template>
+        <gr-messages-list
+          class="hideOnMobileOverlay"
+          change="[[_change]]"
+          change-num="[[_changeNum]]"
+          labels="[[_change.labels]]"
+          messages="[[_change.messages]]"
+          reviewer-updates="[[_change.reviewer_updates]]"
+          change-comments="[[_changeComments]]"
+          project-name="[[_change.project]]"
+          show-reply-buttons="[[_loggedIn]]"
+          on-message-anchor-tap="_handleMessageAnchorTap"
+          on-reply="_handleMessageReply"
+        ></gr-messages-list>
       </template>
     </section>
   </div>
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
deleted file mode 100644
index 3554dff..0000000
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
+++ /dev/null
@@ -1,108 +0,0 @@
-/**
- * @license
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/*
-  The custom CSS property `--gr-formatted-text-prose-max-width` controls the max
-  width of formatted text blocks that are not code.
-*/
-/*
-  FIXME(polymer-modulizer): the above comments were extracted
-  from HTML and may be out of place here. Review them and
-  then delete this comment!
-*/
-import '../../shared/gr-formatted-text/gr-formatted-text.js';
-import '../../../styles/shared-styles.js';
-import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
-import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
-import {PolymerElement} from '@polymer/polymer/polymer-element.js';
-import {htmlTemplate} from './gr-comment-list_html.js';
-import {BaseUrlBehavior} from '../../../behaviors/base-url-behavior/base-url-behavior.js';
-import {PathListBehavior} from '../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.js';
-import {URLEncodingBehavior} from '../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.js';
-import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-
-/**
- * @extends PolymerElement
- */
-class GrCommentList extends mixinBehaviors( [
-  BaseUrlBehavior,
-  PathListBehavior,
-  URLEncodingBehavior,
-], GestureEventListeners(
-    LegacyElementMixin(
-        PolymerElement))) {
-  static get template() { return htmlTemplate; }
-
-  static get is() { return 'gr-comment-list'; }
-
-  static get properties() {
-    return {
-      changeNum: Number,
-      comments: Object,
-      patchNum: Number,
-      projectName: String,
-      /** @type {?} */
-      projectConfig: Object,
-    };
-  }
-
-  _computeFilesFromComments(comments) {
-    const arr = Object.keys(comments || {});
-    return arr.sort(this.specialFilePathCompare);
-  }
-
-  _isOnParent(comment) {
-    return comment.side === 'PARENT';
-  }
-
-  _computeDiffURL(filePath, changeNum, allComments) {
-    if ([filePath, changeNum, allComments].includes(undefined)) {
-      return;
-    }
-    const fileComments = this._computeCommentsForFile(allComments, filePath);
-    // This can happen for files that don't exist anymore in the current ps.
-    if (fileComments.length === 0) return;
-    return GerritNav.getUrlForDiffById(changeNum, this.projectName,
-        filePath, fileComments[0].patch_set);
-  }
-
-  _computeDiffLineURL(filePath, changeNum, patchNum, comment) {
-    const basePatchNum = comment.hasOwnProperty('parent') ?
-      -comment.parent : null;
-    return GerritNav.getUrlForDiffById(changeNum, this.projectName,
-        filePath, patchNum, basePatchNum, comment.line,
-        this._isOnParent(comment));
-  }
-
-  _computeCommentsForFile(comments, filePath) {
-    // Changes are not picked up by the dom-repeat due to the array instance
-    // identity not changing even when it has elements added/removed from it.
-    return (comments[filePath] || []).slice();
-  }
-
-  _computePatchDisplayName(comment) {
-    if (this._isOnParent(comment)) {
-      return 'Base, ';
-    }
-    if (comment.patch_set != this.patchNum) {
-      return `PS${comment.patch_set}, `;
-    }
-    return '';
-  }
-}
-
-customElements.define(GrCommentList.is, GrCommentList);
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_html.js b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_html.js
deleted file mode 100644
index 2811828..0000000
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_html.js
+++ /dev/null
@@ -1,103 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag.js';
-
-export const htmlTemplate = html`
-  <style include="shared-styles">
-    :host {
-      display: block;
-      word-wrap: break-word;
-    }
-    .file {
-      padding: var(--spacing-s) 0;
-    }
-    .container {
-      display: flex;
-      padding: var(--spacing-s) 0;
-    }
-    .lineNum {
-      margin-right: var(--spacing-s);
-      min-width: 135px;
-      text-align: right;
-    }
-    .patchset-level-comment-text {
-      margin-right: var(--spacing-m);
-    }
-    .message {
-      flex: 1;
-      --gr-formatted-text-prose-max-width: 80ch;
-    }
-    @media screen and (max-width: 50em) {
-      .container {
-        flex-direction: column;
-      }
-      .lineNum {
-        margin-right: 0;
-        min-width: initial;
-        text-align: left;
-      }
-    }
-  </style>
-  <template
-    is="dom-repeat"
-    items="[[_computeFilesFromComments(comments)]]"
-    as="file"
-  >
-    <div class="file">
-      <template is="dom-if" if="[[!shouldHideFile(file)]]">
-        <a
-          class="fileLink"
-          href="[[_computeDiffURL(file, changeNum, comments)]]"
-          >[[computeDisplayPath(file)]]
-        </a>
-      </template>
-    </div>
-    <template
-      is="dom-repeat"
-      items="[[_computeCommentsForFile(comments, file)]]"
-      as="comment"
-    >
-      <div class="container">
-        <template is="dom-if" if="[[shouldHideFile(file)]]">
-          <span class="patchset-level-comment-text">
-            Patchset Comment:
-          </span>
-        </template>
-        <template is="dom-if" if="[[!shouldHideFile(file)]]">
-          <a
-            class="lineNum"
-            href$="[[_computeDiffLineURL(file, changeNum, comment.patch_set, comment)]]"
-          >
-            <span hidden$="[[!comment.line]]">
-              <span>[[_computePatchDisplayName(comment)]]</span>
-              Line <span>[[comment.line]]</span>
-            </span>
-            <span hidden$="[[comment.line]]">
-              File comment:
-            </span>
-          </a>
-        </template>
-        <gr-formatted-text
-          class="message"
-          no-trailing-margin=""
-          content="[[comment.message]]"
-          config="[[projectConfig.commentlinks]]"
-        ></gr-formatted-text>
-      </div>
-    </template>
-  </template>
-`;
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.js b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.js
deleted file mode 100644
index 704d83f..0000000
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.js
+++ /dev/null
@@ -1,114 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../../../test/common-test-setup-karma.js';
-import './gr-comment-list.js';
-import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
-import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-
-const basicFixture = fixtureFromElement('gr-comment-list');
-
-suite('gr-comment-list tests', () => {
-  let element;
-
-  setup(() => {
-    element = basicFixture.instantiate();
-
-    sinon.stub(GerritNav, 'mapCommentlinks').callsFake( x => x);
-  });
-
-  test('_computeFilesFromComments w/ special file path sorting', () => {
-    const comments = {
-      'file_b.html': [],
-      'file_c.css': [],
-      'file_a.js': [],
-      'test.cc': [],
-      'test.h': [],
-    };
-    const expected = [
-      'file_a.js',
-      'file_b.html',
-      'file_c.css',
-      'test.h',
-      'test.cc',
-    ];
-    const actual = element._computeFilesFromComments(comments);
-    assert.deepEqual(actual, expected);
-
-    assert.deepEqual(element._computeFilesFromComments(null), []);
-  });
-
-  test('_computePatchDisplayName', () => {
-    const comment = {line: 123, side: 'REVISION', patch_set: 10};
-
-    element.patchNum = 10;
-    assert.equal(element._computePatchDisplayName(comment), '');
-
-    element.patchNum = 9;
-    assert.equal(element._computePatchDisplayName(comment), 'PS10, ');
-
-    comment.side = 'PARENT';
-    assert.equal(element._computePatchDisplayName(comment), 'Base, ');
-  });
-
-  test('config commentlinks propagate to formatted text', () => {
-    element.comments = {
-      'test.h': [{
-        author: {name: 'foo'},
-        patch_set: 4,
-        line: 10,
-        updated: '2017-10-30 20:48:40.000000000',
-        message: 'Ideadbeefdeadbeef',
-        unresolved: true,
-      }],
-    };
-    element.projectConfig = {
-      commentlinks: {foo: {link: '#/q/$1', match: '(I[0-9a-f]{8,40})'}},
-    };
-    flushAsynchronousOperations();
-    const formattedText = dom(element.root).querySelector(
-        'gr-formatted-text.message');
-    assert.isOk(formattedText.config);
-    assert.deepEqual(formattedText.config,
-        element.projectConfig.commentlinks);
-  });
-
-  test('_computeDiffLineURL', () => {
-    const getUrlStub = sinon.stub(GerritNav, 'getUrlForDiffById');
-    element.projectName = 'proj';
-    element.changeNum = 123;
-
-    const comment = {line: 456};
-    element._computeDiffLineURL('foo.cc', 123, 4, comment);
-    assert.isTrue(getUrlStub.calledOnce);
-    assert.deepEqual(getUrlStub.lastCall.args,
-        [123, 'proj', 'foo.cc', 4, null, 456, false]);
-
-    comment.side = 'PARENT';
-    element._computeDiffLineURL('foo.cc', 123, 4, comment);
-    assert.isTrue(getUrlStub.calledTwice);
-    assert.deepEqual(getUrlStub.lastCall.args,
-        [123, 'proj', 'foo.cc', 4, null, 456, true]);
-
-    comment.parent = 12;
-    element._computeDiffLineURL('foo.cc', 123, 4, comment);
-    assert.isTrue(getUrlStub.calledThrice);
-    assert.deepEqual(getUrlStub.lastCall.args,
-        [123, 'proj', 'foo.cc', 4, -12, 456, true]);
-  });
-});
-
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.js b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
index 7163952..0da687f 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.js
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
@@ -24,9 +24,6 @@
 import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
 import '../../../styles/shared-styles.js';
 import '../../../styles/gr-voting-styles.js';
-import '../gr-comment-list/gr-comment-list.js';
-import {appContext} from '../../../services/app-context.js';
-import {ExperimentIds} from '../../../services/flags.js';
 import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
 import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
 import {PolymerElement} from '@polymer/polymer/polymer-element.js';
@@ -136,8 +133,7 @@
       },
       _commentCountText: {
         type: Number,
-        computed: '_computeCommentCountText(comments,'
-            + ' message.commentThreads.length, _isCleanerLogExperimentEnabled)',
+        computed: '_computeCommentCountText(message.commentThreads.length)',
       },
       _loggedIn: {
         type: Boolean,
@@ -151,7 +147,6 @@
         type: Boolean,
         value: false,
       },
-      _isCleanerLogExperimentEnabled: Boolean,
     };
   }
 
@@ -163,7 +158,6 @@
 
   constructor() {
     super();
-    this.flagsService = appContext.flagsService;
   }
 
   /** @override */
@@ -176,8 +170,6 @@
   /** @override */
   ready() {
     super.ready();
-    this._isCleanerLogExperimentEnabled = this.flagsService
-        .isEnabled(ExperimentIds.CLEANER_CHANGELOG);
     this.$.restAPI.getConfig().then(config => {
       this.config = config;
     });
@@ -197,33 +189,13 @@
     }
   }
 
-  _computeCommentCountText(
-      comments, threadsLength, isCleanerLogExperimentEnabled) {
-    // TODO(taoalpha): clean up after cleaner-changelog experiment launched
-    if (isCleanerLogExperimentEnabled) {
-      if (threadsLength === 0) {
-        return undefined;
-      } else if (threadsLength === 1) {
-        return '1 comment';
-      } else {
-        return `${threadsLength} comments`;
-      }
+  _computeCommentCountText(threadsLength) {
+    if (threadsLength === 0) {
+      return undefined;
+    } else if (threadsLength === 1) {
+      return '1 comment';
     } else {
-      if (!comments) return undefined;
-      let count = 0;
-      for (const file in comments) {
-        if (comments.hasOwnProperty(file)) {
-          const commentArray = comments[file] || [];
-          count += commentArray.length;
-        }
-      }
-      if (count === 0) {
-        return undefined;
-      } else if (count === 1) {
-        return '1 comment';
-      } else {
-        return `${count} comments`;
-      }
+      return `${threadsLength} comments`;
     }
   }
 
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.js b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.js
index de4a72a..af57782 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.js
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.js
@@ -65,7 +65,6 @@
     gr-button {
       margin: 0 -4px;
     }
-    .collapsed gr-comment-list,
     .collapsed gr-thread-list,
     .collapsed .replyBtn,
     .collapsed .deleteBtn,
@@ -246,27 +245,16 @@
                 </gr-button>
               </div>
             </template>
-            <template is="dom-if" if="[[!_isCleanerLogExperimentEnabled]]">
-              <gr-comment-list
-                comments="[[comments]]"
-                change-num="[[changeNum]]"
-                patch-num="[[message._revision_number]]"
-                project-name="[[projectName]]"
-                project-config="[[_projectConfig]]"
-              ></gr-comment-list>
-            </template>
-            <template is="dom-if" if="[[_isCleanerLogExperimentEnabled]]">
-              <gr-thread-list
-                change="[[change]]"
-                hidden$="[[!message.commentThreads.length]]"
-                threads="[[message.commentThreads]]"
-                change-num="[[changeNum]]"
-                logged-in="[[_loggedIn]]"
-                hide-toggle-buttons
-                on-thread-list-modified="_onThreadListModified"
-              >
-              </gr-thread-list>
-            </template>
+            <gr-thread-list
+              change="[[change]]"
+              hidden$="[[!message.commentThreads.length]]"
+              threads="[[message.commentThreads]]"
+              change-num="[[changeNum]]"
+              logged-in="[[_loggedIn]]"
+              hide-toggle-buttons
+              on-thread-list-modified="_onThreadListModified"
+            >
+            </gr-thread-list>
           </template>
         </div>
       </template>
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list-experimental.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list-experimental.js
deleted file mode 100644
index ee5f0b9..0000000
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list-experimental.js
+++ /dev/null
@@ -1,433 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import '@polymer/paper-toggle-button/paper-toggle-button.js';
-import '../../shared/gr-button/gr-button.js';
-import '../../shared/gr-icons/gr-icons.js';
-import '../gr-message/gr-message.js';
-import '../../../styles/shared-styles.js';
-import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
-import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
-import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
-import {PolymerElement} from '@polymer/polymer/polymer-element.js';
-import {htmlTemplate} from './gr-messages-list-experimental_html.js';
-import {KeyboardShortcutBehavior} from '../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.js';
-import {parseDate} from '../../../utils/date-util.js';
-import {MessageTag} from '../../../constants/constants.js';
-import {appContext} from '../../../services/app-context.js';
-
-/**
- * The content of the enum is also used in the UI for the button text.
- *
- * @enum {string}
- */
-const ExpandAllState = {
-  EXPAND_ALL: 'Expand All',
-  COLLAPSE_ALL: 'Collapse All',
-};
-
-/**
- * Computes message author's comments for this change message. The backend
- * sets comment.change_message_id for matching, so this computation is fairly
- * straightforward.
- */
-function computeThreads(message, allMessages, changeComments) {
-  if ([message, allMessages, changeComments].includes(undefined)) {
-    return [];
-  }
-  if (message._index === undefined) {
-    return [];
-  }
-
-  return changeComments.getAllThreadsForChange().filter(
-      thread => thread.comments.map(comment => {
-        // collapse all by default
-        comment.collapsed = true;
-        return comment;
-      }).some(comment => {
-        const condition = comment.change_message_id === message.id;
-        // Since getAllThreadsForChange() always returns a new copy of
-        // all comments we can modify them here without worrying about
-        // polluting other threads.
-        comment.collapsed = !condition;
-        return condition;
-      })
-  );
-}
-
-/**
- * If messages have the same tag, then that influences grouping and whether
- * a message is initally hidden or not, see isImportant(). So we are applying
- * some "magic" rules here in order to hide exactly the right messages.
- *
- * 1. If a message does not have a tag, but is associated with robot comments,
- * then it gets a tag.
- *
- * 2. Use the same tag for some of Gerrit's standard events, if they should be
- * considered one group, e.g. normal and wip patchset uploads.
- *
- * 3. Everything beyond the ~ character is cut off from the tag. That gives
- * tools control over which messages will be hidden.
- */
-function computeTag(message) {
-  if (!message.tag) {
-    const threads = message.commentThreads || [];
-    const comments = threads.map(
-        t => t.comments.find(c => c.change_message_id === message.id));
-    const isRobot = comments.some(c => c && !!c.robot_id);
-    return isRobot ? 'autogenerated:has-robot-comments' : undefined;
-  }
-
-  if (message.tag === MessageTag.TAG_NEW_WIP_PATCHSET) {
-    return MessageTag.TAG_NEW_PATCHSET;
-  }
-  if (message.tag === MessageTag.TAG_UNSET_ASSIGNEE) {
-    return MessageTag.TAG_SET_ASSIGNEE;
-  }
-  if (message.tag === MessageTag.TAG_UNSET_PRIVATE) {
-    return MessageTag.TAG_SET_PRIVATE;
-  }
-  if (message.tag === MessageTag.TAG_SET_WIP) {
-    return MessageTag.TAG_SET_READY;
-  }
-
-  return message.tag.replace(/~.*/, '');
-}
-
-/**
- * Try to set a revision number that makes sense, if none is set. Just copy
- * over the revision number of the next older message. This is mostly relevant
- * for reviewer updates. Other messages should typically have the revision
- * number already set.
- */
-function computeRevision(message, allMessages) {
-  if (message._revision_number > 0) return message._revision_number;
-  let revision = 0;
-  for (const m of allMessages) {
-    if (m.date > message.date) break;
-    if (m._revision_number > revision) revision = m._revision_number;
-  }
-  return revision > 0 ? revision : undefined;
-}
-
-/**
- * Unimportant messages are initially hidden.
- *
- * Human messages are always important. They have an undefined tag.
- *
- * Autogenerated messages are unimportant, if there is a message with the same
- * tag and a higher revision number.
- */
-function computeIsImportant(message, allMessages) {
-  if (!message.tag) return true;
-
-  const hasSameTag = m => m.tag === message.tag;
-  const revNumber = message._revision_number || 0;
-  const hasHigherRevisionNumber = m => m._revision_number > revNumber;
-  return !allMessages.filter(hasSameTag).some(hasHigherRevisionNumber);
-}
-
-export const TEST_ONLY = {
-  computeThreads,
-  computeTag,
-  computeRevision,
-  computeIsImportant,
-};
-
-/**
- * @extends PolymerElement
- */
-class GrMessagesListExperimental extends mixinBehaviors( [
-  KeyboardShortcutBehavior,
-], GestureEventListeners(
-    LegacyElementMixin(
-        PolymerElement))) {
-  static get template() { return htmlTemplate; }
-
-  static get is() { return 'gr-messages-list-experimental'; }
-
-  static get properties() {
-    return {
-      /** @type {?} */
-      change: Object,
-      changeNum: Number,
-      /**
-       * These are just the change messages. They are combined with reviewer
-       * updates below. So _combinedMessages is the more important property.
-       */
-      messages: {
-        type: Array,
-        value() { return []; },
-      },
-      /**
-       * These are just the reviewer updates. They are combined with change
-       * messages above. So _combinedMessages is the more important property.
-       */
-      reviewerUpdates: {
-        type: Array,
-        value() { return []; },
-      },
-      changeComments: Object,
-      projectName: String,
-      showReplyButtons: {
-        type: Boolean,
-        value: false,
-      },
-      labels: Object,
-
-      /**
-       * Keeps track of the state of the "Expand All" toggle button. Note that
-       * you can individually expand/collapse some messages without affecting
-       * the toggle button's state.
-       *
-       * @type {ExpandAllState}
-       */
-      _expandAllState: {
-        type: String,
-        value: ExpandAllState.EXPAND_ALL,
-      },
-      _expandAllTitle: {
-        type: String,
-        computed: '_computeExpandAllTitle(_expandAllState)',
-      },
-
-      _showAllActivity: {
-        type: Boolean,
-        value: false,
-        observer: '_observeShowAllActivity',
-      },
-      /**
-       * The merged array of change messages and reviewer updates.
-       */
-      _combinedMessages: {
-        type: Array,
-        computed: '_computeCombinedMessages(messages, reviewerUpdates, '
-            + 'changeComments)',
-        observer: '_combinedMessagesChanged',
-      },
-
-      _labelExtremes: {
-        type: Object,
-        computed: '_computeLabelExtremes(labels.*)',
-      },
-    };
-  }
-
-  constructor() {
-    super();
-    this.reporting = appContext.reportingService;
-  }
-
-  scrollToMessage(messageID) {
-    const selector = `[data-message-id="${messageID}"]`;
-    const el = this.shadowRoot.querySelector(selector);
-
-    if (!el && this._showAllActivity) {
-      console.warn(`Failed to scroll to message: ${messageID}`);
-      return;
-    }
-    if (!el) {
-      this._showAllActivity = true;
-      setTimeout(() => this.scrollToMessage(messageID));
-      return;
-    }
-
-    el.set('message.expanded', true);
-    let top = el.offsetTop;
-    for (let offsetParent = el.offsetParent;
-      offsetParent;
-      offsetParent = offsetParent.offsetParent) {
-      top += offsetParent.offsetTop;
-    }
-    window.scrollTo(0, top);
-    this._highlightEl(el);
-  }
-
-  _observeShowAllActivity(showAllActivity) {
-    // We have to call render() such that the dom-repeat filter picks up the
-    // change.
-    this.$.messageRepeat.render();
-  }
-
-  /**
-   * Filter for the dom-repeat of combinedMessages.
-   */
-  _isMessageVisible(message) {
-    return this._showAllActivity || message.isImportant;
-  }
-
-  /**
-   * Merges change messages and reviewer updates into one array. Also processes
-   * all messages and updates, aligns or massages some of the properties.
-   */
-  _computeCombinedMessages(messages, reviewerUpdates, changeComments) {
-    const params = [messages, reviewerUpdates, changeComments];
-    if (params.some(o => o === undefined)) return [];
-
-    let mi = 0;
-    let ri = 0;
-    let combinedMessages = [];
-    let mDate;
-    let rDate;
-    for (let i = 0; i < messages.length; i++) {
-      messages[i]._index = i;
-    }
-
-    while (mi < messages.length || ri < reviewerUpdates.length) {
-      if (mi >= messages.length) {
-        combinedMessages = combinedMessages.concat(reviewerUpdates.slice(ri));
-        break;
-      }
-      if (ri >= reviewerUpdates.length) {
-        combinedMessages = combinedMessages.concat(messages.slice(mi));
-        break;
-      }
-      mDate = mDate || parseDate(messages[mi].date);
-      rDate = rDate || parseDate(reviewerUpdates[ri].date);
-      if (rDate < mDate) {
-        combinedMessages.push(reviewerUpdates[ri++]);
-        rDate = null;
-      } else {
-        combinedMessages.push(messages[mi++]);
-        mDate = null;
-      }
-    }
-    combinedMessages.forEach(m => {
-      if (m.expanded === undefined) {
-        m.expanded = false;
-      }
-      m.commentThreads = computeThreads(m, combinedMessages, changeComments);
-      m._revision_number = computeRevision(m, combinedMessages);
-      m.tag = computeTag(m);
-    });
-    // computeIsImportant() depends on tags and revision numbers already being
-    // updated for all messages, so we have to compute this in its own forEach
-    // loop.
-    combinedMessages.forEach(m => {
-      m.isImportant = computeIsImportant(m, combinedMessages);
-    });
-    return combinedMessages;
-  }
-
-  _updateExpandedStateOfAllMessages(exp) {
-    if (this._combinedMessages) {
-      for (let i = 0; i < this._combinedMessages.length; i++) {
-        this._combinedMessages[i].expanded = exp;
-        this.notifyPath(`_combinedMessages.${i}.expanded`);
-      }
-    }
-  }
-
-  _computeExpandAllTitle(_expandAllState) {
-    if (_expandAllState === ExpandAllState.COLLAPSED_ALL) {
-      return this.createTitle(
-          this.Shortcut.COLLAPSE_ALL_MESSAGES, this.ShortcutSection.ACTIONS);
-    }
-    if (_expandAllState === ExpandAllState.EXPAND_ALL) {
-      return this.createTitle(
-          this.Shortcut.EXPAND_ALL_MESSAGES, this.ShortcutSection.ACTIONS);
-    }
-    return '';
-  }
-
-  _highlightEl(el) {
-    const highlightedEls =
-        dom(this.root).querySelectorAll('.highlighted');
-    for (const highlightedEl of highlightedEls) {
-      highlightedEl.classList.remove('highlighted');
-    }
-    function handleAnimationEnd() {
-      el.removeEventListener('animationend', handleAnimationEnd);
-      el.classList.remove('highlighted');
-    }
-    el.addEventListener('animationend', handleAnimationEnd);
-    el.classList.add('highlighted');
-  }
-
-  /**
-   * @param {boolean} expand
-   */
-  handleExpandCollapse(expand) {
-    this._expandAllState = expand ? ExpandAllState.COLLAPSE_ALL
-      : ExpandAllState.EXPAND_ALL;
-    this._updateExpandedStateOfAllMessages(expand);
-  }
-
-  _handleExpandCollapseTap(e) {
-    e.preventDefault();
-    this.handleExpandCollapse(
-        this._expandAllState === ExpandAllState.EXPAND_ALL);
-  }
-
-  _handleAnchorClick(e) {
-    this.scrollToMessage(e.detail.id);
-  }
-
-  _isVisibleShowAllActivityToggle(messages = []) {
-    return messages.some(m => !m.isImportant);
-  }
-
-  _computeHiddenEntriesCount(messages = []) {
-    return messages.filter(m => !m.isImportant).length;
-  }
-
-  /**
-   * This method is for reporting stats only.
-   */
-  _combinedMessagesChanged(combinedMessages) {
-    if (combinedMessages) {
-      if (combinedMessages.length === 0) return;
-      const tags = combinedMessages.map(
-          message => message.tag || message.type ||
-              (message.comments ? 'comments' : 'none'));
-      const tagsCounted = tags.reduce((acc, val) => {
-        acc[val] = (acc[val] || 0) + 1;
-        return acc;
-      }, {all: combinedMessages.length});
-      this.reporting.reportInteraction('messages-count', tagsCounted);
-    }
-  }
-
-  /**
-   * Compute a mapping from label name to objects representing the minimum and
-   * maximum possible values for that label.
-   */
-  _computeLabelExtremes(labelRecord) {
-    const extremes = {};
-    const labels = labelRecord.base;
-    if (!labels) { return extremes; }
-    for (const key of Object.keys(labels)) {
-      if (!labels[key] || !labels[key].values) { continue; }
-      const values = Object.keys(labels[key].values)
-          .map(v => parseInt(v, 10));
-      values.sort((a, b) => a - b);
-      if (!values.length) { continue; }
-      extremes[key] = {min: values[0], max: values[values.length - 1]};
-    }
-    return extremes;
-  }
-
-  /**
-   * Work around a issue on iOS when clicking turns into double tap
-   */
-  _onTapShowAllActivityToggle(e) {
-    e.preventDefault();
-  }
-}
-
-customElements.define(GrMessagesListExperimental.is,
-    GrMessagesListExperimental);
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list-experimental_html.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list-experimental_html.js
deleted file mode 100644
index 212de59..0000000
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list-experimental_html.js
+++ /dev/null
@@ -1,115 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag.js';
-
-export const htmlTemplate = html`
-  <style include="shared-styles">
-    :host {
-      display: flex;
-      justify-content: space-between;
-    }
-    .header {
-      align-items: center;
-      border-top: 1px solid var(--border-color);
-      border-bottom: 1px solid var(--border-color);
-      display: flex;
-      justify-content: space-between;
-      padding: var(--spacing-s) var(--spacing-l);
-    }
-    .highlighted {
-      animation: 3s fadeOut;
-    }
-    @keyframes fadeOut {
-      0% {
-        background-color: var(--emphasis-color);
-      }
-      100% {
-        background-color: var(--view-background-color);
-      }
-    }
-    .container {
-      align-items: center;
-      display: flex;
-    }
-    .hiddenEntries {
-      color: var(--deemphasized-text-color);
-    }
-    gr-message:not(:last-of-type) {
-      border-bottom: 1px solid var(--border-color);
-    }
-    gr-message {
-      background-color: var(--background-color-secondary);
-    }
-    .experimentMessage {
-      padding: var(--spacing-s) var(--spacing-m);
-      background-color: var(--emphasis-color);
-      border-radius: var(--border-radius);
-    }
-    .experimentMessage iron-icon {
-      vertical-align: top;
-    }
-  </style>
-  <div class="header">
-    <div id="showAllActivityToggleContainer" class="container">
-      <template
-        is="dom-if"
-        if="[[_isVisibleShowAllActivityToggle(_combinedMessages)]]"
-      >
-        <paper-toggle-button
-          class="showAllActivityToggle"
-          checked="{{_showAllActivity}}"
-          aria-labelledby="showAllEntriesLabel"
-          role="switch"
-          on-tap="_onTapShowAllActivityToggle"
-        ></paper-toggle-button>
-        <div id="showAllEntriesLabel">
-          <span>Show all entries</span>
-          <span class="hiddenEntries" hidden$="[[_showAllActivity]]">
-            ([[_computeHiddenEntriesCount(_combinedMessages)]] hidden)
-          </span>
-        </div>
-        <span class="transparent separator"></span>
-      </template>
-    </div>
-    <gr-button
-      id="collapse-messages"
-      link=""
-      title="[[_expandAllTitle]]"
-      on-click="_handleExpandCollapseTap"
-    >
-      [[_expandAllState]]
-    </gr-button>
-  </div>
-  <template
-    id="messageRepeat"
-    is="dom-repeat"
-    items="[[_combinedMessages]]"
-    as="message"
-    filter="_isMessageVisible"
-  >
-    <gr-message
-      change="[[change]]"
-      change-num="[[changeNum]]"
-      message="[[message]]"
-      project-name="[[projectName]]"
-      show-reply-button="[[showReplyButtons]]"
-      on-message-anchor-tap="_handleAnchorClick"
-      label-extremes="[[_labelExtremes]]"
-      data-message-id$="[[message.id]]"
-    ></gr-message>
-  </template>
-`;
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list-experimental_test.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list-experimental_test.js
deleted file mode 100644
index 1a0969f..0000000
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list-experimental_test.js
+++ /dev/null
@@ -1,544 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../../../test/common-test-setup-karma.js';
-import '../../diff/gr-comment-api/gr-comment-api.js';
-import './gr-messages-list-experimental.js';
-import {createCommentApiMockWithTemplateElement} from '../../../test/mocks/comment-api.js';
-import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
-import {TEST_ONLY} from './gr-messages-list-experimental.js';
-import {MessageTag} from '../../../constants/constants.js';
-import {html} from '@polymer/polymer/lib/utils/html-tag.js';
-
-createCommentApiMockWithTemplateElement(
-    'gr-messages-list-experimental-comment-mock-api', html`
-     <gr-messages-list-experimental
-         id="messagesList"
-         change-comments="[[_changeComments]]"></gr-messages-list-experimental>
-     <gr-comment-api id="commentAPI"></gr-comment-api>
-`);
-
-const basicFixture = fixtureFromTemplate(html`
-<gr-messages-list-experimental-comment-mock-api>
-  <gr-messages-list-experimental></gr-messages-list-experimental>
-</gr-messages-list-experimental-comment-mock-api>
-`);
-
-const randomMessage = function(opt_params) {
-  const params = opt_params || {};
-  const author1 = {
-    _account_id: 1115495,
-    name: 'Andrew Bonventre',
-    email: 'andybons@chromium.org',
-  };
-  return {
-    id: params.id || Math.random().toString(),
-    date: params.date || '2016-01-12 20:28:33.038000',
-    message: params.message || Math.random().toString(),
-    _revision_number: params._revision_number || 1,
-    author: params.author || author1,
-    tag: params.tag,
-  };
-};
-
-function generateRandomMessages(count) {
-  return new Array(count).fill()
-      .map(() => randomMessage());
-}
-
-suite('gr-messages-list-experimental tests', () => {
-  let element;
-  let messages;
-
-  let commentApiWrapper;
-
-  const getMessages = function() {
-    return dom(element.root).querySelectorAll('gr-message');
-  };
-
-  const MESSAGE_ID_0 = '1234ccc949c6d482b061be6a28e10782abf0e7af';
-  const MESSAGE_ID_1 = '8c19ccc949c6d482b061be6a28e10782abf0e7af';
-  const MESSAGE_ID_2 = 'e7bfdbc842f6b6d8064bc68e0f52b673f40c0ca5';
-
-  const author = {
-    _account_id: 42,
-    name: 'Marvin the Paranoid Android',
-    email: 'marvin@sirius.org',
-  };
-
-  const createComment = function() {
-    return {
-      id: '1a2b3c4d',
-      message: 'some random test text',
-      change_message_id: '8a7b6c5d',
-      updated: '2016-01-01 01:02:03.000000000',
-      line: 1,
-      patch_set: 1,
-      author,
-    };
-  };
-
-  const comments = {
-    file1: [
-      {
-        ...createComment(),
-        change_message_id: MESSAGE_ID_0,
-        in_reply_to: '6505d749_f0bec0aa',
-        author: {
-          email: 'some@email.com',
-          _account_id: 123,
-        },
-      },
-      {
-        ...createComment(),
-        id: '2b3c4d5e',
-        change_message_id: MESSAGE_ID_1,
-        in_reply_to: 'c5912363_6b820105',
-      },
-      {
-        ...createComment(),
-        id: '2b3c4d5e',
-        change_message_id: MESSAGE_ID_1,
-        in_reply_to: '6505d749_f0bec0aa',
-      },
-      {
-        ...createComment(),
-        id: '34ed05d749_10ed44b2',
-        change_message_id: MESSAGE_ID_2,
-      },
-    ],
-    file2: [
-      {
-        ...createComment(),
-        change_message_id: MESSAGE_ID_1,
-        in_reply_to: 'c5912363_4b7d450a',
-        id: '450a935e_4f260d25',
-      },
-    ],
-  };
-
-  suite('basic tests', () => {
-    setup(() => {
-      stub('gr-rest-api-interface', {
-        getConfig() { return Promise.resolve({}); },
-        getLoggedIn() { return Promise.resolve(false); },
-        getDiffComments() { return Promise.resolve(comments); },
-        getDiffRobotComments() { return Promise.resolve({}); },
-        getDiffDrafts() { return Promise.resolve({}); },
-      });
-
-      messages = generateRandomMessages(3);
-      // Element must be wrapped in an element with direct access to the
-      // comment API.
-      commentApiWrapper = basicFixture.instantiate();
-      element = commentApiWrapper.$.messagesList;
-      element.messages = messages;
-
-      // Stub methods on the changeComments object after changeComments has
-      // been initialized.
-      return commentApiWrapper.loadComments();
-    });
-
-    test('expand/collapse all', () => {
-      let allMessageEls = getMessages();
-      for (const message of allMessageEls) {
-        message._expanded = false;
-      }
-      MockInteractions.tap(allMessageEls[1]);
-      assert.isTrue(allMessageEls[1]._expanded);
-
-      MockInteractions.tap(element.shadowRoot
-          .querySelector('#collapse-messages'));
-      allMessageEls = getMessages();
-      for (const message of allMessageEls) {
-        assert.isTrue(message._expanded);
-      }
-
-      MockInteractions.tap(element.shadowRoot
-          .querySelector('#collapse-messages'));
-      allMessageEls = getMessages();
-      for (const message of allMessageEls) {
-        assert.isFalse(message._expanded);
-      }
-    });
-
-    test('expand/collapse from external keypress', () => {
-      // Start with one expanded message. -> not all collapsed
-      element.scrollToMessage(messages[1].id);
-      assert.isFalse([...getMessages()].filter(m => m._expanded).length == 0);
-
-      // Press 'z' -> all collapsed
-      element.handleExpandCollapse(false);
-      assert.isTrue([...getMessages()].filter(m => m._expanded).length == 0);
-
-      // Press 'x' -> all expanded
-      element.handleExpandCollapse(true);
-      assert.isTrue([...getMessages()].filter(m => !m._expanded).length == 0);
-
-      // Press 'z' -> all collapsed
-      element.handleExpandCollapse(false);
-      assert.isTrue([...getMessages()].filter(m => m._expanded).length == 0);
-    });
-
-    test('showAllActivity does not appear when all msgs are important', () => {
-      assert.isOk(element.shadowRoot
-          .querySelector('#showAllActivityToggleContainer'));
-      assert.isNotOk(element.shadowRoot
-          .querySelector('.showAllActivityToggle'));
-    });
-
-    test('scroll to message', () => {
-      const allMessageEls = getMessages();
-      for (const message of allMessageEls) {
-        message.set('message.expanded', false);
-      }
-
-      const scrollToStub = sinon.stub(window, 'scrollTo');
-      const highlightStub = sinon.stub(element, '_highlightEl');
-
-      element.scrollToMessage('invalid');
-
-      for (const message of allMessageEls) {
-        assert.isFalse(message._expanded,
-            'expected gr-message to not be expanded');
-      }
-
-      const messageID = messages[1].id;
-      element.scrollToMessage(messageID);
-      assert.isTrue(
-          element.shadowRoot
-              .querySelector('[data-message-id="' + messageID + '"]')
-              ._expanded);
-
-      assert.isTrue(scrollToStub.calledOnce);
-      assert.isTrue(highlightStub.calledOnce);
-    });
-
-    test('scroll to message offscreen', () => {
-      const scrollToStub = sinon.stub(window, 'scrollTo');
-      const highlightStub = sinon.stub(element, '_highlightEl');
-      element.messages = generateRandomMessages(25);
-      flushAsynchronousOperations();
-      assert.isFalse(scrollToStub.called);
-      assert.isFalse(highlightStub.called);
-
-      const messageID = element.messages[1].id;
-      element.scrollToMessage(messageID);
-      assert.isTrue(scrollToStub.calledOnce);
-      assert.isTrue(highlightStub.calledOnce);
-      assert.isTrue(
-          element.shadowRoot
-              .querySelector('[data-message-id="' + messageID + '"]')
-              ._expanded);
-    });
-
-    test('associating messages with comments', () => {
-      const messages = [].concat(
-          randomMessage(),
-          {
-            _index: 5,
-            _revision_number: 4,
-            message: 'Uploaded patch set 4.',
-            date: '2016-09-28 13:36:33.000000000',
-            author,
-            id: '8c19ccc949c6d482b061be6a28e10782abf0e7af',
-          },
-          {
-            _index: 6,
-            _revision_number: 4,
-            message: 'Patch Set 4:\n\n(6 comments)',
-            date: '2016-09-28 13:36:33.000000000',
-            author,
-            id: 'e7bfdbc842f6b6d8064bc68e0f52b673f40c0ca5',
-          }
-      );
-      element.messages = messages;
-      flushAsynchronousOperations();
-      const messageElements = getMessages();
-      assert.equal(messageElements.length, messages.length);
-      assert.deepEqual(messageElements[1].message, messages[1]);
-      assert.deepEqual(messageElements[2].message, messages[2]);
-    });
-
-    test('threads', () => {
-      const messages = [
-        {
-          _index: 5,
-          _revision_number: 4,
-          message: 'Uploaded patch set 4.',
-          date: '2016-09-28 13:36:33.000000000',
-          author,
-          id: '8c19ccc949c6d482b061be6a28e10782abf0e7af',
-        },
-      ];
-      element.messages = messages;
-      flushAsynchronousOperations();
-      const messageElements = getMessages();
-      // threads
-      assert.equal(
-          messageElements[0].message.commentThreads.length,
-          3);
-      // first thread contains 1 comment
-      assert.equal(
-          messageElements[0].message.commentThreads[0].comments.length,
-          1);
-    });
-
-    test('updateTag human message', () => {
-      const m = randomMessage();
-      assert.equal(TEST_ONLY.computeTag(m), undefined);
-    });
-
-    test('updateTag nothing to change', () => {
-      const m = randomMessage();
-      const tag = 'something-normal';
-      m.tag = tag;
-      assert.equal(TEST_ONLY.computeTag(m), tag);
-    });
-
-    test('updateTag TAG_NEW_WIP_PATCHSET', () => {
-      const m = randomMessage();
-      m.tag = MessageTag.TAG_NEW_WIP_PATCHSET;
-      assert.equal(TEST_ONLY.computeTag(m), MessageTag.TAG_NEW_PATCHSET);
-    });
-
-    test('updateTag remove postfix', () => {
-      const m = randomMessage();
-      m.tag = 'something~withpostfix';
-      assert.equal(TEST_ONLY.computeTag(m), 'something');
-    });
-
-    test('updateTag with robot comments', () => {
-      const m = randomMessage();
-      m.commentThreads = [{
-        comments: [{
-          robot_id: 'id314',
-          change_message_id: m.id,
-        }],
-      }];
-      assert.notEqual(TEST_ONLY.computeTag(m), undefined);
-    });
-
-    test('setRevisionNumber nothing to change', () => {
-      const m1 = randomMessage();
-      const m2 = randomMessage();
-      assert.equal(TEST_ONLY.computeRevision(m1, [m1, m2]), 1);
-      assert.equal(TEST_ONLY.computeRevision(m2, [m1, m2]), 1);
-    });
-
-    test('setRevisionNumber reviewer updates', () => {
-      const m1 = randomMessage(
-          {
-            tag: MessageTag.TAG_REVIEWER_UPDATE,
-            date: '2020-01-01 10:00:00.000000000',
-          });
-      m1._revision_number = undefined;
-      const m2 = randomMessage(
-          {
-            date: '2020-01-02 10:00:00.000000000',
-          });
-      m2._revision_number = 1;
-      const m3 = randomMessage(
-          {
-            tag: MessageTag.TAG_REVIEWER_UPDATE,
-            date: '2020-01-03 10:00:00.000000000',
-          });
-      m3._revision_number = undefined;
-      const m4 = randomMessage(
-          {
-            date: '2020-01-04 10:00:00.000000000',
-          });
-      m4._revision_number = 2;
-      const m5 = randomMessage(
-          {
-            tag: MessageTag.TAG_REVIEWER_UPDATE,
-            date: '2020-01-05 10:00:00.000000000',
-          });
-      m5._revision_number = undefined;
-      const allMessages = [m1, m2, m3, m4, m5];
-      assert.equal(TEST_ONLY.computeRevision(m1, allMessages), undefined);
-      assert.equal(TEST_ONLY.computeRevision(m2, allMessages), 1);
-      assert.equal(TEST_ONLY.computeRevision(m3, allMessages), 1);
-      assert.equal(TEST_ONLY.computeRevision(m4, allMessages), 2);
-      assert.equal(TEST_ONLY.computeRevision(m5, allMessages), 2);
-    });
-
-    test('isImportant human message', () => {
-      const m = randomMessage();
-      assert.isTrue(TEST_ONLY.computeIsImportant(m, []));
-      assert.isTrue(TEST_ONLY.computeIsImportant(m, [m]));
-    });
-
-    test('isImportant even with a tag', () => {
-      const m1 = randomMessage();
-      const m2 = randomMessage({tag: 'autogenerated:gerrit1'});
-      const m3 = randomMessage({tag: 'autogenerated:gerrit2'});
-      assert.isTrue(TEST_ONLY.computeIsImportant(m2, []));
-      assert.isTrue(TEST_ONLY.computeIsImportant(m1, [m1, m2, m3]));
-      assert.isTrue(TEST_ONLY.computeIsImportant(m2, [m1, m2, m3]));
-      assert.isTrue(TEST_ONLY.computeIsImportant(m3, [m1, m2, m3]));
-    });
-
-    test('isImportant filters same tag and older revision', () => {
-      const m1 = randomMessage({tag: 'auto', _revision_number: 2});
-      const m2 = randomMessage({tag: 'auto', _revision_number: 1});
-      const m3 = randomMessage({tag: 'auto'});
-      assert.isTrue(TEST_ONLY.computeIsImportant(m1, [m1]));
-      assert.isTrue(TEST_ONLY.computeIsImportant(m2, [m2]));
-      assert.isTrue(TEST_ONLY.computeIsImportant(m1, [m1, m2]));
-      assert.isFalse(TEST_ONLY.computeIsImportant(m2, [m1, m2]));
-      assert.isTrue(TEST_ONLY.computeIsImportant(m1, [m1, m3]));
-      assert.isFalse(TEST_ONLY.computeIsImportant(m3, [m1, m3]));
-      assert.isTrue(TEST_ONLY.computeIsImportant(m1, [m1, m2, m3]));
-      assert.isFalse(TEST_ONLY.computeIsImportant(m2, [m1, m2, m3]));
-      assert.isFalse(TEST_ONLY.computeIsImportant(m3, [m1, m2, m3]));
-    });
-
-    test('isImportant is evaluated after tag update', () => {
-      const m1 = randomMessage(
-          {tag: MessageTag.TAG_NEW_PATCHSET, _revision_number: 1});
-      const m2 = randomMessage(
-          {tag: MessageTag.TAG_NEW_WIP_PATCHSET, _revision_number: 2});
-      element.messages = [m1, m2];
-      flushAsynchronousOperations();
-      assert.isFalse(m1.isImportant);
-      assert.isTrue(m2.isImportant);
-    });
-
-    test('messages without author do not throw', () => {
-      const messages = [{
-        _index: 5,
-        _revision_number: 4,
-        message: 'Uploaded patch set 4.',
-        date: '2016-09-28 13:36:33.000000000',
-        id: '8c19ccc949c6d482b061be6a28e10782abf0e7af',
-      }];
-      element.messages = messages;
-      flushAsynchronousOperations();
-      const messageEls = getMessages();
-      assert.equal(messageEls.length, 1);
-      assert.equal(messageEls[0].message.message, messages[0].message);
-    });
-  });
-
-  suite('gr-messages-list-experimental automate tests', () => {
-    let element;
-    let messages;
-
-    let commentApiWrapper;
-
-    setup(() => {
-      stub('gr-rest-api-interface', {
-        getConfig() { return Promise.resolve({}); },
-        getLoggedIn() { return Promise.resolve(false); },
-        getDiffComments() { return Promise.resolve({}); },
-        getDiffRobotComments() { return Promise.resolve({}); },
-        getDiffDrafts() { return Promise.resolve({}); },
-      });
-
-      messages = [
-        randomMessage(),
-        randomMessage({tag: 'auto', _revision_number: 2}),
-        randomMessage({tag: 'auto', _revision_number: 3}),
-      ];
-
-      // Element must be wrapped in an element with direct access to the
-      // comment API.
-      commentApiWrapper = basicFixture.instantiate();
-      element = commentApiWrapper.$.messagesList;
-      sinon.spy(commentApiWrapper.$.commentAPI, 'loadAll');
-      element.messages = messages;
-
-      // Stub methods on the changeComments object after changeComments has
-      // been initialized.
-      return commentApiWrapper.loadComments();
-    });
-
-    test('hide autogenerated button is not hidden', () => {
-      const toggle = dom(element.root).querySelector('.showAllActivityToggle');
-      assert.isOk(toggle);
-    });
-
-    test('one unimportant message is hidden initially', () => {
-      const displayedMsgs = dom(element.root).querySelectorAll('gr-message');
-      assert.equal(displayedMsgs.length, 2);
-    });
-
-    test('unimportant messages hidden after toggle', () => {
-      element._showAllActivity = true;
-      const toggle = dom(element.root).querySelector('.showAllActivityToggle');
-      assert.isOk(toggle);
-      MockInteractions.tap(toggle);
-      flushAsynchronousOperations();
-      const displayedMsgs = dom(element.root).querySelectorAll('gr-message');
-      assert.equal(displayedMsgs.length, 2);
-    });
-
-    test('unimportant messages shown after toggle', () => {
-      element._showAllActivity = false;
-      const toggle = dom(element.root).querySelector('.showAllActivityToggle');
-      assert.isOk(toggle);
-      MockInteractions.tap(toggle);
-      flushAsynchronousOperations();
-      const displayedMsgs = dom(element.root).querySelectorAll('gr-message');
-      assert.equal(displayedMsgs.length, 3);
-    });
-
-    test('_computeLabelExtremes', () => {
-      const computeSpy = sinon.spy(element, '_computeLabelExtremes');
-
-      element.labels = null;
-      assert.isTrue(computeSpy.calledOnce);
-      assert.deepEqual(computeSpy.lastCall.returnValue, {});
-
-      element.labels = {};
-      assert.isTrue(computeSpy.calledTwice);
-      assert.deepEqual(computeSpy.lastCall.returnValue, {});
-
-      element.labels = {'my-label': {}};
-      assert.isTrue(computeSpy.calledThrice);
-      assert.deepEqual(computeSpy.lastCall.returnValue, {});
-
-      element.labels = {'my-label': {values: {}}};
-      assert.equal(computeSpy.callCount, 4);
-      assert.deepEqual(computeSpy.lastCall.returnValue, {});
-
-      element.labels = {'my-label': {values: {'-12': {}}}};
-      assert.equal(computeSpy.callCount, 5);
-      assert.deepEqual(computeSpy.lastCall.returnValue,
-          {'my-label': {min: -12, max: -12}});
-
-      element.labels = {
-        'my-label': {values: {'-2': {}, '-1': {}, '0': {}, '+1': {}, '+2': {}}},
-      };
-      assert.equal(computeSpy.callCount, 6);
-      assert.deepEqual(computeSpy.lastCall.returnValue,
-          {'my-label': {min: -2, max: 2}});
-
-      element.labels = {
-        'my-label': {values: {'-12': {}}},
-        'other-label': {values: {'-1': {}, ' 0': {}, '+1': {}}},
-      };
-      assert.equal(computeSpy.callCount, 7);
-      assert.deepEqual(computeSpy.lastCall.returnValue, {
-        'my-label': {min: -12, max: -12},
-        'other-label': {min: -1, max: 1},
-      });
-    });
-  });
-});
-
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
index 30482e2..cd61396 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
@@ -1,6 +1,6 @@
 /**
  * @license
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,9 +16,10 @@
  */
 import '@polymer/paper-toggle-button/paper-toggle-button.js';
 import '../../shared/gr-button/gr-button.js';
+import '../../shared/gr-icons/gr-icons.js';
 import '../gr-message/gr-message.js';
 import '../../../styles/shared-styles.js';
-import {flush, dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
+import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
 import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
 import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
 import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
@@ -26,16 +27,9 @@
 import {htmlTemplate} from './gr-messages-list_html.js';
 import {KeyboardShortcutBehavior} from '../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.js';
 import {parseDate} from '../../../utils/date-util.js';
+import {MessageTag} from '../../../constants/constants.js';
 import {appContext} from '../../../services/app-context.js';
 
-const MAX_INITIAL_SHOWN_MESSAGES = 20;
-const MESSAGES_INCREMENT = 5;
-
-const ReportingEvent = {
-  SHOW_ALL: 'show-all-messages',
-  SHOW_MORE: 'show-more-messages',
-};
-
 /**
  * The content of the enum is also used in the UI for the button text.
  *
@@ -47,6 +41,114 @@
 };
 
 /**
+ * Computes message author's comments for this change message. The backend
+ * sets comment.change_message_id for matching, so this computation is fairly
+ * straightforward.
+ */
+function computeThreads(message, allMessages, changeComments) {
+  if ([message, allMessages, changeComments].includes(undefined)) {
+    return [];
+  }
+  if (message._index === undefined) {
+    return [];
+  }
+
+  return changeComments.getAllThreadsForChange().filter(
+      thread => thread.comments.map(comment => {
+        // collapse all by default
+        comment.collapsed = true;
+        return comment;
+      }).some(comment => {
+        const condition = comment.change_message_id === message.id;
+        // Since getAllThreadsForChange() always returns a new copy of
+        // all comments we can modify them here without worrying about
+        // polluting other threads.
+        comment.collapsed = !condition;
+        return condition;
+      })
+  );
+}
+
+/**
+ * If messages have the same tag, then that influences grouping and whether
+ * a message is initally hidden or not, see isImportant(). So we are applying
+ * some "magic" rules here in order to hide exactly the right messages.
+ *
+ * 1. If a message does not have a tag, but is associated with robot comments,
+ * then it gets a tag.
+ *
+ * 2. Use the same tag for some of Gerrit's standard events, if they should be
+ * considered one group, e.g. normal and wip patchset uploads.
+ *
+ * 3. Everything beyond the ~ character is cut off from the tag. That gives
+ * tools control over which messages will be hidden.
+ */
+function computeTag(message) {
+  if (!message.tag) {
+    const threads = message.commentThreads || [];
+    const comments = threads.map(
+        t => t.comments.find(c => c.change_message_id === message.id));
+    const isRobot = comments.some(c => c && !!c.robot_id);
+    return isRobot ? 'autogenerated:has-robot-comments' : undefined;
+  }
+
+  if (message.tag === MessageTag.TAG_NEW_WIP_PATCHSET) {
+    return MessageTag.TAG_NEW_PATCHSET;
+  }
+  if (message.tag === MessageTag.TAG_UNSET_ASSIGNEE) {
+    return MessageTag.TAG_SET_ASSIGNEE;
+  }
+  if (message.tag === MessageTag.TAG_UNSET_PRIVATE) {
+    return MessageTag.TAG_SET_PRIVATE;
+  }
+  if (message.tag === MessageTag.TAG_SET_WIP) {
+    return MessageTag.TAG_SET_READY;
+  }
+
+  return message.tag.replace(/~.*/, '');
+}
+
+/**
+ * Try to set a revision number that makes sense, if none is set. Just copy
+ * over the revision number of the next older message. This is mostly relevant
+ * for reviewer updates. Other messages should typically have the revision
+ * number already set.
+ */
+function computeRevision(message, allMessages) {
+  if (message._revision_number > 0) return message._revision_number;
+  let revision = 0;
+  for (const m of allMessages) {
+    if (m.date > message.date) break;
+    if (m._revision_number > revision) revision = m._revision_number;
+  }
+  return revision > 0 ? revision : undefined;
+}
+
+/**
+ * Unimportant messages are initially hidden.
+ *
+ * Human messages are always important. They have an undefined tag.
+ *
+ * Autogenerated messages are unimportant, if there is a message with the same
+ * tag and a higher revision number.
+ */
+function computeIsImportant(message, allMessages) {
+  if (!message.tag) return true;
+
+  const hasSameTag = m => m.tag === message.tag;
+  const revNumber = message._revision_number || 0;
+  const hasHigherRevisionNumber = m => m._revision_number > revNumber;
+  return !allMessages.filter(hasSameTag).some(hasHigherRevisionNumber);
+}
+
+export const TEST_ONLY = {
+  computeThreads,
+  computeTag,
+  computeRevision,
+  computeIsImportant,
+};
+
+/**
  * @extends PolymerElement
  */
 class GrMessagesList extends mixinBehaviors( [
@@ -60,11 +162,21 @@
 
   static get properties() {
     return {
+      /** @type {?} */
+      change: Object,
       changeNum: Number,
+      /**
+       * These are just the change messages. They are combined with reviewer
+       * updates below. So _combinedMessages is the more important property.
+       */
       messages: {
         type: Array,
         value() { return []; },
       },
+      /**
+       * These are just the reviewer updates. They are combined with change
+       * messages above. So _combinedMessages is the more important property.
+       */
       reviewerUpdates: {
         type: Array,
         value() { return []; },
@@ -93,24 +205,19 @@
         computed: '_computeExpandAllTitle(_expandAllState)',
       },
 
-      _hideAutomated: {
+      _showAllActivity: {
         type: Boolean,
         value: false,
+        observer: '_observeShowAllActivity',
       },
       /**
-       * The messages after processing and including merged reviewer updates.
+       * The merged array of change messages and reviewer updates.
        */
-      _processedMessages: {
+      _combinedMessages: {
         type: Array,
-        computed: '_computeItems(messages, reviewerUpdates)',
-        observer: '_processedMessagesChanged',
-      },
-      /**
-       * The subset of _processedMessages that is visible to the user.
-       */
-      _visibleMessages: {
-        type: Array,
-        value() { return []; },
+        computed: '_computeCombinedMessages(messages, reviewerUpdates, '
+            + 'changeComments)',
+        observer: '_combinedMessagesChanged',
       },
 
       _labelExtremes: {
@@ -126,27 +233,17 @@
   }
 
   scrollToMessage(messageID) {
-    let el = this.shadowRoot
-        .querySelector('[data-message-id="' + messageID + '"]');
-    // If the message is hidden, expand the hidden messages back to that
-    // point.
-    if (!el) {
-      let index;
-      for (index = 0; index < this._processedMessages.length; index++) {
-        if (this._processedMessages[index].id === messageID) {
-          break;
-        }
-      }
-      if (index === this._processedMessages.length) { return; }
+    const selector = `[data-message-id="${messageID}"]`;
+    const el = this.shadowRoot.querySelector(selector);
 
-      const newMessages = this._processedMessages.slice(index,
-          -this._visibleMessages.length);
-      // Add newMessages to the beginning of _visibleMessages.
-      this.splice(...['_visibleMessages', 0, 0].concat(newMessages));
-      // Allow the dom-repeat to stamp.
-      flush();
-      el = this.shadowRoot
-          .querySelector('[data-message-id="' + messageID + '"]');
+    if (!el && this._showAllActivity) {
+      console.warn(`Failed to scroll to message: ${messageID}`);
+      return;
+    }
+    if (!el) {
+      this._showAllActivity = true;
+      setTimeout(() => this.scrollToMessage(messageID));
+      return;
     }
 
     el.set('message.expanded', true);
@@ -160,22 +257,30 @@
     this._highlightEl(el);
   }
 
-  _isAutomated(message) {
-    return !!(message.reviewer ||
-        (message.tag && message.tag.startsWith('autogenerated')));
+  _observeShowAllActivity(showAllActivity) {
+    // We have to call render() such that the dom-repeat filter picks up the
+    // change.
+    this.$.messageRepeat.render();
   }
 
-  _computeItems(messages, reviewerUpdates) {
-    // Polymer 2: check for undefined
-    if ([messages, reviewerUpdates].includes(undefined)) {
-      return [];
-    }
+  /**
+   * Filter for the dom-repeat of combinedMessages.
+   */
+  _isMessageVisible(message) {
+    return this._showAllActivity || message.isImportant;
+  }
 
-    messages = messages || [];
-    reviewerUpdates = reviewerUpdates || [];
+  /**
+   * Merges change messages and reviewer updates into one array. Also processes
+   * all messages and updates, aligns or massages some of the properties.
+   */
+  _computeCombinedMessages(messages, reviewerUpdates, changeComments) {
+    const params = [messages, reviewerUpdates, changeComments];
+    if (params.some(o => o === undefined)) return [];
+
     let mi = 0;
     let ri = 0;
-    let result = [];
+    let combinedMessages = [];
     let mDate;
     let rDate;
     for (let i = 0; i < messages.length; i++) {
@@ -184,44 +289,45 @@
 
     while (mi < messages.length || ri < reviewerUpdates.length) {
       if (mi >= messages.length) {
-        result = result.concat(reviewerUpdates.slice(ri));
+        combinedMessages = combinedMessages.concat(reviewerUpdates.slice(ri));
         break;
       }
       if (ri >= reviewerUpdates.length) {
-        result = result.concat(messages.slice(mi));
+        combinedMessages = combinedMessages.concat(messages.slice(mi));
         break;
       }
       mDate = mDate || parseDate(messages[mi].date);
       rDate = rDate || parseDate(reviewerUpdates[ri].date);
       if (rDate < mDate) {
-        result.push(reviewerUpdates[ri++]);
+        combinedMessages.push(reviewerUpdates[ri++]);
         rDate = null;
       } else {
-        result.push(messages[mi++]);
+        combinedMessages.push(messages[mi++]);
         mDate = null;
       }
     }
-    result.forEach(m => {
+    combinedMessages.forEach(m => {
       if (m.expanded === undefined) {
         m.expanded = false;
       }
+      m.commentThreads = computeThreads(m, combinedMessages, changeComments);
+      m._revision_number = computeRevision(m, combinedMessages);
+      m.tag = computeTag(m);
     });
-    return result;
+    // computeIsImportant() depends on tags and revision numbers already being
+    // updated for all messages, so we have to compute this in its own forEach
+    // loop.
+    combinedMessages.forEach(m => {
+      m.isImportant = computeIsImportant(m, combinedMessages);
+    });
+    return combinedMessages;
   }
 
-  _updateExpandedStateOfAllMessages(expanded) {
-    if (this._processedMessages) {
-      for (let i = 0; i < this._processedMessages.length; i++) {
-        this._processedMessages[i].expanded = expanded;
-      }
-    }
-    // _visibleMessages is a subarray of _processedMessages
-    // _processedMessages contains all items from _visibleMessages
-    // At this point all _visibleMessages.expanded values are set,
-    // and notifyPath must be used to notify Polymer about changes.
-    if (this._visibleMessages) {
-      for (let i = 0; i < this._visibleMessages.length; i++) {
-        this.notifyPath(`_visibleMessages.${i}.expanded`);
+  _updateExpandedStateOfAllMessages(exp) {
+    if (this._combinedMessages) {
+      for (let i = 0; i < this._combinedMessages.length; i++) {
+        this._combinedMessages[i].expanded = exp;
+        this.notifyPath(`_combinedMessages.${i}.expanded`);
       }
     }
   }
@@ -271,181 +377,31 @@
     this.scrollToMessage(e.detail.id);
   }
 
-  _hasAutomatedMessages(messages) {
-    if (!messages) { return false; }
-    for (const message of messages) {
-      if (this._isAutomated(message)) {
-        return true;
-      }
-    }
-    return false;
+  _isVisibleShowAllActivityToggle(messages = []) {
+    return messages.some(m => !m.isImportant);
+  }
+
+  _computeHiddenEntriesCount(messages = []) {
+    return messages.filter(m => !m.isImportant).length;
   }
 
   /**
-   * Computes message author's file comments for change's message.
-   * Method uses this.messages to find next message and relies on messages
-   * to be sorted by date field descending.
-   *
-   * @param {!Object} changeComments changeComment object, which includes
-   *     a method to get all published comments (including robot comments),
-   *     which returns a Hash of arrays of comments, filename as key.
-   * @param {!Object} message
-   * @return {!Object} Hash of arrays of comments, filename as key.
+   * This method is for reporting stats only.
    */
-  _computeCommentsForMessage(changeComments, message) {
-    if ([changeComments, message].includes(undefined)) {
-      return {};
-    }
-    const comments = changeComments.getAllPublishedComments();
-    if (message._index === undefined || !comments || !this.messages) {
-      return {};
-    }
-    const messages = this.messages || [];
-    const index = message._index;
-    const authorId = message.author && message.author._account_id;
-    const mDate = parseDate(message.date).getTime();
-    // NB: Messages array has oldest messages first.
-    let nextMDate;
-    if (index > 0) {
-      for (let i = index - 1; i >= 0; i--) {
-        if (messages[i] && messages[i].author &&
-            messages[i].author._account_id === authorId) {
-          nextMDate = parseDate(messages[i].date).getTime();
-          break;
-        }
-      }
-    }
-    const msgComments = {};
-    for (const file in comments) {
-      if (!comments.hasOwnProperty(file)) { continue; }
-      const fileComments = comments[file];
-      for (let i = 0; i < fileComments.length; i++) {
-        if (fileComments[i].author &&
-            fileComments[i].author._account_id !== authorId) {
-          continue;
-        }
-        const cDate = parseDate(fileComments[i].updated).getTime();
-        if (cDate <= mDate) {
-          if (nextMDate && cDate <= nextMDate) {
-            continue;
-          }
-          msgComments[file] = msgComments[file] || [];
-          msgComments[file].push(fileComments[i]);
-        }
-      }
-    }
-    return msgComments;
-  }
-
-  /**
-   * Returns the number of messages to splice to the beginning of
-   * _visibleMessages. This is the minimum of the total number of messages
-   * remaining in the list and the number of messages needed to display five
-   * more visible messages in the list.
-   */
-  _getDelta(visibleMessages, messages, hideAutomated) {
-    if ([visibleMessages, messages].includes(undefined)) {
-      return 0;
-    }
-
-    let delta = MESSAGES_INCREMENT;
-    const msgsRemaining = messages.length - visibleMessages.length;
-
-    if (hideAutomated) {
-      let counter = 0;
-      let i;
-      for (i = msgsRemaining; i > 0 && counter < MESSAGES_INCREMENT; i--) {
-        if (!this._isAutomated(messages[i - 1])) { counter++; }
-      }
-      delta = msgsRemaining - i;
-    }
-    return Math.min(msgsRemaining, delta);
-  }
-
-  /**
-   * Gets the number of messages that would be visible, but do not currently
-   * exist in _visibleMessages.
-   */
-  _numRemaining(visibleMessages, messages, hideAutomated) {
-    if ([visibleMessages, messages].includes(undefined)) {
-      return 0;
-    }
-
-    if (hideAutomated) {
-      return this._getHumanMessages(messages).length -
-          this._getHumanMessages(visibleMessages).length;
-    }
-    return messages.length - visibleMessages.length;
-  }
-
-  _computeIncrementText(visibleMessages, messages, hideAutomated) {
-    let delta = this._getDelta(visibleMessages, messages, hideAutomated);
-    delta = Math.min(
-        this._numRemaining(visibleMessages, messages, hideAutomated), delta);
-    return 'Show ' + Math.min(MESSAGES_INCREMENT, delta) + ' more';
-  }
-
-  _getHumanMessages(messages) {
-    return messages.filter(msg => !this._isAutomated(msg));
-  }
-
-  _computeShowHideTextHidden(visibleMessages, messages,
-      hideAutomated) {
-    if ([visibleMessages, messages].includes(undefined)) {
-      return 0;
-    }
-
-    if (hideAutomated) {
-      messages = this._getHumanMessages(messages);
-      visibleMessages = this._getHumanMessages(visibleMessages);
-    }
-    return visibleMessages.length >= messages.length;
-  }
-
-  _handleShowAllTap() {
-    this._visibleMessages = this._processedMessages;
-    this.reporting.reportInteraction(ReportingEvent.SHOW_ALL);
-  }
-
-  _handleIncrementShownMessages() {
-    const delta = this._getDelta(this._visibleMessages,
-        this._processedMessages, this._hideAutomated);
-    const len = this._visibleMessages.length;
-    const newMessages = this._processedMessages.slice(-(len + delta), -len);
-    // Add newMessages to the beginning of _visibleMessages
-    this.splice(...['_visibleMessages', 0, 0].concat(newMessages));
-    this.reporting.reportInteraction(ReportingEvent.SHOW_MORE);
-  }
-
-  _processedMessagesChanged(messages) {
-    if (messages) {
-      this._visibleMessages = messages.slice(-MAX_INITIAL_SHOWN_MESSAGES);
-
-      if (messages.length === 0) return;
-      const tags = messages.map(message => message.tag || message.type ||
-          (message.comments ? 'comments' : 'none'));
+  _combinedMessagesChanged(combinedMessages) {
+    if (combinedMessages) {
+      if (combinedMessages.length === 0) return;
+      const tags = combinedMessages.map(
+          message => message.tag || message.type ||
+              (message.comments ? 'comments' : 'none'));
       const tagsCounted = tags.reduce((acc, val) => {
         acc[val] = (acc[val] || 0) + 1;
         return acc;
-      }, {all: messages.length});
+      }, {all: combinedMessages.length});
       this.reporting.reportInteraction('messages-count', tagsCounted);
     }
   }
 
-  _computeNumMessagesText(visibleMessages, messages,
-      hideAutomated) {
-    const total =
-        this._numRemaining(visibleMessages, messages, hideAutomated);
-    return total === 1 ? 'Show 1 message' : 'Show all ' + total + ' messages';
-  }
-
-  _computeIncrementHidden(visibleMessages, messages,
-      hideAutomated) {
-    const total =
-        this._numRemaining(visibleMessages, messages, hideAutomated);
-    return total <= this._getDelta(visibleMessages, messages, hideAutomated);
-  }
-
   /**
    * Compute a mapping from label name to objects representing the minimum and
    * maximum possible values for that label.
@@ -468,9 +424,10 @@
   /**
    * Work around a issue on iOS when clicking turns into double tap
    */
-  _onTapHideAutomated(e) {
+  _onTapShowAllActivityToggle(e) {
     e.preventDefault();
   }
 }
 
-customElements.define(GrMessagesList.is, GrMessagesList);
+customElements.define(GrMessagesList.is,
+    GrMessagesList);
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_html.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_html.js
index 2636a54..c696c8c 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_html.js
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_html.js
@@ -18,8 +18,7 @@
 
 export const htmlTemplate = html`
   <style include="shared-styles">
-    :host,
-    .messageListControls {
+    :host {
       display: flex;
       justify-content: space-between;
     }
@@ -31,9 +30,6 @@
       justify-content: space-between;
       padding: var(--spacing-s) var(--spacing-l);
     }
-    #messageControlsContainer {
-      padding: 0 var(--spacing-l);
-    }
     .highlighted {
       animation: 3s fadeOut;
     }
@@ -45,47 +41,42 @@
         background-color: var(--view-background-color);
       }
     }
-    #messageControlsContainer {
-      align-items: center;
-      background-color: var(--background-color-secondary);
-      border-bottom: 1px solid var(--border-color);
-      display: flex;
-      height: 2.25em;
-      justify-content: center;
-    }
-    #messageControlsContainer gr-button {
-      padding: var(--spacing-s) 0;
-    }
     .container {
       align-items: center;
       display: flex;
     }
+    .hiddenEntries {
+      color: var(--deemphasized-text-color);
+    }
     gr-message:not(:last-of-type) {
       border-bottom: 1px solid var(--border-color);
     }
-    gr-message:nth-child(2n) {
+    gr-message {
       background-color: var(--background-color-secondary);
     }
-    gr-message:nth-child(2n + 1) {
-      background-color: var(--background-color-tertiary);
-    }
   </style>
   <div class="header">
-    <span
-      id="automatedMessageToggleContainer"
-      class="container"
-      hidden$="[[!_hasAutomatedMessages(messages)]]"
-    >
-      <paper-toggle-button
-        id="automatedMessageToggle"
-        checked="{{_hideAutomated}}"
-        aria-labelledby="onlyCommentsLabel"
-        role="switch"
-        on-tap="_onTapHideAutomated"
-      ></paper-toggle-button>
-      <span id="onlyCommentsLabel">Only comments</span>
-      <span class="transparent separator"></span>
-    </span>
+    <div id="showAllActivityToggleContainer" class="container">
+      <template
+        is="dom-if"
+        if="[[_isVisibleShowAllActivityToggle(_combinedMessages)]]"
+      >
+        <paper-toggle-button
+          class="showAllActivityToggle"
+          checked="{{_showAllActivity}}"
+          aria-labelledby="showAllEntriesLabel"
+          role="switch"
+          on-tap="_onTapShowAllActivityToggle"
+        ></paper-toggle-button>
+        <div id="showAllEntriesLabel">
+          <span>Show all entries</span>
+          <span class="hiddenEntries" hidden$="[[_showAllActivity]]">
+            ([[_computeHiddenEntriesCount(_combinedMessages)]] hidden)
+          </span>
+        </div>
+        <span class="transparent separator"></span>
+      </template>
+    </div>
     <gr-button
       id="collapse-messages"
       link=""
@@ -95,35 +86,17 @@
       [[_expandAllState]]
     </gr-button>
   </div>
-  <span
-    id="messageControlsContainer"
-    hidden$="[[_computeShowHideTextHidden(_visibleMessages, _processedMessages, _hideAutomated, _visibleMessages.length)]]"
+  <template
+    id="messageRepeat"
+    is="dom-repeat"
+    items="[[_combinedMessages]]"
+    as="message"
+    filter="_isMessageVisible"
   >
-    <gr-button id="oldMessagesBtn" link="" on-click="_handleShowAllTap">
-      [[_computeNumMessagesText(_visibleMessages, _processedMessages,
-      _hideAutomated, _visibleMessages.length)]]
-    </gr-button>
-    <span
-      class="container"
-      hidden$="[[_computeIncrementHidden(_visibleMessages, _processedMessages, _hideAutomated, _visibleMessages.length)]]"
-    >
-      <span class="transparent separator"></span>
-      <gr-button
-        id="incrementMessagesBtn"
-        link=""
-        on-click="_handleIncrementShownMessages"
-      >
-        [[_computeIncrementText(_visibleMessages, _processedMessages,
-        _hideAutomated, _visibleMessages.length)]]
-      </gr-button>
-    </span>
-  </span>
-  <template is="dom-repeat" items="[[_visibleMessages]]" as="message">
     <gr-message
+      change="[[change]]"
       change-num="[[changeNum]]"
       message="[[message]]"
-      comments="[[_computeCommentsForMessage(changeComments, message)]]"
-      hide-automated="[[_hideAutomated]]"
       project-name="[[projectName]]"
       show-reply-button="[[showReplyButtons]]"
       on-message-anchor-tap="_handleAnchorClick"
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.js
index 4f05dfc..c3245fe 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.js
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.js
@@ -1,7 +1,6 @@
-import {createCommentApiMockWithTemplateElement} from '../../../test/mocks/comment-api';
 /**
  * @license
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,8 +18,10 @@
 import '../../../test/common-test-setup-karma.js';
 import '../../diff/gr-comment-api/gr-comment-api.js';
 import './gr-messages-list.js';
-import '../../../test/mocks/comment-api.js';
+import {createCommentApiMockWithTemplateElement} from '../../../test/mocks/comment-api.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
+import {TEST_ONLY} from './gr-messages-list.js';
+import {MessageTag} from '../../../constants/constants.js';
 import {html} from '@polymer/polymer/lib/utils/html-tag.js';
 
 createCommentApiMockWithTemplateElement(
@@ -50,40 +51,15 @@
     message: params.message || Math.random().toString(),
     _revision_number: params._revision_number || 1,
     author: params.author || author1,
+    tag: params.tag,
   };
 };
 
-const randomAutomated = function(opt_params) {
-  return Object.assign({tag: 'autogenerated:gerrit:replace'},
-      randomMessage(opt_params));
-};
-
 function generateRandomMessages(count) {
   return new Array(count).fill()
       .map(() => randomMessage());
 }
 
-function generateRandomAutomatedMessages(count) {
-  return new Array(count).fill()
-      .map(() => randomAutomated());
-}
-
-// Returns a shuffled copy of array
-export function shuffle(arr) {
-  const result = [];
-  for (const item of arr) {
-    // Random number in the interval [0..array.length]
-    const j = Math.floor(Math.random() * (arr.length + 1));
-    if (j < result.length) {
-      result.push(result[j]);
-      result[j] = item;
-    } else {
-      result.push(item);
-    }
-  }
-  return result;
-}
-
 suite('gr-messages-list tests', () => {
   let element;
   let messages;
@@ -94,54 +70,63 @@
     return dom(element.root).querySelectorAll('gr-message');
   };
 
+  const MESSAGE_ID_0 = '1234ccc949c6d482b061be6a28e10782abf0e7af';
+  const MESSAGE_ID_1 = '8c19ccc949c6d482b061be6a28e10782abf0e7af';
+  const MESSAGE_ID_2 = 'e7bfdbc842f6b6d8064bc68e0f52b673f40c0ca5';
+
   const author = {
     _account_id: 42,
     name: 'Marvin the Paranoid Android',
     email: 'marvin@sirius.org',
   };
 
+  const createComment = function() {
+    return {
+      id: '1a2b3c4d',
+      message: 'some random test text',
+      change_message_id: '8a7b6c5d',
+      updated: '2016-01-01 01:02:03.000000000',
+      line: 1,
+      patch_set: 1,
+      author,
+    };
+  };
+
   const comments = {
     file1: [
       {
-        message: 'message text',
-        updated: '2016-09-27 00:18:03.000000000',
+        ...createComment(),
+        change_message_id: MESSAGE_ID_0,
         in_reply_to: '6505d749_f0bec0aa',
-        line: 62,
-        id: '6505d749_10ed44b2',
-        patch_set: 2,
         author: {
           email: 'some@email.com',
           _account_id: 123,
         },
       },
       {
-        message: 'message text',
-        updated: '2016-09-27 00:18:03.000000000',
+        ...createComment(),
+        id: '2b3c4d5e',
+        change_message_id: MESSAGE_ID_1,
         in_reply_to: 'c5912363_6b820105',
-        line: 42,
-        id: '450a935e_0f1c05db',
-        patch_set: 2,
-        author,
       },
       {
-        message: 'message text',
-        updated: '2016-09-27 00:18:03.000000000',
+        ...createComment(),
+        id: '2b3c4d5e',
+        change_message_id: MESSAGE_ID_1,
         in_reply_to: '6505d749_f0bec0aa',
-        line: 62,
-        id: '6505d749_10ed44b2',
-        patch_set: 2,
-        author,
+      },
+      {
+        ...createComment(),
+        id: '34ed05d749_10ed44b2',
+        change_message_id: MESSAGE_ID_2,
       },
     ],
     file2: [
       {
-        message: 'message text',
-        updated: '2016-09-27 00:18:03.000000000',
+        ...createComment(),
+        change_message_id: MESSAGE_ID_1,
         in_reply_to: 'c5912363_4b7d450a',
-        line: 132,
         id: '450a935e_4f260d25',
-        patch_set: 2,
-        author,
       },
     ],
   };
@@ -168,126 +153,6 @@
       return commentApiWrapper.loadComments();
     });
 
-    test('show some old messages', () => {
-      assert.isTrue(element.$.messageControlsContainer.hasAttribute('hidden'));
-      element.messages = generateRandomMessages(26);
-      flushAsynchronousOperations();
-
-      assert.isFalse(element.$.messageControlsContainer.hasAttribute('hidden'));
-      assert.equal(getMessages().length, 20);
-      assert.equal(element.$.incrementMessagesBtn.innerText.toUpperCase()
-          .trim(), 'SHOW 5 MORE');
-      MockInteractions.tap(element.$.incrementMessagesBtn);
-      flushAsynchronousOperations();
-
-      assert.equal(getMessages().length, 25);
-      assert.equal(element.$.incrementMessagesBtn.innerText.toUpperCase()
-          .trim(), 'SHOW 1 MORE');
-      MockInteractions.tap(element.$.incrementMessagesBtn);
-      flushAsynchronousOperations();
-
-      assert.isTrue(element.$.messageControlsContainer.hasAttribute('hidden'));
-      assert.equal(getMessages().length, 26);
-    });
-
-    test('show all old messages', () => {
-      assert.isTrue(element.$.messageControlsContainer.hasAttribute('hidden'));
-      element.messages = generateRandomMessages(26);
-      flushAsynchronousOperations();
-
-      assert.isFalse(element.$.messageControlsContainer.hasAttribute('hidden'));
-      assert.equal(getMessages().length, 20);
-      assert.equal(element.$.oldMessagesBtn.innerText.toUpperCase(),
-          'SHOW ALL 6 MESSAGES');
-      MockInteractions.tap(element.$.oldMessagesBtn);
-      flushAsynchronousOperations();
-
-      assert.equal(getMessages().length, 26);
-      assert.isTrue(element.$.messageControlsContainer.hasAttribute('hidden'));
-    });
-
-    test('message count respects automated', () => {
-      element.messages = generateRandomAutomatedMessages(10)
-          .concat(generateRandomMessages(11));
-      flushAsynchronousOperations();
-
-      assert.equal(element.$.oldMessagesBtn.innerText.toUpperCase(),
-          'SHOW 1 MESSAGE');
-      assert.isFalse(element.$.messageControlsContainer.hasAttribute('hidden'));
-      MockInteractions.tap(element.$.automatedMessageToggle);
-      flushAsynchronousOperations();
-
-      assert.isTrue(element.$.messageControlsContainer.hasAttribute('hidden'));
-    });
-
-    test('message count still respects non-automated on toggle', () => {
-      element.messages = generateRandomMessages(10)
-          .concat(generateRandomAutomatedMessages(11));
-      flushAsynchronousOperations();
-
-      assert.equal(element.$.oldMessagesBtn.innerText.toUpperCase(),
-          'SHOW 1 MESSAGE');
-      assert.isFalse(element.$.messageControlsContainer.hasAttribute('hidden'));
-      MockInteractions.tap(element.$.automatedMessageToggle);
-      flushAsynchronousOperations();
-
-      assert.equal(element.$.oldMessagesBtn.innerText.toUpperCase(),
-          'SHOW 1 MESSAGE');
-      assert.isFalse(element.$.messageControlsContainer.hasAttribute('hidden'));
-    });
-
-    test('show all messages respects expand', () => {
-      element.messages = generateRandomAutomatedMessages(10)
-          .concat(generateRandomMessages(11));
-      flushAsynchronousOperations();
-
-      MockInteractions.tap(element.shadowRoot
-          .querySelector('#collapse-messages')); // Expand all.
-      flushAsynchronousOperations();
-
-      let messages = getMessages();
-      assert.equal(messages.length, 20);
-      for (const message of messages) {
-        assert.isTrue(message._expanded);
-      }
-
-      MockInteractions.tap(element.$.oldMessagesBtn);
-      flushAsynchronousOperations();
-
-      messages = getMessages();
-      assert.equal(messages.length, 21);
-      for (const message of messages) {
-        assert.isTrue(message._expanded);
-      }
-    });
-
-    test('show all messages respects collapse', () => {
-      element.messages = generateRandomAutomatedMessages(10)
-          .concat(generateRandomMessages(11));
-      flushAsynchronousOperations();
-
-      MockInteractions.tap(element.shadowRoot
-          .querySelector('#collapse-messages')); // Expand all.
-      MockInteractions.tap(element.shadowRoot
-          .querySelector('#collapse-messages')); // Collapse all.
-      flushAsynchronousOperations();
-
-      let messages = getMessages();
-      assert.equal(messages.length, 20);
-      for (const message of messages) {
-        assert.isFalse(message._expanded);
-      }
-
-      MockInteractions.tap(element.$.oldMessagesBtn);
-      flushAsynchronousOperations();
-
-      messages = getMessages();
-      assert.equal(messages.length, 21);
-      for (const message of messages) {
-        assert.isFalse(message._expanded);
-      }
-    });
-
     test('expand/collapse all', () => {
       let allMessageEls = getMessages();
       for (const message of allMessageEls) {
@@ -329,9 +194,11 @@
       assert.isTrue([...getMessages()].filter(m => m._expanded).length == 0);
     });
 
-    test('hide messages does not appear when no automated messages', () => {
+    test('showAllActivity does not appear when all msgs are important', () => {
       assert.isOk(element.shadowRoot
-          .querySelector('#automatedMessageToggleContainer[hidden]'));
+          .querySelector('#showAllActivityToggleContainer'));
+      assert.isNotOk(element.shadowRoot
+          .querySelector('.showAllActivityToggle'));
     });
 
     test('scroll to message', () => {
@@ -373,14 +240,13 @@
       element.scrollToMessage(messageID);
       assert.isTrue(scrollToStub.calledOnce);
       assert.isTrue(highlightStub.calledOnce);
-      assert.equal(element._visibleMessages.length, 24);
       assert.isTrue(
           element.shadowRoot
               .querySelector('[data-message-id="' + messageID + '"]')
               ._expanded);
     });
 
-    test('messages', () => {
+    test('associating messages with comments', () => {
       const messages = [].concat(
           randomMessage(),
           {
@@ -401,26 +267,156 @@
           }
       );
       element.messages = messages;
-      const isAuthor = function(author, message) {
-        return message.author._account_id === author._account_id;
-      };
-      const isMarvin = isAuthor.bind(null, author);
       flushAsynchronousOperations();
       const messageElements = getMessages();
       assert.equal(messageElements.length, messages.length);
       assert.deepEqual(messageElements[1].message, messages[1]);
       assert.deepEqual(messageElements[2].message, messages[2]);
-      assert.deepEqual(messageElements[1].comments.file1,
-          comments.file1.filter(isMarvin).map(c => {
-            return {...c,
-              path: 'file1'};
-          }));
-      assert.deepEqual(messageElements[1].comments.file2,
-          comments.file2.filter(isMarvin).map(c => {
-            return {...c,
-              path: 'file2'};
-          }));
-      assert.deepEqual(messageElements[2].comments, {});
+    });
+
+    test('threads', () => {
+      const messages = [
+        {
+          _index: 5,
+          _revision_number: 4,
+          message: 'Uploaded patch set 4.',
+          date: '2016-09-28 13:36:33.000000000',
+          author,
+          id: '8c19ccc949c6d482b061be6a28e10782abf0e7af',
+        },
+      ];
+      element.messages = messages;
+      flushAsynchronousOperations();
+      const messageElements = getMessages();
+      // threads
+      assert.equal(
+          messageElements[0].message.commentThreads.length,
+          3);
+      // first thread contains 1 comment
+      assert.equal(
+          messageElements[0].message.commentThreads[0].comments.length,
+          1);
+    });
+
+    test('updateTag human message', () => {
+      const m = randomMessage();
+      assert.equal(TEST_ONLY.computeTag(m), undefined);
+    });
+
+    test('updateTag nothing to change', () => {
+      const m = randomMessage();
+      const tag = 'something-normal';
+      m.tag = tag;
+      assert.equal(TEST_ONLY.computeTag(m), tag);
+    });
+
+    test('updateTag TAG_NEW_WIP_PATCHSET', () => {
+      const m = randomMessage();
+      m.tag = MessageTag.TAG_NEW_WIP_PATCHSET;
+      assert.equal(TEST_ONLY.computeTag(m), MessageTag.TAG_NEW_PATCHSET);
+    });
+
+    test('updateTag remove postfix', () => {
+      const m = randomMessage();
+      m.tag = 'something~withpostfix';
+      assert.equal(TEST_ONLY.computeTag(m), 'something');
+    });
+
+    test('updateTag with robot comments', () => {
+      const m = randomMessage();
+      m.commentThreads = [{
+        comments: [{
+          robot_id: 'id314',
+          change_message_id: m.id,
+        }],
+      }];
+      assert.notEqual(TEST_ONLY.computeTag(m), undefined);
+    });
+
+    test('setRevisionNumber nothing to change', () => {
+      const m1 = randomMessage();
+      const m2 = randomMessage();
+      assert.equal(TEST_ONLY.computeRevision(m1, [m1, m2]), 1);
+      assert.equal(TEST_ONLY.computeRevision(m2, [m1, m2]), 1);
+    });
+
+    test('setRevisionNumber reviewer updates', () => {
+      const m1 = randomMessage(
+          {
+            tag: MessageTag.TAG_REVIEWER_UPDATE,
+            date: '2020-01-01 10:00:00.000000000',
+          });
+      m1._revision_number = undefined;
+      const m2 = randomMessage(
+          {
+            date: '2020-01-02 10:00:00.000000000',
+          });
+      m2._revision_number = 1;
+      const m3 = randomMessage(
+          {
+            tag: MessageTag.TAG_REVIEWER_UPDATE,
+            date: '2020-01-03 10:00:00.000000000',
+          });
+      m3._revision_number = undefined;
+      const m4 = randomMessage(
+          {
+            date: '2020-01-04 10:00:00.000000000',
+          });
+      m4._revision_number = 2;
+      const m5 = randomMessage(
+          {
+            tag: MessageTag.TAG_REVIEWER_UPDATE,
+            date: '2020-01-05 10:00:00.000000000',
+          });
+      m5._revision_number = undefined;
+      const allMessages = [m1, m2, m3, m4, m5];
+      assert.equal(TEST_ONLY.computeRevision(m1, allMessages), undefined);
+      assert.equal(TEST_ONLY.computeRevision(m2, allMessages), 1);
+      assert.equal(TEST_ONLY.computeRevision(m3, allMessages), 1);
+      assert.equal(TEST_ONLY.computeRevision(m4, allMessages), 2);
+      assert.equal(TEST_ONLY.computeRevision(m5, allMessages), 2);
+    });
+
+    test('isImportant human message', () => {
+      const m = randomMessage();
+      assert.isTrue(TEST_ONLY.computeIsImportant(m, []));
+      assert.isTrue(TEST_ONLY.computeIsImportant(m, [m]));
+    });
+
+    test('isImportant even with a tag', () => {
+      const m1 = randomMessage();
+      const m2 = randomMessage({tag: 'autogenerated:gerrit1'});
+      const m3 = randomMessage({tag: 'autogenerated:gerrit2'});
+      assert.isTrue(TEST_ONLY.computeIsImportant(m2, []));
+      assert.isTrue(TEST_ONLY.computeIsImportant(m1, [m1, m2, m3]));
+      assert.isTrue(TEST_ONLY.computeIsImportant(m2, [m1, m2, m3]));
+      assert.isTrue(TEST_ONLY.computeIsImportant(m3, [m1, m2, m3]));
+    });
+
+    test('isImportant filters same tag and older revision', () => {
+      const m1 = randomMessage({tag: 'auto', _revision_number: 2});
+      const m2 = randomMessage({tag: 'auto', _revision_number: 1});
+      const m3 = randomMessage({tag: 'auto'});
+      assert.isTrue(TEST_ONLY.computeIsImportant(m1, [m1]));
+      assert.isTrue(TEST_ONLY.computeIsImportant(m2, [m2]));
+      assert.isTrue(TEST_ONLY.computeIsImportant(m1, [m1, m2]));
+      assert.isFalse(TEST_ONLY.computeIsImportant(m2, [m1, m2]));
+      assert.isTrue(TEST_ONLY.computeIsImportant(m1, [m1, m3]));
+      assert.isFalse(TEST_ONLY.computeIsImportant(m3, [m1, m3]));
+      assert.isTrue(TEST_ONLY.computeIsImportant(m1, [m1, m2, m3]));
+      assert.isFalse(TEST_ONLY.computeIsImportant(m2, [m1, m2, m3]));
+      assert.isFalse(TEST_ONLY.computeIsImportant(m3, [m1, m2, m3]));
+    });
+
+    test('isImportant is evaluated after tag update', () => {
+      const m1 = randomMessage(
+          {tag: MessageTag.TAG_NEW_PATCHSET, _revision_number: 1});
+      const m2 = randomMessage(
+          {tag: MessageTag.TAG_NEW_WIP_PATCHSET, _revision_number: 2});
+      element.messages = [m1, m2];
+      flushAsynchronousOperations();
+      assert.isFalse(m1.isImportant);
+      assert.isTrue(m2.isImportant);
     });
 
     test('messages without author do not throw', () => {
@@ -437,18 +433,6 @@
       assert.equal(messageEls.length, 1);
       assert.equal(messageEls[0].message.message, messages[0].message);
     });
-
-    test('hide increment text if increment >= total remaining', () => {
-      // Test with stubbed return values, as _numRemaining and _getDelta have
-      // their own tests.
-      sinon.stub(element, '_getDelta').returns(5);
-      const remainingStub = sinon.stub(element, '_numRemaining').returns(6);
-      assert.isFalse(element._computeIncrementHidden(null, null, null));
-      remainingStub.restore();
-
-      sinon.stub(element, '_numRemaining').returns(4);
-      assert.isTrue(element._computeIncrementHidden(null, null, null));
-    });
   });
 
   suite('gr-messages-list automate tests', () => {
@@ -457,18 +441,6 @@
 
     let commentApiWrapper;
 
-    const getMessages = function() {
-      return dom(element.root).querySelectorAll('gr-message');
-    };
-    const getHiddenMessages = function() {
-      return dom(element.root).querySelectorAll('gr-message[hidden]');
-    };
-
-    const randomMessageReviewer = {
-      reviewer: {},
-      date: '2016-01-13 20:30:33.038000',
-    };
-
     setup(() => {
       stub('gr-rest-api-interface', {
         getConfig() { return Promise.resolve({}); },
@@ -478,8 +450,11 @@
         getDiffDrafts() { return Promise.resolve({}); },
       });
 
-      messages = generateRandomAutomatedMessages(2);
-      messages.push(randomMessageReviewer);
+      messages = [
+        randomMessage(),
+        randomMessage({tag: 'auto', _revision_number: 2}),
+        randomMessage({tag: 'auto', _revision_number: 3}),
+      ];
 
       // Element must be wrapped in an element with direct access to the
       // comment API.
@@ -494,89 +469,33 @@
     });
 
     test('hide autogenerated button is not hidden', () => {
-      assert.isNotOk(element.shadowRoot
-          .querySelector('#automatedMessageToggle[hidden]'));
+      const toggle = dom(element.root).querySelector('.showAllActivityToggle');
+      assert.isOk(toggle);
     });
 
-    test('autogenerated messages are not hidden initially', () => {
-      const allHiddenMessageEls = getHiddenMessages();
-
-      // There are no hidden messages.
-      assert.isFalse(!!allHiddenMessageEls.length);
+    test('one unimportant message is hidden initially', () => {
+      const displayedMsgs = dom(element.root).querySelectorAll('gr-message');
+      assert.equal(displayedMsgs.length, 2);
     });
 
-    test('autogenerated messages hidden after comments only toggle', () => {
-      let allHiddenMessageEls = getHiddenMessages();
-
-      element._hideAutomated = false;
-      MockInteractions.tap(element.$.automatedMessageToggle);
+    test('unimportant messages hidden after toggle', () => {
+      element._showAllActivity = true;
+      const toggle = dom(element.root).querySelector('.showAllActivityToggle');
+      assert.isOk(toggle);
+      MockInteractions.tap(toggle);
       flushAsynchronousOperations();
-      const allMessageEls = getMessages();
-      allHiddenMessageEls = getHiddenMessages();
-
-      // Autogenerated messages are now hidden.
-      assert.equal(allHiddenMessageEls.length, allMessageEls.length);
+      const displayedMsgs = dom(element.root).querySelectorAll('gr-message');
+      assert.equal(displayedMsgs.length, 2);
     });
 
-    test('autogenerated messages not hidden after comments only toggle',
-        () => {
-          let allHiddenMessageEls = getHiddenMessages();
-
-          element._hideAutomated = true;
-          MockInteractions.tap(element.$.automatedMessageToggle);
-          allHiddenMessageEls = getHiddenMessages();
-
-          // Autogenerated messages are now hidden.
-          assert.isFalse(!!allHiddenMessageEls.length);
-        });
-
-    test('_getDelta', () => {
-      let messages = [randomMessage()];
-      assert.equal(element._getDelta([], messages, false), 1);
-      assert.equal(element._getDelta([], messages, true), 1);
-
-      messages = generateRandomMessages(7);
-      assert.equal(element._getDelta([], messages, false), 5);
-      assert.equal(element._getDelta([], messages, true), 5);
-
-      messages = generateRandomMessages(4)
-          .concat(generateRandomAutomatedMessages(2))
-          .concat(generateRandomMessages(3));
-
-      const dummyArr = generateRandomMessages(2);
-      assert.equal(element._getDelta(dummyArr, messages, false), 5);
-      assert.equal(element._getDelta(dummyArr, messages, true), 7);
-    });
-
-    test('_getHumanMessages', () => {
-      assert.equal(
-          element._getHumanMessages(
-              generateRandomAutomatedMessages(5)).length, 0);
-      assert.equal(
-          element._getHumanMessages(generateRandomMessages(5)).length, 5);
-
-      let messages = shuffle(generateRandomMessages(5)
-          .concat(generateRandomAutomatedMessages(5)));
-      messages = element._getHumanMessages(messages);
-      assert.equal(messages.length, 5);
-      assert.isFalse(element._hasAutomatedMessages(messages));
-    });
-
-    test('initially show only 20 messages', () => {
-      sinon.stub(element.reporting, 'reportInteraction').callsFake(
-          (eventName, details) => {
-            assert.equal(typeof(eventName), 'string');
-            if (details) {
-              assert.equal(typeof(details), 'object');
-            }
-          });
-      const messages = Array.from(Array(23).keys())
-          .map(() => {
-            return {};
-          });
-      element._processedMessagesChanged(messages);
-
-      assert.equal(element._visibleMessages.length, 20);
+    test('unimportant messages shown after toggle', () => {
+      element._showAllActivity = false;
+      const toggle = dom(element.root).querySelector('.showAllActivityToggle');
+      assert.isOk(toggle);
+      MockInteractions.tap(toggle);
+      flushAsynchronousOperations();
+      const displayedMsgs = dom(element.root).querySelectorAll('gr-message');
+      assert.equal(displayedMsgs.length, 3);
     });
 
     test('_computeLabelExtremes', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js
index 9a94d27..63ab267 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js
@@ -20,7 +20,6 @@
 import './gr-reply-dialog.js';
 import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
 import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
-import {pluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js';
 import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
 const basicFixture = fixtureFromElement('gr-reply-dialog');
 const pluginApi = _testOnly_initGerritPluginApi();
@@ -126,8 +125,6 @@
     }, null, 'http://test.com/plugins/lgtm.js');
     element = basicFixture.instantiate();
     setupElement(element);
-    sinon.stub(pluginEndpoints, 'importUrl')
-        .callsFake( url => Promise.resolve());
     pluginLoader.loadPlugins([]);
     pluginLoader.awaitPluginsLoaded().then(() => {
       flush(() => {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 24bc142..b5d40d2 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -28,7 +28,6 @@
 import '../gr-label-scores/gr-label-scores.js';
 import '../gr-thread-list/gr-thread-list.js';
 import '../../../styles/shared-styles.js';
-import '../gr-comment-list/gr-comment-list.js';
 import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
 import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
 import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
index fa686da..eb68024 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
@@ -247,7 +247,7 @@
   _computeDisplayPath(path) {
     const displayPath = this.computeDisplayPath(path);
     if (displayPath === SpecialFilePath.PATCHSET_LEVEL_COMMENTS) {
-      return `Patchset ${this.patchNum}`;
+      return `Patchset`;
     }
     return displayPath;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.js b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.js
index 3837514..db5ade1 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.js
@@ -208,7 +208,7 @@
 
       element.patchNum = '3';
       path = SpecialFilePath.PATCHSET_LEVEL_COMMENTS;
-      assert.equal(element._computeDisplayPath(path), 'Patchset 3');
+      assert.equal(element._computeDisplayPath(path), 'Patchset');
     });
 
     test('_computeDisplayLine', () => {
diff --git a/polygerrit-ui/app/externs/BUILD b/polygerrit-ui/app/externs/BUILD
deleted file mode 100644
index 26ead9a..0000000
--- a/polygerrit-ui/app/externs/BUILD
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_library")
-
-package(
-    default_visibility = ["//visibility:public"],
-)
-
-closure_js_library(
-    name = "plugin",
-    srcs = ["plugin.js"],
-    no_closure_library = True,
-)
diff --git a/polygerrit-ui/app/externs/plugin.js b/polygerrit-ui/app/externs/plugin.js
deleted file mode 100644
index c88c724..0000000
--- a/polygerrit-ui/app/externs/plugin.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * @license
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * @fileoverview Closure compiler externs for the Gerrit UI plugins.
- * @externs
- */
-
-/* eslint-disable no-var */
-
-var Gerrit = {};
-
-/**
- * @param {!Function} callback
- */
-Gerrit.install = function(callback) {};
diff --git a/polygerrit-ui/app/services/flags.js b/polygerrit-ui/app/services/flags.js
index 64f0115..6313255 100644
--- a/polygerrit-ui/app/services/flags.js
+++ b/polygerrit-ui/app/services/flags.js
@@ -20,7 +20,6 @@
  * @desc Experiment ids used in Gerrit.
  */
 export const ExperimentIds = {
-  CLEANER_CHANGELOG: 'UiFeature__cleaner_changelog',
   PATCHSET_COMMENTS: 'UiFeature__patchset_comments',
 };
 
diff --git a/polygerrit-ui/package.json b/polygerrit-ui/package.json
index ea039d5..6e85b77 100644
--- a/polygerrit-ui/package.json
+++ b/polygerrit-ui/package.json
@@ -4,7 +4,7 @@
   "browser": true,
   "dependencies": {},
   "devDependencies": {
-    "@open-wc/karma-esm": "^2.13.21",
+    "@open-wc/karma-esm": "^2.16.16",
     "@polymer/iron-test-helpers": "^3.0.1",
     "@polymer/test-fixture": "^4.0.2",
     "accessibility-developer-tools": "^2.12.0",
diff --git a/polygerrit-ui/yarn.lock b/polygerrit-ui/yarn.lock
index d73e1d0..0f42737 100644
--- a/polygerrit-ui/yarn.lock
+++ b/polygerrit-ui/yarn.lock
@@ -2,35 +2,35 @@
 # yarn lockfile v1
 
 
-"@babel/code-frame@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
-  integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==
+"@babel/code-frame@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
+  integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==
   dependencies:
-    "@babel/highlight" "^7.8.3"
+    "@babel/highlight" "^7.10.4"
 
-"@babel/compat-data@^7.8.6", "@babel/compat-data@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.0.tgz#04815556fc90b0c174abd2c0c1bb966faa036a6c"
-  integrity sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g==
+"@babel/compat-data@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.4.tgz#706a6484ee6f910b719b696a9194f8da7d7ac241"
+  integrity sha512-t+rjExOrSVvjQQXNp5zAIYDp00KjdvGl/TpDX5REPr0S9IAIPQMTilcfG6q8c0QFmj9lSTVySV2VTsyggvtNIw==
   dependencies:
-    browserslist "^4.9.1"
+    browserslist "^4.12.0"
     invariant "^2.2.4"
     semver "^5.5.0"
 
 "@babel/core@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e"
-  integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.4.tgz#780e8b83e496152f8dd7df63892b2e052bf1d51d"
+  integrity sha512-3A0tS0HWpy4XujGc7QtOIHTeNwUgWaZc/WuS5YQrfhU67jnVmsD6OGPc1AKHH0LJHQICGncy3+YUjIhVlfDdcA==
   dependencies:
-    "@babel/code-frame" "^7.8.3"
-    "@babel/generator" "^7.9.0"
-    "@babel/helper-module-transforms" "^7.9.0"
-    "@babel/helpers" "^7.9.0"
-    "@babel/parser" "^7.9.0"
-    "@babel/template" "^7.8.6"
-    "@babel/traverse" "^7.9.0"
-    "@babel/types" "^7.9.0"
+    "@babel/code-frame" "^7.10.4"
+    "@babel/generator" "^7.10.4"
+    "@babel/helper-module-transforms" "^7.10.4"
+    "@babel/helpers" "^7.10.4"
+    "@babel/parser" "^7.10.4"
+    "@babel/template" "^7.10.4"
+    "@babel/traverse" "^7.10.4"
+    "@babel/types" "^7.10.4"
     convert-source-map "^1.7.0"
     debug "^4.1.0"
     gensync "^1.0.0-beta.1"
@@ -40,316 +40,312 @@
     semver "^5.4.1"
     source-map "^0.5.0"
 
-"@babel/generator@^7.4.0", "@babel/generator@^7.9.0":
-  version "7.9.4"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.4.tgz#12441e90c3b3c4159cdecf312075bf1a8ce2dbce"
-  integrity sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==
+"@babel/generator@^7.10.4", "@babel/generator@^7.4.0":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.4.tgz#e49eeed9fe114b62fa5b181856a43a5e32f5f243"
+  integrity sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng==
   dependencies:
-    "@babel/types" "^7.9.0"
+    "@babel/types" "^7.10.4"
     jsesc "^2.5.1"
     lodash "^4.17.13"
     source-map "^0.5.0"
 
-"@babel/generator@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.3.tgz#0e22c005b0a94c1c74eafe19ef78ce53a4d45c03"
-  integrity sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug==
+"@babel/helper-annotate-as-pure@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3"
+  integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==
   dependencies:
-    "@babel/types" "^7.8.3"
-    jsesc "^2.5.1"
-    lodash "^4.17.13"
-    source-map "^0.5.0"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-annotate-as-pure@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee"
-  integrity sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==
+"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3"
+  integrity sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==
   dependencies:
-    "@babel/types" "^7.8.3"
+    "@babel/helper-explode-assignable-expression" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503"
-  integrity sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==
+"@babel/helper-compilation-targets@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz#804ae8e3f04376607cc791b9d47d540276332bd2"
+  integrity sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==
   dependencies:
-    "@babel/helper-explode-assignable-expression" "^7.8.3"
-    "@babel/types" "^7.8.3"
-
-"@babel/helper-compilation-targets@^7.8.7":
-  version "7.8.7"
-  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde"
-  integrity sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==
-  dependencies:
-    "@babel/compat-data" "^7.8.6"
-    browserslist "^4.9.1"
+    "@babel/compat-data" "^7.10.4"
+    browserslist "^4.12.0"
     invariant "^2.2.4"
     levenary "^1.1.1"
     semver "^5.5.0"
 
-"@babel/helper-create-regexp-features-plugin@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.3.tgz#c774268c95ec07ee92476a3862b75cc2839beb79"
-  integrity sha512-Gcsm1OHCUr9o9TcJln57xhWHtdXbA2pgQ58S0Lxlks0WMGNXuki4+GLfX0p+L2ZkINUGZvfkz8rzoqJQSthI+Q==
+"@babel/helper-create-class-features-plugin@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.4.tgz#2d4015d0136bd314103a70d84a7183e4b344a355"
+  integrity sha512-9raUiOsXPxzzLjCXeosApJItoMnX3uyT4QdM2UldffuGApNrF8e938MwNpDCK9CPoyxrEoCgT+hObJc3mZa6lQ==
   dependencies:
-    "@babel/helper-regex" "^7.8.3"
-    regexpu-core "^4.6.0"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/helper-member-expression-to-functions" "^7.10.4"
+    "@babel/helper-optimise-call-expression" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-replace-supers" "^7.10.4"
+    "@babel/helper-split-export-declaration" "^7.10.4"
 
-"@babel/helper-create-regexp-features-plugin@^7.8.8":
-  version "7.8.8"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087"
-  integrity sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==
+"@babel/helper-create-regexp-features-plugin@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8"
+  integrity sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.8.3"
-    "@babel/helper-regex" "^7.8.3"
+    "@babel/helper-annotate-as-pure" "^7.10.4"
+    "@babel/helper-regex" "^7.10.4"
     regexpu-core "^4.7.0"
 
-"@babel/helper-define-map@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15"
-  integrity sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==
+"@babel/helper-define-map@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.4.tgz#f037ad794264f729eda1889f4ee210b870999092"
+  integrity sha512-nIij0oKErfCnLUCWaCaHW0Bmtl2RO9cN7+u2QT8yqTywgALKlyUVOvHDElh+b5DwVC6YB1FOYFOTWcN/+41EDA==
   dependencies:
-    "@babel/helper-function-name" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/types" "^7.10.4"
     lodash "^4.17.13"
 
-"@babel/helper-explode-assignable-expression@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982"
-  integrity sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==
+"@babel/helper-explode-assignable-expression@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz#40a1cd917bff1288f699a94a75b37a1a2dbd8c7c"
+  integrity sha512-4K71RyRQNPRrR85sr5QY4X3VwG4wtVoXZB9+L3r1Gp38DhELyHCtovqydRi7c1Ovb17eRGiQ/FD5s8JdU0Uy5A==
   dependencies:
-    "@babel/traverse" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/traverse" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-function-name@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca"
-  integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==
+"@babel/helper-function-name@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a"
+  integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==
   dependencies:
-    "@babel/helper-get-function-arity" "^7.8.3"
-    "@babel/template" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/helper-get-function-arity" "^7.10.4"
+    "@babel/template" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-get-function-arity@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5"
-  integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==
+"@babel/helper-get-function-arity@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2"
+  integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==
   dependencies:
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-hoist-variables@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134"
-  integrity sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==
+"@babel/helper-hoist-variables@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e"
+  integrity sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==
   dependencies:
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-member-expression-to-functions@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c"
-  integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==
+"@babel/helper-member-expression-to-functions@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.4.tgz#7cd04b57dfcf82fce9aeae7d4e4452fa31b8c7c4"
+  integrity sha512-m5j85pK/KZhuSdM/8cHUABQTAslV47OjfIB9Cc7P+PvlAoBzdb79BGNfw8RhT5Mq3p+xGd0ZfAKixbrUZx0C7A==
   dependencies:
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-module-imports@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498"
-  integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==
+"@babel/helper-module-imports@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620"
+  integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==
   dependencies:
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-module-transforms@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5"
-  integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==
+"@babel/helper-module-transforms@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.4.tgz#ca1f01fdb84e48c24d7506bb818c961f1da8805d"
+  integrity sha512-Er2FQX0oa3nV7eM1o0tNCTx7izmQtwAQsIiaLRWtavAAEcskb0XJ5OjJbVrYXWOTr8om921Scabn4/tzlx7j1Q==
   dependencies:
-    "@babel/helper-module-imports" "^7.8.3"
-    "@babel/helper-replace-supers" "^7.8.6"
-    "@babel/helper-simple-access" "^7.8.3"
-    "@babel/helper-split-export-declaration" "^7.8.3"
-    "@babel/template" "^7.8.6"
-    "@babel/types" "^7.9.0"
+    "@babel/helper-module-imports" "^7.10.4"
+    "@babel/helper-replace-supers" "^7.10.4"
+    "@babel/helper-simple-access" "^7.10.4"
+    "@babel/helper-split-export-declaration" "^7.10.4"
+    "@babel/template" "^7.10.4"
+    "@babel/types" "^7.10.4"
     lodash "^4.17.13"
 
-"@babel/helper-optimise-call-expression@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9"
-  integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==
+"@babel/helper-optimise-call-expression@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673"
+  integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==
   dependencies:
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670"
-  integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==
+"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375"
+  integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==
 
-"@babel/helper-regex@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965"
-  integrity sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==
+"@babel/helper-regex@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.4.tgz#59b373daaf3458e5747dece71bbaf45f9676af6d"
+  integrity sha512-inWpnHGgtg5NOF0eyHlC0/74/VkdRITY9dtTpB2PrxKKn+AkVMRiZz/Adrx+Ssg+MLDesi2zohBW6MVq6b4pOQ==
   dependencies:
     lodash "^4.17.13"
 
-"@babel/helper-remap-async-to-generator@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86"
-  integrity sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==
+"@babel/helper-remap-async-to-generator@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz#fce8bea4e9690bbe923056ded21e54b4e8b68ed5"
+  integrity sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.8.3"
-    "@babel/helper-wrap-function" "^7.8.3"
-    "@babel/template" "^7.8.3"
-    "@babel/traverse" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/helper-annotate-as-pure" "^7.10.4"
+    "@babel/helper-wrap-function" "^7.10.4"
+    "@babel/template" "^7.10.4"
+    "@babel/traverse" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-replace-supers@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.3.tgz#91192d25f6abbcd41da8a989d4492574fb1530bc"
-  integrity sha512-xOUssL6ho41U81etpLoT2RTdvdus4VfHamCuAm4AHxGr+0it5fnwoVdwUJ7GFEqCsQYzJUhcbsN9wB9apcYKFA==
+"@babel/helper-replace-supers@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf"
+  integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==
   dependencies:
-    "@babel/helper-member-expression-to-functions" "^7.8.3"
-    "@babel/helper-optimise-call-expression" "^7.8.3"
-    "@babel/traverse" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/helper-member-expression-to-functions" "^7.10.4"
+    "@babel/helper-optimise-call-expression" "^7.10.4"
+    "@babel/traverse" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-replace-supers@^7.8.6":
-  version "7.8.6"
-  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8"
-  integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==
+"@babel/helper-simple-access@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461"
+  integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==
   dependencies:
-    "@babel/helper-member-expression-to-functions" "^7.8.3"
-    "@babel/helper-optimise-call-expression" "^7.8.3"
-    "@babel/traverse" "^7.8.6"
-    "@babel/types" "^7.8.6"
+    "@babel/template" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-simple-access@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae"
-  integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==
+"@babel/helper-split-export-declaration@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz#2c70576eaa3b5609b24cb99db2888cc3fc4251d1"
+  integrity sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==
   dependencies:
-    "@babel/template" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-split-export-declaration@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9"
-  integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==
+"@babel/helper-validator-identifier@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2"
+  integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==
+
+"@babel/helper-wrap-function@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87"
+  integrity sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==
   dependencies:
-    "@babel/types" "^7.8.3"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/template" "^7.10.4"
+    "@babel/traverse" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-validator-identifier@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed"
-  integrity sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==
-
-"@babel/helper-wrap-function@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610"
-  integrity sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==
+"@babel/helpers@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044"
+  integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==
   dependencies:
-    "@babel/helper-function-name" "^7.8.3"
-    "@babel/template" "^7.8.3"
-    "@babel/traverse" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/template" "^7.10.4"
+    "@babel/traverse" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helpers@^7.9.0":
-  version "7.9.2"
-  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f"
-  integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==
+"@babel/highlight@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143"
+  integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==
   dependencies:
-    "@babel/template" "^7.8.3"
-    "@babel/traverse" "^7.9.0"
-    "@babel/types" "^7.9.0"
-
-"@babel/highlight@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797"
-  integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==
-  dependencies:
+    "@babel/helper-validator-identifier" "^7.10.4"
     chalk "^2.0.0"
-    esutils "^2.0.2"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.4.3", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0":
-  version "7.9.4"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8"
-  integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==
+"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.4.3":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.4.tgz#9eedf27e1998d87739fb5028a5120557c06a1a64"
+  integrity sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA==
 
-"@babel/parser@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.3.tgz#790874091d2001c9be6ec426c2eed47bc7679081"
-  integrity sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==
-
-"@babel/plugin-proposal-async-generator-functions@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f"
-  integrity sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==
+"@babel/plugin-proposal-async-generator-functions@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.4.tgz#4b65abb3d9bacc6c657aaa413e56696f9f170fc6"
+  integrity sha512-MJbxGSmejEFVOANAezdO39SObkURO5o/8b6fSH6D1pi9RZQt+ldppKPXfqgUWpSQ9asM6xaSaSJIaeWMDRP0Zg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
-    "@babel/helper-remap-async-to-generator" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-remap-async-to-generator" "^7.10.4"
     "@babel/plugin-syntax-async-generators" "^7.8.0"
 
-"@babel/plugin-proposal-dynamic-import@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054"
-  integrity sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==
+"@babel/plugin-proposal-class-properties@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz#a33bf632da390a59c7a8c570045d1115cd778807"
+  integrity sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-create-class-features-plugin" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-proposal-dynamic-import@^7.10.4", "@babel/plugin-proposal-dynamic-import@^7.8.3":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz#ba57a26cb98b37741e9d5bca1b8b0ddf8291f17e"
+  integrity sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.10.4"
     "@babel/plugin-syntax-dynamic-import" "^7.8.0"
 
-"@babel/plugin-proposal-json-strings@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b"
-  integrity sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==
+"@babel/plugin-proposal-json-strings@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz#593e59c63528160233bd321b1aebe0820c2341db"
+  integrity sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
     "@babel/plugin-syntax-json-strings" "^7.8.0"
 
-"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2"
-  integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==
+"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4", "@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz#02a7e961fc32e6d5b2db0649e01bf80ddee7e04a"
+  integrity sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
     "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
 
-"@babel/plugin-proposal-numeric-separator@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz#5d6769409699ec9b3b68684cd8116cedff93bad8"
-  integrity sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==
+"@babel/plugin-proposal-numeric-separator@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz#ce1590ff0a65ad12970a609d78855e9a4c1aef06"
+  integrity sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
-    "@babel/plugin-syntax-numeric-separator" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/plugin-syntax-numeric-separator" "^7.10.4"
 
-"@babel/plugin-proposal-object-rest-spread@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz#a28993699fc13df165995362693962ba6b061d6f"
-  integrity sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow==
+"@babel/plugin-proposal-object-rest-spread@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz#50129ac216b9a6a55b3853fdd923e74bf553a4c0"
+  integrity sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
     "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
+    "@babel/plugin-transform-parameters" "^7.10.4"
 
-"@babel/plugin-proposal-optional-catch-binding@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9"
-  integrity sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==
+"@babel/plugin-proposal-optional-catch-binding@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz#31c938309d24a78a49d68fdabffaa863758554dd"
+  integrity sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
     "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
 
-"@babel/plugin-proposal-optional-chaining@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58"
-  integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==
+"@babel/plugin-proposal-optional-chaining@^7.10.4", "@babel/plugin-proposal-optional-chaining@^7.9.0":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.4.tgz#750f1255e930a1f82d8cdde45031f81a0d0adff7"
+  integrity sha512-ZIhQIEeavTgouyMSdZRap4VPPHqJJ3NEs2cuHs5p0erH+iz6khB0qfgU8g7UuJkG88+fBMy23ZiU+nuHvekJeQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
     "@babel/plugin-syntax-optional-chaining" "^7.8.0"
 
-"@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3":
-  version "7.8.8"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz#ee3a95e90cdc04fe8cd92ec3279fa017d68a0d1d"
-  integrity sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==
+"@babel/plugin-proposal-private-methods@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz#b160d972b8fdba5c7d111a145fc8c421fc2a6909"
+  integrity sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.8.8"
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-create-class-features-plugin" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-proposal-unicode-property-regex@^7.10.4", "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz#4483cda53041ce3413b7fe2f00022665ddfaa75d"
+  integrity sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
 "@babel/plugin-syntax-async-generators@^7.8.0":
   version "7.8.4"
@@ -358,12 +354,12 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-class-properties@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.8.3.tgz#6cb933a8872c8d359bfde69bbeaae5162fd1e8f7"
-  integrity sha512-UcAyQWg2bAN647Q+O811tG9MrJ38Z10jjhQdKNAL8fsyPzE3cCN/uT+f55cFVY4aGO4jqJAvmqsuY3GQDwAoXg==
+"@babel/plugin-syntax-class-properties@^7.10.4", "@babel/plugin-syntax-class-properties@^7.8.3":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz#6644e6a0baa55a61f9e3231f6c9eeb6ee46c124c"
+  integrity sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
 "@babel/plugin-syntax-dynamic-import@^7.8.0", "@babel/plugin-syntax-dynamic-import@^7.8.3":
   version "7.8.3"
@@ -373,11 +369,11 @@
     "@babel/helper-plugin-utils" "^7.8.0"
 
 "@babel/plugin-syntax-import-meta@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.8.3.tgz#230afff79d3ccc215b5944b438e4e266daf3d84d"
-  integrity sha512-vYiGd4wQ9gx0Lngb7+bPCwQXGK/PR6FeTIJ+TIOlq+OfOKG/kCAOO2+IBac3oMM9qV7/fU76hfcqxUaLKZf1hQ==
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51"
+  integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
 "@babel/plugin-syntax-json-strings@^7.8.0":
   version "7.8.3"
@@ -393,12 +389,12 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-numeric-separator@^7.8.0", "@babel/plugin-syntax-numeric-separator@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz#0e3fb63e09bea1b11e96467271c8308007e7c41f"
-  integrity sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==
+"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97"
+  integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
 "@babel/plugin-syntax-object-rest-spread@^7.8.0":
   version "7.8.3"
@@ -421,318 +417,329 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-top-level-await@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391"
-  integrity sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==
+"@babel/plugin-syntax-top-level-await@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz#4bbeb8917b54fcf768364e0a81f560e33a3ef57d"
+  integrity sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-arrow-functions@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6"
-  integrity sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==
+"@babel/plugin-transform-arrow-functions@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz#e22960d77e697c74f41c501d44d73dbf8a6a64cd"
+  integrity sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-async-to-generator@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086"
-  integrity sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==
+"@babel/plugin-transform-async-to-generator@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz#41a5017e49eb6f3cda9392a51eef29405b245a37"
+  integrity sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==
   dependencies:
-    "@babel/helper-module-imports" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
-    "@babel/helper-remap-async-to-generator" "^7.8.3"
+    "@babel/helper-module-imports" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-remap-async-to-generator" "^7.10.4"
 
-"@babel/plugin-transform-block-scoped-functions@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3"
-  integrity sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==
+"@babel/plugin-transform-block-scoped-functions@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz#1afa595744f75e43a91af73b0d998ecfe4ebc2e8"
+  integrity sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-block-scoping@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a"
-  integrity sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==
+"@babel/plugin-transform-block-scoping@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.4.tgz#a670d1364bb5019a621b9ea2001482876d734787"
+  integrity sha512-J3b5CluMg3hPUii2onJDRiaVbPtKFPLEaV5dOPY5OeAbDi1iU/UbbFFTgwb7WnanaDy7bjU35kc26W3eM5Qa0A==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
     lodash "^4.17.13"
 
-"@babel/plugin-transform-classes@^7.9.0":
-  version "7.9.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz#8603fc3cc449e31fdbdbc257f67717536a11af8d"
-  integrity sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ==
+"@babel/plugin-transform-classes@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz#405136af2b3e218bc4a1926228bc917ab1a0adc7"
+  integrity sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.8.3"
-    "@babel/helper-define-map" "^7.8.3"
-    "@babel/helper-function-name" "^7.8.3"
-    "@babel/helper-optimise-call-expression" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
-    "@babel/helper-replace-supers" "^7.8.6"
-    "@babel/helper-split-export-declaration" "^7.8.3"
+    "@babel/helper-annotate-as-pure" "^7.10.4"
+    "@babel/helper-define-map" "^7.10.4"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/helper-optimise-call-expression" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-replace-supers" "^7.10.4"
+    "@babel/helper-split-export-declaration" "^7.10.4"
     globals "^11.1.0"
 
-"@babel/plugin-transform-computed-properties@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b"
-  integrity sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==
+"@babel/plugin-transform-computed-properties@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz#9ded83a816e82ded28d52d4b4ecbdd810cdfc0eb"
+  integrity sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-destructuring@^7.8.3":
-  version "7.8.8"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz#fadb2bc8e90ccaf5658de6f8d4d22ff6272a2f4b"
-  integrity sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ==
+"@babel/plugin-transform-destructuring@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz#70ddd2b3d1bea83d01509e9bb25ddb3a74fc85e5"
+  integrity sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-dotall-regex@^7.4.4", "@babel/plugin-transform-dotall-regex@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e"
-  integrity sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==
+"@babel/plugin-transform-dotall-regex@^7.10.4", "@babel/plugin-transform-dotall-regex@^7.4.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz#469c2062105c1eb6a040eaf4fac4b488078395ee"
+  integrity sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-create-regexp-features-plugin" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-duplicate-keys@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1"
-  integrity sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==
+"@babel/plugin-transform-duplicate-keys@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz#697e50c9fee14380fe843d1f306b295617431e47"
+  integrity sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-exponentiation-operator@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7"
-  integrity sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==
+"@babel/plugin-transform-exponentiation-operator@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz#5ae338c57f8cf4001bdb35607ae66b92d665af2e"
+  integrity sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==
   dependencies:
-    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-for-of@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e"
-  integrity sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ==
+"@babel/plugin-transform-for-of@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz#c08892e8819d3a5db29031b115af511dbbfebae9"
+  integrity sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-function-name@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b"
-  integrity sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==
+"@babel/plugin-transform-function-name@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz#6a467880e0fc9638514ba369111811ddbe2644b7"
+  integrity sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==
   dependencies:
-    "@babel/helper-function-name" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-literals@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1"
-  integrity sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==
+"@babel/plugin-transform-literals@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz#9f42ba0841100a135f22712d0e391c462f571f3c"
+  integrity sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-member-expression-literals@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410"
-  integrity sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==
+"@babel/plugin-transform-member-expression-literals@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz#b1ec44fcf195afcb8db2c62cd8e551c881baf8b7"
+  integrity sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-modules-amd@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz#19755ee721912cf5bb04c07d50280af3484efef4"
-  integrity sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q==
+"@babel/plugin-transform-modules-amd@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.4.tgz#cb407c68b862e4c1d13a2fc738c7ec5ed75fc520"
+  integrity sha512-3Fw+H3WLUrTlzi3zMiZWp3AR4xadAEMv6XRCYnd5jAlLM61Rn+CRJaZMaNvIpcJpQ3vs1kyifYvEVPFfoSkKOA==
   dependencies:
-    "@babel/helper-module-transforms" "^7.9.0"
-    "@babel/helper-plugin-utils" "^7.8.3"
-    babel-plugin-dynamic-import-node "^2.3.0"
+    "@babel/helper-module-transforms" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    babel-plugin-dynamic-import-node "^2.3.3"
 
-"@babel/plugin-transform-modules-commonjs@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz#e3e72f4cbc9b4a260e30be0ea59bdf5a39748940"
-  integrity sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g==
+"@babel/plugin-transform-modules-commonjs@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz#66667c3eeda1ebf7896d41f1f16b17105a2fbca0"
+  integrity sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==
   dependencies:
-    "@babel/helper-module-transforms" "^7.9.0"
-    "@babel/helper-plugin-utils" "^7.8.3"
-    "@babel/helper-simple-access" "^7.8.3"
-    babel-plugin-dynamic-import-node "^2.3.0"
+    "@babel/helper-module-transforms" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-simple-access" "^7.10.4"
+    babel-plugin-dynamic-import-node "^2.3.3"
 
-"@babel/plugin-transform-modules-systemjs@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz#e9fd46a296fc91e009b64e07ddaa86d6f0edeb90"
-  integrity sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ==
+"@babel/plugin-transform-modules-systemjs@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.4.tgz#8f576afd943ac2f789b35ded0a6312f929c633f9"
+  integrity sha512-Tb28LlfxrTiOTGtZFsvkjpyjCl9IoaRI52AEU/VIwOwvDQWtbNJsAqTXzh+5R7i74e/OZHH2c2w2fsOqAfnQYQ==
   dependencies:
-    "@babel/helper-hoist-variables" "^7.8.3"
-    "@babel/helper-module-transforms" "^7.9.0"
-    "@babel/helper-plugin-utils" "^7.8.3"
-    babel-plugin-dynamic-import-node "^2.3.0"
+    "@babel/helper-hoist-variables" "^7.10.4"
+    "@babel/helper-module-transforms" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    babel-plugin-dynamic-import-node "^2.3.3"
 
-"@babel/plugin-transform-modules-umd@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz#e909acae276fec280f9b821a5f38e1f08b480697"
-  integrity sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ==
+"@babel/plugin-transform-modules-umd@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz#9a8481fe81b824654b3a0b65da3df89f3d21839e"
+  integrity sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==
   dependencies:
-    "@babel/helper-module-transforms" "^7.9.0"
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-module-transforms" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-named-capturing-groups-regex@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c"
-  integrity sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==
+"@babel/plugin-transform-named-capturing-groups-regex@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz#78b4d978810b6f3bcf03f9e318f2fc0ed41aecb6"
+  integrity sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.8.3"
+    "@babel/helper-create-regexp-features-plugin" "^7.10.4"
 
-"@babel/plugin-transform-new-target@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43"
-  integrity sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==
+"@babel/plugin-transform-new-target@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz#9097d753cb7b024cb7381a3b2e52e9513a9c6888"
+  integrity sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-object-super@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725"
-  integrity sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==
+"@babel/plugin-transform-object-super@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz#d7146c4d139433e7a6526f888c667e314a093894"
+  integrity sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
-    "@babel/helper-replace-supers" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-replace-supers" "^7.10.4"
 
-"@babel/plugin-transform-parameters@^7.8.7":
-  version "7.9.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz#3028d0cc20ddc733166c6e9c8534559cee09f54a"
-  integrity sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg==
+"@babel/plugin-transform-parameters@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.4.tgz#7b4d137c87ea7adc2a0f3ebf53266871daa6fced"
+  integrity sha512-RurVtZ/D5nYfEg0iVERXYKEgDFeesHrHfx8RT05Sq57ucj2eOYAP6eu5fynL4Adju4I/mP/I6SO0DqNWAXjfLQ==
   dependencies:
-    "@babel/helper-get-function-arity" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-get-function-arity" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-property-literals@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263"
-  integrity sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==
+"@babel/plugin-transform-property-literals@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz#f6fe54b6590352298785b83edd815d214c42e3c0"
+  integrity sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-regenerator@^7.8.7":
-  version "7.8.7"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz#5e46a0dca2bee1ad8285eb0527e6abc9c37672f8"
-  integrity sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==
+"@babel/plugin-transform-regenerator@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz#2015e59d839074e76838de2159db421966fd8b63"
+  integrity sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==
   dependencies:
     regenerator-transform "^0.14.2"
 
-"@babel/plugin-transform-reserved-words@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5"
-  integrity sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==
+"@babel/plugin-transform-reserved-words@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz#8f2682bcdcef9ed327e1b0861585d7013f8a54dd"
+  integrity sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-shorthand-properties@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8"
-  integrity sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==
+"@babel/plugin-transform-shorthand-properties@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz#9fd25ec5cdd555bb7f473e5e6ee1c971eede4dd6"
+  integrity sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-spread@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8"
-  integrity sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==
+"@babel/plugin-transform-spread@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz#4e2c85ea0d6abaee1b24dcfbbae426fe8d674cff"
+  integrity sha512-1e/51G/Ni+7uH5gktbWv+eCED9pP8ZpRhZB3jOaI3mmzfvJTWHkuyYTv0Z5PYtyM+Tr2Ccr9kUdQxn60fI5WuQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-sticky-regex@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100"
-  integrity sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==
+"@babel/plugin-transform-sticky-regex@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz#8f3889ee8657581130a29d9cc91d7c73b7c4a28d"
+  integrity sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
-    "@babel/helper-regex" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-regex" "^7.10.4"
 
-"@babel/plugin-transform-template-literals@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80"
-  integrity sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==
+"@babel/plugin-transform-template-literals@^7.10.4", "@babel/plugin-transform-template-literals@^7.8.3":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.4.tgz#e6375407b30fcb7fcfdbba3bb98ef3e9d36df7bc"
+  integrity sha512-4NErciJkAYe+xI5cqfS8pV/0ntlY5N5Ske/4ImxAVX7mk9Rxt2bwDTGv1Msc2BRJvWQcmYEC+yoMLdX22aE4VQ==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-annotate-as-pure" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-typeof-symbol@^7.8.4":
-  version "7.8.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412"
-  integrity sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==
+"@babel/plugin-transform-typeof-symbol@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz#9509f1a7eec31c4edbffe137c16cc33ff0bc5bfc"
+  integrity sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-unicode-regex@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad"
-  integrity sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==
+"@babel/plugin-transform-unicode-escapes@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz#feae523391c7651ddac115dae0a9d06857892007"
+  integrity sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-unicode-regex@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz#e56d71f9282fac6db09c82742055576d5e6d80a8"
+  integrity sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
 "@babel/preset-env@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.0.tgz#a5fc42480e950ae8f5d9f8f2bbc03f52722df3a8"
-  integrity sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ==
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.4.tgz#fbf57f9a803afd97f4f32e4f798bb62e4b2bef5f"
+  integrity sha512-tcmuQ6vupfMZPrLrc38d0sF2OjLT3/bZ0dry5HchNCQbrokoQi4reXqclvkkAT5b+gWc23meVWpve5P/7+w/zw==
   dependencies:
-    "@babel/compat-data" "^7.9.0"
-    "@babel/helper-compilation-targets" "^7.8.7"
-    "@babel/helper-module-imports" "^7.8.3"
-    "@babel/helper-plugin-utils" "^7.8.3"
-    "@babel/plugin-proposal-async-generator-functions" "^7.8.3"
-    "@babel/plugin-proposal-dynamic-import" "^7.8.3"
-    "@babel/plugin-proposal-json-strings" "^7.8.3"
-    "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3"
-    "@babel/plugin-proposal-numeric-separator" "^7.8.3"
-    "@babel/plugin-proposal-object-rest-spread" "^7.9.0"
-    "@babel/plugin-proposal-optional-catch-binding" "^7.8.3"
-    "@babel/plugin-proposal-optional-chaining" "^7.9.0"
-    "@babel/plugin-proposal-unicode-property-regex" "^7.8.3"
+    "@babel/compat-data" "^7.10.4"
+    "@babel/helper-compilation-targets" "^7.10.4"
+    "@babel/helper-module-imports" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/plugin-proposal-async-generator-functions" "^7.10.4"
+    "@babel/plugin-proposal-class-properties" "^7.10.4"
+    "@babel/plugin-proposal-dynamic-import" "^7.10.4"
+    "@babel/plugin-proposal-json-strings" "^7.10.4"
+    "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4"
+    "@babel/plugin-proposal-numeric-separator" "^7.10.4"
+    "@babel/plugin-proposal-object-rest-spread" "^7.10.4"
+    "@babel/plugin-proposal-optional-catch-binding" "^7.10.4"
+    "@babel/plugin-proposal-optional-chaining" "^7.10.4"
+    "@babel/plugin-proposal-private-methods" "^7.10.4"
+    "@babel/plugin-proposal-unicode-property-regex" "^7.10.4"
     "@babel/plugin-syntax-async-generators" "^7.8.0"
+    "@babel/plugin-syntax-class-properties" "^7.10.4"
     "@babel/plugin-syntax-dynamic-import" "^7.8.0"
     "@babel/plugin-syntax-json-strings" "^7.8.0"
     "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
-    "@babel/plugin-syntax-numeric-separator" "^7.8.0"
+    "@babel/plugin-syntax-numeric-separator" "^7.10.4"
     "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
     "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
     "@babel/plugin-syntax-optional-chaining" "^7.8.0"
-    "@babel/plugin-syntax-top-level-await" "^7.8.3"
-    "@babel/plugin-transform-arrow-functions" "^7.8.3"
-    "@babel/plugin-transform-async-to-generator" "^7.8.3"
-    "@babel/plugin-transform-block-scoped-functions" "^7.8.3"
-    "@babel/plugin-transform-block-scoping" "^7.8.3"
-    "@babel/plugin-transform-classes" "^7.9.0"
-    "@babel/plugin-transform-computed-properties" "^7.8.3"
-    "@babel/plugin-transform-destructuring" "^7.8.3"
-    "@babel/plugin-transform-dotall-regex" "^7.8.3"
-    "@babel/plugin-transform-duplicate-keys" "^7.8.3"
-    "@babel/plugin-transform-exponentiation-operator" "^7.8.3"
-    "@babel/plugin-transform-for-of" "^7.9.0"
-    "@babel/plugin-transform-function-name" "^7.8.3"
-    "@babel/plugin-transform-literals" "^7.8.3"
-    "@babel/plugin-transform-member-expression-literals" "^7.8.3"
-    "@babel/plugin-transform-modules-amd" "^7.9.0"
-    "@babel/plugin-transform-modules-commonjs" "^7.9.0"
-    "@babel/plugin-transform-modules-systemjs" "^7.9.0"
-    "@babel/plugin-transform-modules-umd" "^7.9.0"
-    "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3"
-    "@babel/plugin-transform-new-target" "^7.8.3"
-    "@babel/plugin-transform-object-super" "^7.8.3"
-    "@babel/plugin-transform-parameters" "^7.8.7"
-    "@babel/plugin-transform-property-literals" "^7.8.3"
-    "@babel/plugin-transform-regenerator" "^7.8.7"
-    "@babel/plugin-transform-reserved-words" "^7.8.3"
-    "@babel/plugin-transform-shorthand-properties" "^7.8.3"
-    "@babel/plugin-transform-spread" "^7.8.3"
-    "@babel/plugin-transform-sticky-regex" "^7.8.3"
-    "@babel/plugin-transform-template-literals" "^7.8.3"
-    "@babel/plugin-transform-typeof-symbol" "^7.8.4"
-    "@babel/plugin-transform-unicode-regex" "^7.8.3"
+    "@babel/plugin-syntax-top-level-await" "^7.10.4"
+    "@babel/plugin-transform-arrow-functions" "^7.10.4"
+    "@babel/plugin-transform-async-to-generator" "^7.10.4"
+    "@babel/plugin-transform-block-scoped-functions" "^7.10.4"
+    "@babel/plugin-transform-block-scoping" "^7.10.4"
+    "@babel/plugin-transform-classes" "^7.10.4"
+    "@babel/plugin-transform-computed-properties" "^7.10.4"
+    "@babel/plugin-transform-destructuring" "^7.10.4"
+    "@babel/plugin-transform-dotall-regex" "^7.10.4"
+    "@babel/plugin-transform-duplicate-keys" "^7.10.4"
+    "@babel/plugin-transform-exponentiation-operator" "^7.10.4"
+    "@babel/plugin-transform-for-of" "^7.10.4"
+    "@babel/plugin-transform-function-name" "^7.10.4"
+    "@babel/plugin-transform-literals" "^7.10.4"
+    "@babel/plugin-transform-member-expression-literals" "^7.10.4"
+    "@babel/plugin-transform-modules-amd" "^7.10.4"
+    "@babel/plugin-transform-modules-commonjs" "^7.10.4"
+    "@babel/plugin-transform-modules-systemjs" "^7.10.4"
+    "@babel/plugin-transform-modules-umd" "^7.10.4"
+    "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4"
+    "@babel/plugin-transform-new-target" "^7.10.4"
+    "@babel/plugin-transform-object-super" "^7.10.4"
+    "@babel/plugin-transform-parameters" "^7.10.4"
+    "@babel/plugin-transform-property-literals" "^7.10.4"
+    "@babel/plugin-transform-regenerator" "^7.10.4"
+    "@babel/plugin-transform-reserved-words" "^7.10.4"
+    "@babel/plugin-transform-shorthand-properties" "^7.10.4"
+    "@babel/plugin-transform-spread" "^7.10.4"
+    "@babel/plugin-transform-sticky-regex" "^7.10.4"
+    "@babel/plugin-transform-template-literals" "^7.10.4"
+    "@babel/plugin-transform-typeof-symbol" "^7.10.4"
+    "@babel/plugin-transform-unicode-escapes" "^7.10.4"
+    "@babel/plugin-transform-unicode-regex" "^7.10.4"
     "@babel/preset-modules" "^0.1.3"
-    "@babel/types" "^7.9.0"
-    browserslist "^4.9.1"
+    "@babel/types" "^7.10.4"
+    browserslist "^4.12.0"
     core-js-compat "^3.6.2"
     invariant "^2.2.2"
     levenary "^1.1.1"
@@ -750,82 +757,56 @@
     esutils "^2.0.2"
 
 "@babel/runtime@^7.8.4":
-  version "7.9.2"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06"
-  integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.4.tgz#a6724f1a6b8d2f6ea5236dbfe58c7d7ea9c5eb99"
+  integrity sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==
   dependencies:
     regenerator-runtime "^0.13.4"
 
-"@babel/template@^7.4.0", "@babel/template@^7.8.6":
-  version "7.8.6"
-  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b"
-  integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==
+"@babel/template@^7.10.4", "@babel/template@^7.4.0":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278"
+  integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==
   dependencies:
-    "@babel/code-frame" "^7.8.3"
-    "@babel/parser" "^7.8.6"
-    "@babel/types" "^7.8.6"
+    "@babel/code-frame" "^7.10.4"
+    "@babel/parser" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/template@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8"
-  integrity sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==
+"@babel/traverse@^7.10.4", "@babel/traverse@^7.4.3":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.4.tgz#e642e5395a3b09cc95c8e74a27432b484b697818"
+  integrity sha512-aSy7p5THgSYm4YyxNGz6jZpXf+Ok40QF3aA2LyIONkDHpAcJzDUqlCKXv6peqYUs2gmic849C/t2HKw2a2K20Q==
   dependencies:
-    "@babel/code-frame" "^7.8.3"
-    "@babel/parser" "^7.8.3"
-    "@babel/types" "^7.8.3"
-
-"@babel/traverse@^7.4.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.0.tgz#d3882c2830e513f4fe4cec9fe76ea1cc78747892"
-  integrity sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==
-  dependencies:
-    "@babel/code-frame" "^7.8.3"
-    "@babel/generator" "^7.9.0"
-    "@babel/helper-function-name" "^7.8.3"
-    "@babel/helper-split-export-declaration" "^7.8.3"
-    "@babel/parser" "^7.9.0"
-    "@babel/types" "^7.9.0"
+    "@babel/code-frame" "^7.10.4"
+    "@babel/generator" "^7.10.4"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/helper-split-export-declaration" "^7.10.4"
+    "@babel/parser" "^7.10.4"
+    "@babel/types" "^7.10.4"
     debug "^4.1.0"
     globals "^11.1.0"
     lodash "^4.17.13"
 
-"@babel/traverse@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.3.tgz#a826215b011c9b4f73f3a893afbc05151358bf9a"
-  integrity sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg==
+"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.4.tgz#369517188352e18219981efd156bfdb199fff1ee"
+  integrity sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==
   dependencies:
-    "@babel/code-frame" "^7.8.3"
-    "@babel/generator" "^7.8.3"
-    "@babel/helper-function-name" "^7.8.3"
-    "@babel/helper-split-export-declaration" "^7.8.3"
-    "@babel/parser" "^7.8.3"
-    "@babel/types" "^7.8.3"
-    debug "^4.1.0"
-    globals "^11.1.0"
-    lodash "^4.17.13"
-
-"@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.8.6", "@babel/types@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.0.tgz#00b064c3df83ad32b2dbf5ff07312b15c7f1efb5"
-  integrity sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==
-  dependencies:
-    "@babel/helper-validator-identifier" "^7.9.0"
+    "@babel/helper-validator-identifier" "^7.10.4"
     lodash "^4.17.13"
     to-fast-properties "^2.0.0"
 
-"@babel/types@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c"
-  integrity sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==
+"@koa/cors@^3.1.0":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.1.0.tgz#618bb073438cfdbd3ebd0e648a76e33b84f3a3b2"
+  integrity sha512-7ulRC1da/rBa6kj6P4g2aJfnET3z8Uf3SWu60cjbtxTA5g8lxRdX/Bd2P92EagGwwAhANeNw8T8if99rJliR6Q==
   dependencies:
-    esutils "^2.0.2"
-    lodash "^4.17.13"
-    to-fast-properties "^2.0.0"
+    vary "^1.1.2"
 
-"@open-wc/building-utils@^2.16.1":
-  version "2.16.1"
-  resolved "https://registry.yarnpkg.com/@open-wc/building-utils/-/building-utils-2.16.1.tgz#093d74881b996fe9497d628cdf55b6757422d894"
-  integrity sha512-0nUktFyelvSbCc8+T4w4PCyIy3i8blOFS0/EiG5xbVJ0HejDPQLTSmRBpZkn6X57tHhwUfjIdv0EAQuo2sbHEw==
+"@open-wc/building-utils@^2.18.0":
+  version "2.18.0"
+  resolved "https://registry.yarnpkg.com/@open-wc/building-utils/-/building-utils-2.18.0.tgz#f80929dfcfb6d8a6cb5c933654c721808b4bb2d3"
+  integrity sha512-U1n8sLQlLt3IuqhU7tDsGQAGUfVMiB64xJsAmJEtekposrjqkjtRLU/WipvROl1A2GTsrMojMjNbFqzJghpd6g==
   dependencies:
     "@babel/core" "^7.9.0"
     "@babel/plugin-syntax-dynamic-import" "^7.8.3"
@@ -837,7 +818,7 @@
     clean-css "^4.2.1"
     clone "^2.1.2"
     core-js-bundle "^3.6.0"
-    deepmerge "^3.2.0"
+    deepmerge "^4.2.2"
     es-module-shims "^0.4.6"
     html-minifier "^4.0.0"
     lru-cache "^5.1.1"
@@ -846,26 +827,27 @@
     path-is-inside "^1.0.2"
     regenerator-runtime "^0.13.3"
     resolve "^1.11.1"
-    rimraf "^3.0.0"
+    rimraf "^3.0.2"
     shady-css-scoped-element "^0.0.2"
-    systemjs "^4.0.0"
-    terser "^4.6.4"
+    systemjs "^6.3.1"
+    terser "^4.6.7"
     valid-url "^1.0.9"
     whatwg-fetch "^3.0.0"
     whatwg-url "^7.0.0"
 
-"@open-wc/karma-esm@^2.13.21":
-  version "2.13.21"
-  resolved "https://registry.yarnpkg.com/@open-wc/karma-esm/-/karma-esm-2.13.21.tgz#bef38b4e153b5728a6934de8a926d8bd9b9bb4db"
-  integrity sha512-qJREvj5HbYpUb6IeQXXiylPtqSnknUhBeK3PmhlnVdsXCeuPucmKJHbInd8ThYjX5/UJSp/cWe/Dt4H8GqHPHw==
+"@open-wc/karma-esm@^2.16.16":
+  version "2.16.16"
+  resolved "https://registry.yarnpkg.com/@open-wc/karma-esm/-/karma-esm-2.16.16.tgz#6ebff57f249e95f777b7e04782ef08ed41e22f53"
+  integrity sha512-IALT10JfwK+h7T0hGKTUliGdkWzQbyQg195D+RfUteIoTof6Z5+dBp7JUh2fQygIyNj7IIYHJ9ej816QlgHjdA==
   dependencies:
-    "@open-wc/building-utils" "^2.16.1"
+    "@open-wc/building-utils" "^2.18.0"
     babel-plugin-istanbul "^5.1.4"
     chokidar "^3.0.0"
-    deepmerge "^3.2.0"
-    es-dev-server "^1.46.0"
+    deepmerge "^4.2.2"
+    es-dev-server "^1.56.0"
     minimatch "^3.0.4"
     node-fetch "^2.6.0"
+    polyfills-loader "^1.6.1"
     portfinder "^1.0.21"
     request "^2.88.0"
 
@@ -888,23 +870,25 @@
   resolved "https://registry.yarnpkg.com/@polymer/test-fixture/-/test-fixture-4.0.2.tgz#2f4777ecdcfb22ee000db35a05e0edf27c722c19"
   integrity sha512-tLX8tFE4mkc4p84YG5239G0hbgTVv2irZYrSyO0OblUqIRbRoCPmbydm3HRFQkJeAB3rPCtyeZ2roJULsmTG3A==
 
-"@rollup/plugin-node-resolve@^6.1.0":
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-6.1.0.tgz#0d2909f4bf606ae34d43a9bc8be06a9b0c850cf0"
-  integrity sha512-Cv7PDIvxdE40SWilY5WgZpqfIUEaDxFxs89zCAHjqyRwlTSuql4M5hjIuc5QYJkOH0/vyiyNXKD72O+LhRipGA==
+"@rollup/plugin-node-resolve@^7.1.1":
+  version "7.1.3"
+  resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz#80de384edfbd7bfc9101164910f86078151a3eca"
+  integrity sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==
   dependencies:
-    "@rollup/pluginutils" "^3.0.0"
+    "@rollup/pluginutils" "^3.0.8"
     "@types/resolve" "0.0.8"
     builtin-modules "^3.1.0"
     is-module "^1.0.0"
-    resolve "^1.11.1"
+    resolve "^1.14.2"
 
-"@rollup/pluginutils@^3.0.0":
-  version "3.0.8"
-  resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.0.8.tgz#4e94d128d94b90699e517ef045422960d18c8fde"
-  integrity sha512-rYGeAc4sxcZ+kPG/Tw4/fwJODC3IXHYDH4qusdN/b6aLw5LPUbzpecYbEJh4sVQGPFJxd2dBU4kc1H3oy9/bnw==
+"@rollup/pluginutils@^3.0.0", "@rollup/pluginutils@^3.0.8":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
+  integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
   dependencies:
+    "@types/estree" "0.0.39"
     estree-walker "^1.0.1"
+    picomatch "^2.2.2"
 
 "@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.7.2":
   version "1.8.0"
@@ -942,15 +926,239 @@
   resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
   integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
 
+"@types/accepts@*":
+  version "1.3.5"
+  resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575"
+  integrity sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==
+  dependencies:
+    "@types/node" "*"
+
+"@types/babel__core@^7.1.3":
+  version "7.1.9"
+  resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d"
+  integrity sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==
+  dependencies:
+    "@babel/parser" "^7.1.0"
+    "@babel/types" "^7.0.0"
+    "@types/babel__generator" "*"
+    "@types/babel__template" "*"
+    "@types/babel__traverse" "*"
+
+"@types/babel__generator@*":
+  version "7.6.1"
+  resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04"
+  integrity sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==
+  dependencies:
+    "@babel/types" "^7.0.0"
+
+"@types/babel__template@*":
+  version "7.0.2"
+  resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307"
+  integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==
+  dependencies:
+    "@babel/parser" "^7.1.0"
+    "@babel/types" "^7.0.0"
+
+"@types/babel__traverse@*":
+  version "7.0.12"
+  resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.12.tgz#22f49a028e69465390f87bb103ebd61bd086b8f5"
+  integrity sha512-t4CoEokHTfcyfb4hUaF9oOHu9RmmNWnm1CP0YmMqOOfClKascOmvlEM736vlqeScuGvBDsHkf8R2INd4DWreQA==
+  dependencies:
+    "@babel/types" "^7.3.0"
+
+"@types/body-parser@*":
+  version "1.19.0"
+  resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f"
+  integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==
+  dependencies:
+    "@types/connect" "*"
+    "@types/node" "*"
+
+"@types/browserslist-useragent@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@types/browserslist-useragent/-/browserslist-useragent-3.0.0.tgz#d425c9818182ce71ce53866798cee9c7d41d6e53"
+  integrity sha512-ZBvKzg3yyWNYEkwxAzdmUzp27sFvw+1m080/+2lwrt+eltNefn1f4fnpMyrjOla31p8zLleCYqQXw+3EETfn0w==
+
+"@types/browserslist@^4.8.0":
+  version "4.8.0"
+  resolved "https://registry.yarnpkg.com/@types/browserslist/-/browserslist-4.8.0.tgz#60489aefdf0fcb56c2d8eb65267ff08dad7a526d"
+  integrity sha512-4PyO9OM08APvxxo1NmQyQKlJdowPCOQIy5D/NLO3aO0vGC57wsMptvGp3b8IbYnupFZr92l1dlVief1JvS6STQ==
+
+"@types/caniuse-api@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@types/caniuse-api/-/caniuse-api-3.0.0.tgz#af31cc52062be0ab24583be072fd49b634dcc2fe"
+  integrity sha512-wT1VfnScjAftZsvLYaefu/UuwYJdYBwD2JDL2OQd01plGmuAoir5V6HnVHgrfh7zEwcasoiyO2wQ+W58sNh2sw==
+
+"@types/command-line-args@^5.0.0":
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/@types/command-line-args/-/command-line-args-5.0.0.tgz#484e704d20dbb8754a8f091eee45cdd22bcff28c"
+  integrity sha512-4eOPXyn5DmP64MCMF8ePDvdlvlzt2a+F8ZaVjqmh2yFCpGjc1kI3kGnCFYX9SCsGTjQcWIyVZ86IHCEyjy/MNg==
+
+"@types/command-line-usage@^5.0.1":
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/@types/command-line-usage/-/command-line-usage-5.0.1.tgz#99424950da567ba67b6b65caee57ff03c4e751ec"
+  integrity sha512-/xUgezxxYePeXhg5S04hUjxG9JZi+rJTs1+4NwpYPfSaS7BeDa6tVJkH6lN9Cb6rl8d24Fi2uX0s0Ngg2JT6gg==
+
+"@types/connect@*":
+  version "3.4.33"
+  resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546"
+  integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==
+  dependencies:
+    "@types/node" "*"
+
+"@types/content-disposition@*":
+  version "0.5.3"
+  resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.3.tgz#0aa116701955c2faa0717fc69cd1596095e49d96"
+  integrity sha512-P1bffQfhD3O4LW0ioENXUhZ9OIa0Zn+P7M+pWgkCKaT53wVLSq0mrKksCID/FGHpFhRSxRGhgrQmfhRuzwtKdg==
+
+"@types/cookies@*":
+  version "0.7.4"
+  resolved "https://registry.yarnpkg.com/@types/cookies/-/cookies-0.7.4.tgz#26dedf791701abc0e36b5b79a5722f40e455f87b"
+  integrity sha512-oTGtMzZZAVuEjTwCjIh8T8FrC8n/uwy+PG0yTvQcdZ7etoel7C7/3MSd7qrukENTgQtotG7gvBlBojuVs7X5rw==
+  dependencies:
+    "@types/connect" "*"
+    "@types/express" "*"
+    "@types/keygrip" "*"
+    "@types/node" "*"
+
+"@types/debounce@^1.2.0":
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.2.0.tgz#9ee99259f41018c640b3929e1bb32c3dcecdb192"
+  integrity sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw==
+
+"@types/estree@0.0.39":
+  version "0.0.39"
+  resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
+  integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
+
+"@types/etag@*":
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/@types/etag/-/etag-1.8.0.tgz#37f0b1f3ea46da7ae319bbedb607e375b4c99f7e"
+  integrity sha512-EdSN0x+Y0/lBv7YAb8IU4Jgm6DWM+Bqtz7o5qozl96fzaqdqbdfHS5qjdpFeIv7xQ8jSLyjMMNShgYtMajEHyQ==
+  dependencies:
+    "@types/node" "*"
+
+"@types/express-serve-static-core@*":
+  version "4.17.8"
+  resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz#b8f7b714138536742da222839892e203df569d1c"
+  integrity sha512-1SJZ+R3Q/7mLkOD9ewCBDYD2k0WyZQtWYqF/2VvoNN2/uhI49J9CDN4OAm+wGMA0DbArA4ef27xl4+JwMtGggw==
+  dependencies:
+    "@types/node" "*"
+    "@types/qs" "*"
+    "@types/range-parser" "*"
+
+"@types/express@*":
+  version "4.17.6"
+  resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.6.tgz#6bce49e49570507b86ea1b07b806f04697fac45e"
+  integrity sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==
+  dependencies:
+    "@types/body-parser" "*"
+    "@types/express-serve-static-core" "*"
+    "@types/qs" "*"
+    "@types/serve-static" "*"
+
+"@types/http-assert@*":
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/@types/http-assert/-/http-assert-1.5.1.tgz#d775e93630c2469c2f980fc27e3143240335db3b"
+  integrity sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ==
+
+"@types/keygrip@*":
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72"
+  integrity sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==
+
+"@types/koa-compose@*":
+  version "3.2.5"
+  resolved "https://registry.yarnpkg.com/@types/koa-compose/-/koa-compose-3.2.5.tgz#85eb2e80ac50be95f37ccf8c407c09bbe3468e9d"
+  integrity sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==
+  dependencies:
+    "@types/koa" "*"
+
+"@types/koa-compress@^2.0.9":
+  version "2.0.9"
+  resolved "https://registry.yarnpkg.com/@types/koa-compress/-/koa-compress-2.0.9.tgz#5d19f7d928f78b451a9afd148863e2b45f51e541"
+  integrity sha512-1Sa9OsbHd2N2N7gLpdIRHe8W99EZbfIR31D7Iisx16XgwZCnWUtGXzXQejhu74Y1pE/wILqBP6VL49ch/MVpZw==
+  dependencies:
+    "@types/koa" "*"
+    "@types/node" "*"
+
+"@types/koa-etag@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@types/koa-etag/-/koa-etag-3.0.0.tgz#d14d3dab45d5577b94bc72960631de96751341d3"
+  integrity sha512-gXQUtKGEnCy0sZLG+uE3wL4mvY1CBPcb6ECjpAoD8RGYy/8ACY1B084k8LTFPIdVcmy7GD6Y4n3up3jnupofcQ==
+  dependencies:
+    "@types/etag" "*"
+    "@types/koa" "*"
+
+"@types/koa-send@*":
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/@types/koa-send/-/koa-send-4.1.2.tgz#978f8267ad116d12ac6a18fecd8f34c5657e09ad"
+  integrity sha512-rfqKIv9bFds39Jxvsp8o3YJLnEQVPVriYA14AuO2OY65IHh/4UX4U/iMs5L0wATpcRmm1bbe0BNk23TRwx3VQQ==
+  dependencies:
+    "@types/koa" "*"
+
+"@types/koa-static@^4.0.1":
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/@types/koa-static/-/koa-static-4.0.1.tgz#b740d80a549b0a0a7a3b38918daecde88a7a50ec"
+  integrity sha512-SSpct5fEcAeRkBHa3RiwCIRfDHcD1cZRhwRF///ZfvRt8KhoqRrhK6wpDlYPk/vWHVFE9hPGqh68bhzsHkir4w==
+  dependencies:
+    "@types/koa" "*"
+    "@types/koa-send" "*"
+
+"@types/koa@*", "@types/koa@^2.0.48":
+  version "2.11.3"
+  resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.11.3.tgz#540ece376581b12beadf9a417dd1731bc31c16ce"
+  integrity sha512-ABxVkrNWa4O/Jp24EYI/hRNqEVRlhB9g09p48neQp4m3xL1TJtdWk2NyNQSMCU45ejeELMQZBYyfstyVvO2H3Q==
+  dependencies:
+    "@types/accepts" "*"
+    "@types/content-disposition" "*"
+    "@types/cookies" "*"
+    "@types/http-assert" "*"
+    "@types/keygrip" "*"
+    "@types/koa-compose" "*"
+    "@types/node" "*"
+
+"@types/koa__cors@^3.0.1":
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/@types/koa__cors/-/koa__cors-3.0.1.tgz#a8cf8535f0fe682c9421f1b9379837c585f8b66b"
+  integrity sha512-loqZNXliley8kncc4wrX9KMqLGN6YfiaO3a3VFX+yVkkXJwOrZU4lipdudNjw5mFyC+5hd7h9075hQWcVVpeOg==
+  dependencies:
+    "@types/koa" "*"
+
+"@types/lru-cache@^5.1.0":
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.0.tgz#57f228f2b80c046b4a1bd5cac031f81f207f4f03"
+  integrity sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w==
+
+"@types/mime@*":
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.2.tgz#857a118d8634c84bba7ae14088e4508490cd5da5"
+  integrity sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==
+
 "@types/minimatch@^3.0.3":
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
   integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
 
 "@types/node@*":
-  version "13.1.8"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.8.tgz#1d590429fe8187a02707720ecf38a6fe46ce294b"
-  integrity sha512-6XzyyNM9EKQW4HKuzbo/CkOIjn/evtCmsU+MUM1xDfJ+3/rNjBttM1NgN7AOQvN6tP1Sl1D1PIKMreTArnxM9A==
+  version "14.0.14"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce"
+  integrity sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ==
+
+"@types/path-is-inside@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@types/path-is-inside/-/path-is-inside-1.0.0.tgz#02d6ff38975d684bdec96204494baf9f29f0e17f"
+  integrity sha512-hfnXRGugz+McgX2jxyy5qz9sB21LRzlGn24zlwN2KEgoPtEvjzNRrLtUkOOebPDPZl3Rq7ywKxYvylVcEZDnEw==
+
+"@types/qs@*":
+  version "6.9.3"
+  resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.3.tgz#b755a0934564a200d3efdf88546ec93c369abd03"
+  integrity sha512-7s9EQWupR1fTc2pSMtXRQ9w9gLOcrJn+h7HOXw4evxyvVqMi4f+q7d2tnFe3ng3SNHjtK+0EzGMGFUQX4/AQRA==
+
+"@types/range-parser@*":
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
+  integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
 
 "@types/resolve@0.0.8":
   version "0.0.8"
@@ -959,15 +1167,30 @@
   dependencies:
     "@types/node" "*"
 
+"@types/serve-static@*":
+  version "1.13.4"
+  resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.4.tgz#6662a93583e5a6cabca1b23592eb91e12fa80e7c"
+  integrity sha512-jTDt0o/YbpNwZbQmE/+2e+lfjJEJJR0I3OFaKQKPWkASkCoW3i6fsUnqudSMcNAfbtmADGu8f4MV4q+GqULmug==
+  dependencies:
+    "@types/express-serve-static-core" "*"
+    "@types/mime" "*"
+
+"@types/whatwg-url@^6.4.0":
+  version "6.4.0"
+  resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-6.4.0.tgz#1e59b8c64bc0dbdf66d037cf8449d1c3d5270237"
+  integrity sha512-tonhlcbQ2eho09am6RHnHOgvtDfDYINd5rgxD+2YSkKENooVCFsWizJz139MQW/PV8FfClyKrNe9ZbdHrSCxGg==
+  dependencies:
+    "@types/node" "*"
+
 "@webcomponents/shadycss@^1.9.1":
   version "1.9.4"
   resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.9.4.tgz#4f9d8ea1526bab084c60b53d4854dc39fdb2bb48"
   integrity sha512-tgNcVEaKssyeZPbUBjVQf4aryO5Fi7fxRvOxV982ZJuRVDcefmIblBh0SXAbcvAAlQ2zpNEP4SuQUnr8uApIpw==
 
 "@webcomponents/shadycss@^1.9.4":
-  version "1.9.6"
-  resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.9.6.tgz#a8c5db867e49200a05cf8d5008029c09b7861979"
-  integrity sha512-5fFjvP0jQJZoXK6YzYeYcIDGJ5oEsdjr1L9VaYLw5yxNd4aRz4srMpwCwldeNG0A6Hvr9igbG7fCsBeiiCXd7A==
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.10.0.tgz#7a80ec1e8b271fb3f0cc02cd4358b877a303545d"
+  integrity sha512-UMS+dF4DXDrcUmQqK6aLd/3mFyfGktKG/hZR6FtrsQK/INO07G0H8FxElLkuvHj0iePeZGpR7R4lWFTvX7rc9g==
 
 "@webcomponents/webcomponentsjs@^2.4.0":
   version "2.4.3"
@@ -998,9 +1221,9 @@
   integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
 
 ajv@^6.5.5:
-  version "6.11.0"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9"
-  integrity sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==
+  version "6.12.2"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd"
+  integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==
   dependencies:
     fast-deep-equal "^3.1.1"
     fast-json-stable-stringify "^2.0.0"
@@ -1109,14 +1332,14 @@
   integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
 
 aws4@^1.8.0:
-  version "1.9.1"
-  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
-  integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2"
+  integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==
 
-babel-plugin-dynamic-import-node@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f"
-  integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==
+babel-plugin-dynamic-import-node@^2.3.3:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
+  integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==
   dependencies:
     object.assign "^4.1.0"
 
@@ -1216,23 +1439,23 @@
   integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
 
 browserslist-useragent@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/browserslist-useragent/-/browserslist-useragent-3.0.2.tgz#f0e209b2742baa5de0e451b52e678e8b4402617c"
-  integrity sha512-/UPzK9xZnk5mwwWx4wcuBKAKx/mD3MNY8sUuZ2NPqnr4RVFWZogX+8mOP0cQEYo8j78sHk0hiDNaVXZ1U3hM9A==
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/browserslist-useragent/-/browserslist-useragent-3.0.3.tgz#d06c062a4e444ad5e1a80323131d4508450c9af5"
+  integrity sha512-8KKO6kOXu/93IkMi8zVqzU72BgpoxcITIHtkM1qmlnxJtIMF9Y+2uWL9JS2uUbzj/PaS3kaA6LcICBThMojGjA==
   dependencies:
-    browserslist "^4.6.6"
-    semver "^6.3.0"
+    browserslist "^4.12.0"
+    semver "^7.3.2"
     useragent "^2.3.0"
 
-browserslist@^4.0.0, browserslist@^4.6.6, browserslist@^4.8.3, browserslist@^4.9.1:
-  version "4.11.1"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.1.tgz#92f855ee88d6e050e7e7311d987992014f1a1f1b"
-  integrity sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g==
+browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.8.5, browserslist@^4.9.1:
+  version "4.12.2"
+  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.2.tgz#76653d7e4c57caa8a1a28513e2f4e197dc11a711"
+  integrity sha512-MfZaeYqR8StRZdstAK9hCKDd2StvePCYp5rHzQCPicUjfFliDgmuaBNPHYUTpAywBN8+Wc/d7NYVFkO0aqaBUw==
   dependencies:
-    caniuse-lite "^1.0.30001038"
-    electron-to-chromium "^1.3.390"
-    node-releases "^1.1.53"
-    pkg-up "^2.0.0"
+    caniuse-lite "^1.0.30001088"
+    electron-to-chromium "^1.3.483"
+    escalade "^3.0.1"
+    node-releases "^1.1.58"
 
 buffer-alloc-unsafe@^1.1.0:
   version "1.1.0"
@@ -1303,10 +1526,10 @@
     lodash.memoize "^4.1.2"
     lodash.uniq "^4.5.0"
 
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001033, caniuse-lite@^1.0.30001038:
-  version "1.0.30001038"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001038.tgz#44da3cbca2ab6cb6aa83d1be5d324e17f141caff"
-  integrity sha512-zii9quPo96XfOiRD4TrfYGs+QsGZpb2cGiMAzPjtf/hpFgB6zCPZgJb7I1+EATeMw/o+lG8FyRAnI+CWStHcaQ==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001033, caniuse-lite@^1.0.30001088:
+  version "1.0.30001093"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001093.tgz#833e80f64b1a0455cbceed2a4a3baf19e4abd312"
+  integrity sha512-0+ODNoOjtWD5eS9aaIpf4K0gQqZfILNY4WSNuYzeT1sXni+lMrrVjc0odEobJt6wrODofDZUX8XYi/5y7+xl8g==
 
 caseless@~0.12.0:
   version "0.12.0"
@@ -1439,7 +1662,7 @@
     table-layout "^1.0.0"
     typical "^5.2.0"
 
-commander@^2.19.0, commander@^2.20.0, commander@~2.20.3:
+commander@^2.19.0, commander@^2.20.0:
   version "2.20.3"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
   integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@@ -1514,16 +1737,16 @@
     keygrip "~1.1.0"
 
 core-js-bundle@^3.6.0:
-  version "3.6.4"
-  resolved "https://registry.yarnpkg.com/core-js-bundle/-/core-js-bundle-3.6.4.tgz#d4e098323c035f4a1b61f00db0b8def04c243920"
-  integrity sha512-qDHS3GbIEs5dZaBiCVhhtCoF79KU/ek0w+H7zfJf9RuGN0GiKfxHZfAtDy4zFtQ6X00t7Wvvr3wHzMj+/IgbPg==
+  version "3.6.5"
+  resolved "https://registry.yarnpkg.com/core-js-bundle/-/core-js-bundle-3.6.5.tgz#3a425ad66ad19aeefea89acfd48cff674ff58590"
+  integrity sha512-awf49McIBT3sDXceSex69w/i7PMXQwxI4ZqknCtaYbW4Q0u0HUZiaQLlPD6pU2nFBofIowgWIS1ANgHjqnQu4Q==
 
 core-js-compat@^3.6.2:
-  version "3.6.4"
-  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17"
-  integrity sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA==
+  version "3.6.5"
+  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c"
+  integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==
   dependencies:
-    browserslist "^4.8.3"
+    browserslist "^4.8.5"
     semver "7.0.0"
 
 core-util-is@1.0.2:
@@ -1603,10 +1826,10 @@
   resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
   integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
 
-deepmerge@^3.2.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.3.0.tgz#d3c47fd6f3a93d517b14426b0628a17b0125f5f7"
-  integrity sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==
+deepmerge@^4.2.2:
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
+  integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
 
 define-properties@^1.1.2, define-properties@^1.1.3:
   version "1.1.3"
@@ -1683,10 +1906,10 @@
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
   integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
 
-electron-to-chromium@^1.3.390:
-  version "1.3.392"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.392.tgz#280ab4f7a3ae47419cfabb15dbfc1567be7f1111"
-  integrity sha512-/hsgeVdReDsyTBE0aU9FRdh1wnNPrX3xlz3t61F+CJPOT+Umfi9DXHsCX85TEgWZQqlow0Rw44/4/jbU2Sqgkg==
+electron-to-chromium@^1.3.483:
+  version "1.3.486"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.486.tgz#90856e6c9f488079225cf5a0b4d4af6c241e0965"
+  integrity sha512-fmnACh6Jiuagm9tAfEZNe6QrwvOYAC5y0BwzoEOGCsbqriKOCaafXf3lsIvL55xa75Jmg4oboI7f5tMuoXrjNg==
 
 emoji-regex@^7.0.1:
   version "7.0.3"
@@ -1757,11 +1980,6 @@
   dependencies:
     is-arrayish "^0.2.1"
 
-error-inject@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/error-inject/-/error-inject-1.0.0.tgz#e2b3d91b54aed672f309d950d154850fa11d4f37"
-  integrity sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=
-
 es-abstract@^1.17.0-next.1:
   version "1.17.4"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184"
@@ -1779,10 +1997,10 @@
     string.prototype.trimleft "^2.1.1"
     string.prototype.trimright "^2.1.1"
 
-es-dev-server@^1.46.0:
-  version "1.46.0"
-  resolved "https://registry.yarnpkg.com/es-dev-server/-/es-dev-server-1.46.0.tgz#6fa8615604d8bfaa6a181f3bfb8b62d9f4b3dd81"
-  integrity sha512-+6RDz/YeBEkEHcf84I2pS+JYY1ov3d24JAda8hVMFQtpI21+G4/t0YA3lZSIYlPZ6AIoTgmb/+pKQh6JzmOUVA==
+es-dev-server@^1.56.0:
+  version "1.56.0"
+  resolved "https://registry.yarnpkg.com/es-dev-server/-/es-dev-server-1.56.0.tgz#8703af87595f02fe9a1c92a07e64b7c7cc915a87"
+  integrity sha512-SL4CXdiku0hiB8zpsBLtEd7b8etIZE6IV0tIi02m0CcpTYV0rDMEvCBUYsQIN5hggJDDTBURgQjOWcT5kQv2eA==
   dependencies:
     "@babel/core" "^7.9.0"
     "@babel/plugin-proposal-dynamic-import" "^7.8.3"
@@ -1795,10 +2013,26 @@
     "@babel/plugin-syntax-optional-chaining" "^7.8.3"
     "@babel/plugin-transform-template-literals" "^7.8.3"
     "@babel/preset-env" "^7.9.0"
-    "@open-wc/building-utils" "^2.16.1"
-    "@rollup/plugin-node-resolve" "^6.1.0"
+    "@koa/cors" "^3.1.0"
+    "@open-wc/building-utils" "^2.18.0"
+    "@rollup/plugin-node-resolve" "^7.1.1"
     "@rollup/pluginutils" "^3.0.0"
+    "@types/babel__core" "^7.1.3"
+    "@types/browserslist" "^4.8.0"
+    "@types/browserslist-useragent" "^3.0.0"
+    "@types/caniuse-api" "^3.0.0"
+    "@types/command-line-args" "^5.0.0"
+    "@types/command-line-usage" "^5.0.1"
+    "@types/debounce" "^1.2.0"
+    "@types/koa" "^2.0.48"
+    "@types/koa-compress" "^2.0.9"
+    "@types/koa-etag" "^3.0.0"
+    "@types/koa-static" "^4.0.1"
+    "@types/koa__cors" "^3.0.1"
+    "@types/lru-cache" "^5.1.0"
     "@types/minimatch" "^3.0.3"
+    "@types/path-is-inside" "^1.0.0"
+    "@types/whatwg-url" "^6.4.0"
     browserslist "^4.9.1"
     browserslist-useragent "^3.0.2"
     builtin-modules "^3.1.0"
@@ -1809,7 +2043,7 @@
     command-line-args "^5.0.2"
     command-line-usage "^6.1.0"
     debounce "^1.2.0"
-    deepmerge "^3.2.0"
+    deepmerge "^4.2.2"
     es-module-lexer "^0.3.13"
     get-stream "^5.1.0"
     is-stream "^2.0.0"
@@ -1819,26 +2053,29 @@
     koa-etag "^3.0.0"
     koa-static "^5.0.0"
     lru-cache "^5.1.1"
+    mime-types "^2.1.27"
     minimatch "^3.0.4"
-    opn "^5.4.0"
+    open "^7.0.3"
     parse5 "^5.1.1"
     path-is-inside "^1.0.2"
-    polyfills-loader "^1.5.2"
+    polyfills-loader "^1.6.1"
     portfinder "^1.0.21"
+    rollup "^2.7.2"
     strip-ansi "^5.2.0"
-    systemjs "^4.0.0"
+    systemjs "^6.3.1"
+    tslib "^1.11.1"
     useragent "^2.3.0"
     whatwg-url "^7.0.0"
 
 es-module-lexer@^0.3.13:
-  version "0.3.17"
-  resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.3.17.tgz#a248dec2870934d9054420fead19db095ea21537"
-  integrity sha512-nwvMtzyEB6FhlyXBlV+BW2By3Vn2sUvlQBYP4LvdK8YpdbFQUOiBoeuB7/ip1+EbjmgNydkJ8+dIlyO09VP9BA==
+  version "0.3.24"
+  resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.3.24.tgz#e6b2900758e9e210d23aec2092efc13ca235adea"
+  integrity sha512-jm/i7KdJtaMDle921xIsA/MQQOGuZ6goYxhlV+k+gQNI7FtP4N6jknrmJvj++3ODpiyFGwQ4PIstJfHJQJNc+g==
 
 es-module-shims@^0.4.6:
-  version "0.4.6"
-  resolved "https://registry.yarnpkg.com/es-module-shims/-/es-module-shims-0.4.6.tgz#5decb313d52e5c62f6c19ed7e664ee9d66317d8a"
-  integrity sha512-EzVhnLyA/zvmGrAy2RU8m9xpxX7u2yb2by1GZH80SHF6lakG21YAm3Vo56KsLIXaIjT9QabqjYpQU1S5FkM8+Q==
+  version "0.4.7"
+  resolved "https://registry.yarnpkg.com/es-module-shims/-/es-module-shims-0.4.7.tgz#1419b65bbd38dfe91ab8ea5d7b4b454561e44641"
+  integrity sha512-0LTiSQoPWwdcaTVIQXhGlaDwTneD0g9/tnH1PNs3zHFFH+xoCeJclDM3rQeqF9nurXPfMKm3l9+kfPRa5VpbKg==
 
 es-to-primitive@^1.2.1:
   version "1.2.1"
@@ -1849,6 +2086,11 @@
     is-date-object "^1.0.1"
     is-symbol "^1.0.2"
 
+escalade@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.1.tgz#52568a77443f6927cd0ab9c73129137533c965ed"
+  integrity sha512-DR6NO3h9niOT+MZs7bjxlj2a1k+POu5RN8CLTPX2+i78bRi9eLe7+0zXgUHMnGXWybYcL61E9hGhPKqedy8tQA==
+
 escape-html@^1.0.3, escape-html@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
@@ -1900,9 +2142,9 @@
   integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
 
 fast-deep-equal@^3.1.1:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
-  integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+  integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
 
 fast-json-stable-stringify@^2.0.0:
   version "2.1.0"
@@ -1943,13 +2185,6 @@
   dependencies:
     locate-path "^3.0.0"
 
-find-up@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
-  integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
-  dependencies:
-    locate-path "^2.0.0"
-
 flat@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2"
@@ -2140,9 +2375,9 @@
   integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
 
 hosted-git-info@^2.1.4:
-  version "2.8.5"
-  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c"
-  integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==
+  version "2.8.8"
+  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
+  integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
 
 html-minifier@^4.0.0:
   version "4.0.0"
@@ -2176,14 +2411,14 @@
     statuses ">= 1.5.0 < 2"
     toidentifier "1.0.0"
 
-http-errors@^1.6.3, http-errors@~1.7.2:
-  version "1.7.3"
-  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
-  integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
+http-errors@^1.6.3:
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.0.tgz#75d1bbe497e1044f51e4ee9e704a62f28d336507"
+  integrity sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==
   dependencies:
     depd "~1.1.2"
     inherits "2.0.4"
-    setprototypeof "1.1.1"
+    setprototypeof "1.2.0"
     statuses ">= 1.5.0 < 2"
     toidentifier "1.0.0"
 
@@ -2197,6 +2432,17 @@
     setprototypeof "1.1.0"
     statuses ">= 1.4.0 < 2"
 
+http-errors@~1.7.2:
+  version "1.7.3"
+  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
+  integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
+  dependencies:
+    depd "~1.1.2"
+    inherits "2.0.4"
+    setprototypeof "1.1.1"
+    statuses ">= 1.5.0 < 2"
+    toidentifier "1.0.0"
+
 http-proxy@^1.13.0:
   version "1.18.0"
   resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a"
@@ -2284,6 +2530,11 @@
   resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
   integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
 
+is-docker@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b"
+  integrity sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==
+
 is-extglob@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -2340,10 +2591,12 @@
   resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
   integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
 
-is-wsl@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
-  integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
+is-wsl@^2.1.1:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
+  integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
+  dependencies:
+    is-docker "^2.0.0"
 
 isarray@0.0.1:
   version "0.0.1"
@@ -2363,9 +2616,9 @@
     buffer-alloc "^1.2.0"
 
 isbinaryfile@^4.0.2:
-  version "4.0.5"
-  resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.5.tgz#7193454fdd7fc0b12855c36c48d4ac7368fa3ec9"
-  integrity sha512-Jvz0gpTh1AILHMCBUyqq7xv1ZOQrxTDwyp1/QUq1xFpOBvp4AH5uEobPePJht8KnBGqQIH7We6OR73mXsjG0cA==
+  version "4.0.6"
+  resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b"
+  integrity sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==
 
 isexe@^2.0.0:
   version "2.0.0"
@@ -2444,9 +2697,9 @@
   integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
 
 json5@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.2.tgz#43ef1f0af9835dd624751a6b7fa48874fb2d608e"
-  integrity sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
+  integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
   dependencies:
     minimist "^1.2.5"
 
@@ -2547,9 +2800,9 @@
   integrity sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==
 
 koa-compress@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/koa-compress/-/koa-compress-3.0.0.tgz#3194059c215cbc24e59bbc84c2c7453a4c88564f"
-  integrity sha512-xol+LkNB1mozKJkB5Kj6nYXbJXhkLkZlXl9BsGBPjujVfZ8MsIXwU4GHRTT7TlSfUcl2DU3JtC+j6wOWcovfuQ==
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/koa-compress/-/koa-compress-3.1.0.tgz#00fb0af695dc4661c6de261a18da669626ea3ca1"
+  integrity sha512-0m24/yS/GbhWI+g9FqtvStY+yJwTObwoxOvPok6itVjRen7PBWkjsJ8pre76m+99YybXLKhOJ62mJ268qyBFMQ==
   dependencies:
     bytes "^3.0.0"
     compressible "^2.0.0"
@@ -2596,9 +2849,9 @@
     koa-send "^5.0.0"
 
 koa@^2.7.0:
-  version "2.11.0"
-  resolved "https://registry.yarnpkg.com/koa/-/koa-2.11.0.tgz#fe5a51c46f566d27632dd5dc8fd5d7dd44f935a4"
-  integrity sha512-EpR9dElBTDlaDgyhDMiLkXrPwp6ZqgAIBvhhmxQ9XN4TFgW+gEz6tkcsNI6BnUbUftrKDjVFj4lW2/J2aNBMMA==
+  version "2.13.0"
+  resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.0.tgz#25217e05efd3358a7e5ddec00f0a380c9b71b501"
+  integrity sha512-i/XJVOfPw7npbMv67+bOeXr3gPqOAw6uh5wFyNs3QvJ47tUx3M3V9rIE0//WytY42MKz4l/MXKyGkQ2LQTfLUQ==
   dependencies:
     accepts "^1.3.5"
     cache-content-type "^1.0.0"
@@ -2610,7 +2863,6 @@
     depd "^1.1.2"
     destroy "^1.0.4"
     encodeurl "^1.0.2"
-    error-inject "^1.0.0"
     escape-html "^1.0.3"
     fresh "~0.5.2"
     http-assert "^1.3.0"
@@ -2647,14 +2899,6 @@
     pify "^3.0.0"
     strip-bom "^3.0.0"
 
-locate-path@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
-  integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=
-  dependencies:
-    p-locate "^2.0.0"
-    path-exists "^3.0.0"
-
 locate-path@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
@@ -2750,12 +2994,24 @@
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
   integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
 
-mime-db@1.43.0, "mime-db@>= 1.43.0 < 2":
+mime-db@1.43.0:
   version "1.43.0"
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
   integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
 
-mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.19, mime-types@~2.1.24:
+mime-db@1.44.0, "mime-db@>= 1.43.0 < 2":
+  version "1.44.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
+  integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
+
+mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.27, mime-types@~2.1.19:
+  version "2.1.27"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
+  integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
+  dependencies:
+    mime-db "1.44.0"
+
+mime-types@~2.1.24:
   version "2.1.26"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
   integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==
@@ -2774,11 +3030,6 @@
   dependencies:
     brace-expansion "^1.1.7"
 
-minimist@0.0.8:
-  version "0.0.8"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
-  integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
-
 minimist@^1.2.3, minimist@^1.2.5:
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
@@ -2797,11 +3048,11 @@
     minimist "^1.2.5"
 
 mkdirp@^0.5.1:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
-  integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
+  version "0.5.5"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
+  integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
   dependencies:
-    minimist "0.0.8"
+    minimist "^1.2.5"
 
 mocha@^7.1.1:
   version "7.1.1"
@@ -2893,10 +3144,10 @@
   resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
   integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
 
-node-releases@^1.1.53:
-  version "1.1.53"
-  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4"
-  integrity sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==
+node-releases@^1.1.58:
+  version "1.1.58"
+  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.58.tgz#8ee20eef30fa60e52755fcc0942def5a734fe935"
+  integrity sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg==
 
 normalize-package-data@^2.3.2:
   version "2.5.0"
@@ -2975,12 +3226,13 @@
   resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4"
   integrity sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=
 
-opn@^5.4.0:
-  version "5.5.0"
-  resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
-  integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==
+open@^7.0.3:
+  version "7.0.4"
+  resolved "https://registry.yarnpkg.com/open/-/open-7.0.4.tgz#c28a9d315e5c98340bf979fdcb2e58664aa10d83"
+  integrity sha512-brSA+/yq+b08Hsr4c8fsEW2CRzk1BmfN3SAK/5VCHQ9bdoZJ4qa/+AfR0xHjlbbZUyPkUHs1b8x1RqdyZdkVqQ==
   dependencies:
-    is-wsl "^1.1.0"
+    is-docker "^2.0.0"
+    is-wsl "^2.1.1"
 
 optimist@^0.6.1:
   version "0.6.1"
@@ -2995,13 +3247,6 @@
   resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
   integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
 
-p-limit@^1.1.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
-  integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
-  dependencies:
-    p-try "^1.0.0"
-
 p-limit@^2.0.0:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e"
@@ -3009,13 +3254,6 @@
   dependencies:
     p-try "^2.0.0"
 
-p-locate@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
-  integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=
-  dependencies:
-    p-limit "^1.1.0"
-
 p-locate@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
@@ -3023,11 +3261,6 @@
   dependencies:
     p-limit "^2.0.0"
 
-p-try@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
-  integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
-
 p-try@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
@@ -3116,7 +3349,7 @@
   resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
   integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
 
-picomatch@^2.0.4, picomatch@^2.0.7:
+picomatch@^2.0.4, picomatch@^2.0.7, picomatch@^2.2.2:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
   integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
@@ -3126,24 +3359,17 @@
   resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
   integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
 
-pkg-up@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f"
-  integrity sha1-yBmscoBZpGHKscOImivjxJoATX8=
-  dependencies:
-    find-up "^2.1.0"
-
-polyfills-loader@^1.5.2:
-  version "1.5.2"
-  resolved "https://registry.yarnpkg.com/polyfills-loader/-/polyfills-loader-1.5.2.tgz#2fe63063da0d74aa69b611bd189d64ee443358c7"
-  integrity sha512-bcv8Id4Ylae0eSmnlMpKfba36TNr1Mh7uMGN4OEIspHLGR5/IBGSBteQp/NpbZ45T7UWQuTBvVJNQCS16E1N4A==
+polyfills-loader@^1.6.1:
+  version "1.6.1"
+  resolved "https://registry.yarnpkg.com/polyfills-loader/-/polyfills-loader-1.6.1.tgz#134ab74b9a6160efb4d72066a5150bfb2228fad3"
+  integrity sha512-GK3jZGLy9nApfRYfHrrO4RYkBkpjiXUVWVdp169g4Y8HV+ZazrGQX46tNpbwP0dtrgHgADyJvZYPfdFuooHy5Q==
   dependencies:
     "@babel/core" "^7.9.0"
-    "@open-wc/building-utils" "^2.16.1"
+    "@open-wc/building-utils" "^2.18.0"
     "@webcomponents/webcomponentsjs" "^2.4.0"
     abortcontroller-polyfill "^1.4.0"
     core-js-bundle "^3.6.0"
-    deepmerge "^3.2.0"
+    deepmerge "^4.2.2"
     dynamic-import-polyfill "^0.1.1"
     es-module-shims "^0.4.6"
     html-minifier "^4.0.0"
@@ -3151,24 +3377,19 @@
     parse5 "^5.1.1"
     regenerator-runtime "^0.13.3"
     resize-observer-polyfill "^1.5.1"
-    systemjs "^4.0.0"
-    terser "^4.6.4"
+    systemjs "^6.3.1"
+    terser "^4.6.7"
     whatwg-fetch "^3.0.0"
 
 portfinder@^1.0.21:
-  version "1.0.25"
-  resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.25.tgz#254fd337ffba869f4b9d37edc298059cb4d35eca"
-  integrity sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==
+  version "1.0.26"
+  resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.26.tgz#475658d56ca30bed72ac7f1378ed350bd1b64e70"
+  integrity sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==
   dependencies:
     async "^2.6.2"
     debug "^3.1.1"
     mkdirp "^0.5.1"
 
-private@^0.1.8:
-  version "0.1.8"
-  resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
-  integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
-
 pseudomap@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
@@ -3258,13 +3479,6 @@
   resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27"
   integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==
 
-regenerate-unicode-properties@^8.1.0:
-  version "8.1.0"
-  resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e"
-  integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==
-  dependencies:
-    regenerate "^1.4.0"
-
 regenerate-unicode-properties@^8.2.0:
   version "8.2.0"
   resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
@@ -3273,9 +3487,9 @@
     regenerate "^1.4.0"
 
 regenerate@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
-  integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f"
+  integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==
 
 regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4:
   version "0.13.5"
@@ -3283,24 +3497,11 @@
   integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
 
 regenerator-transform@^0.14.2:
-  version "0.14.4"
-  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7"
-  integrity sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==
+  version "0.14.5"
+  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4"
+  integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==
   dependencies:
     "@babel/runtime" "^7.8.4"
-    private "^0.1.8"
-
-regexpu-core@^4.6.0:
-  version "4.6.0"
-  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6"
-  integrity sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==
-  dependencies:
-    regenerate "^1.4.0"
-    regenerate-unicode-properties "^8.1.0"
-    regjsgen "^0.5.0"
-    regjsparser "^0.6.0"
-    unicode-match-property-ecmascript "^1.0.4"
-    unicode-match-property-value-ecmascript "^1.1.0"
 
 regexpu-core@^4.7.0:
   version "4.7.0"
@@ -3314,17 +3515,10 @@
     unicode-match-property-ecmascript "^1.0.4"
     unicode-match-property-value-ecmascript "^1.2.0"
 
-regjsgen@^0.5.0, regjsgen@^0.5.1:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c"
-  integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==
-
-regjsparser@^0.6.0:
-  version "0.6.2"
-  resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.2.tgz#fd62c753991467d9d1ffe0a9f67f27a529024b96"
-  integrity sha512-E9ghzUtoLwDekPT0DYCp+c4h+bvuUpe6rRHCTYn6eGoqj1LgKXxT6I0Il4WbjhQkOghzi/V+y03bPKvbllL93Q==
-  dependencies:
-    jsesc "~0.5.0"
+regjsgen@^0.5.1:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733"
+  integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==
 
 regjsparser@^0.6.4:
   version "0.6.4"
@@ -3392,17 +3586,10 @@
     http-errors "~1.6.2"
     path-is-absolute "1.0.1"
 
-resolve@^1.10.0, resolve@^1.3.2:
-  version "1.15.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.0.tgz#1b7ca96073ebb52e741ffd799f6b39ea462c67f5"
-  integrity sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==
-  dependencies:
-    path-parse "^1.0.6"
-
-resolve@^1.11.1:
-  version "1.15.1"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8"
-  integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==
+resolve@^1.10.0, resolve@^1.11.1, resolve@^1.14.2, resolve@^1.3.2:
+  version "1.17.0"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
+  integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
   dependencies:
     path-parse "^1.0.6"
 
@@ -3418,23 +3605,35 @@
   dependencies:
     glob "^7.1.3"
 
-rimraf@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.0.tgz#614176d4b3010b75e5c390eb0ee96f6dc0cebb9b"
-  integrity sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==
+rimraf@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+  integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
   dependencies:
     glob "^7.1.3"
 
+rollup@^2.7.2:
+  version "2.18.2"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.18.2.tgz#886ac6e4549e493df106c3e2580c89aeb997be25"
+  integrity sha512-+mzyZhL9ZyLB3eHBISxRNTep9Z2qCuwXzAYkUbFyz7yNKaKH03MFKeiGOS1nv2uvPgDb4ASKv+FiS5mC4h5IFQ==
+  optionalDependencies:
+    fsevents "~2.1.2"
+
 safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
 
-safe-buffer@^5.0.1, safe-buffer@^5.1.2:
+safe-buffer@^5.0.1:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
   integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
 
+safe-buffer@^5.1.2:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+  integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
 "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
@@ -3450,11 +3649,16 @@
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
   integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
 
-semver@^6.0.0, semver@^6.3.0:
+semver@^6.0.0:
   version "6.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
+semver@^7.3.2:
+  version "7.3.2"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
+  integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
+
 set-blocking@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@@ -3470,6 +3674,11 @@
   resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
   integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
 
+setprototypeof@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
+  integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
+
 shady-css-scoped-element@^0.0.2:
   version "0.0.2"
   resolved "https://registry.yarnpkg.com/shady-css-scoped-element/-/shady-css-scoped-element-0.0.2.tgz#c538fcfe2317e979cd02dfec533898b95b4ea8fe"
@@ -3535,9 +3744,9 @@
     socket.io-parser "~3.2.0"
 
 source-map-support@~0.5.12:
-  version "0.5.16"
-  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
-  integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==
+  version "0.5.19"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
+  integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
   dependencies:
     buffer-from "^1.0.0"
     source-map "^0.6.0"
@@ -3553,22 +3762,22 @@
   integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
 
 spdx-correct@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4"
-  integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
+  integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==
   dependencies:
     spdx-expression-parse "^3.0.0"
     spdx-license-ids "^3.0.0"
 
 spdx-exceptions@^2.1.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977"
-  integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
+  integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==
 
 spdx-expression-parse@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0"
-  integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679"
+  integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==
   dependencies:
     spdx-exceptions "^2.1.0"
     spdx-license-ids "^3.0.0"
@@ -3692,10 +3901,10 @@
   dependencies:
     has-flag "^4.0.0"
 
-systemjs@^4.0.0:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/systemjs/-/systemjs-4.1.1.tgz#c90061456f9707478d487b47f3b92b9896032889"
-  integrity sha512-/0x3bcMrl1pxDCLw6sJWEKPVy0ZGEu7I0nItFSHxfPoDU2Lll6TUyB1wqltvbm7n5y5jVOoK4lei4oMpmW7XJQ==
+systemjs@^6.3.1:
+  version "6.3.3"
+  resolved "https://registry.yarnpkg.com/systemjs/-/systemjs-6.3.3.tgz#c0f2bec5cc72d0b36a8b971b1fa32bfc828b50d4"
+  integrity sha512-djQ6mZ4/cWKnVnhAWvr/4+5r7QHnC7WiA8sS9VuYRdEv3wYZYTIIQv8zPT79PdDSUwfX3bgvu5mZ8eTyLm2YQA==
 
 table-layout@^1.0.0:
   version "1.0.1"
@@ -3707,10 +3916,10 @@
     typical "^5.2.0"
     wordwrapjs "^4.0.0"
 
-terser@^4.6.4:
-  version "4.6.10"
-  resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.10.tgz#90f5bd069ff456ddbc9503b18e52f9c493d3b7c2"
-  integrity sha512-qbF/3UOo11Hggsbsqm2hPa6+L4w7bkr+09FNseEe8xrcVD3APGLFqE+Oz1ZKAxjYnFsj80rLOfgAtJ0LNJjtTA==
+terser@^4.6.7:
+  version "4.8.0"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17"
+  integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==
   dependencies:
     commander "^2.20.0"
     source-map "~0.6.1"
@@ -3734,9 +3943,9 @@
     thenify ">= 3.1.0 < 4"
 
 "thenify@>= 3.1.0 < 4":
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839"
-  integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f"
+  integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
   dependencies:
     any-promise "^1.0.0"
 
@@ -3784,6 +3993,11 @@
   dependencies:
     punycode "^2.1.0"
 
+tslib@^1.11.1:
+  version "1.13.0"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
+  integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
+
 tsscmp@1.0.6:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb"
@@ -3825,12 +4039,9 @@
   integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==
 
 uglify-js@^3.5.1:
-  version "3.8.1"
-  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.8.1.tgz#43bb15ce6f545eaa0a64c49fd29375ea09fa0f93"
-  integrity sha512-W7KxyzeaQmZvUFbGj4+YFshhVrMBGSg2IbcYAjGWGvx8DHvJMclbTDMpffdxFUGPBHjIytk7KJUR/KUXstUGDw==
-  dependencies:
-    commander "~2.20.3"
-    source-map "~0.6.1"
+  version "3.10.0"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.10.0.tgz#397a7e6e31ce820bfd1cb55b804ee140c587a9e7"
+  integrity sha512-Esj5HG5WAyrLIdYU74Z3JdG2PxdIusvj6IWHMtlyESxc7kcDz7zYlYjpnSokn1UbpV0d/QX9fan7gkCNd/9BQA==
 
 ultron@~1.1.0:
   version "1.1.1"
@@ -3850,20 +4061,15 @@
     unicode-canonical-property-names-ecmascript "^1.0.4"
     unicode-property-aliases-ecmascript "^1.0.4"
 
-unicode-match-property-value-ecmascript@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277"
-  integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==
-
 unicode-match-property-value-ecmascript@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531"
   integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==
 
 unicode-property-aliases-ecmascript@^1.0.4:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57"
-  integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4"
+  integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==
 
 universalify@^0.1.0:
   version "0.1.2"
@@ -3943,9 +4149,9 @@
   integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
 
 whatwg-fetch@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
-  integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.1.0.tgz#49d630cdfa308dba7f2819d49d09364f540dbcc6"
+  integrity sha512-pgmbsVWKpH9GxLXZmtdowDIqtb/rvPyjjQv3z9wLcmgWKFHilKnZD3ldgrOlwJoPGOUluQsRPWd52yVkPfmI1A==
 
 whatwg-url@^7.0.0:
   version "7.1.0"
diff --git a/tools/bzl/js.bzl b/tools/bzl/js.bzl
index 5440b88..eeb5e6b 100644
--- a/tools/bzl/js.bzl
+++ b/tools/bzl/js.bzl
@@ -1,4 +1,4 @@
-load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_binary", "closure_js_library")
+load("@npm_bazel_terser//:index.bzl", "terser_minified")
 load("//lib/js:npm.bzl", "NPM_SHA1S", "NPM_VERSIONS")
 
 NPMJS = "NPMJS"
@@ -439,7 +439,7 @@
     """Combine html, js, css files and optionally split into js and html bundles."""
     _bundle_rule(pkg = native.package_name(), *args, **kwargs)
 
-def polygerrit_plugin(name, app, srcs = [], deps = [], externs = [], assets = None, plugin_name = None, **kwargs):
+def polygerrit_plugin(name, app, srcs = [], deps = [], assets = None, plugin_name = None, **kwargs):
     """Bundles plugin dependencies for deployment.
 
     This rule bundles all Polymer elements and JS dependencies into .html and .js files.
@@ -449,7 +449,6 @@
     Args:
       name: String, rule name.
       app: String, the main or root source file.
-      externs: Fileset, external definitions that should not be bundled.
       assets: Fileset, additional files to be used by plugin in runtime, exported to "plugins/${name}/static".
       plugin_name: String, plugin name. ${name} is used if not provided.
     """
@@ -473,29 +472,15 @@
     else:
         js_srcs = srcs
 
-    closure_js_library(
-        name = name + "_closure_lib",
-        srcs = js_srcs + externs,
-        convention = "GOOGLE",
-        no_closure_library = True,
-        deps = [
-            "//lib/polymer_externs:polymer_closure",
-            "//polygerrit-ui/app/externs:plugin",
-        ],
+    native.filegroup(
+        name = name + "-src-fg",
+        srcs = js_srcs,
     )
 
-    closure_js_binary(
-        name = name + "_bin",
-        compilation_level = "WHITESPACE_ONLY",
-        defs = [
-            "--polymer_version=2",
-            "--language_out=ECMASCRIPT_2017",
-            "--rewrite_polyfills=false",
-        ],
-        deps = [
-            name + "_closure_lib",
-        ],
-        dependency_mode = "PRUNE_LEGACY",
+    terser_minified(
+        name = name + ".min",
+        sourcemap = False,
+        src = name + "-src-fg",
     )
 
     if html_plugin:
@@ -519,7 +504,7 @@
 
     native.genrule(
         name = name + "_rename_js",
-        srcs = [name + "_bin.js"],
+        srcs = [name + ".min"],
         outs = [plugin_name + ".js"],
         cmd = "cp $< $@",
         output_to_bindir = True,
diff --git a/yarn.lock b/yarn.lock
index 8e35c84..0c4383f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -490,6 +490,11 @@
   resolved "https://registry.yarnpkg.com/@bazel/rollup/-/rollup-1.6.1.tgz#7ec9d39a3fca23256fca55410339724804802616"
   integrity sha512-FhblJkpd8VKl9txhAAIotSsIOHRpPd2FgJG7Op3uV7LfaCVBmUs3XDBZCgfwt5wmEpd3lwCHA1Ei+O/URS2+5w==
 
+"@bazel/terser@^1.7.0":
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/@bazel/terser/-/terser-1.7.0.tgz#c43e711e13b9a71c7abd3ade04fb4650d547ad01"
+  integrity sha512-u/UXk0WUinvkk1g5xxfqGieBz3r12Bj2y2m25lC5GjHBgCpGk7DyeGGi9H3QQNO1Wmpw51QSE9gaPzKzjUVGug==
+
 "@bazel/typescript@^1.6.1":
   version "1.6.1"
   resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-1.6.1.tgz#1bf83c20021d359bc9b532181981ac540584a30c"
@@ -2705,7 +2710,7 @@
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
   integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
 
-commander@^2.19.0:
+commander@^2.19.0, commander@^2.20.0:
   version "2.20.3"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
   integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@@ -8641,6 +8646,14 @@
   dependencies:
     source-map "^0.5.6"
 
+source-map-support@~0.5.12:
+  version "0.5.19"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
+  integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
+  dependencies:
+    buffer-from "^1.0.0"
+    source-map "^0.6.0"
+
 source-map-url@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
@@ -9136,6 +9149,15 @@
     merge-stream "^1.0.0"
     through2 "^2.0.1"
 
+terser@^4.8.0:
+  version "4.8.0"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17"
+  integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==
+  dependencies:
+    commander "^2.20.0"
+    source-map "~0.6.1"
+    source-map-support "~0.5.12"
+
 text-encoding@0.6.4:
   version "0.6.4"
   resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"