feat(bulk-report-replies): add feature with non-functional UI

This commit adds a new "bulk report replies" feature along with its UI.
The underlying logic will be implemented soon.

Bug: twpowertools:192
Change-Id: I6a6a636c7186485154c32dbbec001493b3b0458f
diff --git a/src/common/options/optionsPrototype.ts b/src/common/options/optionsPrototype.ts
index a5f7a93..fa38b1b 100644
--- a/src/common/options/optionsPrototype.ts
+++ b/src/common/options/optionsPrototype.ts
@@ -158,6 +158,11 @@
     context: OptionContext.Experiments,
     killSwitchType: KillSwitchType.Experiment,
   },
+  bulkreportreplies: {
+    defaultValue: false,
+    context: OptionContext.Experiments,
+    killSwitchType: KillSwitchType.Experiment,
+  },
 
   // Internal options
   ccdarktheme_switch_status: {
@@ -170,6 +175,11 @@
     context: OptionContext.Internal,
     killSwitchType: KillSwitchType.Ignore,
   },
+  bulkreportreplies_switch_enabled: {
+    defaultValue: true,
+    context: OptionContext.Internal,
+    killSwitchType: KillSwitchType.Ignore,
+  },
 
   // Internal kill switches
   killswitch_xhrproxy: {
diff --git a/src/contentScripts/communityConsole/threadToolbar/components/index.js b/src/contentScripts/communityConsole/threadToolbar/components/index.js
index 8e1bcd9..ae28a0b 100644
--- a/src/contentScripts/communityConsole/threadToolbar/components/index.js
+++ b/src/contentScripts/communityConsole/threadToolbar/components/index.js
@@ -1,15 +1,31 @@
 import '@material/web/icon/icon.js';
+import '@material/web/iconbutton/icon-button.js';
+import '@material/web/menu/menu.js';
+import '@material/web/menu/menu-item.js';
 import '@material/web/switch/switch.js';
 import '../../../../common/components/FormField.js';
 
 import consoleCommonStyles from '!!raw-loader!../../../../static/css/common/console.css';
 import {msg} from '@lit/localize';
+import {Corner} from '@material/web/menu/menu.js';
 import {css, html, nothing, unsafeCSS} from 'lit';
+import {map} from 'lit/directives/map.js';
 import {createRef, ref} from 'lit/directives/ref.js';
 
 import {I18nLitElement} from '../../../../common/litI18nUtils.js';
 import {SHARED_MD3_STYLES} from '../../../../common/styles/md3.js';
-import {kEventFlattenThreadsUpdated} from '../constants.js';
+import {kEventOptionUpdated} from '../constants.js';
+
+const getOverflowMenuItems = (options) =>
+    [{
+      label: msg('Bulk report replies', {
+        'desc':
+            'Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.',
+      }),
+      isShown: options['bulkreportreplies'] === true,
+      option: 'bulkreportreplies_switch_enabled'
+    },
+].filter(item => item.isShown);
 
 export default class TwptThreadToolbarInject extends I18nLitElement {
   static properties = {
@@ -27,10 +43,18 @@
 
       .toolbar {
         display: flex;
+        flex-flow: row nowrap;
+        align-items: center;
+        justify-content: space-between;
+        row-gap: 0.5rem;
+        padding: 0.5rem 0.25rem;
+      }
+
+      .toolbar-start, .toolbar-end {
+        display: flex;
         flex-flow: row wrap;
         align-items: center;
         row-gap: 0.5rem;
-        padding: 0.5rem 0.25rem;
       }
 
       .badge-container {
@@ -46,6 +70,7 @@
   ];
 
   nestedViewRef = createRef();
+  overflowMenuRef = createRef();
 
   constructor() {
     super();
@@ -82,27 +107,75 @@
     `;
   }
 
+  renderOverflowMenu() {
+    const items = getOverflowMenuItems(this.options);
+
+    if (items.length === 0) return nothing;
+
+    return html`
+      <md-icon-button
+          id="thread-toolbar-overflow-menu-anchor"
+          @click="${this._toggleOverflowMenu}">
+        <md-icon>settings</md-icon>
+      </md-icon-button>
+      <md-menu ${ref(this.overflowMenuRef)}
+          anchor="thread-toolbar-overflow-menu-anchor"
+          anchor-corner="${Corner.END_END}"
+          menu-corner="${Corner.START_END}"
+          positioning="popover">
+        ${
+        map(items,
+            item => html`
+          <md-menu-item
+              @click="${
+                () => this._onOptionChanged(
+                    item.option, !this.options[item.option], false)}">
+            <md-icon slot="start">${
+                this.options[item.option] ? 'check' : ''}</md-icon>
+              <span>
+                ${item.label}
+              </span>
+          </md-menu-item>
+        `)}
+      </md-menu>
+    `;
+  }
+
   render() {
     // NOTE: Keep this in sync!
-    if (!this.options.flattenthreads) return nothing;
+    if (!this.options.flattenthreads && !this.options.bulkreportreplies)
+      return nothing;
 
     return html`
       <div class="toolbar">
-        ${this.renderBadge()}
-        ${this.renderFlattenRepliesSwitch()}
+        <div class="toolbar-start">
+          ${this.renderBadge()}
+          ${this.renderFlattenRepliesSwitch()}
+        </div>
+        <div class="toolbar-end">
+          ${this.renderOverflowMenu()}
+        </div>
       </div>
     `;
   }
 
   _flattenThreadsChanged() {
     const enabled = !this.nestedViewRef.value.selected;
-    const e = new CustomEvent(kEventFlattenThreadsUpdated, {
+    this._onOptionChanged('flattenthreads_switch_enabled', enabled, true);
+  }
+
+  _onOptionChanged(option, enabled, softRefreshView = false) {
+    const e = new CustomEvent(kEventOptionUpdated, {
       bubbles: true,
       composed: true,
-      detail: {enabled},
+      detail: {option, enabled, softRefreshView},
     });
     this.dispatchEvent(e);
   }
+
+  _toggleOverflowMenu() {
+    this.overflowMenuRef.value.open = !this.overflowMenuRef.value.open;
+  }
 }
 window.customElements.define(
     'twpt-thread-toolbar-inject', TwptThreadToolbarInject);
diff --git a/src/contentScripts/communityConsole/threadToolbar/constants.js b/src/contentScripts/communityConsole/threadToolbar/constants.js
index 756d880..14f8c24 100644
--- a/src/contentScripts/communityConsole/threadToolbar/constants.js
+++ b/src/contentScripts/communityConsole/threadToolbar/constants.js
@@ -1,5 +1,5 @@
-export const kEventFlattenThreadsUpdated =
-    'TWPTThreadToolbarFlattenThreadsUpdated';
+export const kEventOptionUpdated =
+    'TWPTThreadToolbarOptionUpdated';
 
 export const kRepliesSectionSelector =
     'ec-thread .scTailwindThreadThreadcontentreplies-section';
diff --git a/src/contentScripts/communityConsole/threadToolbar/threadToolbar.js b/src/contentScripts/communityConsole/threadToolbar/threadToolbar.js
index db11640..f13907e 100644
--- a/src/contentScripts/communityConsole/threadToolbar/threadToolbar.js
+++ b/src/contentScripts/communityConsole/threadToolbar/threadToolbar.js
@@ -27,17 +27,30 @@
   }
 
   getOptions() {
-    return getOptions(['flattenthreads', 'flattenthreads_switch_enabled']);
+    return getOptions([
+      'flattenthreads',
+      'flattenthreads_switch_enabled',
+      'bulkreportreplies',
+      'bulkreportreplies_switch_enabled',
+    ]);
   }
 
   inject(node, options) {
     const toolbar = document.createElement('twpt-thread-toolbar-inject');
     toolbar.setAttribute('options', JSON.stringify(options));
-    toolbar.addEventListener(consts.kEventFlattenThreadsUpdated, e => {
+    toolbar.addEventListener(consts.kEventOptionUpdated, e => {
+      const option = e.detail?.option;
       const enabled = e.detail?.enabled;
-      if (typeof enabled != 'boolean') return;
-      chrome.storage.sync.set({flattenthreads_switch_enabled: enabled}, _ => {
-        softRefreshView();
+      if (typeof option != 'string' || typeof enabled != 'boolean') return;
+      chrome.storage.sync.set({[option]: enabled}, _ => {
+        options = {
+          ...options,
+          [option]: enabled,
+        };
+        toolbar.setAttribute('options', JSON.stringify(options));
+        if (e.detail?.softRefreshView) {
+          softRefreshView();
+        }
       });
     });
     node.parentElement.insertBefore(toolbar, node);
diff --git a/src/entryPoints/communityConsole/contentScripts/main.ts b/src/entryPoints/communityConsole/contentScripts/main.ts
index 7eda953..008b1b6 100644
--- a/src/entryPoints/communityConsole/contentScripts/main.ts
+++ b/src/entryPoints/communityConsole/contentScripts/main.ts
@@ -53,6 +53,10 @@
 import RepositionExpandThreadStylesScript from '../../../features/repositionExpandThread/presentation/scripts/styles.script';
 import StickySidebarHeadersStylesScript from '../../../features/stickySidebarHeaders/presentation/scripts/styles.script';
 import IncreaseContrastStylesScript from '../../../features/increaseContrast/presentation/scripts/styles.script';
+import BulkReportRepliesMessageCardHandler from '../../../features/bulkReportReplies/presentation/nodeWatcherHandlers/messageCard.handler';
+import BulkReportRepliesHandleBodyClassScript from '../../../features/bulkReportReplies/presentation/scripts/handleBodyClass.script';
+import { IsBulkReportRepliesFeatureEnabledServiceAdapter } from '../../../features/bulkReportReplies/services/isFeatureEnabled.service';
+import BulkReportRepliesStylesScript from '../../../features/bulkReportReplies/presentation/scripts/styles.script';
 
 const scriptRunner = createScriptRunner();
 scriptRunner.run();
@@ -92,6 +96,10 @@
               'autoRefreshThreadListHide',
               new AutoRefreshThreadListHideHandler(autoRefresh),
             ],
+            [
+              'bulkReportRepliesMessageCard',
+              new BulkReportRepliesMessageCardHandler(optionsProvider),
+            ],
             ['ccDarkThemeEcApp', new CCDarkThemeEcAppHandler(optionsProvider)],
             [
               'ccDarkThemeReportDialog',
@@ -177,6 +185,11 @@
 
         // Individual feature scripts
         new AutoRefreshStylesScript(),
+        new BulkReportRepliesHandleBodyClassScript(
+          optionsProvider,
+          new IsBulkReportRepliesFeatureEnabledServiceAdapter(),
+        ),
+        new BulkReportRepliesStylesScript(),
         new CCExtraInfoInjectScript(),
         new CCExtraInfoStylesScript(),
         new EnhancedAnnouncementsDotStylesScript(),
diff --git a/src/features/bulkReportReplies/presentation/consts.ts b/src/features/bulkReportReplies/presentation/consts.ts
new file mode 100644
index 0000000..674647f
--- /dev/null
+++ b/src/features/bulkReportReplies/presentation/consts.ts
@@ -0,0 +1,2 @@
+export const bulkReportRepliesEnabledClassName =
+  'TWPT-bulkreportreplies-enabled';
diff --git a/src/features/bulkReportReplies/presentation/nodeWatcherHandlers/messageCard.handler.ts b/src/features/bulkReportReplies/presentation/nodeWatcherHandlers/messageCard.handler.ts
new file mode 100644
index 0000000..1084566
--- /dev/null
+++ b/src/features/bulkReportReplies/presentation/nodeWatcherHandlers/messageCard.handler.ts
@@ -0,0 +1,28 @@
+import CssSelectorNodeWatcherHandler from '../../../../infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter';
+import { NodeMutation } from '../../../../presentation/nodeWatcher/NodeWatcherHandler';
+import { OptionsProviderPort } from '../../../../services/options/OptionsProvider';
+import BulkReportControlsInjector from '../../ui/injectors/bulkReportControls.injector';
+
+/**
+ * Injects the bulk report reply controls.
+ */
+export default class BulkReportRepliesMessageCardHandler extends CssSelectorNodeWatcherHandler {
+  cssSelector =
+    ':is(.scTailwindThreadMessageMessagecardcontent:not(.scTailwindThreadMessageMessagecardpromoted), .scTailwindThreadMessageCommentcardcomment) sc-tailwind-thread-message-message-actions';
+
+  bulkReportControlsInjector = new BulkReportControlsInjector();
+
+  constructor(private optionsProvider: OptionsProviderPort) {
+    super();
+  }
+
+  async onMutatedNode(mutation: NodeMutation) {
+    if (!(mutation.node instanceof Element)) return;
+
+    const isFeatureEnabled =
+      await this.optionsProvider.isEnabled('bulkreportreplies');
+    if (!isFeatureEnabled) return;
+
+    this.bulkReportControlsInjector.inject(mutation.node);
+  }
+}
diff --git a/src/features/bulkReportReplies/presentation/scripts/handleBodyClass.script.ts b/src/features/bulkReportReplies/presentation/scripts/handleBodyClass.script.ts
new file mode 100644
index 0000000..4a56117
--- /dev/null
+++ b/src/features/bulkReportReplies/presentation/scripts/handleBodyClass.script.ts
@@ -0,0 +1,35 @@
+import Script from '../../../../common/architecture/scripts/Script';
+import { OptionsConfiguration } from '../../../../common/options/OptionsConfiguration';
+import { OptionsProviderPort } from '../../../../services/options/OptionsProvider';
+import { IsBulkReportRepliesFeatureEnabledService } from '../../services/isFeatureEnabled.service';
+import { bulkReportRepliesEnabledClassName } from '../consts';
+
+export default class BulkReportRepliesHandleBodyClassScript extends Script {
+  page: never;
+  environment: never;
+  runPhase: never;
+
+  constructor(
+    private optionsProvider: OptionsProviderPort,
+    private isFeatureEnabledService: IsBulkReportRepliesFeatureEnabledService,
+  ) {
+    super();
+  }
+
+  async execute() {
+    this.optionsProvider.addListener(
+      (_: OptionsConfiguration, currentConf: OptionsConfiguration) =>
+        this.updateBodyClass(currentConf),
+    );
+
+    this.updateBodyClass(await this.optionsProvider.getOptionsConfiguration());
+  }
+
+  private updateBodyClass(currentConf: OptionsConfiguration) {
+    if (this.isFeatureEnabledService.isEnabled(currentConf)) {
+      document.body?.classList.add(bulkReportRepliesEnabledClassName);
+    } else {
+      document.body?.classList.remove(bulkReportRepliesEnabledClassName);
+    }
+  }
+}
diff --git a/src/features/bulkReportReplies/presentation/scripts/styles.script.ts b/src/features/bulkReportReplies/presentation/scripts/styles.script.ts
new file mode 100644
index 0000000..fd358b4
--- /dev/null
+++ b/src/features/bulkReportReplies/presentation/scripts/styles.script.ts
@@ -0,0 +1,10 @@
+import StylesheetScript from '../../../../common/architecture/scripts/stylesheet/StylesheetScript';
+
+export default class BulkReportRepliesStylesScript extends StylesheetScript {
+  stylesheet = 'css/bulk_report_replies.css';
+  page: never;
+
+  async shouldBeInjected(): Promise<boolean> {
+    return true;
+  }
+}
diff --git a/src/features/bulkReportReplies/services/isFeatureEnabled.service.ts b/src/features/bulkReportReplies/services/isFeatureEnabled.service.ts
new file mode 100644
index 0000000..3c2455a
--- /dev/null
+++ b/src/features/bulkReportReplies/services/isFeatureEnabled.service.ts
@@ -0,0 +1,14 @@
+import { OptionsConfiguration } from '../../../common/options/OptionsConfiguration';
+
+export interface IsBulkReportRepliesFeatureEnabledService {
+  isEnabled(configuration: OptionsConfiguration): boolean;
+}
+
+export class IsBulkReportRepliesFeatureEnabledServiceAdapter {
+  isEnabled(configuration: OptionsConfiguration): boolean {
+    return (
+      configuration.isEnabled('bulkreportreplies') &&
+      configuration.isEnabled('bulkreportreplies_switch_enabled')
+    );
+  }
+}
diff --git a/src/features/bulkReportReplies/ui/components/BulkReportControls.ts b/src/features/bulkReportReplies/ui/components/BulkReportControls.ts
new file mode 100644
index 0000000..fee942e
--- /dev/null
+++ b/src/features/bulkReportReplies/ui/components/BulkReportControls.ts
@@ -0,0 +1,53 @@
+import '@material/web/chips/assist-chip.js';
+import '@material/web/chips/chip-set.js';
+import '@material/web/icon/icon.js';
+import { customElement, property } from 'lit/decorators.js';
+import { I18nLitElement } from '../../../../common/litI18nUtils';
+import { css, html } from 'lit';
+import { SHARED_MD3_STYLES } from '../../../../common/styles/md3';
+
+@customElement('bulk-report-controls')
+export default class BulkReportControls extends I18nLitElement {
+  @property({ type: String })
+  accessor messageId: string;
+
+  static styles = [
+    SHARED_MD3_STYLES,
+    css`
+      :host {
+        display: flex;
+        align-items: center;
+        margin-left: auto;
+      }
+
+      md-assist-chip {
+        --md-assist-chip-leading-icon-color: var(--md-sys-color-error);
+        --md-assist-chip-focus-leading-icon-color: var(--md-sys-color-error);
+        --md-assist-chip-hover-leading-icon-color: var(--md-sys-color-error);
+        --md-assist-chip-pressed-leading-icon-color: var(--md-sys-color-error);
+      }
+    `,
+  ];
+
+  // TODO(https://iavm.xyz/b/twpowertools/192): Make the buttons work.
+  render() {
+    return html`
+      <md-chip-set aria-label="Report actions">
+        <md-assist-chip>
+          <md-icon slot="icon">block</md-icon>
+          Mark as off-topic
+        </md-assist-chip>
+        <md-assist-chip>
+          <md-icon slot="icon">error</md-icon>
+          Mark as abuse
+        </md-assist-chip>
+      </md-chip-set>
+    `;
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'bulk-report-controls': BulkReportControls;
+  }
+}
diff --git a/src/features/bulkReportReplies/ui/components/index.ts b/src/features/bulkReportReplies/ui/components/index.ts
new file mode 100644
index 0000000..7203e3a
--- /dev/null
+++ b/src/features/bulkReportReplies/ui/components/index.ts
@@ -0,0 +1 @@
+export * from './BulkReportControls';
diff --git a/src/features/bulkReportReplies/ui/injectors/bulkReportControls.injector.ts b/src/features/bulkReportReplies/ui/injectors/bulkReportControls.injector.ts
new file mode 100644
index 0000000..18bdff7
--- /dev/null
+++ b/src/features/bulkReportReplies/ui/injectors/bulkReportControls.injector.ts
@@ -0,0 +1,7 @@
+export default class BulkReportControlsInjector {
+  inject(messageActions: Element) {
+    const controls = document.createElement('bulk-report-controls');
+    // TODO(https://iavm.xyz/b/twpowertools/192): Add message ID to the controls.
+    messageActions.append(controls);
+  }
+}
diff --git a/src/features/bulkReportReplies/ui/styles.css b/src/features/bulkReportReplies/ui/styles.css
new file mode 100644
index 0000000..7880703
--- /dev/null
+++ b/src/features/bulkReportReplies/ui/styles.css
@@ -0,0 +1,9 @@
+/**
+ * When the option is disabled or the feature is temporarily disabled via the
+ * toolbar, we hide bulk-report-controls.
+ */
+body:not(.TWPT-bulkreportreplies-enabled) {
+  bulk-report-controls {
+    display: none;
+  }
+}
diff --git a/src/injections/litComponentsInject.js b/src/injections/litComponentsInject.js
index 7143bd5..77813b5 100644
--- a/src/injections/litComponentsInject.js
+++ b/src/injections/litComponentsInject.js
@@ -6,6 +6,7 @@
 import '../contentScripts/communityConsole/threadToolbar/components/index.js';
 import '../features/flattenThreads/core/components/index.js';
 import '../contentScripts/communityConsole/updateHandler/banner/components/index.js';
+import '../features/bulkReportReplies/ui/components/index';
 
 import {injectStylesheet} from '../common/contentScriptsUtils';
 
diff --git a/src/lit-locales/source/ar.xlf b/src/lit-locales/source/ar.xlf
index 3325f80..dac395d 100644
--- a/src/lit-locales/source/ar.xlf
+++ b/src/lit-locales/source/ar.xlf
@@ -41,6 +41,10 @@
         <target state="translated">إعادة التّحميل</target>
         <note from="lit-localize">Button which reloads the current page.</note>
       </trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+  <source>Bulk report replies</source>
+  <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
     </body>
   </file>
 </xliff>
diff --git a/src/lit-locales/source/ca.xlf b/src/lit-locales/source/ca.xlf
index 518dba7..b1f8e20 100644
--- a/src/lit-locales/source/ca.xlf
+++ b/src/lit-locales/source/ca.xlf
@@ -41,6 +41,10 @@
         <target state="translated">Recarrega</target>
         <note from="lit-localize">Button which reloads the current page.</note>
       </trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+  <source>Bulk report replies</source>
+  <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
     </body>
   </file>
 </xliff>
diff --git a/src/lit-locales/source/de.xlf b/src/lit-locales/source/de.xlf
index c51a337..dd32051 100644
--- a/src/lit-locales/source/de.xlf
+++ b/src/lit-locales/source/de.xlf
@@ -41,6 +41,10 @@
         <target state="translated">Neu laden</target>
         <note from="lit-localize">Button which reloads the current page.</note>
       </trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+  <source>Bulk report replies</source>
+  <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
     </body>
   </file>
 </xliff>
diff --git a/src/lit-locales/source/es.xlf b/src/lit-locales/source/es.xlf
index 8b92079..2bc52b5 100644
--- a/src/lit-locales/source/es.xlf
+++ b/src/lit-locales/source/es.xlf
@@ -41,6 +41,10 @@
         <target state="translated">Recargar</target>
         <note from="lit-localize">Button which reloads the current page.</note>
       </trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+  <source>Bulk report replies</source>
+  <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
     </body>
   </file>
 </xliff>
diff --git a/src/lit-locales/source/fr.xlf b/src/lit-locales/source/fr.xlf
index 17819d7..02b1c83 100644
--- a/src/lit-locales/source/fr.xlf
+++ b/src/lit-locales/source/fr.xlf
@@ -41,6 +41,10 @@
         <target state="translated">Actualiser</target>
         <note from="lit-localize">Button which reloads the current page.</note>
       </trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+  <source>Bulk report replies</source>
+  <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
     </body>
   </file>
 </xliff>
diff --git a/src/lit-locales/source/id.xlf b/src/lit-locales/source/id.xlf
index c706ab1..22d1a3b 100644
--- a/src/lit-locales/source/id.xlf
+++ b/src/lit-locales/source/id.xlf
@@ -33,6 +33,10 @@
   <source>Reload</source>
   <note from="lit-localize">Button which reloads the current page.</note>
 </trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+  <source>Bulk report replies</source>
+  <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
 </body>
 </file>
 </xliff>
diff --git a/src/lit-locales/source/it.xlf b/src/lit-locales/source/it.xlf
index a25dc54..de573c6 100644
--- a/src/lit-locales/source/it.xlf
+++ b/src/lit-locales/source/it.xlf
@@ -35,6 +35,10 @@
         <source>Reload</source>
         <note from="lit-localize">Button which reloads the current page.</note>
       </trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+  <source>Bulk report replies</source>
+  <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
     </body>
   </file>
 </xliff>
diff --git a/src/lit-locales/source/ja.xlf b/src/lit-locales/source/ja.xlf
index ea15fdd..4e7b21a 100644
--- a/src/lit-locales/source/ja.xlf
+++ b/src/lit-locales/source/ja.xlf
@@ -41,6 +41,10 @@
         <target state="translated">再読み込み</target>
         <note from="lit-localize">Button which reloads the current page.</note>
       </trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+  <source>Bulk report replies</source>
+  <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
     </body>
   </file>
 </xliff>
diff --git a/src/lit-locales/source/ko.xlf b/src/lit-locales/source/ko.xlf
index d82bd54..b111b65 100644
--- a/src/lit-locales/source/ko.xlf
+++ b/src/lit-locales/source/ko.xlf
@@ -41,6 +41,10 @@
         <target state="translated">새로고침</target>
         <note from="lit-localize">Button which reloads the current page.</note>
       </trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+  <source>Bulk report replies</source>
+  <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
     </body>
   </file>
 </xliff>
diff --git a/src/lit-locales/source/pl.xlf b/src/lit-locales/source/pl.xlf
index 85ff3ea..4c08fa9 100644
--- a/src/lit-locales/source/pl.xlf
+++ b/src/lit-locales/source/pl.xlf
@@ -33,6 +33,10 @@
   <source>Reload</source>
   <note from="lit-localize">Button which reloads the current page.</note>
 </trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+  <source>Bulk report replies</source>
+  <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
 </body>
 </file>
 </xliff>
diff --git a/src/lit-locales/source/pt_BR.xlf b/src/lit-locales/source/pt_BR.xlf
index a69913f..ece9aad 100644
--- a/src/lit-locales/source/pt_BR.xlf
+++ b/src/lit-locales/source/pt_BR.xlf
@@ -33,6 +33,10 @@
   <source>Reload</source>
   <note from="lit-localize">Button which reloads the current page.</note>
 </trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+  <source>Bulk report replies</source>
+  <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
 </body>
 </file>
 </xliff>
diff --git a/src/lit-locales/source/ru.xlf b/src/lit-locales/source/ru.xlf
index 20b92e2..db843df 100644
--- a/src/lit-locales/source/ru.xlf
+++ b/src/lit-locales/source/ru.xlf
@@ -41,6 +41,10 @@
         <target state="translated">Перезагрузить</target>
         <note from="lit-localize">Button which reloads the current page.</note>
       </trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+  <source>Bulk report replies</source>
+  <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
     </body>
   </file>
 </xliff>
diff --git a/src/lit-locales/source/th.xlf b/src/lit-locales/source/th.xlf
index 2db0fdf..0ccee42 100644
--- a/src/lit-locales/source/th.xlf
+++ b/src/lit-locales/source/th.xlf
@@ -33,6 +33,10 @@
   <source>Reload</source>
   <note from="lit-localize">Button which reloads the current page.</note>
 </trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+  <source>Bulk report replies</source>
+  <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
 </body>
 </file>
 </xliff>
diff --git a/src/lit-locales/source/tr.xlf b/src/lit-locales/source/tr.xlf
index 63b2570..4149755 100644
--- a/src/lit-locales/source/tr.xlf
+++ b/src/lit-locales/source/tr.xlf
@@ -33,6 +33,10 @@
   <source>Reload</source>
   <note from="lit-localize">Button which reloads the current page.</note>
 </trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+  <source>Bulk report replies</source>
+  <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
 </body>
 </file>
 </xliff>
diff --git a/src/lit-locales/source/vi.xlf b/src/lit-locales/source/vi.xlf
index b82dae0..dc42ad3 100644
--- a/src/lit-locales/source/vi.xlf
+++ b/src/lit-locales/source/vi.xlf
@@ -33,6 +33,10 @@
   <source>Reload</source>
   <note from="lit-localize">Button which reloads the current page.</note>
 </trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+  <source>Bulk report replies</source>
+  <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
 </body>
 </file>
 </xliff>
diff --git a/src/static/_locales/en/messages.json b/src/static/_locales/en/messages.json
index 4c655f7..d4f2571 100644
--- a/src/static/_locales/en/messages.json
+++ b/src/static/_locales/en/messages.json
@@ -183,6 +183,10 @@
     "message": "Tentative fix for CC slowness and high memory usage (<a href='https://s.iavm.xyz/cc-running-slow-pekb'><code>pekb/269560789</code></a>).",
     "description": "Feature checkbox in the options page"
   },
+  "options_bulkreportreplies": {
+    "message": "Enable the ability to bulk report replies.",
+    "description": "Feature checkbox in the options page"
+  },
   "options_save": {
     "message": "Save",
     "description": "Button in the options page to save the settings"
diff --git a/src/static/css/common/console.css b/src/static/css/common/console.css
index 68724b4..805e1e2 100644
--- a/src/static/css/common/console.css
+++ b/src/static/css/common/console.css
@@ -169,7 +169,7 @@
  * that case, only users who have enabled one of the specified features will
  * experience the breakage.
  **/
-body.TWPT-flattenthreads-enabled {
+body:is(.TWPT-flattenthreads-enabled, .TWPT-bulkreportreplies-enabled) {
   ec-thread sc-tailwind-thread-message-message-list sc-tailwind-thread-message-message-actions {
     display: flex;
     flex-direction: row;
diff --git a/src/static/options/experiments.html b/src/static/options/experiments.html
index 9a21fdf..7eed0fb 100644
--- a/src/static/options/experiments.html
+++ b/src/static/options/experiments.html
@@ -15,6 +15,7 @@
         <div id="optional-permissions-warning" hidden data-i18n="optionalpermissionswarning_header"></div>
         <div class="option"><input type="checkbox" id="workflows"> <label for="workflows" data-i18n="workflows"></label> <button id="manage-workflows" data-i18n="workflows_manage"></button></div>
         <div class="option"><input type="checkbox" id="extrainfo"> <label for="extrainfo" data-i18n="extrainfo"></label></div>
+        <div class="option"><input type="checkbox" id="bulkreportreplies"> <label for="bulkreportreplies" data-i18n="bulkreportreplies"></label></div>
         <div class="actions"><button id="save" data-i18n="save"></button></div>
       </form>
       <div id="save-indicator"></div>