Merge changes Iaba47567,I2387d566

* changes:
  Update Typescript version to 4.1.4
  Fixes for Typescript 4.2
diff --git a/Documentation/note-db.txt b/Documentation/note-db.txt
index a13cbfb..b376d6e 100644
--- a/Documentation/note-db.txt
+++ b/Documentation/note-db.txt
@@ -36,6 +36,67 @@
 not available in 3.0, so any upgrade from Gerrit 2.x to 3.x must go through
 2.16 to effect the NoteDb upgrade.
 
+== Format
+
+Each review ("change") in Gerrit is numbered. The different revisions
+("patchsets") of a change 12345 are stored under
+----
+  refs/changes/45/12345/${PATCHSET_NUMBER}
+----
+
+The revisions are stored as commits to the main project, ie. if you
+fetch this ref, you can check out the proposed change.
+
+A change 12345 has its review metadata under
+----
+  refs/changes/45/12345/meta
+----
+The metadata is a notes branch. The commit messages on the branch hold
+modifications to global data of the change (votes, global comments). The inline
+comments are in a
+link:https://git.eclipse.org/r/plugins/gitiles/jgit/jgit/\+/master/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java[NoteMap],
+where the key is the commit SHA-1 of the patchset
+that the comment refers to, and the value is JSON data. The format of the
+JSON is in the
+link:https://gerrit.googlesource.com/gerrit/\+/master/java/com/google/gerrit/server/notedb/RevisionNoteData.java[RevisionNoteData]
+which contains 
+link:https://gerrit.googlesource.com/gerrit/\+/master/java/com/google/gerrit/entities/Comment.java[Comment] entities.
+
+For example:
+----
+   {
+      "key": {
+        "uuid": "c7be1334_47885e36",
+        "filename":
+"java/com/google/gerrit/server/restapi/project/CommitsCollection.java",
+        "patchSetId": 7
+      },
+      "lineNbr": 158,
+      "author": {
+        "id": 1026112
+      },
+      "writtenOn": "2019-11-06T09:00:50Z",
+      "side": 1,
+      "message": "nit: factor this out in a variable, use
+toImmutableList as collector",
+      "range": {
+        "startLine": 156,
+        "startChar": 32,
+        "endLine": 158,
+        "endChar": 66
+      },
+      "revId": "071c601d6ee1a2a9f520415fd9efef8e00f9cf60",
+      "serverId": "173816e5-2b9a-37c3-8a2e-48639d4f1153",
+      "unresolved": true
+    },
+----
+
+Automated systems may post "robot comments" instead of normal
+comments, which are an extension of the previous comment, defined in
+the
+link:https://gerrit.googlesource.com/gerrit/\+/master/java/com/google/gerrit/entities/RobotComment.java[RobotComment]
+class.
+
 [[migration]]
 == Migration
 
diff --git a/java/com/google/gerrit/testing/TestLoggingActivator.java b/java/com/google/gerrit/testing/TestLoggingActivator.java
index 6b5d8fd..b3ad862 100644
--- a/java/com/google/gerrit/testing/TestLoggingActivator.java
+++ b/java/com/google/gerrit/testing/TestLoggingActivator.java
@@ -48,11 +48,6 @@
           .put("org.openid4java.server.RealmVerifier", Level.ERROR)
           .put("org.openid4java.message.AuthSuccess", Level.ERROR)
 
-          // Silence non-critical messages from c3p0 (if used).
-          .put("com.mchange.v2.c3p0", Level.WARN)
-          .put("com.mchange.v2.resourcepool", Level.WARN)
-          .put("com.mchange.v2.sql", Level.WARN)
-
           // Silence non-critical messages from apache.http.
           .put("org.apache.http", Level.WARN)
 
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index b97d9f2..cbeb59d 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
 import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
 import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWED;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
@@ -705,6 +706,23 @@
   }
 
   @Test
+  public void byProjectWithHidden() throws Exception {
+    TestRepository<Repo> hiddenProject = createProject("hiddenProject");
+    insert(hiddenProject, newChange(hiddenProject));
+    projectOperations
+        .project(Project.nameKey("hiddenProject"))
+        .forUpdate()
+        .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+        .update();
+
+    TestRepository<Repo> visibleProject = createProject("visibleProject");
+    Change visibleChange = insert(visibleProject, newChange(visibleProject));
+    assertQuery("project:visibleProject", visibleChange);
+    assertQuery("project:hiddenProject");
+    assertQuery("project:visibleProject OR project:hiddenProject", visibleChange);
+  }
+
+  @Test
   public void byParentOf() throws Exception {
     TestRepository<Repo> repo1 = createProject("repo1");
     RevCommit commit1 = repo1.parseBody(repo1.commit().message("message").create());
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor
index 3cd520b..30c774f 160000
--- a/plugins/codemirror-editor
+++ b/plugins/codemirror-editor
@@ -1 +1 @@
-Subproject commit 3cd520b1521ff7c558d0cd95274628a3a20de30a
+Subproject commit 30c774f30c1709f71efc250a195dd6fb50c7503b
diff --git a/plugins/delete-project b/plugins/delete-project
index bfe159d..549de03 160000
--- a/plugins/delete-project
+++ b/plugins/delete-project
@@ -1 +1 @@
-Subproject commit bfe159d3007db0f07e967473b53f679ba8f432df
+Subproject commit 549de033d60b13aaeef45ce5c4bf42be39506268
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index 42876d9..4ae3c68 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -171,7 +171,7 @@
   method: HttpMethod.POST,
 };
 
