Merge "ReviewJson: Load SubmitRecords from ChangeData to avoid reevaluation in loop"
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index f047543..bf56000 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -872,36 +872,25 @@
   }
 
   public List<SubmitRecord> submitRecords(SubmitRuleOptions options) {
-    List<SubmitRecord> records = getCachedSubmitRecord(options);
+    // If the change is not submitted yet, 'strict' and 'lenient' both have the same result. If the
+    // change is submitted, SubmitRecord requested with 'strict' will contain just a single entry
+    // that with status=CLOSED. The latter is cheap to evaluate as we don't have to run any actual
+    // evaluation.
+    List<SubmitRecord> records = submitRecords.get(options);
     if (records == null) {
       if (!lazyLoad) {
         return Collections.emptyList();
       }
       records = submitRuleEvaluatorFactory.create(options).evaluate(this);
       submitRecords.put(options, records);
+      if (!change().isClosed() && submitRecords.size() == 1) {
+        // Cache the SubmitRecord with allowClosed = !allowClosed as the SubmitRecord are the same.
+        submitRecords.put(options.toBuilder().allowClosed(!options.allowClosed()).build(), records);
+      }
     }
     return records;
   }
 
-  @Nullable
-  public List<SubmitRecord> getSubmitRecords(SubmitRuleOptions options) {
-    return getCachedSubmitRecord(options);
-  }
-
-  private List<SubmitRecord> getCachedSubmitRecord(SubmitRuleOptions options) {
-    List<SubmitRecord> records = submitRecords.get(options);
-    if (records != null) {
-      return records;
-    }
-
-    if (options.allowClosed() && change != null && change.getStatus().isOpen()) {
-      SubmitRuleOptions openSubmitRuleOptions = options.toBuilder().allowClosed(false).build();
-      return submitRecords.get(openSubmitRuleOptions);
-    }
-
-    return null;
-  }
-
   public void setSubmitRecords(SubmitRuleOptions options, List<SubmitRecord> records) {
     submitRecords.put(options, records);
   }
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index 4e732a4..f486650 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -189,7 +189,7 @@
       // date by this point.
       ChangeData cd = requireNonNull(changes.get(id), () -> String.format("ChangeData for %s", id));
       return requireNonNull(
-          cd.getSubmitRecords(submitRuleOptions(allowClosed)),
+          cd.submitRecords(submitRuleOptions(allowClosed)),
           "getSubmitRecord only valid after submit rules are evalutated");
     }
 
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_html.ts b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_html.ts
index 9812933..add7ca5 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_html.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_html.ts
@@ -36,7 +36,7 @@
       width: 10em;
     }
     #graphic iron-icon {
-      color: #9e9e9e;
+      color: var(--gray-foreground);
       height: 5em;
       width: 5em;
     }
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
index c5c73c5..59bf8ad 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
@@ -78,7 +78,7 @@
     }
     .icon.help,
     .icon.notTrusted {
-      color: #ffa62f;
+      color: var(--warning-foreground);
     }
     .icon.invalid {
       color: var(--negative-red-text-color);
@@ -87,7 +87,7 @@
       color: var(--positive-green-text-color);
     }
     .parentList.notCurrent.nonMerge #parentNotCurrentMessage {
