Merge changes I5f12df88,Ib2b1b6be

* changes:
  Rename gr-messages-list-experimental to gr-messages-list
  Remove the cleaner change log experiment
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 072708e..2cd03f5 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 = {
@@ -453,7 +451,6 @@
 
   constructor() {
     super();
-    this.flagsService = appContext.flagsService;
     this.reporting = appContext.reportingService;
   }
 
@@ -544,14 +541,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.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/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',
 };