-function isQuckApproveAction(
+function isQuickApproveAction(
   action: UIActionInfo
 ): action is QuickApproveUIActionInfo {
   return (action as QuickApproveUIActionInfo).key === QUICK_APPROVE_ACTION.key;
@@ -943,9 +943,10 @@
       const status = this._getLabelStatus(labelInfo);
       if (status === LabelStatus.NEED) {
         if (result) {
-          // More than one label is missing, so it's unclear which to quick
-          // approve, return null;
-          return null;
+          // More than one label is missing, so check if Code Review can be
+          // given
+          result = null;
+          break;
         }
         result = label;
       } else if (
@@ -999,7 +1000,7 @@
       throw new Error('_topLevelSecondaryActions must be set');
     }
     this._topLevelSecondaryActions = this._topLevelSecondaryActions.filter(
-      sa => !isQuckApproveAction(sa)
+      sa => !isQuickApproveAction(sa)
     );
     this._hideQuickApproveAction = true;
   }
@@ -1271,7 +1272,7 @@
         this._showActionDialog(this.$.confirmAbandonDialog);
         break;
       case QUICK_APPROVE_ACTION.key: {
-        const action = this._allActionValues.find(isQuckApproveAction);
+        const action = this._allActionValues.find(isQuickApproveAction);
         if (!action) {
           return;
         }
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.js
index 8324fdd..c193e60 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.js
@@ -1629,23 +1629,52 @@
         assert.deepEqual(payload.labels, {foo: 1});
       });
 
-      test('not added when multiple labels are required', () => {
+      test('not added when multiple labels are required without code review',
+          () => {
+            element.change = {
+              current_revision: 'abc1234',
+              labels: {
+                foo: {values: {}},
+                bar: {values: {}},
+              },
+              permitted_labels: {
+                foo: [' 0', '+1'],
+                bar: [' 0', '+1', '+2'],
+              },
+            };
+            flush();
+            const approveButton =
+            element.shadowRoot
+                .querySelector('gr-button[data-action-key=\'review\']');
+            assert.isNull(approveButton);
+          });
+
+      test('code review shown with multiple missing approval', () => {
         element.change = {
           current_revision: 'abc1234',
           labels: {
-            foo: {values: {}},
-            bar: {values: {}},
+            'foo': {values: {}},
+            'bar': {values: {}},
+            'Code-Review': {
+              approved: {},
+              values: {
+                ' 0': '',
+                '+1': '',
+                '+2': '',
+              },
+            },
           },
           permitted_labels: {
-            foo: [' 0', '+1'],
-            bar: [' 0', '+1', '+2'],
+            'foo': [' 0', '+1'],
+            'bar': [' 0', '+1', '+2'],
+            'Code-Review': [' 0', '+1', '+2'],
           },
         };
         flush();
         const approveButton =
             element.shadowRoot
                 .querySelector('gr-button[data-action-key=\'review\']');
-        assert.isNull(approveButton);
+        assert.isOk(approveButton);
       });
 
       test('button label for missing approval', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
index aae8bb6..94324f3d 100644
--- a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
@@ -470,7 +470,6 @@
                     html`<gr-avatar
                       .account="${account}"
                       image-size="32"
-                      aria-label="Account avatar"
                     ></gr-avatar>`
                 )}
                 ${countUnresolvedComments} unresolved</gr-summary-chip
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index 2bf19ece..7a5bc6f 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -156,6 +156,7 @@
   ThreadListModifiedEvent,
   TabState,
   EventType,
+  CloseFixPreviewEvent,
 } from '../../../types/events';
 import {GrButton} from '../../shared/gr-button/gr-button';
 import {GrMessagesList} from '../gr-messages-list/gr-messages-list';
@@ -678,7 +679,7 @@
       this._handleCommitMessageCancel()
     );
     this.addEventListener('open-fix-preview', e => this._onOpenFixPreview(e));
-    this.addEventListener('close-fix-preview', () => this._onCloseFixPreview());
+    this.addEventListener('close-fix-preview', e => this._onCloseFixPreview(e));
     window.addEventListener('scroll', this.handleScroll);
     document.addEventListener('visibilitychange', this.handleVisibilityChange);
 
@@ -744,8 +745,8 @@
     this.$.applyFixDialog.open(e);
   }
 