-      --arrow-color: #ffa62f;
+      --arrow-color: var(--warning-foreground);
       display: inline-block;
     }
     .separatedSection {
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.ts b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.ts
index c0e87f3..adc7fd3 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.ts
@@ -37,6 +37,7 @@
 import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
 import {appContext} from '../../../services/app-context';
 import {KnownExperimentId} from '../../../services/flags/flags';
+import {labelCompare} from '../../../utils/label-util';
 
 interface ChangeRequirement extends Requirement {
   satisfied: boolean;
@@ -136,7 +137,7 @@
     const labels = labelsRecord.base || {};
     const allLabels: Label[] = [];
 
-    for (const label of Object.keys(labels).sort()) {
+    for (const label of Object.keys(labels).sort(labelCompare)) {
       allLabels.push({
         labelName: label,
         icon: this._computeLabelIcon(labels[label]),
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_html.ts b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_html.ts
index e02b337..a502949 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_html.ts
@@ -23,7 +23,7 @@
       width: 100%;
     }
     .status {
-      color: #ffa62f;
+      color: var(--warning-foreground);
       display: inline-block;
       text-align: center;
       vertical-align: top;
diff --git a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
index 4af51a9..beec0a4 100644
--- a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
@@ -56,7 +56,7 @@
 import {notUndefined} from '../../../types/types';
 import {uniqueDefinedAvatar} from '../../../utils/account-util';
 import {PrimaryTab} from '../../../constants/constants';
-import {CommentTabState} from '../../../types/events';
+import {ChecksTabState, CommentTabState} from '../../../types/events';
 
 export enum SummaryChipStyles {
   INFO = 'info',
@@ -229,19 +229,13 @@
     const chipClass = `checksChip font-small ${this.icon}`;
     const grIcon = `gr-icons:${this.icon}`;
     return html`
-      <div class="${chipClass}" role="button" @click="${this.handleClick}">
+      <div class="${chipClass}" role="button">
         <iron-icon icon="${grIcon}"></iron-icon>
         <div class="text">${this.text}</div>
         <slot></slot>
       </div>
     `;
   }
-
-  private handleClick(e: MouseEvent) {
-    e.stopPropagation();
-    e.preventDefault();
-    fireShowPrimaryTab(this, PrimaryTab.CHECKS);
-  }
 }
 
 /** What is the maximum number of expanded checks chips? */
@@ -325,7 +319,7 @@
     const icon = iconForCategory(category);
     const runs = this.runs.filter(run => hasResultsOf(run, category));
     const count = (run: CheckRun) => getResultsOf(run, category);
-    return this.renderChecksChip(icon, runs, count);
+    return this.renderChecksChip(icon, runs, category, count);
   }
 
   renderChecksChipForStatus(
@@ -334,12 +328,13 @@
   ) {
     const icon = iconForStatus(status);
     const runs = this.runs.filter(filter);
-    return this.renderChecksChip(icon, runs, () => []);
+    return this.renderChecksChip(icon, runs, status, () => []);
   }
 
   renderChecksChip(
     icon: string,
     runs: CheckRun[],
+    statusOrCategory: RunStatus | Category,
     resultFilter: (run: CheckRun) => CheckResult[]
   ) {
     if (runs.length === 0) {
@@ -359,9 +354,10 @@
           class="${icon}"
           .icon="${icon}"
           .text="${text}"
+          @click="${() => this.onChipClick({checkName: run.checkName})}"
           >${links.map(
             link => html`
-              <a href="${link.url}" target="_blank" @click="${this.onClick}"
+              <a href="${link.url}" target="_blank" @click="${this.onLinkClick}"
                 ><iron-icon class="launch" icon="gr-icons:launch"></iron-icon
               ></a>
             `
@@ -380,11 +376,18 @@
       class="${icon}"
       .icon="${icon}"
       .text="${sum}"
+      @click="${() => this.onChipClick({statusOrCategory})}"
     ></gr-checks-chip>`;
   }
 
-  private onClick(e: MouseEvent) {
-    // Prevents handleClick() from reacting to <a> link clicks.
+  private onChipClick(state: ChecksTabState) {
+    fireShowPrimaryTab(this, PrimaryTab.CHECKS, true, {
+      checksTab: state,
+    });
+  }
+
+  private onLinkClick(e: MouseEvent) {
+    // Prevents onChipClick() from reacting to <a> link clicks.
     e.stopPropagation();
   }
 
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
index f3fb860..08c04e8 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -690,7 +690,10 @@
         is="dom-if"
         if="[[_isTabActive(_constants.PrimaryTab.CHECKS, _activeTabs)]]"
       >
-        <gr-checks-tab id="checksTab"></gr-checks-tab>
+        <gr-checks-tab
+          id="checksTab"
+          tab-state="[[_tabState.checksTab]]"
+        ></gr-checks-tab>
       </template>
       <template
         is="dom-if"
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
index 23718fa..c57a2d5 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
@@ -154,7 +154,7 @@
       padding-left: var(--spacing-s);
     }
     .drafts {
-      color: #c62828;
+      color: var(--error-foreground);
       font-weight: var(--font-weight-bold);
     }
     .show-hide-icon:focus {
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts
index a966186..661cd1a 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts
@@ -37,6 +37,7 @@
 } from '../gr-label-score-row/gr-label-score-row';
 import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
 import {appContext} from '../../../services/app-context';
+import {labelCompare} from '../../../utils/label-util';
 
 @customElement('gr-label-scores')
 export class GrLabelScores extends GestureEventListeners(
@@ -147,7 +148,7 @@
     if (!labelRecord?.base) return [];
     const labelsObj = labelRecord.base;
     return Object.keys(labelsObj)
-      .sort()
+      .sort(labelCompare)
       .map(key => {
         return {
           name: key,
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-change.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-change.ts
index 3ed545e..7b698f1 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-change.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-change.ts
@@ -77,10 +77,10 @@
           margin-left: var(--spacing-xs);
         }
         .notCurrent {
-          color: #e65100;
+          color: var(--warning-foreground);
         }
         .indirectAncestor {
-          color: #33691e;
+          color: var(--indirect-ancestor-text-color);
         }
         .submittableCheck {
           padding-left: var(--spacing-s);
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts
index 9941fa9..2f53319 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts
@@ -66,10 +66,10 @@
       margin-left: var(--spacing-xs);
     }
     .notCurrent {
-      color: #e65100;
+      color: var(--warning-foreground);
     }
     .indirectAncestor {
-      color: #33691e;
+      color: var(--indirect-ancestor-text-color);
     }
     .submittableCheck {
       padding-left: var(--spacing-s);
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index e448374..002c8c3 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -295,6 +295,8 @@
   @property()
   runs: CheckRun[] = [];
 
+  private isSectionExpanded = new Map<Category | 'SUCCESS', boolean>();
+
   static get styles() {
     return [
       sharedStyles,
@@ -312,23 +314,36 @@
           margin-top: var(--spacing-l);
           margin-left: var(--spacing-l);
           text-transform: capitalize;
+          cursor: default;
         }
-        .categoryHeader iron-icon {
+        .categoryHeader .expandIcon {
+          width: var(--line-height-h3);
+          height: var(--line-height-h3);
+          margin-right: var(--spacing-s);
+        }
+        .categoryHeader .statusIcon {
           position: relative;
-          top: 1px;
+          top: 2px;
         }
-        .categoryHeader iron-icon.error {
+        .categoryHeader .statusIcon.error {
           color: var(--error-foreground);
         }
-        .categoryHeader iron-icon.warning {
+        .categoryHeader .statusIcon.warning {
           color: var(--warning-foreground);
         }
-        .categoryHeader iron-icon.info {
+        .categoryHeader .statusIcon.info {
           color: var(--info-foreground);
         }
-        .categoryHeader iron-icon.success {
+        .categoryHeader .statusIcon.success {
           color: var(--success-foreground);
         }
+        .collapsed table {
+          display: none;
+        }
+        .collapsed {
+          border-bottom: 1px solid var(--border-color);
+          padding-bottom: var(--spacing-m);
+        }
         .noCompleted {
           margin-top: var(--spacing-l);
         }
@@ -354,7 +369,7 @@
       ${this.renderFilter()} ${this.renderNoCompleted()}
       ${this.renderSection(Category.ERROR)}
       ${this.renderSection(Category.WARNING)}
-      ${this.renderSection(Category.INFO)} ${this.renderSuccess()}
+      ${this.renderSection(Category.INFO)} ${this.renderSection('SUCCESS')}
     `;
   }
 
@@ -384,36 +399,62 @@
     return html`<div class="noCompleted">${text}</div>`;
   }
 
-  renderSection(category: Category) {
+  renderSection(category: Category | 'SUCCESS') {
     const catString = category.toString().toLowerCase();
-    const runs = this.runs.filter(r =>
-      (r.results ?? []).some(res => res.category === category)
-    );
+    let runs = this.runs;
+    if (category === 'SUCCESS') {
+      runs = runs
+        .filter(hasCompletedWithoutResults)
+        .filter(r => this.filterRegExp.test(r.checkName));
+    } else {
+      runs = runs.filter(r =>
+        (r.results ?? []).some(res => res.category === category)
+      );
+    }
     if (runs.length === 0) return;
+    const expanded = this.isSectionExpanded.get(category) ?? true;
+    const expandedClass = expanded ? 'expanded' : 'collapsed';
+    const icon = expanded ? 'gr-icons:expand-more' : 'gr-icons:expand-less';
     return html`
-      <h3 class="categoryHeader heading-3">
-        <iron-icon
-          icon="gr-icons:${iconForCategory(category)}"
-          class="${catString}"
-        ></iron-icon>
-        ${catString}
-      </h3>
-      <table class="resultsTable">
-        <thead>
-          <tr class="headerRow">
-            <th class="iconCol"></th>
-            <th class="nameCol">Run</th>
-            <th class="summaryCol">Summary</th>
-            <th class="expanderCol"></th>
-          </tr>
-        </thead>
-        <tbody>
-          ${runs.map(run => this.renderRun(category, run))}
-        </tbody>
-      </table>
+      <div class="${expandedClass}">
+        <h3
+          class="categoryHeader heading-3"
+          @click="${() => this.toggleExpanded(category)}"
+        >
+          <iron-icon class="expandIcon" icon="${icon}"></iron-icon>
+          <iron-icon
+            icon="gr-icons:${iconForCategory(category)}"
+            class="statusIcon ${catString}"
+          ></iron-icon>
+          ${catString}
+        </h3>
+        <table class="resultsTable">
+          <thead>
+            <tr class="headerRow">
+              <th class="iconCol"></th>
+              <th class="nameCol">Run</th>
+              <th class="summaryCol">Summary</th>
+              <th class="expanderCol"></th>
+            </tr>
+          </thead>
+          <tbody>
+            ${runs.map(run =>
+              category === 'SUCCESS'
+                ? this.renderSuccessfulRun(run)
+                : this.renderRun(category, run)
+            )}
+          </tbody>
+        </table>
+      </div>
     `;
   }
 
+  toggleExpanded(category: Category | 'SUCCESS') {
+    const expanded = this.isSectionExpanded.get(category) ?? true;
+    this.isSectionExpanded.set(category, !expanded);
+    this.requestUpdate();
+  }
+
   renderRun(category: Category, run: CheckRun) {
     return html`${run.results
       ?.filter(result => result.category === category)
@@ -428,31 +469,6 @@
       )}`;
   }
 
-  renderSuccess() {
-    const runs = this.runs
-      .filter(hasCompletedWithoutResults)
-      .filter(r => this.filterRegExp.test(r.checkName));
-    if (runs.length === 0) return;
-    return html`
-      <h3 class="categoryHeader heading-3">
-        <iron-icon
-          icon="gr-icons:check-circle-outline"
-          class="success"
-        ></iron-icon>
-        Success
-      </h3>
-      <table class="resultsTable">
-        <tr class="headerRow">
-          <th class="iconCol"></th>
-          <th class="nameCol">Run</th>
-          <th class="summaryCol">Summary</th>
-          <th class="expanderCol"></th>
-        </tr>
-        ${runs.map(run => this.renderSuccessfulRun(run))}
-      </table>
-    `;
-  }
-
   renderSuccessfulRun(run: CheckRun) {
     const adaptedRun: RunResult = {
       category: Category.INFO, // will not be used, but is required
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
index f8584bc..1b49f8a 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
@@ -41,7 +41,7 @@
   fakeRun4,
   updateStateSetResults,
 } from '../../services/checks/checks-model';
-import {assertIsDefined, toggleSetMembership} from '../../utils/common-util';
+import {assertIsDefined} from '../../utils/common-util';
 import {whenVisible} from '../../utils/dom-util';
 
 export interface RunSelectedEventDetail {
@@ -239,7 +239,10 @@
   @property()
   runs: CheckRun[] = [];
 
-  private selectedRuns = new Set<string>();
+  @property()
+  selectedRuns: string[] = [];
+
+  private isSectionExpanded = new Map<RunStatus, boolean>();
 
   constructor() {
     super();
@@ -254,9 +257,24 @@
           display: block;
           padding: var(--spacing-xl);
         }
-        .statusHeader {
+        .expandIcon {
+          width: var(--line-height-h3);
+          height: var(--line-height-h3);
+        }
+        .sectionHeader {
           padding-top: var(--spacing-l);
           text-transform: capitalize;
+          cursor: default;
+        }
+        .sectionHeader h3 {
+          display: inline-block;
+        }
+        .collapsed .sectionRuns {
+          display: none;
+        }
+        .collapsed {
+          border-bottom: 1px solid var(--border-color);
+          padding-bottom: var(--spacing-m);
         }
         input#filterInput {
           margin-top: var(--spacing-s);
@@ -347,29 +365,40 @@
       .filter(r => this.filterRegExp.test(r.checkName))
       .sort(compareByWorstCategory);
     if (runs.length === 0) return;
+    const expanded = this.isSectionExpanded.get(status) ?? true;
+    const expandedClass = expanded ? 'expanded' : 'collapsed';
+    const icon = expanded ? 'gr-icons:expand-more' : 'gr-icons:expand-less';
     return html`
-      <div class="${status.toLowerCase()}">
-        <h3 class="statusHeader heading-3">${status.toLowerCase()}</h3>
-        ${runs.map(run => this.renderRun(run))}
+      <div class="${status.toLowerCase()} ${expandedClass}">
+        <div
+          class="sectionHeader"
+          @click="${() => this.toggleExpanded(status)}"
+        >
+          <iron-icon class="expandIcon" icon="${icon}"></iron-icon>
+          <h3 class="heading-3">${status.toLowerCase()}</h3>
+        </div>
+        <div class="sectionRuns">
+          ${runs.map(run => this.renderRun(run))}
+        </div>
       </div>
     `;
   }
 
+  toggleExpanded(status: RunStatus) {
+    const expanded = this.isSectionExpanded.get(status) ?? true;
+    this.isSectionExpanded.set(status, !expanded);
+    this.requestUpdate();
+  }
+
   renderRun(run: CheckRun) {
-    const selected = this.selectedRuns.has(run.checkName);
-    const deselected = !selected && this.selectedRuns.size > 0;
+    const selected = this.selectedRuns.includes(run.checkName);
+    const deselected = !selected && this.selectedRuns.length > 0;
     return html`<gr-checks-run
       .run="${run}"
       .selected="${selected}"
       .deselected="${deselected}"
-      @run-selected="${this.handleRunSelected}"
     ></gr-checks-run>`;
   }
-
-  handleRunSelected(e: RunSelectedEvent) {
-    toggleSetMembership(this.selectedRuns, e.detail.checkName);
-    this.requestUpdate();
-  }
 }
 
 declare global {
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
index 0ce81ed..ad4f2ae 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
@@ -15,7 +15,13 @@
  * limitations under the License.
  */
 import {html} from 'lit-html';
-import {css, customElement, property} from 'lit-element';
+import {
+  css,
+  customElement,
+  internalProperty,
+  property,
+  PropertyValues,
+} from 'lit-element';
 import {GrLitElement} from '../lit/gr-lit-element';
 import {Action, CheckResult, CheckRun} from '../../api/checks';
 import {
@@ -32,11 +38,9 @@
   ActionTriggeredEvent,
   fireActionTriggered,
 } from '../../services/checks/checks-util';
-import {
-  checkRequiredProperty,
-  toggleSetMembership,
-} from '../../utils/common-util';
+import {checkRequiredProperty} from '../../utils/common-util';
 import {RunSelectedEvent} from './gr-checks-runs';
+import {ChecksTabState} from '../../types/events';
 
 /**
  * The "Checks" tab on the Gerrit change page. Gets its data from plugins that
@@ -52,12 +56,16 @@
   actions: Action[] = [];
 
   @property()
+  tabState?: ChecksTabState;
+
+  @property()
   currentPatchNum: PatchSetNum | undefined = undefined;
 
   @property()
   changeNum: NumericChangeId | undefined = undefined;
 
-  private selectedRuns = new Set<string>();
+  @internalProperty()
+  selectedRuns: string[] = [];
 
   constructor() {
     super();
@@ -107,7 +115,9 @@
   render() {
     const ps = `Patchset ${this.currentPatchNum} (Latest)`;
     const filteredRuns = this.runs.filter(
-      r => this.selectedRuns.size === 0 || this.selectedRuns.has(r.checkName)
+      r =>
+        this.selectedRuns.length === 0 ||
+        this.selectedRuns.includes(r.checkName)
     );
     return html`
       <div class="header">
@@ -130,6 +140,7 @@
         <gr-checks-runs
           class="runs"
           .runs="${this.runs}"
+          .selectedRuns="${this.selectedRuns}"
           @run-selected="${this.handleRunSelected}"
         ></gr-checks-runs>
         <gr-checks-results
@@ -140,6 +151,16 @@
     `;
   }
 
+  protected updated(changedProperties: PropertyValues) {
+    super.updated(changedProperties);
+    if (changedProperties.has('tabState')) {
+      const check = this.tabState?.checkName;
+      if (check) {
+        this.selectedRuns = [check];
+      }
+    }
+  }
+
   renderAction(action: Action) {
     return html`<gr-checks-top-level-action
       .action="${action}"
@@ -163,8 +184,15 @@
   }
 
   handleRunSelected(e: RunSelectedEvent) {
-    toggleSetMembership(this.selectedRuns, e.detail.checkName);
-    this.requestUpdate();
+    this.toggleSelected(e.detail.checkName);
+  }
+
+  toggleSelected(checkName: string) {
+    if (this.selectedRuns.includes(checkName)) {
+      this.selectedRuns = this.selectedRuns.filter(r => r !== checkName);
+    } else {
+      this.selectedRuns = [...this.selectedRuns, checkName];
+    }
   }
 }
 
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts
index 52465b3..5ab8449 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts
@@ -35,7 +35,6 @@
         text-transform: none;
         font-family: var(--font-family);
       }
-      --trigger-hover-color: rgba(0, 0, 0, 0.6);
     }
     @media screen and (max-width: 50em) {
       .filesWeblinks {
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts
index 91ca402..59203d3 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts
@@ -25,7 +25,7 @@
       margin-bottom: var(--spacing-m);
     }
     .agreementsUrl {
-      border: 1px solid #b0bdcc;
+      border: 1px solid var(--border-color);
       margin-bottom: var(--spacing-xl);
       margin-left: var(--spacing-xl);
       margin-right: var(--spacing-xl);
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip_html.ts b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip_html.ts
index 3bb1458..4e6dd1a 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip_html.ts
@@ -58,7 +58,6 @@
     gr-button.remove:focus {
       --gr-button: {
         @apply --gr-remove-button-style;
-        color: #333;
       }
     }
     gr-button.remove {
diff --git a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_html.ts b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_html.ts
index d105c5d..e55c8f1 100644
--- a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_html.ts
@@ -22,7 +22,7 @@
       display: inline-block;
       border-radius: 50%;
       background-size: cover;
-      background-color: var(--avatar-background-color, #f1f2f3);
+      background-color: var(--avatar-background-color, var(--gray-background));
     }
   </style>
 `;
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_html.ts b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_html.ts
index a335db7..8581a0c 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_html.ts
@@ -46,7 +46,6 @@
     gr-button.remove:focus {
       --gr-button: {
         @apply --gr-remove-button-style;
-        color: #333;
       }
     }
     gr-button.remove {
diff --git a/polygerrit-ui/app/services/checks/checks-util.ts b/polygerrit-ui/app/services/checks/checks-util.ts
index 176464f..1613359 100644
--- a/polygerrit-ui/app/services/checks/checks-util.ts
+++ b/polygerrit-ui/app/services/checks/checks-util.ts
@@ -24,7 +24,7 @@
   return undefined;
 }
 
-export function iconForCategory(category: Category) {
+export function iconForCategory(category: Category | 'SUCCESS') {
   switch (category) {
     case Category.ERROR:
       return 'error';
@@ -32,6 +32,8 @@
       return 'info-outline';
     case Category.WARNING:
       return 'warning';
+    case 'SUCCESS':
+      return 'check-circle-outline';
     default:
       assertNever(category, `Unsupported category: ${category}`);
   }
diff --git a/polygerrit-ui/app/styles/gr-voting-styles.ts b/polygerrit-ui/app/styles/gr-voting-styles.ts
index d4e6d52..c1989de 100644
--- a/polygerrit-ui/app/styles/gr-voting-styles.ts
+++ b/polygerrit-ui/app/styles/gr-voting-styles.ts
@@ -27,7 +27,7 @@
     <style>
       :host {
         --vote-chip-styles: {
-          border: 1px solid rgba(0,0,0,.12);
+          border: 1px solid var(--border-color);
           border-radius: 1em;
           box-shadow: none;
           box-sizing: border-box;
diff --git a/polygerrit-ui/app/styles/themes/app-theme.ts b/polygerrit-ui/app/styles/themes/app-theme.ts
index c3b0681..2d83cf5 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.ts
+++ b/polygerrit-ui/app/styles/themes/app-theme.ts
@@ -101,6 +101,7 @@
     --tooltip-text-color: white;
     --negative-red-text-color: #d93025;
     --positive-green-text-color: #188038;
+    --indirect-ancestor-text-color: var(--green-700);
 
     /* background colors */
     /* primary background colors */
@@ -171,7 +172,7 @@
     --line-height-mono: 1.286rem;   /* 18px */
     --line-height-small: 1.143rem;  /* 16px */
     --line-height-normal: 1.429rem; /* 20px */
-    --line-height-h3: 1.714rem;     /* 24px */
+    --line-height-h3: 1.715rem;     /* 24px */
     --line-height-h2: 2rem;         /* 28px */
     --line-height-h1: 2.286rem;     /* 32px */
     --font-weight-normal: 400; /* 400 is the same as 'normal' */
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.ts b/polygerrit-ui/app/styles/themes/dark-theme.ts
index 4057f7f..072ca8f 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.ts
+++ b/polygerrit-ui/app/styles/themes/dark-theme.ts
@@ -44,9 +44,12 @@
       --warning-background: var(--orange-900);
       --info-foreground: var(--blue-200);
       --info-background: var(--blue-900);
+      --selected-foreground: var(--blue-200);
+      --selected-background: var(--blue-900);
       --info-deemphasized-foreground: var(--gray-700);
       --info-deemphasized-background: var(--primary-text-color);
       --success-foreground: var(--green-200);
+      --success-background: var(--green-900);
       --gray-foreground: var(--gray-100);
       --gray-background: var(--gray-900);
       --tag-background: var(--cyan-900);
diff --git a/polygerrit-ui/app/types/events.ts b/polygerrit-ui/app/types/events.ts
index 6b05fad..5965453 100644
--- a/polygerrit-ui/app/types/events.ts
+++ b/polygerrit-ui/app/types/events.ts
@@ -19,6 +19,7 @@
 import {UIComment} from '../utils/comment-util';
 import {FetchRequest} from './types';
 import {MovedLinkClickedEventDetail} from '../api/diff';
+import {Category, RunStatus} from '../api/checks';
 
 export interface TitleChangeEventDetail {
   title: string;
@@ -152,6 +153,7 @@
 
 export interface TabState {
   commentTab?: CommentTabState;
+  checksTab?: ChecksTabState;
 }
 
 export enum CommentTabState {
@@ -160,6 +162,11 @@
   SHOW_ALL = 'show all',
 }
 
+export interface ChecksTabState {
+  statusOrCategory?: RunStatus | Category;
+  checkName?: string;
+}
+
 export type SwitchTabEvent = CustomEvent<SwitchTabEventDetail>;
 
 declare global {
diff --git a/polygerrit-ui/app/utils/label-util.ts b/polygerrit-ui/app/utils/label-util.ts
index 60ac4d8..4eed0a0 100644
--- a/polygerrit-ui/app/utils/label-util.ts
+++ b/polygerrit-ui/app/utils/label-util.ts
@@ -51,3 +51,11 @@
 ): ApprovalInfo | undefined {
   return label.all?.filter(x => x._account_id === account._account_id)[0];
 }
+
+export function labelCompare(labelName1: string, labelName2: string) {
+  if (labelName1 === CODE_REVIEW && labelName2 === CODE_REVIEW) return 0;
+  if (labelName1 === CODE_REVIEW) return -1;
+  if (labelName2 === CODE_REVIEW) return 1;
+
+  return labelName1.localeCompare(labelName2);
+}
diff --git a/polygerrit-ui/app/utils/label-util_test.js b/polygerrit-ui/app/utils/label-util_test.js
index 6a2f768..f9a30df 100644
--- a/polygerrit-ui/app/utils/label-util_test.js
+++ b/polygerrit-ui/app/utils/label-util_test.js
@@ -21,6 +21,7 @@
   getVotingRangeOrDefault,
   getMaxAccounts,
   getApprovalInfo,
+  labelCompare,
 } from './label-util.js';
 
 const VALUES_1 = {
@@ -113,4 +114,11 @@
     };
     assert.isUndefined(getApprovalInfo(label, myAccountInfo));
   });
+
+  test('labelCompare', () => {
+    let sorted = ['c', 'b', 'a'].sort(labelCompare);
+    assert.sameOrderedMembers(sorted, ['a', 'b', 'c']);
+    sorted = ['b', 'a', 'Code-Review'].sort(labelCompare);
+    assert.sameOrderedMembers(sorted, ['Code-Review', 'a', 'b']);
+  });
 });