Merge "Remove all generic CustomEvents"
diff --git a/polygerrit-ui/app/api/diff.ts b/polygerrit-ui/app/api/diff.ts
index e3b3ad4..19e9d99 100644
--- a/polygerrit-ui/app/api/diff.ts
+++ b/polygerrit-ui/app/api/diff.ts
@@ -336,6 +336,12 @@
   lineNum: LineNumber;
 }
 
+export declare interface LineSelectedEventDetail {
+  number: LineNumber;
+  side: Side;
+  path?: string;
+}
+
 // TODO: Currently unused and not fired.
 export declare interface RenderProgressEventDetail {
   linesRendered: number;
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
index 3254b5c..13a8cd0 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
@@ -23,7 +23,7 @@
 import {sharedStyles} from '../../../styles/shared-styles';
 import {LitElement, PropertyValues, css, html} from 'lit';
 import {customElement, property, query, state} from 'lit/decorators.js';
-import {BindValueChangeEvent} from '../../../types/events';
+import {BindValueChangeEvent, ValueChangedEvent} from '../../../types/events';
 import {fireEvent} from '../../../utils/event-util';
 import {subscribe} from '../../lit/subscription-controller';
 import {configModelToken} from '../../../models/config/config-model';
@@ -125,7 +125,7 @@
               .text=${this.branch}
               .query=${this.query}
               placeholder="Destination branch"
-              @text-changed=${(e: CustomEvent) => {
+              @text-changed=${(e: ValueChangedEvent<BranchName>) => {
                 this.branch = e.detail.value;
               }}
             >
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
index ed57830..3548130 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
@@ -25,6 +25,7 @@
 import {createRepoUrl} from '../../../models/views/repo';
 import {resolve} from '../../../models/dependency';
 import {navigationToken} from '../../core/gr-navigation/gr-navigation';
+import {ValueChangedEvent} from '../../../types/events';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -232,20 +233,20 @@
     return groups;
   }
 
-  private handleRightsTextChanged(e: CustomEvent) {
+  private handleRightsTextChanged(e: ValueChangedEvent) {
     this.repoConfig.parent = e.detail.value as RepoName;
     this.requestUpdate();
   }
 
-  private handleOwnerTextChanged(e: CustomEvent) {
+  private handleOwnerTextChanged(e: ValueChangedEvent) {
     this.repoOwner = e.detail.value;
   }
 
-  private handleOwnerValueChanged(e: CustomEvent) {
+  private handleOwnerValueChanged(e: ValueChangedEvent) {
     this.repoOwnerId = e.detail.value as GroupId;
   }
 
-  private handleNameBindValueChanged(e: CustomEvent) {
+  private handleNameBindValueChanged(e: ValueChangedEvent) {
     this.repoConfig.name = e.detail.value as RepoName;
     // nameChanged needs to be set before the event is fired,
     // because when the event is fired, gr-repo-list gets
@@ -255,16 +256,18 @@
     this.requestUpdate();
   }
 
-  private handleBranchNameBindValueChanged(e: CustomEvent) {
+  private handleBranchNameBindValueChanged(e: ValueChangedEvent) {
     this.defaultBranch = e.detail.value as BranchName;
   }
 
-  private handleCreateEmptyCommitBindValueChanged(e: CustomEvent) {
+  private handleCreateEmptyCommitBindValueChanged(
+    e: ValueChangedEvent<boolean>
+  ) {
     this.repoConfig.create_empty_commit = e.detail.value;
     this.requestUpdate();
   }
 
-  private handlePermissionsOnlyBindValueChanged(e: CustomEvent) {
+  private handlePermissionsOnlyBindValueChanged(e: ValueChangedEvent<boolean>) {
     this.repoConfig.permissions_only = e.detail.value;
     this.requestUpdate();
   }
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
index af9977b..2fcec51 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
@@ -43,6 +43,7 @@
 import {resolve} from '../../../models/dependency';
 import {modalStyles} from '../../../styles/gr-modal-styles';
 import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
+import {ValueChangedEvent} from '../../../types/events';
 
 const SAVING_ERROR_TEXT =
   'Group may not exist, or you may not have ' + 'permission to add it';
@@ -546,22 +547,22 @@
       });
   }
 
-  private handleGroupMemberTextChanged(e: CustomEvent) {
+  private handleGroupMemberTextChanged(e: ValueChangedEvent) {
     if (this.loading) return;
     this.groupMemberSearchName = e.detail.value;
   }
 
-  private handleGroupMemberValueChanged(e: CustomEvent) {
+  private handleGroupMemberValueChanged(e: ValueChangedEvent<number>) {
     if (this.loading) return;
     this.groupMemberSearchId = e.detail.value;
   }
 
-  private handleIncludedGroupTextChanged(e: CustomEvent) {
+  private handleIncludedGroupTextChanged(e: ValueChangedEvent) {
     if (this.loading) return;
     this.includedGroupSearchName = e.detail.value;
   }
 
-  private handleIncludedGroupValueChanged(e: CustomEvent) {
+  private handleIncludedGroupValueChanged(e: ValueChangedEvent) {
     if (this.loading) return;
     this.includedGroupSearchId = e.detail.value;
   }
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts b/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
index ba2b3fd..8e36aa8 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
@@ -17,7 +17,7 @@
 import {getAppContext} from '../../../services/app-context';
 import {ErrorCallback} from '../../../api/rest';
 import {convertToString} from '../../../utils/string-util';
-import {BindValueChangeEvent} from '../../../types/events';
+import {BindValueChangeEvent, ValueChangedEvent} from '../../../types/events';
 import {fontStyles} from '../../../styles/gr-font-styles';
 import {formStyles} from '../../../styles/gr-form-styles';
 import {sharedStyles} from '../../../styles/shared-styles';
@@ -455,25 +455,25 @@
     return id.match(INTERNAL_GROUP_REGEX) ? id : decodeURIComponent(id);
   }
 
-  private handleNameTextChanged(e: CustomEvent) {
+  private handleNameTextChanged(e: ValueChangedEvent) {
     if (!this.groupConfig || this.loading) return;
     this.groupConfig.name = e.detail.value as GroupName;
     this.requestUpdate();
   }
 
-  private handleOwnerTextChanged(e: CustomEvent) {
+  private handleOwnerTextChanged(e: ValueChangedEvent) {
     if (!this.groupConfig || this.loading) return;
     this.groupConfig.owner = e.detail.value;
     this.requestUpdate();
   }
 
-  private handleOwnerValueChanged(e: CustomEvent) {
+  private handleOwnerValueChanged(e: ValueChangedEvent) {
     if (this.loading) return;
     this.groupConfigOwner = e.detail.value;
     this.requestUpdate();
   }
 
-  private handleDescriptionTextChanged(e: CustomEvent) {
+  private handleDescriptionTextChanged(e: ValueChangedEvent) {
     if (!this.groupConfig || this.loading) return;
     this.groupConfig.description = e.detail.value;
     this.requestUpdate();
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
index 07b7c87..46e8ae0 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
@@ -565,6 +565,11 @@
     e.preventDefault();
   }
 
+  // TODO: Do not use generic `CustomEvent`.
+  // There is something fishy going on here though.
+  // `e.detail.value` is of type `Rule`, but `splice()` expects a `number`.
+  // Did not look closer, but this seems to be broken. Should `e.detail.value`
+  // be replaced by `1` maybe??
   private handleRuleChanged(e: CustomEvent, index: number) {
     this.rules!.splice(index, e.detail.value);
     this.handleRulesChanged();
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-hashtag-flow/gr-change-list-hashtag-flow.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-hashtag-flow/gr-change-list-hashtag-flow.ts
index f8fcad8..ea61bfc 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-hashtag-flow/gr-change-list-hashtag-flow.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-hashtag-flow/gr-change-list-hashtag-flow.ts
@@ -159,7 +159,7 @@
         .horizontalAlign=${'auto'}
         .verticalAlign=${'auto'}
         .verticalOffset=${24}
-        @opened-changed=${(e: CustomEvent) =>
+        @opened-changed=${(e: ValueChangedEvent<boolean>) =>
           (this.isDropdownOpen = e.detail.value)}
       >
         ${when(
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-topic-flow/gr-change-list-topic-flow.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-topic-flow/gr-change-list-topic-flow.ts
index 7752476..4a01412 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-topic-flow/gr-change-list-topic-flow.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-topic-flow/gr-change-list-topic-flow.ts
@@ -159,7 +159,7 @@
         .horizontalAlign=${'auto'}
         .verticalAlign=${'auto'}
         .verticalOffset=${24}
-        @opened-changed=${(e: CustomEvent) =>
+        @opened-changed=${(e: ValueChangedEvent<boolean>) =>
           (this.isDropdownOpen = e.detail.value)}
       >
         ${when(
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
index b9a04bd..01399e9 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
@@ -947,7 +947,7 @@
     return createSearchUrl({hashtag, statuses: ['open', 'merged']});
   }
 
-  private async handleTopicRemoved(e: CustomEvent) {
+  private async handleTopicRemoved(e: Event) {
     assertIsDefined(this.change, 'change');
     const target = e.composedPath()[0] as GrLinkedChip;
     target.disabled = true;
@@ -962,7 +962,7 @@
   }
 
   // private but used in test
-  async handleHashtagRemoved(e: CustomEvent) {
+  async handleHashtagRemoved(e: Event) {
     e.preventDefault();
     assertIsDefined(this.change, 'change');
     const target = e.target as GrLinkedChip;
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 c3f934d..4c7c857 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
@@ -117,6 +117,7 @@
 import {
   EditableContentSaveEvent,
   EventType,
+  FileActionTapEvent,
   OpenFixPreviewEvent,
   ShowAlertEventDetail,
   SwitchTabEvent,
@@ -3096,7 +3097,7 @@
     return classes.join(' ');
   }
 
-  private handleFileActionTap(e: CustomEvent<{path: string; action: string}>) {
+  private handleFileActionTap(e: FileActionTapEvent) {
     e.preventDefault();
     assertIsDefined(this.fileListHeader);
     const controls =
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
index f73dab9..5744b02 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
@@ -41,6 +41,7 @@
 import {createChangeUrl} from '../../../models/views/change';
 import {userModelToken} from '../../../models/user/user-model';
 import {changeModelToken} from '../../../models/change/change-model';
+import {PatchRangeChangeEvent} from '../../diff/gr-patch-range-select/gr-patch-range-select';
 
 @customElement('gr-file-list-header')
 export class GrFileListHeader extends LitElement {
@@ -403,7 +404,7 @@
     return shownFileCount <= maxFilesForBulkActions;
   }
 
-  handlePatchChange(e: CustomEvent) {
+  handlePatchChange(e: PatchRangeChangeEvent) {
     const {basePatchNum, patchNum} = e.detail;
     if (
       (basePatchNum === this.basePatchNum && patchNum === this.patchNum) ||
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index f3c23aa..da49af0 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -116,7 +116,11 @@
 import {sharedStyles} from '../../../styles/shared-styles';
 import {when} from 'lit/directives/when.js';
 import {classMap} from 'lit/directives/class-map.js';
-import {ValueChangedEvent} from '../../../types/events';
+import {
+  AddReviewerEvent,
+  RemoveReviewerEvent,
+  ValueChangedEvent,
+} from '../../../types/events';
 import {customElement, property, state, query} from 'lit/decorators.js';
 import {subscribe} from '../../lit/subscription-controller';
 import {configModelToken} from '../../../models/config/config-model';
@@ -723,17 +727,19 @@
     // Plugins on reply-reviewers endpoint can take advantage of these
     // events to add / remove reviewers
 
-    this.addEventListener('add-reviewer', e => {
+    this.addEventListener('add-reviewer', (e: AddReviewerEvent) => {
+      const reviewer = e.detail.reviewer;
       // Only support account type, see more from:
       // elements/shared/gr-account-list/gr-account-list.js#addAccountItem
       this.reviewersList?.addAccountItem({
-        account: (e as CustomEvent).detail.reviewer,
+        account: reviewer,
         count: 1,
       });
     });
 
-    this.addEventListener('remove-reviewer', e => {
-      this.reviewersList?.removeAccount((e as CustomEvent).detail.reviewer);
+    this.addEventListener('remove-reviewer', (e: RemoveReviewerEvent) => {
+      const reviewer = e.detail.reviewer;
+      this.reviewersList?.removeAccount(reviewer);
     });
   }
 
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
index c09d2a2..599e38b 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
@@ -28,7 +28,11 @@
 } from '../../../utils/comment-util';
 import {pluralize} from '../../../utils/string-util';
 import {assertIsDefined} from '../../../utils/common-util';
-import {CommentTabState, TabState} from '../../../types/events';
+import {
+  CommentTabState,
+  TabState,
+  ValueChangedEvent,
+} from '../../../types/events';
 import {DropdownItem} from '../../shared/gr-dropdown-list/gr-dropdown-list';
 import {GrAccountChip} from '../../shared/gr-account-chip/gr-account-chip';
 import {css, html, LitElement, PropertyValues} from 'lit';
@@ -365,7 +369,7 @@
         <gr-dropdown-list
           id="sortDropdown"
           .value=${this.sortDropdownValue}
-          @value-change=${(e: CustomEvent) =>
+          @value-change=${(e: ValueChangedEvent<SortDropdownState>) =>
             (this.sortDropdownValue = e.detail.value)}
           .items=${this.getSortDropdownEntries()}
         >
@@ -521,7 +525,7 @@
   }
 
   // private, but visible for testing
-  handleCommentsDropdownValueChange(e: CustomEvent) {
+  handleCommentsDropdownValueChange(e: ValueChangedEvent<CommentTabState>) {
     const value = e.detail.value;
     switch (value) {
       case CommentTabState.UNRESOLVED:
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
index 6d8dc2b..9dbb01c 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
@@ -26,6 +26,7 @@
 import {configModelToken} from '../../../models/config/config-model';
 import {resolve} from '../../../models/dependency';
 import {subscribe} from '../../lit/subscription-controller';
+import {ValueChangedEvent} from '../../../types/events';
 
 // Possible static search options for auto complete, without negations.
 const SEARCH_OPERATORS: ReadonlyArray<string> = [
@@ -231,7 +232,7 @@
           @commit=${(e: AutocompleteCommitEvent) => {
             this.handleInputCommit(e);
           }}
-          @text-changed=${(e: CustomEvent) => {
+          @text-changed=${(e: ValueChangedEvent) => {
             this.handleSearchTextChanged(e);
           }}
         >
@@ -426,7 +427,7 @@
     this.searchInput.selectAll();
   }
 
-  private handleSearchTextChanged(e: CustomEvent) {
+  private handleSearchTextChanged(e: ValueChangedEvent) {
     this.inputVal = e.detail.value;
   }
 }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
index fd0e5d2..79145db 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
@@ -68,7 +68,11 @@
 import {Timing, Interaction} from '../../../constants/reporting';
 import {ChangeComments} from '../gr-comment-api/gr-comment-api';
 import {Subscription} from 'rxjs';
-import {DisplayLine, RenderPreferences} from '../../../api/diff';
+import {
+  DisplayLine,
+  LineSelectedEventDetail,
+  RenderPreferences,
+} from '../../../api/diff';
 import {resolve} from '../../../models/dependency';
 import {browserModelToken} from '../../../models/browser/browser-model';
 import {commentsModelToken} from '../../../models/comments/comments-model';
@@ -120,7 +124,7 @@
 declare global {
   interface HTMLElementEventMap {
     /* prettier-ignore */
-    'render': CustomEvent;
+    'render': CustomEvent<void>;
     'diff-context-expanded': CustomEvent<DiffContextExpandedEventDetail>;
     'create-comment': CustomEvent<CreateCommentEventDetail>;
     'is-blame-loaded-changed': ValueChangedEvent<boolean>;
@@ -129,7 +133,7 @@
     'files-weblinks-changed': ValueChangedEvent<FilesWebLinks | undefined>;
     'is-image-diff-changed': ValueChangedEvent<boolean>;
     // Fired when the user selects a line (See gr-diff).
-    'line-selected': CustomEvent;
+    'line-selected': CustomEvent<LineSelectedEventDetail>;
     // Fired if being logged in is required.
     'show-auth-required': void;
   }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index 19186ec..b9074b4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -33,6 +33,7 @@
   DropdownItem,
   GrDropdownList,
 } from '../../shared/gr-dropdown-list/gr-dropdown-list';
+import {CommentAnchorTapEventDetail} from '../../shared/gr-comment/gr-comment';
 import {ChangeComments} from '../../diff/gr-comment-api/gr-comment-api';
 import {
   BasePatchSetNum,
@@ -48,7 +49,10 @@
 } from '../../../types/common';
 import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
 import {FileRange, ParsedChangeInfo} from '../../../types/types';
-import {FilesWebLinks} from '../gr-patch-range-select/gr-patch-range-select';
+import {
+  FilesWebLinks,
+  PatchRangeChangeEvent,
+} from '../gr-patch-range-select/gr-patch-range-select';
 import {GrDiffCursor} from '../../../embed/diff/gr-diff-cursor/gr-diff-cursor';
 import {CommentSide, DiffViewMode, Side} from '../../../constants/constants';
 import {GrApplyFixDialog} from '../gr-apply-fix-dialog/gr-apply-fix-dialog';
@@ -66,7 +70,7 @@
   ShortcutSection,
   shortcutsServiceToken,
 } from '../../../services/shortcuts/shortcuts-service';
-import {DisplayLine} from '../../../api/diff';
+import {DisplayLine, LineSelectedEventDetail} from '../../../api/diff';
 import {GrDownloadDialog} from '../../change/gr-download-dialog/gr-download-dialog';
 import {commentsModelToken} from '../../../models/comments/comments-model';
 import {changeModelToken} from '../../../models/change/change-model';
@@ -768,7 +772,7 @@
         .path=${this.path}
         .projectName=${this.change?.project}
         @is-blame-loaded-changed=${this.onIsBlameLoadedChanged}
-        @comment-anchor-tap=${this.onLineSelected}
+        @comment-anchor-tap=${this.onCommentAnchorTap}
         @line-selected=${this.onLineSelected}
         @diff-changed=${this.onDiffChanged}
         @edit-weblinks-changed=${this.onEditWeblinksChanged}
@@ -1443,7 +1447,7 @@
   }
 
   // Private but used in tests.
-  handlePatchChange(e: CustomEvent) {
+  handlePatchChange(e: PatchRangeChangeEvent) {
     if (!this.path) return;
     if (!this.patchNum) return;
 
@@ -1466,16 +1470,23 @@
   }
 
   // Private but used in tests.
-  onLineSelected(e: CustomEvent) {
-    // for on-comment-anchor-tap side can be PARENT/REVISIONS
-    // for on-line-selected side can be left/right
+  onCommentAnchorTap(e: CustomEvent<CommentAnchorTapEventDetail>) {
+    const lineNumber = e.detail.number;
+    if (!Number.isInteger(lineNumber)) return;
     this.updateUrlToDiffUrl(
-      e.detail.number,
-      e.detail.side === Side.LEFT || e.detail.side === CommentSide.PARENT
+      lineNumber as number,
+      e.detail.side === CommentSide.PARENT
     );
   }
 
   // Private but used in tests.
+  onLineSelected(e: CustomEvent<LineSelectedEventDetail>) {
+    const lineNumber = e.detail.number;
+    if (!Number.isInteger(lineNumber)) return;
+    this.updateUrlToDiffUrl(lineNumber as number, e.detail.side === Side.LEFT);
+  }
+
+  // Private but used in tests.
   computeDownloadDropdownLinks() {
     if (!this.change?.project) return [];
     if (!this.changeNum) return [];
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
index 325798c..58ad721 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
@@ -56,7 +56,7 @@
 }
 
 export interface PatchRangeChangeDetail {
-  patchNum?: PatchSetNum;
+  patchNum?: RevisionPatchSetNum;
   basePatchNum?: BasePatchSetNum;
 }
 
@@ -73,6 +73,12 @@
   }
 }
 
+declare global {
+  interface HTMLElementEventMap {
+    'patch-range-change': PatchRangeChangeEvent;
+  }
+}
+
 /**
  * Fired when the patch range changes
  *
@@ -463,7 +469,9 @@
       basePatchNum: this.basePatchNum,
     };
     const target = e.target;
-    const patchSetValue = convertToPatchSetNum(e.detail.value)!;
+    const patchSetValue = convertToPatchSetNum(
+      e.detail.value
+    ) as RevisionPatchSetNum;
     const latestPatchNum = computeLatestPatchNum(this.availablePatches);
     if (target === this.patchNumDropdown) {
       if (detail.patchNum === patchSetValue) return;
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
index c442aa6..594d9d9 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
@@ -6,8 +6,10 @@
 import '../../shared/gr-dropdown/gr-dropdown';
 import {GrEditConstants} from '../gr-edit-constants';
 import {sharedStyles} from '../../../styles/shared-styles';
+import {FileActionTapEventDetail} from '../../../types/events';
 import {LitElement, css, html} from 'lit';
 import {customElement, property} from 'lit/decorators.js';
+import {DropdownLink} from '../../shared/gr-dropdown/gr-dropdown';
 
 interface EditAction {
   label: string;
@@ -64,15 +66,18 @@
     >`;
   }
 
-  _handleActionTap(e: CustomEvent) {
+  _handleActionTap(e: CustomEvent<DropdownLink>) {
     e.preventDefault();
     e.stopPropagation();
-    this._dispatchFileAction(e.detail.id, this.filePath);
+    const actionId = e.detail.id;
+    if (!actionId) return;
+    if (!this.filePath) return;
+    this._dispatchFileAction(actionId, this.filePath);
   }
 
-  _dispatchFileAction(action: EditAction, path?: string) {
+  _dispatchFileAction(action: string, path: string) {
     this.dispatchEvent(
-      new CustomEvent('file-action-tap', {
+      new CustomEvent<FileActionTapEventDetail>('file-action-tap', {
         detail: {action, path},
         bubbles: true,
         composed: true,
@@ -80,7 +85,7 @@
     );
   }
 
-  _computeFileActions(actions: EditAction[]) {
+  _computeFileActions(actions: EditAction[]): DropdownLink[] {
     // TODO(kaspern): conditionally disable some actions based on file status.
     return actions.map(action => {
       return {
diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
index bc4e701..b34e1c3 100644
--- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
@@ -6,6 +6,7 @@
 import {AttributeHelperPluginApi} from '../../../api/attribute-helper';
 import {PluginApi} from '../../../api/plugin';
 import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
+import {ValueChangedEvent} from '../../../types/events';
 
 export class GrAttributeHelper implements AttributeHelperPluginApi {
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -51,7 +52,7 @@
   bind(name: string, callback: (value: any) => void) {
     this.reporting.trackApi(this.plugin, 'attribute', 'bind');
     const attributeChangedEventName = this._getChangedEventName(name);
-    const changedHandler = (e: CustomEvent) =>
+    const changedHandler = (e: ValueChangedEvent) =>
       this._reportValue(callback, e.detail.value);
     const unbind = () =>
       this.element.removeEventListener(
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
index 661ffa0..10cefd9 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.ts
@@ -30,7 +30,7 @@
   value?: string;
 }
 
-export interface ItemSelectedEvent {
+export interface ItemSelectedEventDetail {
   trigger: string;
   selected: HTMLElement | null;
 }
@@ -275,7 +275,7 @@
   handleTab() {
     if (this.isSuggestionListInteractible()) {
       this.dispatchEvent(
-        new CustomEvent<ItemSelectedEvent>('item-selected', {
+        new CustomEvent<ItemSelectedEventDetail>('item-selected', {
           detail: {
             trigger: 'tab',
             selected: this.cursor.target,
@@ -291,7 +291,7 @@
   handleEnter() {
     if (this.isSuggestionListInteractible()) {
       this.dispatchEvent(
-        new CustomEvent<ItemSelectedEvent>('item-selected', {
+        new CustomEvent<ItemSelectedEventDetail>('item-selected', {
           detail: {
             trigger: 'enter',
             selected: this.cursor.target,
@@ -319,7 +319,7 @@
       selected = selected.parentElement!;
     }
     this.dispatchEvent(
-      new CustomEvent<ItemSelectedEvent>('item-selected', {
+      new CustomEvent<ItemSelectedEventDetail>('item-selected', {
         detail: {
           trigger: 'click',
           selected,
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
index db0d236..023513b 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
@@ -11,6 +11,7 @@
   AutocompleteQueryStatus,
   AutocompleteQueryStatusType,
   GrAutocompleteDropdown,
+  ItemSelectedEventDetail,
 } from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
 import {fire, fireEvent} from '../../../utils/event-util';
 import {
@@ -305,7 +306,7 @@
         class=${this.computeClass()}
         ?disabled=${this.disabled}
         .value=${this.text}
-        @value-changed=${(e: CustomEvent) => {
+        @value-changed=${(e: ValueChangedEvent) => {
           this.text = e.detail.value;
         }}
         .placeholder=${this.placeholder}
@@ -366,14 +367,16 @@
     this.text = '';
   }
 
-  private handleItemSelectEnter(e: CustomEvent | KeyboardEvent) {
+  private handleItemSelectEnter(
+    e: CustomEvent<ItemSelectedEventDetail> | KeyboardEvent
+  ) {
     this.handleInputCommit();
     e.stopPropagation();
     e.preventDefault();
     this.focusWithoutDisplayingSuggestions();
   }
 
-  handleItemSelect(e: CustomEvent) {
+  handleItemSelect(e: CustomEvent<ItemSelectedEventDetail>) {
     if (e.detail.trigger === 'click') {
       this.selected = e.detail.selected;
       this._commit();
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
index 3a8946a..c78a513 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
@@ -242,7 +242,8 @@
         allowOutsideScroll
         .horizontalAlign=${this.horizontalAlign}
         @click=${() => this.close()}
-        @opened-changed=${(e: CustomEvent) => (this.opened = e.detail.value)}
+        @opened-changed=${(e: ValueChangedEvent<boolean>) =>
+          (this.opened = e.detail.value)}
       >
         ${this.renderDropdownContent()}
       </iron-dropdown>`;
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts
index bf8209b..8ca8820 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.ts
@@ -21,6 +21,7 @@
 import {PaperInputElement} from '@polymer/paper-input/paper-input';
 import {IronInputElement} from '@polymer/iron-input';
 import {ShortcutController} from '../../lit/shortcut-controller';
+import {ValueChangedEvent} from '../../../types/events';
 
 const AWAIT_MAX_ITERS = 10;
 const AWAIT_STEP = 5;
@@ -207,7 +208,7 @@
         .text=${this.inputText}
         .query=${this.query}
         @cancel=${this.cancel}
-        @text-changed=${(e: CustomEvent) => {
+        @text-changed=${(e: ValueChangedEvent) => {
           this.inputText = e.detail.value;
         }}
       >
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
index 20a6463..782c055 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
@@ -12,7 +12,7 @@
 import {
   GrAutocompleteDropdown,
   Item,
-  ItemSelectedEvent,
+  ItemSelectedEventDetail,
 } from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
 import {Key} from '../../../utils/dom-util';
 import {ValueChangedEvent} from '../../../types/events';
@@ -67,7 +67,7 @@
 
 declare global {
   interface HTMLElementEventMap {
-    'item-selected': CustomEvent<ItemSelectedEvent>;
+    'item-selected': CustomEvent<ItemSelectedEventDetail>;
   }
 }
 
@@ -375,7 +375,7 @@
   }
 
   // private but used in test
-  handleDropdownItemSelect(e: CustomEvent<ItemSelectedEvent>) {
+  handleDropdownItemSelect(e: CustomEvent<ItemSelectedEventDetail>) {
     if (e.detail.selected?.dataset['value']) {
       this.setValue(e.detail.selected?.dataset['value']);
     }
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
index 711866a..154ea0a 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
@@ -6,7 +6,7 @@
 import '../../../test/common-test-setup';
 import './gr-textarea';
 import {GrTextarea} from './gr-textarea';
-import {ItemSelectedEvent} from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
+import {ItemSelectedEventDetail} from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
 import {pressKey, stubRestApi, waitUntil} from '../../../test/test-utils';
 import {fixture, html, assert} from '@open-wc/testing';
 import {createAccountWithEmail} from '../../../test/test-data-generators';
@@ -482,7 +482,7 @@
     element.specialCharIndex = 10;
     await element.updateComplete;
     const selectedItem = {dataset: {value: '😂'}} as unknown as HTMLElement;
-    const event = new CustomEvent<ItemSelectedEvent>('item-selected', {
+    const event = new CustomEvent<ItemSelectedEventDetail>('item-selected', {
       detail: {trigger: 'click', selected: selectedItem},
     });
     element.handleDropdownItemSelect(event);
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts b/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts
index 14bb17e..cc16de5 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts
@@ -8,7 +8,9 @@
 import {
   DiffViewMode,
   GrDiffCursor as GrDiffCursorApi,
+  LineNumber,
   LineNumberEventDetail,
+  LineSelectedEventDetail,
 } from '../../../api/diff';
 import {ScrollMode, Side} from '../../../constants/constants';
 import {toggleClass} from '../../../utils/dom-util';
@@ -237,7 +239,7 @@
   }
 
   moveToLineNumber(
-    number: number,
+    number: LineNumber,
     side: Side,
     path?: string,
     intentionalMove?: boolean
@@ -352,13 +354,10 @@
     this.preventAutoScrollOnManualScroll = false;
   };
 
-  private _boundHandleDiffLineSelected = (event: Event) => {
-    const customEvent = event as CustomEvent;
-    this.moveToLineNumber(
-      customEvent.detail.number,
-      customEvent.detail.side,
-      customEvent.detail.path
-    );
+  private _boundHandleDiffLineSelected = (
+    e: CustomEvent<LineSelectedEventDetail>
+  ) => {
+    this.moveToLineNumber(e.detail.number, e.detail.side, e.detail.path);
   };
 
   createCommentInPlace() {
@@ -580,7 +579,7 @@
   }
 
   _findRowByNumberAndFile(
-    targetNumber: number,
+    targetNumber: LineNumber,
     side: Side,
     path?: string
   ): HTMLElement | undefined {
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts
index 0714645..a2a0d0a 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts
@@ -459,7 +459,7 @@
 
   private fireCreateRangeComment(side: Side, range: CommentRange) {
     this.diffTable?.dispatchEvent(
-      new CustomEvent('create-range-comment', {
+      new CustomEvent<CreateRangeCommentEventDetail>('create-range-comment', {
         detail: {side, range},
         composed: true,
         bubbles: true,
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
index 8a92bcc..3a3f0f7 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
@@ -32,6 +32,7 @@
   Point,
   Rect,
 } from './util';
+import {ValueChangedEvent} from '../../../types/events';
 
 const DRAG_DEAD_ZONE_PIXELS = 5;
 
@@ -760,7 +761,7 @@
     );
   }
 
-  zoomControlChanged(event: CustomEvent) {
+  zoomControlChanged(event: ValueChangedEvent<'fit' | number>) {
     const value = event.detail.value;
     if (!value) return;
     if (value === 'fit') {
@@ -769,7 +770,7 @@
         createEvent({type: 'zoom-level-changed', scale: 'fit'})
       );
     }
-    if (value > 0) {
+    if (typeof value === 'number' && value > 0) {
       this.scaledSelected = false;
       this.scale = value;
       this.dispatchEvent(
@@ -901,10 +902,10 @@
     event.preventDefault();
   }
 
-  onOverviewCenterUpdated(event: CustomEvent) {
+  onOverviewCenterUpdated(event: CustomEvent<Point>) {
     this.frameConstrainer.requestCenter({
-      x: event.detail.x as number,
-      y: event.detail.y as number,
+      x: event.detail.x,
+      y: event.detail.y,
     });
     this.updateFrames();
   }
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-overview-image.ts b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-overview-image.ts
index 1bc1447..b9354eb 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-overview-image.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-overview-image.ts
@@ -298,7 +298,7 @@
 
   private notifyNewCenter(center: Point) {
     this.dispatchEvent(
-      new CustomEvent('center-updated', {
+      new CustomEvent<Point>('center-updated', {
         detail: {...center},
         bubbles: true,
         composed: true,
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
index b9a01ce..c04549b 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
@@ -29,7 +29,10 @@
 } from './gr-diff-utils';
 import {BlameInfo, CommentRange, ImageInfo} from '../../../types/common';
 import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
-import {GrDiffHighlight} from '../gr-diff-highlight/gr-diff-highlight';
+import {
+  CreateRangeCommentEventDetail,
+  GrDiffHighlight,
+} from '../gr-diff-highlight/gr-diff-highlight';
 import {
   GrDiffBuilderElement,
   getLineNumberCellWidth,
@@ -51,6 +54,7 @@
   RenderPreferences,
   GrDiff as GrDiffApi,
   DisplayLine,
+  LineSelectedEventDetail,
 } from '../../../api/diff';
 import {isSafari, toggleClass} from '../../../utils/dom-util';
 import {assertIsDefined} from '../../../utils/common-util';
@@ -989,8 +993,10 @@
   constructor() {
     super();
     provide(this, diffModelToken, () => this.diffModel);
-    this.addEventListener('create-range-comment', (e: Event) =>
-      this.handleCreateRangeComment(e as CustomEvent)
+    this.addEventListener(
+      'create-range-comment',
+      (e: CustomEvent<CreateRangeCommentEventDetail>) =>
+        this.handleCreateRangeComment(e)
     );
     this.addEventListener('render-content', () => this.handleRenderContent());
     this.addEventListener('moved-link-clicked', (e: MovedLinkClickedEvent) => {
@@ -1344,7 +1350,7 @@
 
   private dispatchSelectedLine(number: LineNumber, side: Side) {
     this.dispatchEvent(
-      new CustomEvent('line-selected', {
+      new CustomEvent<LineSelectedEventDetail>('line-selected', {
         detail: {
           number,
           side,
@@ -1386,7 +1392,9 @@
     }
   }
 
-  private handleCreateRangeComment(e: CustomEvent) {
+  private handleCreateRangeComment(
+    e: CustomEvent<CreateRangeCommentEventDetail>
+  ) {
     const range = e.detail.range;
     const side = e.detail.side;
     this.createCommentForSelection(side, range);
diff --git a/polygerrit-ui/app/models/views/change.ts b/polygerrit-ui/app/models/views/change.ts
index 153777f..d2a0dd8 100644
--- a/polygerrit-ui/app/models/views/change.ts
+++ b/polygerrit-ui/app/models/views/change.ts
@@ -79,6 +79,7 @@
   /** These properties apply to the DIFF child view only. */
   diffView?: {
     path?: string;
+    // TODO: Use LineNumber as a type, i.e. accept FILE and LOST.
     lineNum?: number;
     leftSide?: boolean;
   };
diff --git a/polygerrit-ui/app/types/events.ts b/polygerrit-ui/app/types/events.ts
index e2612b0..d8a4d2c 100644
--- a/polygerrit-ui/app/types/events.ts
+++ b/polygerrit-ui/app/types/events.ts
@@ -3,11 +3,13 @@
  * Copyright 2020 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import {FixSuggestionInfo, PatchSetNum} from './common';
+import {AccountInfo, FixSuggestionInfo, PatchSetNum} from './common';
 import {ChangeMessage} from '../utils/comment-util';
 import {FetchRequest} from './types';
 import {LineNumberEventDetail, MovedLinkClickedEventDetail} from '../api/diff';
 import {Category, RunStatus} from '../api/checks';
+import {DropdownLink} from '../elements/shared/gr-dropdown/gr-dropdown';
+import {AutocompleteCommitEvent} from '../elements/shared/gr-autocomplete/gr-autocomplete';
 
 export enum EventType {
   BIND_VALUE_CHANGED = 'bind-value-changed',
@@ -41,7 +43,7 @@
 
 declare global {
   interface HTMLElementEventMap {
-    /* prettier-ignore */
+    'add-reviewer': AddReviewerEvent;
     'bind-value-changed': BindValueChangeEvent;
     /* prettier-ignore */
     'change': ChangeEvent;
@@ -49,7 +51,7 @@
     'changed': ChangedEvent;
     'change-message-deleted': ChangeMessageDeletedEvent;
     /* prettier-ignore */
-    'commit': CommitEvent;
+    'commit': AutocompleteCommitEvent;
     'dialog-change': DialogChangeEvent;
     /* prettier-ignore */
     'drop': DropEvent;
@@ -65,6 +67,7 @@
     'reply-to-comment': ReplyToCommentEvent;
     /* prettier-ignore */
     'reload': ReloadEvent;
+    'remove-reviewer': RemoveReviewerEvent;
     /* prettier-ignore */
     'reply': ReplyEvent;
     'show-alert': ShowAlertEvent;
@@ -90,6 +93,16 @@
   }
 }
 
+export interface AddReviewerEventDetail {
+  reviewer: AccountInfo;
+}
+export type AddReviewerEvent = CustomEvent<AddReviewerEventDetail>;
+
+export interface RemoveReviewerEventDetail {
+  reviewer: AccountInfo;
+}
+export type RemoveReviewerEvent = CustomEvent<RemoveReviewerEventDetail>;
+
 export interface BindValueChangeEventDetail {
   value: string | undefined;
 }
@@ -105,8 +118,6 @@
 export type ChangeMessageDeletedEvent =
   CustomEvent<ChangeMessageDeletedEventDetail>;
 
-export type CommitEvent = CustomEvent;
-
 // TODO(milutin) - remove once new gr-dialog will do it out of the box
 // This informs gr-app-element to remove footer, header from a11y tree
 export interface DialogChangeEventDetail {
@@ -123,6 +134,13 @@
 export type EditableContentSaveEvent =
   CustomEvent<EditableContentSaveEventDetail>;
 
+export interface FileActionTapEventDetail {
+  path: string;
+  action: string;
+}
+
+export type FileActionTapEvent = CustomEvent<FileActionTapEventDetail>;
+
 export interface RpcLogEventDetail {
   status: number | null;
   method: string;
@@ -231,7 +249,7 @@
 }
 export type SwitchTabEvent = CustomEvent<SwitchTabEventDetail>;
 
-export type TapItemEvent = CustomEvent;
+export type TapItemEvent = CustomEvent<DropdownLink>;
 
 export interface TitleChangeEventDetail {
   title: string;