-  _onCloseFixPreview() {
-    this._reload();
+  _onCloseFixPreview(e: CloseFixPreviewEvent) {
+    if (e.detail.fixApplied) this._reload();
   }
 
   _handleToggleDiffMode(e: CustomKeyboardEvent) {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
index ce5b246..eb4053a 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
@@ -55,7 +55,7 @@
   project?: RepoName;
 
   @property({type: Object})
-  _query?: (_text?: string) => Promise<AutocompleteSuggestion[]>;
+  _query: (input: string) => Promise<AutocompleteSuggestion[]>;
 
   get keyBindings() {
     return {
@@ -67,7 +67,7 @@
 
   constructor() {
     super();
-    this._query = () => this._getProjectBranchesSuggestions();
+    this._query = (text: string) => this._getProjectBranchesSuggestions(text);
   }
 
   _handleConfirmTap(e: Event) {
@@ -93,10 +93,9 @@
   }
 
   _getProjectBranchesSuggestions(
-    input?: string
+    input: string
   ): Promise<AutocompleteSuggestion[]> {
     if (!this.project) return Promise.reject(new Error('Missing project'));
-    if (!input) return Promise.reject(new Error('Missing input'));
     if (input.startsWith('refs/heads/')) {
       input = input.substring('refs/heads/'.length);
     }
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.js b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.js
index db00f6b..36a2ad3 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.js
@@ -64,5 +64,12 @@
       done();
     });
   });
+
+  test('_getProjectBranchesSuggestions input empty string', done => {
+    element._getProjectBranchesSuggestions('').then(branches => {
+      assert.equal(branches.length, 0);
+      done();
+    });
+  });
 });
 
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-changes-list-experimental.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-changes-list-experimental.ts
index ec1235e..c75b8c4 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-changes-list-experimental.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list-experimental/gr-related-changes-list-experimental.ts
@@ -718,16 +718,15 @@
 
     let button: TemplateResult | typeof nothing = nothing;
     if (collapsible) {
-      if (this.showAll) {
-        button = html`<gr-button link="" @click="${this.toggle}"
-          >Show less<iron-icon icon="gr-icons:expand-less"></iron-icon
-        ></gr-button>`;
-      } else {
-        button = html`<gr-button link="" @click="${this.toggle}"
-          >Show all (${this.length})
-          <iron-icon icon="gr-icons:expand-more"></iron-icon
-        ></gr-button>`;
+      let buttonText = 'Show less';
+      let buttonIcon = 'expand-less';
+      if (!this.showAll) {
+        buttonText = `Show all (${this.length})`;
+        buttonIcon = 'expand-more';
       }
+      button = html`<gr-button link="" @click="${this.toggle}"
+        >${buttonText}<iron-icon icon="gr-icons:${buttonIcon}"></iron-icon
+      ></gr-button>`;
     }
 
     return html`<div class="container">${title}${button}</div>
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts
index 382e17c..f6bd04e 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts
@@ -128,7 +128,7 @@
             on-click="_handleAllComments"
             checked="[[_showAllComments(_draftsOnly, unresolvedOnly)]]"
           />
-          <label for="all">
+          <label for="allRadio">
             All ([[_countAllThreads(threads)]])
           </label>
       </template>
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index 716d142..0bf9f1c 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -37,12 +37,14 @@
 import {isRobot} from '../../../utils/comment-util';
 import {OpenFixPreviewEvent} from '../../../types/events';
 import {appContext} from '../../../services/app-context';
-import {fireEvent} from '../../../utils/event-util';
+import {fireCloseFixPreview, fireEvent} from '../../../utils/event-util';
 import {ParsedChangeInfo} from '../../../types/types';
+import {GrButton} from '../../shared/gr-button/gr-button';
 
 export interface GrApplyFixDialog {
   $: {
     applyFixOverlay: GrOverlay;
+    nextFix: GrButton;
   };
 }
 
@@ -168,7 +170,7 @@
         }
       })
       .catch(err => {
-        this._close();
+        this._close(false);
         throw err;
       });
   }
@@ -186,7 +188,7 @@
     if (e) {
       e.stopPropagation();
     }
-    this._close();
+    this._close(false);
   }
 
   addOneTo(_selectedFixIdx: number) {
@@ -225,12 +227,12 @@
     return _selectedFixIdx === fixSuggestions.length - 1;
   }
 
-  _close() {
+  _close(fixApplied: boolean) {
     this._currentFix = undefined;
     this._currentPreviews = [];
     this._isApplyFixLoading = false;
 
-    fireEvent(this, 'close-fix-preview');
+    fireCloseFixPreview(this, fixApplied);
     this.$.applyFixOverlay.close();
   }
 
@@ -282,7 +284,7 @@
             EditPatchSetNum,
             patchNum as BasePatchSetNum
           );
-          this._close();
+          this._close(true);
         }
         this._isApplyFixLoading = false;
       });
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.js b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.js
deleted file mode 100644
index d78961c..0000000
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.js
+++ /dev/null
@@ -1,272 +0,0 @@
-/**
- * @license
- * Copyright (C) 2019 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-apply-fix-dialog.js';
-import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-import {stubRestApi} from '../../../test/test-utils.js';
-
-const basicFixture = fixtureFromElement('gr-apply-fix-dialog');
-
-suite('gr-apply-fix-dialog tests', () => {
-  let element;
-
-  const ROBOT_COMMENT_WITH_TWO_FIXES = {
-    robot_id: 'robot_1',
-    fix_suggestions: [{fix_id: 'fix_1'}, {fix_id: 'fix_2'}],
-  };
-
-  const ROBOT_COMMENT_WITH_ONE_FIX = {
-    robot_id: 'robot_1',
-    fix_suggestions: [{fix_id: 'fix_1'}],
-  };
-
-  setup(() => {
-    element = basicFixture.instantiate();
-    element.changeNum = '1';
-    element._patchNum = 2;
-    element.change = {
-      _number: '1',
-      project: 'project',
-      revisions: {
-        abcd: {_number: 1},
-        efgh: {_number: 2},
-      },
-      current_revision: 'efgh',
-    };
-    element.prefs = {
-      font_size: 12,
-      line_length: 100,
-      tab_size: 4,
-    };
-  });
-
-  suite('dialog open', () => {
-    setup(() => {
-      stubRestApi('getRobotCommentFixPreview')
-          .returns(Promise.resolve({
-            f1: {
-              meta_a: {},
-              meta_b: {},
-              content: [
-                {
-                  ab: ['loqlwkqll'],
-                },
-                {
-                  b: ['qwqqsqw'],
-                },
-                {
-                  ab: ['qwqqsqw', 'qweqeqweqeq', 'qweqweq'],
-                },
-              ],
-            },
-            f2: {
-              meta_a: {},
-              meta_b: {},
-              content: [
-                {
-                  ab: ['eqweqweqwex'],
-                },
-                {
-                  b: ['zassdasd'],
-                },
-                {
-                  ab: ['zassdasd', 'dasdasda', 'asdasdad'],
-                },
-              ],
-            },
-          }));
-      sinon.stub(element.$.applyFixOverlay, 'open')
-          .returns(Promise.resolve());
-    });
-
-    test('dialog opens fetch and sets previews', done => {
-      element.open({detail: {patchNum: 2,
-        comment: ROBOT_COMMENT_WITH_TWO_FIXES}})
-          .then(() => {
-            assert.equal(element._currentFix.fix_id, 'fix_1');
-            assert.equal(element._currentPreviews.length, 2);
-            assert.equal(element._robotId, 'robot_1');
-            const button = element.shadowRoot.querySelector(
-                '#applyFixDialog').shadowRoot.querySelector('#confirm');
-            assert.isFalse(button.hasAttribute('disabled'));
-            assert.equal(button.getAttribute('title'), '');
-            done();
-          });
-    });
-
-    test('tooltip is hidden if apply fix is loading', done => {
-      element.open({detail: {patchNum: 2,
-        comment: ROBOT_COMMENT_WITH_TWO_FIXES}})
-          .then(() => {
-            element._isApplyFixLoading = true;
-            const button = element.shadowRoot.querySelector(
-                '#applyFixDialog').shadowRoot.querySelector('#confirm');
-            assert.isTrue(button.hasAttribute('disabled'));
-            assert.equal(button.getAttribute('title'), '');
-            done();
-          });
-    });
-
-    test('apply fix button is disabled on older patchset', done => {
-      element.change = {
-        _number: '1',
-        project: 'project',
-        revisions: {
-          abcd: {_number: 1},
-          efgh: {_number: 2},
-        },
-        current_revision: 'abcd',
-      };
-      element.open({detail: {patchNum: 2,
-        comment: ROBOT_COMMENT_WITH_ONE_FIX}})
-          .then(() => {
-            flush(() => {
-              const button = element.shadowRoot.querySelector(
-                  '#applyFixDialog').shadowRoot.querySelector('#confirm');
-              assert.isTrue(button.hasAttribute('disabled'));
-              assert.equal(button.getAttribute('title'),
-                  'Fix can only be applied to the latest patchset');
-              done();
-            });
-          });
-    });
-  });
-
-  test('next button state updated when suggestions changed', done => {
-    stubRestApi('getRobotCommentFixPreview').returns(Promise.resolve({}));
-    sinon.stub(element.$.applyFixOverlay, 'open').returns(Promise.resolve());
-
-    element.open({detail: {patchNum: 2, comment: ROBOT_COMMENT_WITH_ONE_FIX}})
-        .then(() => assert.isTrue(element.$.nextFix.disabled))
-        .then(() =>
-          element.open({detail: {patchNum: 2,
-            comment: ROBOT_COMMENT_WITH_TWO_FIXES}}))
-        .then(() => {
-          assert.isFalse(element.$.nextFix.disabled);
-          done();
-        });
-  });
-
-  test('preview endpoint throws error should reset dialog', async () => {
-    stubRestApi('getRobotCommentFixPreview').returns(
-        Promise.reject(new Error('backend error')));
-    element.open({detail: {patchNum: 2,
-      comment: ROBOT_COMMENT_WITH_TWO_FIXES}});
-    await flush();
-    assert.equal(element._currentFix, undefined);
-  });
-
-  test('apply fix button should call apply ' +
-  'and navigate to change view', () => {
-    const stub = stubRestApi('applyFixSuggestion').returns(
-        Promise.resolve({ok: true}));
-    sinon.stub(GerritNav, 'navigateToChange');
-    element._currentFix = {fix_id: '123'};
-
-    return element._handleApplyFix().then(() => {
-      assert.isTrue(stub.calledWithExactly('1', 2, '123'));
-      assert.isTrue(GerritNav.navigateToChange.calledWithExactly({
-        _number: '1',
-        project: 'project',
-        revisions: {
-          abcd: {_number: 1},
-          efgh: {_number: 2},
-        },
-        current_revision: 'efgh',
-      }, 'edit', 2));
-
-      // reset gr-apply-fix-dialog and close
-      assert.equal(element._currentFix, undefined);
-      assert.equal(element._currentPreviews.length, 0);
-    });
-  });
-
-  test('should not navigate to change view if incorect reponse', done => {
-    const stub = stubRestApi('applyFixSuggestion').returns(Promise.resolve({}));
-    sinon.stub(GerritNav, 'navigateToChange');
-    element._currentFix = {fix_id: '123'};
-
-    element._handleApplyFix().then(() => {
-      assert.isTrue(stub.calledWithExactly('1', 2, '123'));
-      assert.isTrue(GerritNav.navigateToChange.notCalled);
-
-      assert.equal(element._isApplyFixLoading, false);
-      done();
-    });
-  });
-
-  test('select fix forward and back of multiple suggested fixes', done => {
-    stubRestApi('getRobotCommentFixPreview')
-        .returns(Promise.resolve({
-          f1: {
-            meta_a: {},
-            meta_b: {},
-            content: [
-              {
-                ab: ['loqlwkqll'],
-              },
-              {
-                b: ['qwqqsqw'],
-              },
-              {
-                ab: ['qwqqsqw', 'qweqeqweqeq', 'qweqweq'],
-              },
-            ],
-          },
-          f2: {
-            meta_a: {},
-            meta_b: {},
-            content: [
-              {
-                ab: ['eqweqweqwex'],
-              },
-              {
-                b: ['zassdasd'],
-              },
-              {
-                ab: ['zassdasd', 'dasdasda', 'asdasdad'],
-              },
-            ],
-          },
-        }));
-    sinon.stub(element.$.applyFixOverlay, 'open').returns(Promise.resolve());
-
-    element.open({detail: {patchNum: 2, comment: ROBOT_COMMENT_WITH_TWO_FIXES}})
-        .then(() => {
-          element._onNextFixClick();
-          assert.equal(element._currentFix.fix_id, 'fix_2');
-          element._onPrevFixClick();
-          assert.equal(element._currentFix.fix_id, 'fix_1');
-          done();
-        });
-  });
-
-  test('server-error should throw for failed apply call', async () => {
-    stubRestApi('applyFixSuggestion').returns(
-        Promise.reject(new Error('backend error')));
-    sinon.stub(GerritNav, 'navigateToChange');
-    element._currentFix = {fix_id: '123'};
-    let expectedError;
-    await element._handleApplyFix().catch(e => {
-      expectedError = e;
-    });
-    assert.isOk(expectedError);
-    assert.isFalse(GerritNav.navigateToChange.called);
-  });
-});
-
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts
new file mode 100644
index 0000000..d3d7615
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts
@@ -0,0 +1,376 @@
+/**
+ * @license
+ * Copyright (C) 2019 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';
+import './gr-apply-fix-dialog';
+import {GerritNav} from '../../core/gr-navigation/gr-navigation';
+import {queryAndAssert, stubRestApi} from '../../../test/test-utils';
+import {GrApplyFixDialog} from './gr-apply-fix-dialog';
+import {
+  BasePatchSetNum,
+  EditPatchSetNum,
+  PatchSetNum,
+  RobotId,
+  RobotRunId,
+  Timestamp,
+  UrlEncodedCommentId,
+} from '../../../types/common';
+import {
+  createFixSuggestionInfo,
+  createParsedChange,
+  createRevisions,
+  getCurrentRevision,
+} from '../../../test/test-data-generators';
+import {createDefaultDiffPrefs} from '../../../constants/constants';
+import {DiffInfo} from '../../../types/diff';
+import {UIRobot} from '../../../utils/comment-util';
+import {
+  CloseFixPreviewEventDetail,
+  EventType,
+  OpenFixPreviewEventDetail,
+} from '../../../types/events';
+import {GrButton} from '../../shared/gr-button/gr-button';
+
+const basicFixture = fixtureFromElement('gr-apply-fix-dialog');
+
+suite('gr-apply-fix-dialog tests', () => {
+  let element: GrApplyFixDialog;
+
+  const ROBOT_COMMENT_WITH_TWO_FIXES: UIRobot = {
+    id: '1' as UrlEncodedCommentId,
+    updated: '2018-02-08 18:49:18.000000000' as Timestamp,
+    robot_id: 'robot_1' as RobotId,
+    robot_run_id: 'run_1' as RobotRunId,
+    properties: {},
+    fix_suggestions: [
+      createFixSuggestionInfo('fix_1'),
+      createFixSuggestionInfo('fix_2'),
+    ],
+  };
+
+  const ROBOT_COMMENT_WITH_ONE_FIX: UIRobot = {
+    id: '2' as UrlEncodedCommentId,
+    updated: '2018-02-08 18:49:18.000000000' as Timestamp,
+    robot_id: 'robot_1' as RobotId,
+    robot_run_id: 'run_1' as RobotRunId,
+    properties: {},
+    fix_suggestions: [createFixSuggestionInfo('fix_1')],
+  };
+
+  function getConfirmButton(): GrButton {
+    return queryAndAssert(
+      queryAndAssert(element, '#applyFixDialog'),
+      '#confirm'
+    );
+  }
+
+  setup(() => {
+    element = basicFixture.instantiate();
+    const change = {
+      ...createParsedChange(),
+      revisions: createRevisions(2),
+      current_revision: getCurrentRevision(1),
+    };
+    element.changeNum = change._number;
+    element._patchNum = change.revisions[change.current_revision]._number;
+    element.change = change;
+    element.prefs = {
+      ...createDefaultDiffPrefs(),
+      font_size: 12,
+      line_length: 100,
+      tab_size: 4,
+    };
+  });
+
+  suite('dialog open', () => {
+    setup(() => {
+      const diffInfo1: DiffInfo = {
+        meta_a: {
+          name: 'f1',
+          content_type: 'text',
+          lines: 10,
+        },
+        meta_b: {
+          name: 'f1',
+          content_type: 'text',
+          lines: 12,
+        },
+        content: [
+          {
+            ab: ['loqlwkqll'],
+          },
+          {
+            b: ['qwqqsqw'],
+          },
+          {
+            ab: ['qwqqsqw', 'qweqeqweqeq', 'qweqweq'],
+          },
+        ],
+        change_type: 'MODIFIED',
+        intraline_status: 'OK',
+      };
+
+      const diffInfo2: DiffInfo = {
+        meta_a: {
+          name: 'f2',
+          content_type: 'text',
+          lines: 10,
+        },
+        meta_b: {
+          name: 'f2',
+          content_type: 'text',
+          lines: 12,
+        },
+        content: [
+          {
+            ab: ['eqweqweqwex'],
+          },
+          {
+            b: ['zassdasd'],
+          },
+          {
+            ab: ['zassdasd', 'dasdasda', 'asdasdad'],
+          },
+        ],
+        change_type: 'MODIFIED',
+        intraline_status: 'OK',
+      };
+
+      stubRestApi('getRobotCommentFixPreview').returns(
+        Promise.resolve({
+          f1: diffInfo1,
+          f2: diffInfo2,
+        })
+      );
+      sinon.stub(element.$.applyFixOverlay, 'open').returns(Promise.resolve());
+    });
+
+    test('dialog opens fetch and sets previews', async () => {
+      await element.open(
+        new CustomEvent<OpenFixPreviewEventDetail>(EventType.OPEN_FIX_PREVIEW, {
+          detail: {
+            patchNum: 2 as PatchSetNum,
+            comment: ROBOT_COMMENT_WITH_TWO_FIXES,
+          },
+        })
+      );
+      assert.equal(element._currentFix!.fix_id, 'fix_1');
+      assert.equal(element._currentPreviews.length, 2);
+      assert.equal(element._robotId, 'robot_1' as RobotId);
+      const button = getConfirmButton();
+      assert.isFalse(button.hasAttribute('disabled'));
+      assert.equal(button.getAttribute('title'), '');
+    });
+
+    test('tooltip is hidden if apply fix is loading', async () => {
+      await element.open(
+        new CustomEvent<OpenFixPreviewEventDetail>(EventType.OPEN_FIX_PREVIEW, {
+          detail: {
+            patchNum: 2 as PatchSetNum,
+            comment: ROBOT_COMMENT_WITH_TWO_FIXES,
+          },
+        })
+      );
+      element._isApplyFixLoading = true;
+      const button = getConfirmButton();
+      assert.isTrue(button.hasAttribute('disabled'));
+      assert.equal(button.getAttribute('title'), '');
+    });
+
+    test('apply fix button is disabled on older patchset', async () => {
+      element.change = element.change = {
+        ...createParsedChange(),
+        revisions: createRevisions(2),
+        current_revision: getCurrentRevision(0),
+      };
+      await element.open(
+        new CustomEvent<OpenFixPreviewEventDetail>(EventType.OPEN_FIX_PREVIEW, {
+          detail: {
+            patchNum: 2 as PatchSetNum,
+            comment: ROBOT_COMMENT_WITH_ONE_FIX,
+          },
+        })
+      );
+      await flush();
+      const button = getConfirmButton();
+      assert.isTrue(button.hasAttribute('disabled'));
+      assert.equal(
+        button.getAttribute('title'),
+        'Fix can only be applied to the latest patchset'
+      );
+    });
+  });
+
+  test('next button state updated when suggestions changed', async () => {
+    stubRestApi('getRobotCommentFixPreview').returns(Promise.resolve({}));
+    sinon.stub(element.$.applyFixOverlay, 'open').returns(Promise.resolve());
+
+    await element.open(
+      new CustomEvent<OpenFixPreviewEventDetail>(EventType.OPEN_FIX_PREVIEW, {
+        detail: {
+          patchNum: 2 as PatchSetNum,
+          comment: ROBOT_COMMENT_WITH_ONE_FIX,
+        },
+      })
+    );
+    assert.isTrue(element.$.nextFix.disabled);
+    await element.open(
+      new CustomEvent<OpenFixPreviewEventDetail>(EventType.OPEN_FIX_PREVIEW, {
+        detail: {
+          patchNum: 2 as PatchSetNum,
+          comment: ROBOT_COMMENT_WITH_TWO_FIXES,
+        },
+      })
+    );
+    assert.isFalse(element.$.nextFix.disabled);
+  });
+
+  test('preview endpoint throws error should reset dialog', async () => {
+    stubRestApi('getRobotCommentFixPreview').returns(
+      Promise.reject(new Error('backend error'))
+    );
+    element.open(
+      new CustomEvent<OpenFixPreviewEventDetail>(EventType.OPEN_FIX_PREVIEW, {
+        detail: {
+          patchNum: 2 as PatchSetNum,
+          comment: ROBOT_COMMENT_WITH_TWO_FIXES,
+        },
+      })
+    );
+    await flush();
+    assert.equal(element._currentFix, undefined);
+  });
+
+  test('apply fix button should call apply, navigate to change view and fire close', async () => {
+    const applyFixSuggestionStub = stubRestApi('applyFixSuggestion').returns(
+      Promise.resolve(new Response(null, {status: 200}))
+    );
+    const navigateToChangeStub = sinon.stub(GerritNav, 'navigateToChange');
+    element._currentFix = createFixSuggestionInfo('123');
+
+    const closeFixPreviewEventSpy = sinon.spy();
+    // Element is recreated after each test, removeEventListener isn't required
+    element.addEventListener(
+      EventType.CLOSE_FIX_PREVIEW,
+      closeFixPreviewEventSpy
+    );
+    await element._handleApplyFix(new CustomEvent('confirm'));
+
+    sinon.assert.calledOnceWithExactly(
+      applyFixSuggestionStub,
+      element.change!._number,
+      2 as PatchSetNum,
+      '123'
+    );
+    sinon.assert.calledWithExactly(
+      navigateToChangeStub,
+      element.change!,
+      EditPatchSetNum,
+      element.change!.revisions[2]._number as BasePatchSetNum
+    );
+
+    sinon.assert.calledOnceWithExactly(
+      closeFixPreviewEventSpy,
+      new CustomEvent<CloseFixPreviewEventDetail>(EventType.CLOSE_FIX_PREVIEW, {
+        detail: {
+          fixApplied: true,
+        },
+      })
+    );
+
+    // reset gr-apply-fix-dialog and close
+    assert.equal(element._currentFix, undefined);
+    assert.equal(element._currentPreviews.length, 0);
+  });
+
+  test('should not navigate to change view if incorect reponse', async () => {
+    const applyFixSuggestionStub = stubRestApi('applyFixSuggestion').returns(
+      Promise.resolve(new Response(null, {status: 500}))
+    );
+    const navigateToChangeStub = sinon.stub(GerritNav, 'navigateToChange');
+    element._currentFix = createFixSuggestionInfo('fix_123');
+
+    await element._handleApplyFix(new CustomEvent('confirm'));
+    sinon.assert.calledWithExactly(
+      applyFixSuggestionStub,
+      element.change!._number,
+      2 as PatchSetNum,
+      'fix_123'
+    );
+    assert.isTrue(navigateToChangeStub.notCalled);
+
+    assert.equal(element._isApplyFixLoading, false);
+  });
+
+  test('select fix forward and back of multiple suggested fixes', async () => {
+    sinon.stub(element.$.applyFixOverlay, 'open').returns(Promise.resolve());
+
+    await element.open(
+      new CustomEvent<OpenFixPreviewEventDetail>(EventType.OPEN_FIX_PREVIEW, {
+        detail: {
+          patchNum: 2 as PatchSetNum,
+          comment: ROBOT_COMMENT_WITH_TWO_FIXES,
+        },
+      })
+    );
+    element._onNextFixClick(new CustomEvent('click'));
+    assert.equal(element._currentFix!.fix_id, 'fix_2');
+    element._onPrevFixClick(new CustomEvent('click'));
+    assert.equal(element._currentFix!.fix_id, 'fix_1');
+  });
+
+  test('server-error should throw for failed apply call', async () => {
+    stubRestApi('applyFixSuggestion').returns(
+      Promise.reject(new Error('backend error'))
+    );
+    const navigateToChangeStub = sinon.stub(GerritNav, 'navigateToChange');
+    element._currentFix = createFixSuggestionInfo('fix_123');
+
+    const closeFixPreviewEventSpy = sinon.spy();
+    // Element is recreated after each test, removeEventListener isn't required
+    element.addEventListener(
+      EventType.CLOSE_FIX_PREVIEW,
+      closeFixPreviewEventSpy
+    );
+
+    let expectedError;
+    await element._handleApplyFix(new CustomEvent('click')).catch(e => {
+      expectedError = e;
+    });
+    assert.isOk(expectedError);
+    assert.isFalse(navigateToChangeStub.called);
+    sinon.assert.notCalled(closeFixPreviewEventSpy);
+  });
+
+  test('onCancel fires close with correct parameters', () => {
+    const closeFixPreviewEventSpy = sinon.spy();
+    // Element is recreated after each test, removeEventListener isn't required
+    element.addEventListener(
+      EventType.CLOSE_FIX_PREVIEW,
+      closeFixPreviewEventSpy
+    );
+    element.onCancel(new CustomEvent('cancel'));
+    sinon.assert.calledOnceWithExactly(
+      closeFixPreviewEventSpy,
+      new CustomEvent<CloseFixPreviewEventDetail>(EventType.CLOSE_FIX_PREVIEW, {
+        detail: {
+          fixApplied: false,
+        },
+      })
+    );
+  });
+});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
index 24a7d78..874f572 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
@@ -72,6 +72,7 @@
 declare global {
   interface HTMLElementEventMap {
     'diff-context-expanded': CustomEvent<DiffContextExpandedEventDetail>;
+    'content-load-needed': CustomEvent<ContentLoadNeededEventDetail>;
   }
 }
 
@@ -511,14 +512,14 @@
             end_line: lastRange.right.end_line,
           },
         };
-        fire<ContentLoadNeededEventDetail>(button, 'content-load-needed', {
+        fire(button, 'content-load-needed', {
           lineRange,
         });
       });
     } else {
       button.addEventListener('click', e => {
         e.stopPropagation();
-        fire<DiffContextExpandedEventDetail>(button, 'diff-context-expanded', {
+        fire(button, 'diff-context-expanded', {
           groups,
           section,
           numLines,
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.ts b/polygerrit-ui/app/styles/themes/dark-theme.ts
index f877771..01cee4c 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.ts
+++ b/polygerrit-ui/app/styles/themes/dark-theme.ts
@@ -174,7 +174,7 @@
       --header-text-color: var(--primary-text-color);
 
       /* diff colors */
-      --dark-add-highlight-color: #133820;
+      --dark-add-highlight-color: var(--green-tonal); 
       --dark-rebased-add-highlight-color: rgba(11, 255, 155, 0.15);
       --dark-rebased-remove-highlight-color: rgba(255, 139, 6, 0.15);
       --dark-remove-highlight-color: #62110f;
@@ -187,7 +187,7 @@
       --diff-selection-background-color: #3a71d8;
       --diff-tab-indicator-color: var(--deemphasized-text-color);
       --diff-trailing-whitespace-indicator: #ff9ad2;
-      --light-add-highlight-color: #0f401f;
+      --light-add-highlight-color: #182b1f;
       --light-rebased-add-highlight-color: #487165;
       --diff-moved-in-background: #1d4042;
       --diff-moved-out-background: #230e34;
diff --git a/polygerrit-ui/app/test/test-data-generators.ts b/polygerrit-ui/app/test/test-data-generators.ts
index 3bfe743..778e58d 100644
--- a/polygerrit-ui/app/test/test-data-generators.ts
+++ b/polygerrit-ui/app/test/test-data-generators.ts
@@ -65,6 +65,8 @@
   RelatedChangeAndCommitInfo,
   SubmittedTogetherInfo,
   RelatedChangesInfo,
+  FixSuggestionInfo,
+  FixId,
 } from '../types/common';
 import {
   AccountsVisibility,
@@ -614,3 +616,11 @@
     non_visible_changes: 0,
   };
 }
+
+export function createFixSuggestionInfo(fixId = 'fix_1'): FixSuggestionInfo {
+  return {
+    fix_id: fixId as FixId,
+    description: `Fix ${fixId}`,
+    replacements: [],
+  };
+}
diff --git a/polygerrit-ui/app/types/events.ts b/polygerrit-ui/app/types/events.ts
index 11155c1..7bde788 100644
--- a/polygerrit-ui/app/types/events.ts
+++ b/polygerrit-ui/app/types/events.ts
@@ -32,6 +32,7 @@
   MOVED_LINK_CLICKED = 'moved-link-clicked',
   NETWORK_ERROR = 'network-error',
   OPEN_FIX_PREVIEW = 'open-fix-preview',
+  CLOSE_FIX_PREVIEW = 'close-fix-preview',
   PAGE_ERROR = 'page-error',
   RELOAD = 'reload',
   REPLY = 'reply',
@@ -54,6 +55,7 @@
     'iron-announce': IronAnnounceEvent;
     'moved-link-clicked': MovedLinkClickedEvent;
     'open-fix-preview': OpenFixPreviewEvent;
+    'close-fix-preview': CloseFixPreviewEvent;
     /* prettier-ignore */
     'reload': ReloadEvent;
     /* prettier-ignore */
@@ -133,6 +135,11 @@
 }
 export type OpenFixPreviewEvent = CustomEvent<OpenFixPreviewEventDetail>;
 
+export interface CloseFixPreviewEventDetail {
+  fixApplied: boolean;
+}
+export type CloseFixPreviewEvent = CustomEvent<CloseFixPreviewEventDetail>;
+
 export interface PageErrorEventDetail {
   response?: Response;
 }
diff --git a/polygerrit-ui/app/utils/event-util.ts b/polygerrit-ui/app/utils/event-util.ts
index 080955f..da075f1 100644
--- a/polygerrit-ui/app/utils/event-util.ts
+++ b/polygerrit-ui/app/utils/event-util.ts
@@ -20,15 +20,8 @@
 import {
   DialogChangeEventDetail,
   EventType,
-  IronAnnounceEventDetail,
-  NetworkErrorEventDetail,
-  PageErrorEventDetail,
-  ServerErrorEventDetail,
-  ShowAlertEventDetail,
   SwitchTabEventDetail,
   TabState,
-  ThreadListModifiedDetail,
-  TitleChangeEventDetail,
 } from '../types/events';
 
 export function fireEvent(target: EventTarget, type: string) {
@@ -40,6 +33,34 @@
   );
 }
 
+type HTMLElementEventDetailType<
+  K extends keyof HTMLElementEventMap
+> = HTMLElementEventMap[K] extends CustomEvent<infer DT>
+  ? unknown extends DT
+    ? never
+    : DT
+  : never;
+
+type DocumentEventDetailType<
+  K extends keyof DocumentEventMap
+> = DocumentEventMap[K] extends CustomEvent<infer DT>
+  ? unknown extends DT
+    ? never
+    : DT
+  : never;
+
+export function fire<K extends keyof DocumentEventMap>(
+  target: Document,
+  type: K,
+  detail: DocumentEventDetailType<K>
+): void;
+
+export function fire<K extends keyof HTMLElementEventMap>(
+  target: EventTarget,
+  type: K,
+  detail: HTMLElementEventDetailType<K>
+): void;
+
 export function fire<T>(target: EventTarget, type: string, detail: T) {
   target.dispatchEvent(
     new CustomEvent<T>(type, {
@@ -51,27 +72,27 @@
 }
 
 export function fireAlert(target: EventTarget, message: string) {
-  fire<ShowAlertEventDetail>(target, EventType.SHOW_ALERT, {message});
+  fire(target, EventType.SHOW_ALERT, {message});
 }
 
 export function firePageError(response?: Response | null) {
   if (response === null) response = undefined;
-  fire<PageErrorEventDetail>(document, EventType.PAGE_ERROR, {response});
+  fire(document, EventType.PAGE_ERROR, {response});
 }
 
 export function fireServerError(response: Response, request?: FetchRequest) {
-  fire<ServerErrorEventDetail>(document, EventType.SERVER_ERROR, {
+  fire(document, EventType.SERVER_ERROR, {
     response,
     request,
   });
 }
 
 export function fireNetworkError(error: Error) {
-  fire<NetworkErrorEventDetail>(document, EventType.NETWORK_ERROR, {error});
+  fire(document, EventType.NETWORK_ERROR, {error});
 }
 
 export function fireTitleChange(target: EventTarget, title: string) {
-  fire<TitleChangeEventDetail>(target, EventType.TITLE_CHANGE, {title});
+  fire(target, EventType.TITLE_CHANGE, {title});
 }
 
 // TODO(milutin) - remove once new gr-dialog will do it out of the box
@@ -80,11 +101,11 @@
   target: EventTarget,
   detail: DialogChangeEventDetail
 ) {
-  fire<DialogChangeEventDetail>(target, EventType.DIALOG_CHANGE, detail);
+  fire(target, EventType.DIALOG_CHANGE, detail);
 }
 
 export function fireIronAnnounce(target: EventTarget, text: string) {
-  fire<IronAnnounceEventDetail>(target, EventType.IRON_ANNOUNCE, {text});
+  fire(target, EventType.IRON_ANNOUNCE, {text});
 }
 
 export function fireThreadListModifiedEvent(
@@ -92,7 +113,7 @@
   rootId: UrlEncodedCommentId,
   path: string
 ) {
-  fire<ThreadListModifiedDetail>(target, EventType.THREAD_LIST_MODIFIED, {
+  fire(target, EventType.THREAD_LIST_MODIFIED, {
     rootId,
     path,
   });
@@ -105,7 +126,11 @@
   tabState?: TabState
 ) {
   const detail: SwitchTabEventDetail = {tab, scrollIntoView, tabState};
-  fire<SwitchTabEventDetail>(target, EventType.SHOW_PRIMARY_TAB, detail);
+  fire(target, EventType.SHOW_PRIMARY_TAB, detail);
+}
+
+export function fireCloseFixPreview(target: EventTarget, fixApplied: boolean) {
+  fire(target, EventType.CLOSE_FIX_PREVIEW, {fixApplied});
 }
 
 export function waitForEventOnce<K extends keyof HTMLElementEventMap>(
diff --git a/resources/log4j.properties b/resources/log4j.properties
index 39246b3..2898cfc 100644
--- a/resources/log4j.properties
+++ b/resources/log4j.properties
@@ -41,11 +41,5 @@
 log4j.logger.org.openid4java.server.RealmVerifier=ERROR
 log4j.logger.org.openid4java.message.AuthSuccess=ERROR
 
-# Silence non-critical messages from c3p0 (if used).
-#
-log4j.logger.com.mchange.v2.c3p0=WARN
-log4j.logger.com.mchange.v2.resourcepool=WARN
-log4j.logger.com.mchange.v2.sql=WARN
-
 # Silence non-critical messages from apache.http
 log4j.logger.org.apache.http=WARN
diff --git a/tools/bzl/js.bzl b/tools/bzl/js.bzl
index bbb1432..facb1ce 100644
--- a/tools/bzl/js.bzl
+++ b/tools/bzl/js.bzl
@@ -455,22 +455,8 @@
     if not plugin_name:
         plugin_name = name
 
-    html_plugin = app.endswith(".html")
     srcs = srcs if app in srcs else srcs + [app]
-
-    if html_plugin:
-        # Combines all .js and .html files into foo_combined.js and foo_combined.html
-        _bundle_rule(
-            name = name + "_combined",
-            app = app,
-            srcs = srcs,
-            deps = deps,
-            pkg = native.package_name(),
-            **kwargs
-        )
-        js_srcs = [name + "_combined.js"]
-    else:
-        js_srcs = srcs
+    js_srcs = srcs
 
     native.filegroup(
         name = name + "-src-fg",
@@ -483,25 +469,6 @@
         src = name + "-src-fg",
     )
 
-    if html_plugin:
-        native.genrule(
-            name = name + "_rename_html",
-            srcs = [name + "_combined.html"],
-            outs = [plugin_name + ".html"],
-            cmd = "sed 's/<script src=\"" + name + "_combined.js\"/<script src=\"" + plugin_name + ".js\"/g' $(SRCS) > $(OUTS)",
-            output_to_bindir = True,
-        )
-    else:
-        # For polymer 3 migration, we will only have js plugins, in case server side
-        # is still asking for *.html, we still want to create a html placeholder just to load the js
-        # TODO(taoalpha): this should be cleaned up once polymer 3 plugins are the only ones gerrit supports
-        native.genrule(
-            name = name + "_rename_html",
-            outs = [plugin_name + ".html"],
-            cmd = "echo \"<script src='" + plugin_name + ".js'></script>\" > $(OUTS)",
-            output_to_bindir = True,
-        )
-
     native.genrule(
         name = name + "_rename_js",
         srcs = [name + ".min"],
@@ -510,7 +477,7 @@
         output_to_bindir = True,
     )
 
-    static_files = [plugin_name + ".js", plugin_name + ".html"]
+    static_files = [plugin_name + ".js"]
 
     if assets:
         nested, direct = [], []