FastTrMail

Translate Fastmail message views inline with built-in web translators.

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey to install this script.

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         FastTrMail
// @namespace    https://github.com/dylanwang/FastTrMail-Tampermonkey
// @version      0.1.1
// @author       slippindylan
// @license      MIT
// @description  Translate Fastmail message views inline with built-in web translators.
// @icon         https://raw.githubusercontent.com/SlippinDylan/FastTrMail-Tampermonkey/main/assets/app.png
// @match        https://app.fastmail.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @connect      edge.microsoft.com
// @connect      api.cognitive.microsofttranslator.com
// @connect      translate.googleapis.com
// ==/UserScript==
(() => {
  var __getOwnPropNames = Object.getOwnPropertyNames;
  var __commonJS = (cb, mod) => function __require() {
    return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
  };

  // src/config/defaults.js
  var require_defaults = __commonJS({
    "src/config/defaults.js"(exports, module) {
      module.exports = {
        DEFAULT_SETTINGS: Object.freeze({
          targetLanguage: "zh-CN",
          preferredProvider: "edge-web"
        }),
        DOM_CONSTANTS: Object.freeze({
          BUTTON_CLASS: "fmt-translate-button",
          BUTTON_ACTIVE_CLASS: "fmt-translate-button-active",
          INLINE_TRANSLATION_CLASS: "fmt-inline-translation",
          TITLE_TRANSLATION_CLASS: "fmt-title-translation",
          MESSAGE_STATUS_CLASS: "fmt-message-status",
          SEGMENT_ANCHOR_CLASS: "fmt-segment-anchor",
          TRANSLATION_WRAPPER_CLASS: "fmt-translation-wrapper",
          MESSAGE_INSTANCE_ATTRIBUTE: "data-fmt-message-instance-id",
          SEGMENT_ATTRIBUTE: "data-fmt-segment-id",
          MODAL_OVERLAY_CLASS: "fmt-settings-overlay",
          MODAL_CLASS: "fmt-settings-modal",
          STYLES_ATTRIBUTE: "data-fasttrmail-styles"
        })
      };
    }
  });

  // src/config/languages.js
  var require_languages = __commonJS({
    "src/config/languages.js"(exports, module) {
      function freezeLanguageDefinition(definition) {
        return Object.freeze({ ...definition });
      }
      module.exports = {
        LANGUAGE_DEFINITIONS: Object.freeze([
          freezeLanguageDefinition({ id: "zh-CN", label: "简体中文", microsoft: "zh-Hans", google: "zh-CN" }),
          freezeLanguageDefinition({ id: "zh-TW", label: "繁體中文", microsoft: "zh-Hant", google: "zh-TW" }),
          freezeLanguageDefinition({ id: "en", label: "English", microsoft: "en", google: "en" }),
          freezeLanguageDefinition({ id: "ja", label: "日本語", microsoft: "ja", google: "ja" })
        ])
      };
    }
  });

  // src/config/providers.js
  var require_providers = __commonJS({
    "src/config/providers.js"(exports, module) {
      function freezeProviderDefinition(definition) {
        return Object.freeze({ ...definition });
      }
      module.exports = {
        PROVIDER_DEFINITIONS: Object.freeze([
          freezeProviderDefinition({ id: "edge-web" }),
          freezeProviderDefinition({ id: "google-web" })
        ])
      };
    }
  });

  // src/core/runtime-state.js
  var require_runtime_state = __commonJS({
    "src/core/runtime-state.js"(exports, module) {
      function createTitleState() {
        return {
          sourceText: "",
          translatedText: "",
          status: "idle",
          error: "",
          requestId: 0,
          settingsRevision: 0
        };
      }
      function createRuntimeState2({ getLocationKey }) {
        const state = {
          threadStates: /* @__PURE__ */ new WeakMap(),
          activeThreadStates: /* @__PURE__ */ new Set(),
          observerMuteDepth: 0,
          observerControl: null,
          documentRefreshScheduled: false,
          observedThreadRoots: /* @__PURE__ */ new Set(),
          observedThreadFlushTimer: 0,
          messageInstanceCounter: 0,
          segmentCounter: 0,
          threadStateCounter: 0,
          currentLocationKey: getLocationKey(),
          documentGeneration: 0,
          settingsRevision: 0
        };
        function nextSegmentId() {
          const segmentId = `fmt-segment-${state.segmentCounter}`;
          state.segmentCounter += 1;
          return segmentId;
        }
        function nextMessageInstanceId() {
          const messageInstanceId = `fmt-message-${state.messageInstanceCounter}`;
          state.messageInstanceCounter += 1;
          return messageInstanceId;
        }
        function nextThreadStateKey() {
          const threadStateKey = `fmt-thread-${state.threadStateCounter}`;
          state.threadStateCounter += 1;
          return threadStateKey;
        }
        function withObserverMuted(task) {
          const shouldSuspendObserver = state.observerMuteDepth === 0;
          state.observerMuteDepth += 1;
          if (shouldSuspendObserver) {
            state.observerControl?.suspend?.();
          }
          try {
            return task();
          } finally {
            state.observerMuteDepth -= 1;
            if (state.observerMuteDepth === 0) {
              state.observerControl?.resume?.();
            }
          }
        }
        function setObserverControl(observerControl) {
          state.observerControl = observerControl || null;
          if (!state.observerControl) {
            return;
          }
          if (state.observerMuteDepth > 0) {
            state.observerControl.suspend?.();
            return;
          }
          state.observerControl.resume?.();
        }
        function createThreadState() {
          return {
            key: nextThreadStateKey(),
            active: false,
            cancelled: false,
            generation: state.documentGeneration,
            runId: 0,
            root: null,
            messages: /* @__PURE__ */ new Map(),
            refreshTimer: 0,
            processing: false,
            pendingRefresh: false,
            pendingRefreshDelayMs: 0,
            pendingRequestIds: /* @__PURE__ */ new Set(),
            title: createTitleState()
          };
        }
        function createMessageState({ key, identity, instanceId }) {
          return {
            key,
            identity,
            instanceId,
            status: "idle",
            segmentSignature: "",
            translatedSegments: null,
            requestSerial: 0,
            error: "",
            settingsRevision: 0
          };
        }
        function clearRefreshTimer(threadState) {
          if (!threadState?.refreshTimer) {
            return;
          }
          globalThis.clearTimeout(threadState.refreshTimer);
          threadState.refreshTimer = 0;
        }
        function activateThreadState(threadState) {
          if (!threadState) {
            return;
          }
          threadState.active = true;
          threadState.cancelled = false;
          threadState.generation = state.documentGeneration;
          state.activeThreadStates.add(threadState);
        }
        function deactivateThreadState(threadState) {
          if (!threadState) {
            return;
          }
          clearRefreshTimer(threadState);
          threadState.active = false;
          threadState.cancelled = true;
          threadState.processing = false;
          threadState.pendingRefresh = false;
          threadState.pendingRefreshDelayMs = 0;
          threadState.runId += 1;
          state.activeThreadStates.delete(threadState);
          if (threadState.title.status === "translating") {
            threadState.title.status = threadState.title.translatedText ? "done" : "idle";
            threadState.title.error = "";
          }
          for (const messageState of threadState.messages.values()) {
            if (messageState.status === "translating") {
              messageState.status = Array.isArray(messageState.translatedSegments) ? "translated" : "idle";
              messageState.error = "";
            }
          }
        }
        function isThreadStateCurrent(threadState) {
          return Boolean(threadState) && threadState.generation === state.documentGeneration;
        }
        function trackThreadRequest(threadState, requestId) {
          if (threadState && requestId) {
            threadState.pendingRequestIds.add(requestId);
          }
        }
        function releaseThreadRequest(threadState, requestId) {
          if (threadState && requestId) {
            threadState.pendingRequestIds.delete(requestId);
          }
        }
        function beginThreadRun(threadState) {
          activateThreadState(threadState);
          threadState.runId += 1;
          return {
            threadKey: threadState.key,
            generation: threadState.generation,
            runId: threadState.runId
          };
        }
        function isRunCurrent(threadState, runToken) {
          if (!threadState || !runToken) {
            return false;
          }
          return threadState.active === true && threadState.cancelled !== true && threadState.generation === runToken.generation && threadState.runId === runToken.runId && state.currentLocationKey === getLocationKey();
        }
        function hasLocationChanged() {
          return state.currentLocationKey !== getLocationKey();
        }
        function syncCurrentLocationKey() {
          state.currentLocationKey = getLocationKey();
          return state.currentLocationKey;
        }
        function advanceDocumentGeneration() {
          syncCurrentLocationKey();
          state.documentGeneration += 1;
          return state.documentGeneration;
        }
        function advanceSettingsRevision() {
          state.settingsRevision += 1;
          return state.settingsRevision;
        }
        return {
          state,
          nextSegmentId,
          nextMessageInstanceId,
          nextThreadStateKey,
          withObserverMuted,
          setObserverControl,
          createTitleState,
          createThreadState,
          createMessageState,
          clearRefreshTimer,
          activateThreadState,
          deactivateThreadState,
          isThreadStateCurrent,
          trackThreadRequest,
          releaseThreadRequest,
          beginThreadRun,
          isRunCurrent,
          hasLocationChanged,
          syncCurrentLocationKey,
          advanceDocumentGeneration,
          advanceSettingsRevision
        };
      }
      module.exports = { createRuntimeState: createRuntimeState2 };
    }
  });

  // src/diagnostics/diagnostics.js
  var require_diagnostics = __commonJS({
    "src/diagnostics/diagnostics.js"(exports, module) {
      var MAX_ENTRY_COUNT = 200;
      var MAX_STRING_LENGTH = 160;
      var CONSOLE_SUPPRESSED_EVENTS = /* @__PURE__ */ new Set([
        "thread.refresh.start",
        "thread.refresh.collected"
      ]);
      function createNoopDiagnostics2() {
        return {
          isEnabled() {
            return false;
          },
          setEnabled() {
          },
          record() {
          },
          recordError() {
          },
          getEntries() {
            return [];
          }
        };
      }
      function createDiagnostics2({
        enabled = false,
        maxEntries = MAX_ENTRY_COUNT,
        now = Date.now,
        console: console2 = globalThis.console
      } = {}) {
        let isEnabled = Boolean(enabled);
        let sequence = 0;
        const entries = [];
        function pushEntry(entry) {
          entries.push(entry);
          if (entries.length > maxEntries) {
            entries.shift();
          }
        }
        function trimString(value) {
          if (typeof value !== "string" || value.length <= MAX_STRING_LENGTH) {
            return value;
          }
          return `${value.slice(0, MAX_STRING_LENGTH)}…`;
        }
        function sanitizeValue(value) {
          if (Array.isArray(value)) {
            return value.map((item) => sanitizeValue(item));
          }
          if (!value || typeof value !== "object") {
            return trimString(value);
          }
          const output = {};
          for (const [key, entryValue] of Object.entries(value)) {
            output[key] = sanitizeValue(entryValue);
          }
          return output;
        }
        function summarizeError(error) {
          return sanitizeValue({
            code: typeof error?.code === "string" ? error.code : "",
            message: typeof error?.message === "string" ? error.message : String(error || ""),
            name: typeof error?.name === "string" ? error.name : "",
            status: typeof error?.status === "number" ? error.status : void 0,
            statusText: typeof error?.statusText === "string" ? error.statusText : void 0,
            metadata: error?.metadata && typeof error.metadata === "object" ? error.metadata : void 0
          });
        }
        function record(event, payload = {}) {
          if (!isEnabled) {
            return null;
          }
          return emit(event, payload);
        }
        function emit(event, payload = {}) {
          const entry = {
            sequence: sequence + 1,
            timestamp: now(),
            event,
            ...sanitizeValue(payload)
          };
          sequence += 1;
          pushEntry(entry);
          if (!CONSOLE_SUPPRESSED_EVENTS.has(event)) {
            console2?.info?.(`[FastTrMail][diagnostics][${event}]`, entry);
          }
          return entry;
        }
        function recordError(event, error, payload = {}) {
          return record(event, {
            ...payload,
            error: summarizeError(error)
          });
        }
        return {
          isEnabled() {
            return isEnabled;
          },
          setEnabled(nextValue) {
            const nextEnabled = Boolean(nextValue);
            if (nextEnabled === isEnabled) {
              return nextEnabled;
            }
            isEnabled = nextEnabled;
            emit("diagnostics.state.changed", {
              enabled: nextEnabled
            });
            return nextEnabled;
          },
          record,
          recordError,
          getEntries() {
            return entries.slice();
          }
        };
      }
      module.exports = {
        createDiagnostics: createDiagnostics2,
        createNoopDiagnostics: createNoopDiagnostics2
      };
    }
  });

  // src/core/app.js
  var require_app = __commonJS({
    "src/core/app.js"(exports, module) {
      var { createNoopDiagnostics: createNoopDiagnostics2 } = require_diagnostics();
      function createApp2({
        defaults,
        document,
        diagnostics = createNoopDiagnostics2(),
        i18n,
        policies,
        renderer,
        requestRegistry,
        runtimeState,
        segmenter,
        settingsModal,
        settingsStore,
        threadDom,
        translateService,
        window = globalThis.window
      }) {
        let currentSettings = {
          targetLanguage: defaults.targetLanguage,
          preferredProvider: defaults.preferredProvider
        };
        let settingsLoadPromise = null;
        async function ensureSettings() {
          if (!settingsLoadPromise) {
            settingsLoadPromise = readSettingsSnapshot().then((value) => {
              currentSettings = value;
              return value;
            }).finally(() => {
              settingsLoadPromise = null;
            });
          }
          return settingsLoadPromise;
        }
        async function ensureTargetLanguageId() {
          const settings = await ensureSettings();
          return settings.targetLanguage;
        }
        async function persistSettings(nextSettings) {
          currentSettings = await writeSettingsSnapshot(nextSettings);
          runtimeState.advanceSettingsRevision();
          invalidateActiveThreadTranslations();
          refreshActiveThreads();
          return currentSettings;
        }
        function getLanguageDefinition() {
          return translateService.getLanguageDefinition(currentSettings.targetLanguage);
        }
        function injectButtons(root = document) {
          threadDom.injectButtons(root, onTranslateClick);
        }
        async function runOpenSettings() {
          const resolvedSettings = await ensureSettings();
          settingsModal.open({
            currentSettings: resolvedSettings,
            onSave: persistSettings
          });
        }
        function openSettings() {
          return runOpenSettings().catch((error) => {
            diagnostics.recordError("ui.settings.open.unexpected-error", error, {});
          });
        }
        function cancelPendingThreadRequests(threadState) {
          if (!threadState?.pendingRequestIds || threadState.pendingRequestIds.size === 0) {
            return;
          }
          const requestIds = Array.from(threadState.pendingRequestIds);
          threadState.pendingRequestIds.clear();
          requestRegistry.cancelMany(requestIds);
        }
        function invalidateThreadTranslationState(threadState) {
          if (!threadState) {
            return;
          }
          const nextSettingsRevision = runtimeState.state.settingsRevision;
          threadState.title.translatedText = "";
          threadState.title.status = "idle";
          threadState.title.error = "";
          threadState.title.requestId += 1;
          threadState.title.settingsRevision = nextSettingsRevision;
          for (const messageState of threadState.messages.values()) {
            messageState.segmentSignature = "";
            messageState.translatedSegments = null;
            messageState.status = "idle";
            messageState.error = "";
            messageState.requestSerial += 1;
            messageState.settingsRevision = nextSettingsRevision;
          }
        }
        function invalidateActiveThreadTranslations() {
          for (const threadState of Array.from(runtimeState.state.activeThreadStates)) {
            cancelPendingThreadRequests(threadState);
            invalidateThreadTranslationState(threadState);
            if (threadState.root instanceof globalThis.HTMLElement) {
              renderer.clearThreadRenderArtifacts(threadState.root);
            }
          }
        }
        function restoreThread(threadRoot) {
          const threadState = threadDom.getExistingThreadState(threadRoot, { includeStale: true });
          if (threadState) {
            cancelPendingThreadRequests(threadState);
          }
          threadDom.resetThreadState(threadRoot);
          threadDom.syncThreadButtons(threadRoot, false);
          renderer.clearThreadRenderArtifacts(threadRoot);
          threadDom.clearThreadDomState(threadRoot);
        }
        function disposeThread(threadRoot) {
          const threadState = threadDom.getExistingThreadState(threadRoot, { includeStale: true });
          if (threadState) {
            cancelPendingThreadRequests(threadState);
          }
          threadDom.resetThreadState(threadRoot);
          renderer.clearThreadRenderArtifacts(threadRoot);
          threadDom.clearThreadDomState(threadRoot);
        }
        function reportLifecycleError(event, error) {
          diagnostics.recordError(event, error, {});
        }
        async function runTranslateClick(event) {
          const button = event.currentTarget;
          if (!(button instanceof globalThis.HTMLElement)) {
            diagnostics.record("ui.translate-click.invalid-target", {
              targetType: typeof button
            });
            return;
          }
          diagnostics.record("ui.translate-click.received", {
            buttonClassName: button.className || ""
          });
          const threadRoot = threadDom.findThreadRoot(button);
          if (!threadRoot) {
            diagnostics.record("ui.translate-click.thread-missing", {
              buttonClassName: button.className || ""
            });
            return;
          }
          const threadState = threadDom.getExistingThreadState(threadRoot);
          const shouldRestoreOriginal = threadState?.active === true || renderer.hasThreadRenderArtifacts(threadRoot);
          if (shouldRestoreOriginal) {
            diagnostics.record("ui.translate-click.restore", {
              threadKey: threadState?.key || ""
            });
            restoreThread(threadRoot);
            return;
          }
          await ensureSettings();
          const nextThreadState = threadDom.ensureThreadState(threadRoot);
          diagnostics.record("ui.translate-click.activate", {
            threadKey: nextThreadState.key
          });
          runtimeState.activateThreadState(nextThreadState);
          threadDom.syncThreadButtons(threadRoot, true);
          scheduleThreadRefresh(threadRoot, { immediate: true });
        }
        function onTranslateClick(event) {
          return runTranslateClick(event).catch((error) => {
            diagnostics.recordError("ui.translate-click.unexpected-error", error, {});
          });
        }
        function scheduleThreadRefresh(threadRoot, { immediate = false, delayMs } = {}) {
          if (!(threadRoot instanceof globalThis.HTMLElement)) {
            return;
          }
          const state = threadDom.getExistingThreadState(threadRoot);
          if (!state?.active) {
            return;
          }
          if (state.processing) {
            state.pendingRefresh = true;
            return;
          }
          if (state.refreshTimer) {
            return;
          }
          const delay = typeof delayMs === "number" ? Math.max(0, delayMs) : immediate ? 0 : 80;
          state.refreshTimer = window.setTimeout(() => {
            state.refreshTimer = 0;
            void refreshThreadSafely(threadRoot, state);
          }, delay);
        }
        async function refreshThreadSafely(threadRoot, threadState) {
          try {
            await refreshThread(threadRoot, threadState);
          } catch (error) {
            diagnostics.recordError("thread.refresh.unexpected-error", error, {
              threadKey: threadState?.key || ""
            });
          }
        }
        async function refreshThread(threadRoot, threadState) {
          if (!(threadRoot instanceof globalThis.HTMLElement) || !threadRoot.isConnected) {
            return;
          }
          const liveThreadState = threadDom.getExistingThreadState(threadRoot, { includeStale: true });
          if (liveThreadState !== threadState || threadState.active !== true || threadState.cancelled === true) {
            return;
          }
          if (threadState.processing) {
            threadState.pendingRefresh = true;
            return;
          }
          threadState.processing = true;
          const runToken = runtimeState.beginThreadRun(threadState);
          const textCache = typeof segmenter.createTextExtractionCache === "function" ? segmenter.createTextExtractionCache() : null;
          diagnostics.record("thread.refresh.start", {
            threadKey: threadState.key,
            runId: runToken.runId
          });
          try {
            threadDom.syncThreadButtons(threadRoot, true);
            const descriptors = threadDom.collectMessageDescriptors(threadRoot, { textCache });
            threadDom.reconcileMessageStates(threadState, descriptors);
            diagnostics.record("thread.refresh.collected", {
              threadKey: threadState.key,
              runId: runToken.runId,
              messageCount: descriptors.length
            });
            if (!runtimeState.isRunCurrent(threadState, runToken)) {
              return;
            }
            await Promise.all([
              processThreadTitle(threadRoot, threadState, runToken),
              processMessageDescriptors(threadRoot, threadState, descriptors, runToken, { textCache })
            ]);
          } finally {
            threadState.processing = false;
            if (threadState.pendingRefresh && threadState.active && !threadState.cancelled) {
              const delay = threadState.pendingRefreshDelayMs || 0;
              threadState.pendingRefresh = false;
              threadState.pendingRefreshDelayMs = 0;
              scheduleThreadRefresh(threadRoot, { delayMs: delay });
            }
          }
        }
        async function processThreadTitle(threadRoot, threadState, runToken) {
          const titleState = threadState.title;
          const settingsRevision = runtimeState.state.settingsRevision;
          const titleElement = threadDom.findTitleElement(threadRoot);
          const titleText = normalizeTranslationText(titleElement?.textContent || "");
          if (!(titleElement instanceof globalThis.HTMLElement) || !titleText || !policies.hasTranslatableText(titleText)) {
            threadState.title = runtimeState.createTitleState();
            renderer.clearTitleTranslation(threadRoot);
            return;
          }
          if (titleState.sourceText === titleText && titleState.status === "done" && titleState.translatedText && titleState.settingsRevision === settingsRevision) {
            if (runtimeState.isRunCurrent(threadState, runToken)) {
              renderer.renderTitleTranslation(threadRoot, titleElement, titleState.translatedText, "done");
            }
            return;
          }
          const requestId = titleState.requestId + 1;
          titleState.sourceText = titleText;
          titleState.translatedText = "";
          titleState.status = "translating";
          titleState.error = "";
          titleState.requestId = requestId;
          titleState.settingsRevision = settingsRevision;
          renderer.renderTitleTranslation(threadRoot, titleElement, i18n.t("content.loading"), "loading");
          const translationRequestId = `title:${threadState.key}:${runToken.runId}:${requestId}`;
          const request = translateService.translateSegments(
            [titleText],
            getLanguageDefinition(),
            { preferredProvider: currentSettings.preferredProvider }
          );
          requestRegistry.register(translationRequestId, request);
          runtimeState.trackThreadRequest(threadState, translationRequestId);
          try {
            const response = await request;
            const translatedTitle = Array.isArray(response?.translatedSegments) ? response.translatedSegments[0] : "";
            if (!translatedTitle) {
              throw new Error(i18n.t("content.titleTranslationEmpty"));
            }
            if (!isTitleRequestCurrent(threadState, requestId, titleText, settingsRevision)) {
              return;
            }
            threadState.title.translatedText = translatedTitle;
            threadState.title.status = "done";
            threadState.title.error = "";
            if (!runtimeState.isRunCurrent(threadState, runToken)) {
              return;
            }
            const liveTitleElement = threadDom.findTitleElement(threadRoot);
            if (liveTitleElement instanceof globalThis.HTMLElement) {
              renderer.renderTitleTranslation(threadRoot, liveTitleElement, translatedTitle, "done");
            }
          } catch (error) {
            if (!isTitleRequestCurrent(threadState, requestId, titleText, settingsRevision)) {
              return;
            }
            const message = resolveErrorMessage(error, i18n.t("content.titleTranslationFailed"));
            threadState.title.translatedText = "";
            threadState.title.status = "error";
            threadState.title.error = message;
            if (!runtimeState.isRunCurrent(threadState, runToken)) {
              return;
            }
            const liveTitleElement = threadDom.findTitleElement(threadRoot);
            if (liveTitleElement instanceof globalThis.HTMLElement) {
              renderer.renderTitleTranslation(threadRoot, liveTitleElement, message, "error");
            }
          } finally {
            requestRegistry.release(translationRequestId);
            runtimeState.releaseThreadRequest(threadState, translationRequestId);
          }
        }
        async function processMessageDescriptors(threadRoot, threadState, descriptors, runToken, { textCache } = {}) {
          for (const descriptor of descriptors) {
            if (!runtimeState.isRunCurrent(threadState, runToken)) {
              return;
            }
            const messageState = descriptor.state;
            const liveElements = threadDom.findLiveMessageElements(threadRoot, descriptor, { textCache });
            descriptor.body = liveElements.body;
            descriptor.contentRoot = liveElements.contentRoot || descriptor.body;
            renderer.clearMessageStatus(descriptor);
            if (!descriptor.body) {
              renderer.clearMessageTranslations?.(descriptor);
              if (threadDom.isMessageBodyDeferred(descriptor)) {
                messageState.status = "pending-body";
                messageState.error = "";
              } else {
                messageState.status = "pending-body";
                messageState.error = i18n.t("content.bodyNotFound");
                renderer.renderMessageStatus(descriptor, messageState.error, "error");
              }
              continue;
            }
            const segments = segmenter.collectTranslatableSegments(descriptor.contentRoot || descriptor.body, { textCache });
            if (segments.length === 0) {
              renderer.clearMessageTranslations?.(descriptor);
              messageState.status = "no-segments";
              messageState.error = i18n.t("content.noSegments");
              renderer.renderMessageStatus(descriptor, messageState.error, "error");
              continue;
            }
            const segmentSignature = segmenter.getSegmentSignature(segments);
            descriptor.segments = segments;
            descriptor.segmentSignature = segmentSignature;
            renderer.pruneMessageTranslations?.(descriptor, segments);
            if (Array.isArray(messageState.translatedSegments) && messageState.translatedSegments.length === segments.length && messageState.segmentSignature === segmentSignature && messageState.settingsRevision === runtimeState.state.settingsRevision) {
              const renderResult = renderer.renderTranslatedSegments(segments, messageState.translatedSegments);
              messageState.status = renderResult.ok ? "translated" : "render-pending";
              messageState.error = renderResult.ok ? "" : "cached-render-failed";
              continue;
            }
            await translateMessageDescriptor(threadState, descriptor, runToken);
          }
        }
        async function translateMessageDescriptor(threadState, descriptor, runToken) {
          const messageState = descriptor.state;
          const segments = descriptor.segments || [];
          const settingsRevision = runtimeState.state.settingsRevision;
          if (segments.length === 0 || !runtimeState.isRunCurrent(threadState, runToken)) {
            return;
          }
          messageState.status = "translating";
          messageState.error = "";
          messageState.settingsRevision = settingsRevision;
          renderer.renderLoadingTranslations(segments);
          messageState.requestSerial += 1;
          const requestSerial = messageState.requestSerial;
          const requestId = `message:${threadState.key}:${messageState.instanceId}:${requestSerial}`;
          const request = translateService.translateSegments(
            segments.map((segment) => segment.text),
            getLanguageDefinition(),
            { preferredProvider: currentSettings.preferredProvider }
          );
          diagnostics.record("thread.message.translate.start", {
            messageKey: messageState.key,
            segmentCount: segments.length,
            requestId
          });
          requestRegistry.register(requestId, request);
          runtimeState.trackThreadRequest(threadState, requestId);
          try {
            const response = await request;
            if (!runtimeState.isRunCurrent(threadState, runToken) || messageState.requestSerial !== requestSerial || messageState.settingsRevision !== settingsRevision) {
              return;
            }
            messageState.segmentSignature = descriptor.segmentSignature || "";
            messageState.translatedSegments = response.translatedSegments;
            messageState.status = "translated";
            messageState.error = "";
            renderer.clearMessageStatus(descriptor);
            const renderResult = renderer.renderTranslatedSegments(segments, response.translatedSegments);
            if (!renderResult?.ok) {
              diagnostics.record("thread.message.render.failed", {
                messageKey: messageState.key,
                phase: "fresh-translation",
                failedSegments: renderResult?.failedSegments || []
              });
            }
          } catch (error) {
            if (!runtimeState.isRunCurrent(threadState, runToken) || messageState.requestSerial !== requestSerial || messageState.settingsRevision !== settingsRevision) {
              return;
            }
            const message = resolveErrorMessage(error, i18n.t("content.translationFailed"));
            messageState.status = "error";
            messageState.error = message;
            diagnostics.recordError("thread.message.translate.error", error, {
              messageKey: messageState.key,
              requestId
            });
            renderer.renderSegmentError(segments, message);
          } finally {
            requestRegistry.release(requestId);
            runtimeState.releaseThreadRequest(threadState, requestId);
          }
        }
        function resetDocumentTranslationState() {
          runtimeState.syncCurrentLocationKey();
          runtimeState.advanceDocumentGeneration();
          for (const threadState of Array.from(runtimeState.state.activeThreadStates)) {
            cancelPendingThreadRequests(threadState);
            runtimeState.deactivateThreadState(threadState);
          }
          runtimeState.state.activeThreadStates.clear();
          renderer.clearThreadRenderArtifacts(document.body || document.documentElement);
          threadDom.clearThreadDomState(document);
        }
        function refreshActiveThreads() {
          for (const threadState of Array.from(runtimeState.state.activeThreadStates)) {
            if (threadState.root instanceof globalThis.HTMLElement && threadState.root.isConnected) {
              scheduleThreadRefresh(threadState.root, { immediate: true });
            }
          }
        }
        function isTitleRequestCurrent(threadState, requestId, titleText, settingsRevision) {
          return threadState.title.requestId === requestId && threadState.title.sourceText === titleText && threadState.title.settingsRevision === settingsRevision;
        }
        function normalizeTranslationText(text) {
          return String(text || "").replace(/\r\n/g, "\n").split("\n").map((line) => line.trim()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
        }
        async function readSettingsSnapshot() {
          if (typeof settingsStore.getSettings === "function") {
            return settingsStore.getSettings();
          }
          const [targetLanguage, preferredProvider] = await Promise.all([
            typeof settingsStore.getTargetLanguage === "function" ? settingsStore.getTargetLanguage() : defaults.targetLanguage,
            typeof settingsStore.getPreferredProvider === "function" ? settingsStore.getPreferredProvider() : defaults.preferredProvider
          ]);
          return { targetLanguage, preferredProvider };
        }
        async function writeSettingsSnapshot(nextSettings) {
          if (typeof settingsStore.setSettings === "function") {
            return settingsStore.setSettings(nextSettings);
          }
          const targetLanguage = typeof settingsStore.setTargetLanguage === "function" ? await settingsStore.setTargetLanguage(nextSettings?.targetLanguage) : defaults.targetLanguage;
          const preferredProvider = typeof settingsStore.setPreferredProvider === "function" ? await settingsStore.setPreferredProvider(nextSettings?.preferredProvider) : defaults.preferredProvider;
          return { targetLanguage, preferredProvider };
        }
        function resolveErrorMessage(error, fallbackMessage) {
          if (typeof error?.message === "string" && error.message.trim()) {
            return error.message.trim();
          }
          return fallbackMessage;
        }
        return {
          ensureTargetLanguageId,
          disposeThread,
          injectButtons,
          onTranslateClick,
          openSettings,
          reportLifecycleError,
          refreshActiveThreads,
          refreshThread,
          resetDocumentTranslationState,
          restoreThread,
          scheduleThreadRefresh
        };
      }
      module.exports = { createApp: createApp2 };
    }
  });

  // src/core/lifecycle.js
  var require_lifecycle = __commonJS({
    "src/core/lifecycle.js"(exports, module) {
      function createLifecycle2({
        app,
        constants,
        document,
        runtimeState,
        threadDom,
        window = globalThis.window
      }) {
        const TRANSLATE_CLICK_HANDLED = /* @__PURE__ */ Symbol.for("fasttrmail.translateClickHandled");
        let observer = null;
        let initialized = false;
        let observing = false;
        let translationSelectionGestureActive = false;
        function initialize() {
          if (initialized) {
            return;
          }
          initialized = true;
          observer = new MutationObserver((mutations) => safelyRunLifecycleWork("lifecycle.observer.unexpected-error", () => {
            if (runtimeState.state.observerMuteDepth > 0) {
              return;
            }
            if (runtimeState.hasLocationChanged()) {
              scheduleDocumentRefresh();
              return;
            }
            if (isTranslationSelectionActive()) {
              return;
            }
            const affectedThreadRoots = collectAffectedThreadRoots(mutations);
            if (affectedThreadRoots.size > 0) {
              scheduleObservedThreadRefresh(affectedThreadRoots);
            }
          }));
          runtimeState.setObserverControl({
            suspend: suspendObserver,
            resume: resumeObserver
          });
          app.injectButtons(document);
          document.addEventListener("click", handleDocumentClick, true);
          document.addEventListener("mousedown", handleDocumentMouseDown, true);
          document.addEventListener("mouseup", handleDocumentMouseUp, true);
          document.addEventListener("selectionchange", handleDocumentSelectionChange, true);
          window.addEventListener?.("blur", clearTranslationSelectionGesture, true);
          resumeObserver();
        }
        function suspendObserver() {
          if (!observer || !observing) {
            return;
          }
          observer.disconnect();
          observing = false;
        }
        function resumeObserver() {
          if (!initialized || !observer || observing || runtimeState.state.observerMuteDepth > 0) {
            return;
          }
          observer.observe(document.body, {
            childList: true,
            subtree: true,
            characterData: true,
            attributes: true,
            attributeFilter: ["class", "aria-hidden", "hidden", "style", "inert"]
          });
          observing = true;
        }
        function handleDocumentClick(event) {
          if (event?.[TRANSLATE_CLICK_HANDLED]) {
            return;
          }
          const target = event.target;
          if (!(target instanceof globalThis.Element)) {
            return;
          }
          const button = target.closest(`.${constants.BUTTON_CLASS}`);
          if (!(button instanceof globalThis.HTMLElement)) {
            return;
          }
          if (threadDom.isManagedTranslateButton?.(button)) {
            return;
          }
          event[TRANSLATE_CLICK_HANDLED] = true;
          void app.onTranslateClick({
            currentTarget: button
          });
        }
        function handleDocumentMouseDown(event) {
          translationSelectionGestureActive = isManagedTranslationNode(event?.target);
        }
        function handleDocumentMouseUp() {
          window.setTimeout(clearTranslationSelectionGestureIfInactive, 0);
        }
        function handleDocumentSelectionChange() {
          clearTranslationSelectionGestureIfInactive();
        }
        function clearTranslationSelectionGesture() {
          translationSelectionGestureActive = false;
        }
        function clearTranslationSelectionGestureIfInactive() {
          if (!isSelectionInsideManagedTranslation()) {
            clearTranslationSelectionGesture();
          }
        }
        function scheduleDocumentRefresh() {
          if (runtimeState.state.documentRefreshScheduled) {
            return;
          }
          runtimeState.state.documentRefreshScheduled = true;
          window.setTimeout(() => {
            runtimeState.state.documentRefreshScheduled = false;
            safelyRunLifecycleWork("lifecycle.document-refresh.unexpected-error", () => {
              if (runtimeState.hasLocationChanged()) {
                app.resetDocumentTranslationState();
              }
              clearObservedThreadRefreshQueue();
              app.injectButtons(document);
              disposeDetachedThreadStates();
            });
          }, 0);
        }
        function scheduleObservedThreadRefresh(threadRoots) {
          for (const threadRoot of threadRoots) {
            if (threadRoot instanceof globalThis.HTMLElement) {
              runtimeState.state.observedThreadRoots.add(threadRoot);
            }
          }
          if (runtimeState.state.observedThreadFlushTimer) {
            return;
          }
          runtimeState.state.observedThreadFlushTimer = window.setTimeout(() => {
            const roots = Array.from(runtimeState.state.observedThreadRoots);
            clearObservedThreadRefreshQueue();
            safelyRunLifecycleWork("lifecycle.thread-refresh.unexpected-error", () => {
              if (runtimeState.hasLocationChanged()) {
                scheduleDocumentRefresh();
                return;
              }
              disposeDetachedThreadStates();
              for (const threadRoot of roots) {
                if (!(threadRoot instanceof globalThis.HTMLElement) || !threadRoot.isConnected) {
                  continue;
                }
                app.injectButtons(threadRoot);
                const state = threadDom.getExistingThreadState(threadRoot);
                if (state?.active) {
                  app.scheduleThreadRefresh(threadRoot, { immediate: true });
                }
              }
            });
          }, 0);
        }
        function safelyRunLifecycleWork(event, task) {
          try {
            return task();
          } catch (error) {
            app.reportLifecycleError?.(event, error);
            return void 0;
          }
        }
        function disposeDetachedThreadStates() {
          const detachedThreadRoots = typeof threadDom.collectDetachedActiveThreadRoots === "function" ? threadDom.collectDetachedActiveThreadRoots() : [];
          for (const threadRoot of detachedThreadRoots) {
            app.disposeThread?.(threadRoot);
          }
          threadDom.pruneDetachedThreadStates();
        }
        function clearObservedThreadRefreshQueue() {
          if (runtimeState.state.observedThreadFlushTimer) {
            window.clearTimeout(runtimeState.state.observedThreadFlushTimer);
            runtimeState.state.observedThreadFlushTimer = 0;
          }
          runtimeState.state.observedThreadRoots.clear();
        }
        function collectAffectedThreadRoots(mutations) {
          const affectedThreadRoots = /* @__PURE__ */ new Set();
          for (const mutation of mutations) {
            if (isInternalMutation(mutation)) {
              continue;
            }
            if (mutation.type === "attributes") {
              if (shouldReactToAttributeMutation(mutation.target)) {
                addAffectedThreadRootsFromNode(mutation.target, affectedThreadRoots);
              }
              continue;
            }
            if (mutation.type === "characterData") {
              addAffectedThreadRootsFromNode(getMutationTargetElement(mutation.target), affectedThreadRoots);
              continue;
            }
            addAffectedThreadRootsFromNode(mutation.target, affectedThreadRoots);
            for (const node of mutation.addedNodes || []) {
              addAffectedThreadRootsFromNode(node, affectedThreadRoots);
            }
            for (const node of mutation.removedNodes || []) {
              addAffectedThreadRootsFromNode(node, affectedThreadRoots);
            }
          }
          return affectedThreadRoots;
        }
        function addAffectedThreadRootsFromNode(node, affectedThreadRoots) {
          if (!(node instanceof globalThis.HTMLElement) || isInternalNode(node)) {
            return;
          }
          const directThreadRoot = node.matches(".v-Thread") ? node : node.closest(".v-Thread");
          if (directThreadRoot instanceof globalThis.HTMLElement) {
            affectedThreadRoots.add(directThreadRoot);
          }
          const shouldResolveRelatedThreadRoot = node.classList.contains(constants.BUTTON_CLASS) || node.closest?.(`.${constants.BUTTON_CLASS}`) !== null;
          const relatedThreadRoot = shouldResolveRelatedThreadRoot ? threadDom.findThreadRoot(node) : null;
          if (relatedThreadRoot instanceof globalThis.HTMLElement) {
            affectedThreadRoots.add(relatedThreadRoot);
          }
          node.querySelectorAll(".v-Thread").forEach((threadRoot) => {
            if (threadRoot instanceof globalThis.HTMLElement) {
              affectedThreadRoots.add(threadRoot);
            }
          });
        }
        function getMutationTargetElement(node) {
          if (node instanceof globalThis.HTMLElement) {
            return node;
          }
          if (node instanceof globalThis.Node) {
            return node.parentElement;
          }
          return null;
        }
        function isInternalMutation(mutation) {
          const nodes = [
            ...Array.from(mutation.addedNodes || []),
            ...Array.from(mutation.removedNodes || [])
          ];
          if (mutation.type === "attributes") {
            return isInternalNode(mutation.target);
          }
          if (mutation.type === "characterData") {
            return isInternalNode(getMutationTargetElement(mutation.target));
          }
          return nodes.length > 0 && nodes.every((node) => isInternalChildMutationNode(node));
        }
        function isInternalChildMutationNode(node) {
          if (!(node instanceof globalThis.HTMLElement)) {
            return false;
          }
          const translateButton = getTranslateButtonNode(node);
          if (translateButton && !threadDom.isManagedTranslateButton?.(translateButton)) {
            return false;
          }
          return isInternalNode(node);
        }
        function getTranslateButtonNode(node) {
          if (!(node instanceof globalThis.HTMLElement)) {
            return null;
          }
          if (node.classList.contains(constants.BUTTON_CLASS)) {
            return node;
          }
          return node.closest?.(`.${constants.BUTTON_CLASS}`) || null;
        }
        function isInternalNode(node) {
          if (!(node instanceof globalThis.HTMLElement)) {
            return false;
          }
          return node.classList.contains(constants.BUTTON_CLASS) || node.classList.contains(constants.INLINE_TRANSLATION_CLASS) || node.classList.contains(constants.MESSAGE_STATUS_CLASS) || node.classList.contains(constants.SEGMENT_ANCHOR_CLASS) || node.classList.contains(constants.TITLE_TRANSLATION_CLASS) || node.classList.contains(constants.TRANSLATION_WRAPPER_CLASS) || node.classList.contains(constants.MODAL_OVERLAY_CLASS) || node.closest?.(`.${constants.BUTTON_CLASS}`) !== null || node.closest?.(`.${constants.INLINE_TRANSLATION_CLASS}`) !== null || node.closest?.(`.${constants.MESSAGE_STATUS_CLASS}`) !== null || node.closest?.(`.${constants.MODAL_OVERLAY_CLASS}`) !== null;
        }
        function isTranslationSelectionActive() {
          return translationSelectionGestureActive || isSelectionInsideManagedTranslation();
        }
        function isSelectionInsideManagedTranslation() {
          const selection = typeof document.getSelection === "function" ? document.getSelection() : null;
          if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
            return false;
          }
          for (let index = 0; index < selection.rangeCount; index += 1) {
            const range = selection.getRangeAt(index);
            if (isManagedTranslationNode(range.commonAncestorContainer)) {
              return true;
            }
          }
          return isManagedTranslationNode(selection.anchorNode) || isManagedTranslationNode(selection.focusNode);
        }
        function isManagedTranslationNode(node) {
          if (node instanceof globalThis.HTMLElement) {
            return Boolean(
              node.closest(`.${constants.INLINE_TRANSLATION_CLASS}`) || node.closest(`.${constants.TITLE_TRANSLATION_CLASS}`)
            );
          }
          if (node instanceof globalThis.Node) {
            return isManagedTranslationNode(node.parentElement);
          }
          return false;
        }
        function shouldReactToAttributeMutation(target) {
          if (!(target instanceof globalThis.HTMLElement)) {
            return false;
          }
          if (target.closest(`.${constants.INLINE_TRANSLATION_CLASS}`) || target.closest(`.${constants.TITLE_TRANSLATION_CLASS}`) || target.closest(`.${constants.MESSAGE_STATUS_CLASS}`) || target.classList.contains(constants.BUTTON_CLASS)) {
            return false;
          }
          return Boolean(
            target.closest(".v-Message") || target.closest(".v-MessageCard") || target.closest(".v-Thread")
          );
        }
        return { initialize };
      }
      module.exports = { createLifecycle: createLifecycle2 };
    }
  });

  // src/diagnostics/store.js
  var require_store = __commonJS({
    "src/diagnostics/store.js"(exports, module) {
      function assertConfig(condition, message) {
        if (!condition) {
          throw new Error(message);
        }
      }
      function normalizeEnabled(value) {
        return value === true || value === 1 || value === "1" || value === "true";
      }
      function createDiagnosticsStore2({ tmApi }) {
        assertConfig(tmApi && typeof tmApi.getValue === "function", "tmApi.getValue must be a function");
        assertConfig(tmApi && typeof tmApi.setValue === "function", "tmApi.setValue must be a function");
        async function getEnabled() {
          return normalizeEnabled(await tmApi.getValue("diagnosticsEnabled", false));
        }
        async function setEnabled(value) {
          const normalizedValue = Boolean(value);
          await tmApi.setValue("diagnosticsEnabled", normalizedValue);
          return normalizedValue;
        }
        async function toggleEnabled() {
          const nextValue = !await getEnabled();
          return setEnabled(nextValue);
        }
        return {
          getEnabled,
          setEnabled,
          toggleEnabled
        };
      }
      module.exports = { createDiagnosticsStore: createDiagnosticsStore2 };
    }
  });

  // src/fastmail/segmenter.js
  var require_segmenter = __commonJS({
    "src/fastmail/segmenter.js"(exports, module) {
      function normalizeKeyText(text) {
        return String(text || "").replace(/\s+/g, " ").trim();
      }
      function normalizeSegmentText(text) {
        return String(text || "").replace(/\r\n/g, "\n").replace(/\n[ \t]+/g, "\n").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
      }
      function shouldSkipSegmentText(text) {
        const compact = normalizeKeyText(text);
        if (!compact) {
          return true;
        }
        return /^--\s*reply above this line\s*--$/i.test(compact);
      }
      function createSegmenter2({
        constants,
        policies,
        runtimeState,
        document = globalThis.document
      }) {
        function createTextExtractionCache() {
          return /* @__PURE__ */ new WeakMap();
        }
        function getTextExtractionCache(options) {
          if (options instanceof WeakMap) {
            return options;
          }
          return options?.textCache instanceof WeakMap ? options.textCache : null;
        }
        function createElementSegment(element, text) {
          const segmentId = getOrAssignSegmentId(element);
          return {
            id: segmentId,
            element,
            sourceElement: element,
            text
          };
        }
        function createAnchoredSegment(referenceNode, sourceElement, text) {
          const anchor = ensureSegmentAnchor(referenceNode);
          const segmentId = getOrAssignSegmentId(anchor);
          return {
            id: segmentId,
            referenceNode,
            sourceElement,
            text
          };
        }
        function getOrAssignSegmentId(node) {
          if (!(node instanceof globalThis.HTMLElement)) {
            return runtimeState.nextSegmentId();
          }
          const existingId = node.getAttribute(constants.SEGMENT_ATTRIBUTE);
          if (existingId) {
            return existingId;
          }
          const segmentId = runtimeState.nextSegmentId();
          node.setAttribute(constants.SEGMENT_ATTRIBUTE, segmentId);
          return segmentId;
        }
        function getExistingSegmentAnchor(referenceNode) {
          let sibling = referenceNode?.nextSibling || null;
          while (sibling) {
            if (sibling instanceof globalThis.HTMLElement && sibling.classList.contains(constants.SEGMENT_ANCHOR_CLASS)) {
              return sibling;
            }
            if (sibling.nodeType === globalThis.Node.TEXT_NODE && !(sibling.textContent || "").trim()) {
              sibling = sibling.nextSibling;
              continue;
            }
            break;
          }
          return null;
        }
        function ensureSegmentAnchor(referenceNode) {
          const existingAnchor = getExistingSegmentAnchor(referenceNode);
          if (existingAnchor) {
            return existingAnchor;
          }
          if (!referenceNode?.parentNode) {
            return null;
          }
          const anchor = document.createElement("span");
          anchor.className = constants.SEGMENT_ANCHOR_CLASS;
          anchor.setAttribute("aria-hidden", "true");
          runtimeState.withObserverMuted(() => {
            referenceNode.parentNode.insertBefore(anchor, referenceNode.nextSibling);
          });
          return anchor;
        }
        function extractBodyText(element, options = void 0) {
          const textCache = getTextExtractionCache(options);
          if (textCache && element instanceof globalThis.HTMLElement && textCache.has(element)) {
            return textCache.get(element);
          }
          const clone = element?.cloneNode?.(true);
          if (!(clone instanceof globalThis.HTMLElement)) {
            return "";
          }
          clone.querySelectorAll(`.${constants.INLINE_TRANSLATION_CLASS}, .${constants.SEGMENT_ANCHOR_CLASS}, .${constants.TRANSLATION_WRAPPER_CLASS}, script, style, noscript, button`).forEach((node) => node.remove());
          const text = clone.innerText || clone.textContent || "";
          const normalizedText = text.replace(/\n{3,}/g, "\n\n").replace(/[ \t]+\n/g, "\n").trim();
          if (textCache && element instanceof globalThis.HTMLElement) {
            textCache.set(element, normalizedText);
          }
          return normalizedText;
        }
        function isVisible(element) {
          if (!(element instanceof globalThis.HTMLElement)) {
            return false;
          }
          const rect = typeof element.getBoundingClientRect === "function" ? element.getBoundingClientRect() : { width: 1, height: 1 };
          return rect.width > 0 || rect.height > 0;
        }
        function isBlockSegmentCandidate(element) {
          const tagName = element.tagName.toLowerCase();
          return ["p", "div", "li", "blockquote", "td", "th", "h1", "h2", "h3", "h4", "pre"].includes(tagName);
        }
        function isStandaloneBlock(element, options = void 0) {
          const tagName = element.tagName.toLowerCase();
          if (tagName === "div" && element.querySelector("p, li, blockquote, td, th, h1, h2, h3, h4, pre")) {
            return false;
          }
          if (element.querySelector("table")) {
            return false;
          }
          for (const child of element.children) {
            if (!(child instanceof globalThis.HTMLElement)) {
              continue;
            }
            if (child.closest(`.${constants.INLINE_TRANSLATION_CLASS}`)) {
              continue;
            }
            if (!isBlockSegmentCandidate(child)) {
              continue;
            }
            const childText = extractBodyText(child, options);
            if (isVisible(child) && childText.length > 0) {
              return false;
            }
          }
          return true;
        }
        function isTranslatableSegment(text, element) {
          if (!text || text.length < 2) {
            return false;
          }
          if (element?.querySelector?.("img, video, iframe")) {
            return false;
          }
          const compact = text.replace(/\s+/g, " ").trim();
          if (compact.length < 2) {
            return false;
          }
          if (/^https?:\/\/\S+$/i.test(compact)) {
            return false;
          }
          return policies.hasTranslatableText(compact);
        }
        function hasNestedTranslatableBlocks(element, options = void 0) {
          const descendants = element.querySelectorAll("p, div, li, blockquote, td, th, h1, h2, h3, h4, pre");
          for (const descendant of descendants) {
            if (!(descendant instanceof globalThis.HTMLElement) || descendant === element) {
              continue;
            }
            if (descendant.closest(`.${constants.INLINE_TRANSLATION_CLASS}`)) {
              continue;
            }
            if (!isVisible(descendant)) {
              continue;
            }
            const text = extractBodyText(descendant, options);
            if (isTranslatableSegment(text, descendant)) {
              return true;
            }
          }
          return false;
        }
        function collectFlowSegments(container, options = void 0) {
          if (!(container instanceof globalThis.HTMLElement)) {
            return [];
          }
          const segments = [];
          let textParts = [];
          let lastContentNode = null;
          let consecutiveBreaks = 0;
          const flushSegment = () => {
            const text = normalizeSegmentText(textParts.join(""));
            if (!text || !lastContentNode || shouldSkipSegmentText(text) || !isTranslatableSegment(text, container)) {
              textParts = [];
              lastContentNode = null;
              consecutiveBreaks = 0;
              return;
            }
            if (!lastContentNode?.parentNode) {
              textParts = [];
              lastContentNode = null;
              consecutiveBreaks = 0;
              return;
            }
            segments.push(createAnchoredSegment(lastContentNode, container, text));
            textParts = [];
            lastContentNode = null;
            consecutiveBreaks = 0;
          };
          for (const node of Array.from(container.childNodes)) {
            if (node instanceof globalThis.HTMLElement && node.classList.contains(constants.SEGMENT_ANCHOR_CLASS)) {
              continue;
            }
            if (node instanceof globalThis.HTMLElement && node.classList.contains(constants.INLINE_TRANSLATION_CLASS)) {
              continue;
            }
            if (node instanceof globalThis.HTMLBRElement) {
              consecutiveBreaks += 1;
              if (consecutiveBreaks >= 2) {
                flushSegment();
              } else {
                textParts.push("\n");
              }
              continue;
            }
            if (node instanceof globalThis.HTMLHRElement) {
              flushSegment();
              continue;
            }
            if (node.nodeType === globalThis.Node.TEXT_NODE) {
              const text = node.textContent || "";
              textParts.push(text);
              if (text.trim()) {
                lastContentNode = node;
              }
              consecutiveBreaks = 0;
              continue;
            }
            if (node instanceof globalThis.HTMLElement) {
              if (isBlockSegmentCandidate(node)) {
                flushSegment();
                if (!isVisible(node)) {
                  consecutiveBreaks = 0;
                  continue;
                }
                const text2 = extractBodyText(node, options);
                if (!text2 || shouldSkipSegmentText(text2) || !isTranslatableSegment(text2, node) || hasNestedTranslatableBlocks(node, options)) {
                  consecutiveBreaks = 0;
                  continue;
                }
                segments.push(createElementSegment(node, text2));
                consecutiveBreaks = 0;
                continue;
              }
              const text = extractBodyText(node, options);
              textParts.push(text);
              if (text.trim()) {
                lastContentNode = node;
              }
              consecutiveBreaks = 0;
            }
          }
          flushSegment();
          return segments;
        }
        function collectBlockSegments(root, options = void 0) {
          if (!(root instanceof globalThis.HTMLElement)) {
            return [];
          }
          const candidates = Array.from(
            root.querySelectorAll("p, div, li, blockquote, td, th, h1, h2, h3, h4, pre")
          );
          const segments = [];
          for (const candidate of candidates) {
            if (!(candidate instanceof globalThis.HTMLElement)) {
              continue;
            }
            if (candidate.closest(`.${constants.INLINE_TRANSLATION_CLASS}`)) {
              continue;
            }
            if (candidate.tagName.toLowerCase() === "pre") {
              segments.push(...collectFlowSegments(candidate, options));
              continue;
            }
            if (!isVisible(candidate) || !isStandaloneBlock(candidate, options)) {
              continue;
            }
            const text = extractBodyText(candidate, options);
            if (shouldSkipSegmentText(text) || !isTranslatableSegment(text, candidate) || hasNestedTranslatableBlocks(candidate, options)) {
              continue;
            }
            segments.push(createElementSegment(candidate, text));
          }
          return segments;
        }
        function findFlowSegmentRoot(bodyElement) {
          if (!(bodyElement instanceof globalThis.HTMLElement)) {
            return null;
          }
          const preferredRoot = bodyElement.querySelector(".u-article, article, pre");
          return preferredRoot instanceof globalThis.HTMLElement ? preferredRoot : bodyElement;
        }
        function shouldUseFlowSegmentation(container, options = void 0) {
          if (!(container instanceof globalThis.HTMLElement)) {
            return false;
          }
          if (container.tagName.toLowerCase() === "pre") {
            return true;
          }
          for (const node of Array.from(container.childNodes)) {
            if (node instanceof globalThis.HTMLElement && node.classList.contains(constants.SEGMENT_ANCHOR_CLASS)) {
              continue;
            }
            if (node instanceof globalThis.HTMLElement && node.classList.contains(constants.INLINE_TRANSLATION_CLASS)) {
              continue;
            }
            if (node.nodeType === globalThis.Node.TEXT_NODE && (node.textContent || "").trim()) {
              return true;
            }
            if (node instanceof globalThis.HTMLBRElement || node instanceof globalThis.HTMLHRElement) {
              return true;
            }
            if (node instanceof globalThis.HTMLElement && !isBlockSegmentCandidate(node)) {
              const text = extractBodyText(node, options);
              if (text.trim()) {
                return true;
              }
            }
          }
          return false;
        }
        function createWholeRootFallbackSegment(root, options = void 0) {
          if (!(root instanceof globalThis.HTMLElement)) {
            return null;
          }
          const text = extractBodyText(root, options);
          if (!text || shouldSkipSegmentText(text) || !policies.hasTranslatableText(text)) {
            return null;
          }
          const lastContentNode = findLastMeaningfulNode(root, options);
          if (lastContentNode?.parentNode) {
            return createAnchoredSegment(lastContentNode, root, text);
          }
          return createElementSegment(root, text);
        }
        function findLastMeaningfulNode(root, options = void 0) {
          const nodes = Array.from(root.childNodes).reverse();
          for (const node of nodes) {
            if (node instanceof globalThis.HTMLElement && node.classList.contains(constants.INLINE_TRANSLATION_CLASS)) {
              continue;
            }
            if (node.nodeType === globalThis.Node.TEXT_NODE && !(node.textContent || "").trim()) {
              continue;
            }
            if (node instanceof globalThis.HTMLElement) {
              const text = extractBodyText(node, options);
              if (!text) {
                continue;
              }
            }
            return node;
          }
          return null;
        }
        function collectFallbackSegments(bodyElement, flowRoot, options = void 0) {
          const roots = [flowRoot, bodyElement].filter((root, index, items) => {
            return root instanceof globalThis.HTMLElement && items.indexOf(root) === index;
          });
          for (const root of roots) {
            const segment = createWholeRootFallbackSegment(root, options);
            if (segment) {
              return [segment];
            }
          }
          return [];
        }
        function collectTranslatableSegments(bodyElement, options = void 0) {
          const flowRoot = findFlowSegmentRoot(bodyElement);
          if (flowRoot && shouldUseFlowSegmentation(flowRoot, options)) {
            const flowSegments = collectFlowSegments(flowRoot, options);
            if (flowSegments.length > 0) {
              return flowSegments;
            }
          }
          const blockSegments = collectBlockSegments(bodyElement, options);
          if (blockSegments.length > 0) {
            return blockSegments;
          }
          return collectFallbackSegments(bodyElement, flowRoot, options);
        }
        function getSegmentSignature(segments) {
          return segments.map((segment) => normalizeKeyText(segment.text)).join("");
        }
        return {
          collectTranslatableSegments,
          createTextExtractionCache,
          ensureSegmentAnchor,
          extractBodyText,
          getExistingSegmentAnchor,
          getSegmentSignature,
          normalizeSegmentText,
          shouldSkipSegmentText
        };
      }
      function collectSegmentsFromTextBlocks(blocks) {
        return blocks.map((block, index) => ({
          id: `segment-${index}`,
          text: normalizeSegmentText(block)
        })).filter((segment) => !shouldSkipSegmentText(segment.text) && segment.text);
      }
      module.exports = {
        collectSegmentsFromTextBlocks,
        createSegmenter: createSegmenter2,
        normalizeSegmentText,
        shouldSkipSegmentText
      };
    }
  });

  // src/fastmail/page-locator.js
  var require_page_locator = __commonJS({
    "src/fastmail/page-locator.js"(exports, module) {
      var FASTMAIL_ORIGIN = "https://app.fastmail.com";
      function isFastmailUrl(locationLike) {
        return locationLike?.origin === FASTMAIL_ORIGIN;
      }
      function getLocationKey(locationLike = globalThis.location) {
        return `${locationLike?.pathname || ""}${locationLike?.search || ""}`;
      }
      function findPageRoot(element) {
        if (!element || typeof element.closest !== "function") {
          return null;
        }
        return element.closest(".v-Page");
      }
      function collectPageRoots(root) {
        const pageRoots = /* @__PURE__ */ new Set();
        if (root?.nodeType === 9 && root.documentElement) {
          root = root.body || root.documentElement;
        }
        if (root instanceof globalThis.HTMLElement && root.matches(".v-Page")) {
          pageRoots.add(root);
        }
        const directRoot = root instanceof globalThis.HTMLElement ? findPageRoot(root) : null;
        if (directRoot) {
          pageRoots.add(directRoot);
        }
        if (root && typeof root.querySelectorAll === "function") {
          root.querySelectorAll(".v-Page").forEach((pageRoot) => {
            if (pageRoot instanceof globalThis.HTMLElement) {
              pageRoots.add(pageRoot);
            }
          });
        }
        return Array.from(pageRoots);
      }
      module.exports = {
        FASTMAIL_ORIGIN,
        collectPageRoots,
        findPageRoot,
        getLocationKey,
        isFastmailUrl
      };
    }
  });

  // src/fastmail/thread-dom.js
  var require_thread_dom = __commonJS({
    "src/fastmail/thread-dom.js"(exports, module) {
      function normalizeKeyText(text) {
        return String(text || "").replace(/\s+/g, " ").trim();
      }
      function createThreadDom2({
        constants,
        i18n,
        pageLocator: pageLocator2,
        policies,
        runtimeState,
        segmenter,
        toolbarButtonApi: toolbarButtonApi2,
        document = globalThis.document
      }) {
        const TRANSLATE_CLICK_HANDLED = /* @__PURE__ */ Symbol.for("fasttrmail.translateClickHandled");
        const toolbarButtonThreadRoots = /* @__PURE__ */ new WeakMap();
        const toolbarButtonClickHandlers = /* @__PURE__ */ new WeakMap();
        const MOUSEUP_CLICK_DEDUP_WINDOW_MS = 400;
        function normalizeIdentityValue(value, maxLength = 240) {
          return normalizeKeyText(value || "").slice(0, maxLength);
        }
        function buildIdentity(prefix, parts, fallback = "unknown") {
          const normalizedParts = parts.map((part) => normalizeIdentityValue(part)).filter(Boolean);
          if (normalizedParts.length > 0) {
            return `${prefix}::${normalizedParts.join("::")}`;
          }
          return `${prefix}::${fallback}`;
        }
        function findThreadRoot(element) {
          if (!(element instanceof globalThis.HTMLElement)) {
            return null;
          }
          if (element.classList.contains(constants.BUTTON_CLASS)) {
            const mappedThreadRoot = toolbarButtonThreadRoots.get(element);
            if (mappedThreadRoot instanceof globalThis.HTMLElement) {
              return mappedThreadRoot;
            }
          }
          const directThreadRoot = element.closest(".v-Thread");
          if (directThreadRoot instanceof globalThis.HTMLElement) {
            return directThreadRoot;
          }
          const pageRoot = pageLocator2.findPageRoot(element);
          if (!(pageRoot instanceof globalThis.HTMLElement)) {
            return null;
          }
          return findPrimaryThreadRoot(pageRoot);
        }
        function findToolbarForPageRoot(pageRoot, threadRoot = null) {
          if (!(pageRoot instanceof globalThis.HTMLElement)) {
            return null;
          }
          if (threadRoot instanceof globalThis.HTMLElement) {
            const localToolbar = findToolbarNearThread(pageRoot, threadRoot);
            if (localToolbar) {
              return localToolbar;
            }
          }
          const toolbar = pageRoot.querySelector(".v-Toolbar");
          return toolbar instanceof globalThis.HTMLElement ? toolbar : null;
        }
        function findToolbarNearThread(pageRoot, threadRoot) {
          const threadToolbar = threadRoot.querySelector(".v-Toolbar");
          if (threadToolbar instanceof globalThis.HTMLElement) {
            return threadToolbar;
          }
          for (let node = threadRoot; node && node !== pageRoot; node = node.parentElement) {
            for (let sibling = node.previousElementSibling; sibling; sibling = sibling.previousElementSibling) {
              if (!(sibling instanceof globalThis.HTMLElement)) {
                continue;
              }
              if (sibling.matches(".v-Toolbar")) {
                return sibling;
              }
              const nestedToolbar = sibling.querySelector(".v-Toolbar");
              if (nestedToolbar instanceof globalThis.HTMLElement) {
                return nestedToolbar;
              }
            }
          }
          return null;
        }
        function findPrimaryThreadRoot(pageRoot) {
          if (!(pageRoot instanceof globalThis.HTMLElement)) {
            return null;
          }
          const candidates = Array.from(pageRoot.querySelectorAll(".v-Page-content .v-Thread")).filter(
            (candidate) => candidate instanceof globalThis.HTMLElement
          );
          if (candidates.length === 0) {
            return null;
          }
          let bestCandidate = null;
          let bestScore = Number.NEGATIVE_INFINITY;
          candidates.forEach((candidate, index) => {
            const title = candidate.querySelector(".v-Thread-title h1");
            const titleText = normalizeIdentityValue(title?.textContent || "", 320);
            const messageCardCount = candidate.querySelectorAll(".v-MessageCard.app-contentCard").length;
            const rect = typeof candidate.getBoundingClientRect === "function" ? candidate.getBoundingClientRect() : { width: 1, height: 1 };
            const isVisible = rect.width > 0 || rect.height > 0;
            const score = (titleText ? 1e3 : 0) + (isVisible ? 100 : 0) + Math.min(messageCardCount, 20) * 10 + index;
            if (score >= bestScore) {
              bestScore = score;
              bestCandidate = candidate;
            }
          });
          return bestCandidate instanceof globalThis.HTMLElement ? bestCandidate : null;
        }
        function findTitleElement(threadRoot) {
          if (!(threadRoot instanceof globalThis.HTMLElement)) {
            return null;
          }
          const title = threadRoot.querySelector(".v-Thread-title h1");
          return title instanceof globalThis.HTMLElement ? title : null;
        }
        function getTitleText(threadRoot) {
          return normalizeIdentityValue(findTitleElement(threadRoot)?.textContent || "", 320);
        }
        function ensureThreadState(threadRoot) {
          let state = getExistingThreadState(threadRoot);
          if (state) {
            return state;
          }
          const staleState = getExistingThreadState(threadRoot, { includeStale: true });
          if (staleState) {
            runtimeState.deactivateThreadState(staleState);
          }
          state = runtimeState.createThreadState();
          state.root = threadRoot;
          runtimeState.state.threadStates.set(threadRoot, state);
          return state;
        }
        function getExistingThreadState(threadRoot, { includeStale = false } = {}) {
          if (!(threadRoot instanceof globalThis.HTMLElement)) {
            return null;
          }
          const state = runtimeState.state.threadStates.get(threadRoot) || null;
          if (!includeStale && state && !runtimeState.isThreadStateCurrent(state)) {
            return null;
          }
          return state;
        }
        function resetThreadState(threadRoot) {
          const threadState = getExistingThreadState(threadRoot, { includeStale: true });
          if (threadState) {
            runtimeState.deactivateThreadState(threadState);
            runtimeState.state.threadStates.delete(threadRoot);
          }
        }
        function isThreadActive(threadRoot) {
          return getExistingThreadState(threadRoot)?.active === true;
        }
        function ensureTranslateDivider(toolbar) {
          const existingDivider = toolbar.querySelector(".fmt-translate-divider");
          if (existingDivider instanceof globalThis.HTMLElement) {
            return existingDivider;
          }
          const divider = document.createElement("span");
          divider.className = "v-Toolbar-divider fmt-translate-divider";
          return divider;
        }
        function ensureTranslateButton(toolbar, onTranslateClick) {
          const buttons = Array.from(toolbar.querySelectorAll(`.${constants.BUTTON_CLASS}`)).filter((candidate) => {
            return candidate instanceof globalThis.HTMLElement && candidate.closest(".v-Toolbar") === toolbar;
          });
          const [existingButton, ...duplicates] = buttons;
          duplicates.forEach((duplicate) => duplicate.remove());
          const button = existingButton instanceof globalThis.HTMLButtonElement ? existingButton : toolbarButtonApi2.createToolbarButton({
            document,
            constants,
            i18n
          });
          const divider = ensureTranslateDivider(toolbar);
          bindTranslateButton(button, onTranslateClick);
          toolbarButtonApi2.placeToolbarButton(toolbar, button, divider);
          return button;
        }
        function removeToolbarTranslateButtons(toolbar) {
          if (!(toolbar instanceof globalThis.HTMLElement)) {
            return;
          }
          toolbar.querySelectorAll(`.${constants.BUTTON_CLASS}`).forEach((button) => {
            if (!(button instanceof globalThis.HTMLElement)) {
              return;
            }
            unbindTranslateButton(button);
            toolbarButtonThreadRoots.delete(button);
            button.remove();
          });
          toolbar.querySelectorAll(".fmt-translate-divider").forEach((divider) => {
            if (divider instanceof globalThis.HTMLElement) {
              divider.remove();
            }
          });
        }
        function bindTranslateButton(button, onTranslateClick) {
          if (!(button instanceof globalThis.HTMLButtonElement) || typeof onTranslateClick !== "function") {
            return;
          }
          const binding = toolbarButtonClickHandlers.get(button) || null;
          if (binding?.onTranslateClick === onTranslateClick) {
            return;
          }
          if (typeof binding?.clickHandler === "function") {
            button.removeEventListener("click", binding.clickHandler);
          }
          if (typeof binding?.mouseUpHandler === "function") {
            button.removeEventListener("mouseup", binding.mouseUpHandler);
          }
          const activationState = {
            lastMouseUpTimeStamp: Number.NEGATIVE_INFINITY
          };
          const invokeTranslateClick = (event) => {
            if (event?.[TRANSLATE_CLICK_HANDLED]) {
              return;
            }
            event[TRANSLATE_CLICK_HANDLED] = true;
            void onTranslateClick({
              currentTarget: button,
              type: event?.type || "click",
              originalEvent: event
            });
          };
          const mouseUpHandler = (event) => {
            if (!isPrimaryMouseActivation(event)) {
              return;
            }
            activationState.lastMouseUpTimeStamp = getEventTimeStamp(event);
            invokeTranslateClick(event);
          };
          const clickHandler = (event) => {
            if (shouldSkipClickAfterMouseUp(event, activationState.lastMouseUpTimeStamp)) {
              event[TRANSLATE_CLICK_HANDLED] = true;
              return;
            }
            invokeTranslateClick(event);
          };
          button.addEventListener("mouseup", mouseUpHandler);
          button.addEventListener("click", clickHandler);
          toolbarButtonClickHandlers.set(button, {
            clickHandler,
            mouseUpHandler,
            onTranslateClick
          });
        }
        function unbindTranslateButton(button) {
          if (!(button instanceof globalThis.HTMLButtonElement)) {
            return;
          }
          const binding = toolbarButtonClickHandlers.get(button) || null;
          if (typeof binding?.clickHandler === "function") {
            button.removeEventListener("click", binding.clickHandler);
          }
          if (typeof binding?.mouseUpHandler === "function") {
            button.removeEventListener("mouseup", binding.mouseUpHandler);
          }
          toolbarButtonClickHandlers.delete(button);
        }
        function isManagedTranslateButton(button) {
          return button instanceof globalThis.HTMLButtonElement && toolbarButtonClickHandlers.has(button);
        }
        function isPrimaryMouseActivation(event) {
          if (!event) {
            return false;
          }
          return event.button === void 0 || event.button === 0;
        }
        function getEventTimeStamp(event) {
          return typeof event?.timeStamp === "number" ? event.timeStamp : Number.NEGATIVE_INFINITY;
        }
        function shouldSkipClickAfterMouseUp(event, lastMouseUpTimeStamp) {
          if (!event || event.detail <= 0 || !Number.isFinite(lastMouseUpTimeStamp)) {
            return false;
          }
          const clickTimeStamp = getEventTimeStamp(event);
          return clickTimeStamp >= lastMouseUpTimeStamp && clickTimeStamp - lastMouseUpTimeStamp <= MOUSEUP_CLICK_DEDUP_WINDOW_MS;
        }
        function injectButtons(root, onTranslateClick) {
          runtimeState.withObserverMuted(() => {
            const pageRoots = pageLocator2.collectPageRoots(root);
            for (const pageRoot of pageRoots) {
              if (!(pageRoot instanceof globalThis.HTMLElement)) {
                continue;
              }
              const threadRoot = findPrimaryThreadRoot(pageRoot);
              const toolbar = findToolbarForPageRoot(pageRoot, threadRoot);
              const threadTitle = threadRoot instanceof globalThis.HTMLElement ? threadRoot.querySelector(".v-Thread-title") : null;
              if (!(toolbar instanceof globalThis.HTMLElement) || !(threadTitle instanceof globalThis.HTMLElement)) {
                removeToolbarTranslateButtons(toolbar);
                continue;
              }
              const button = ensureTranslateButton(toolbar, onTranslateClick);
              toolbarButtonThreadRoots.set(button, threadRoot);
              updateButtonState(button, isThreadActive(threadRoot));
            }
          });
        }
        function getThreadMessageEntries(threadRoot) {
          const cards = Array.from(threadRoot.querySelectorAll(".v-MessageCard.app-contentCard")).filter(
            (card) => card instanceof globalThis.HTMLElement
          );
          return cards.map((card) => ({
            card,
            header: card.querySelector(".v-MessageCard-header"),
            messageNode: findMessageNodeForCard(card)
          }));
        }
        function findMessageNodeForCard(card) {
          if (!(card instanceof globalThis.HTMLElement)) {
            return null;
          }
          const nestedMessage = card.querySelector(".v-Message");
          if (nestedMessage instanceof globalThis.HTMLElement) {
            return nestedMessage;
          }
          for (let sibling = card.nextElementSibling; sibling; sibling = sibling.nextElementSibling) {
            if (!(sibling instanceof globalThis.HTMLElement)) {
              continue;
            }
            if (sibling.matches(".v-MessageCard.app-contentCard")) {
              break;
            }
            if (sibling.matches(".v-Message")) {
              return sibling;
            }
            const nestedSiblingMessage = sibling.querySelector(".v-Message");
            if (nestedSiblingMessage instanceof globalThis.HTMLElement) {
              return nestedSiblingMessage;
            }
          }
          return null;
        }
        function getMessageDataId(entry) {
          return normalizeIdentityValue(
            entry.messageNode?.getAttribute("data-message-id") || entry.card?.getAttribute("data-message-id") || "",
            320
          );
        }
        function getMessageDetailsFingerprint(messageNode) {
          if (!(messageNode instanceof globalThis.HTMLElement)) {
            return "";
          }
          const detailsList = messageNode.querySelector(".v-Message-detailsList");
          if (detailsList instanceof globalThis.HTMLElement) {
            return normalizeIdentityValue(detailsList.textContent || "", 400);
          }
          return "";
        }
        function getMessageInstanceId(entry) {
          return normalizeIdentityValue(
            entry.card?.getAttribute(constants.MESSAGE_INSTANCE_ATTRIBUTE) || entry.messageNode?.getAttribute(constants.MESSAGE_INSTANCE_ATTRIBUTE) || "",
            320
          );
        }
        function getOrAssignMessageInstanceId(entry) {
          const existingId = getMessageInstanceId(entry);
          if (existingId) {
            return existingId;
          }
          const instanceId = runtimeState.nextMessageInstanceId();
          runtimeState.withObserverMuted(() => {
            if (entry.card instanceof globalThis.HTMLElement) {
              entry.card.setAttribute(constants.MESSAGE_INSTANCE_ATTRIBUTE, instanceId);
            }
            if (entry.messageNode instanceof globalThis.HTMLElement) {
              entry.messageNode.setAttribute(constants.MESSAGE_INSTANCE_ATTRIBUTE, instanceId);
            }
          });
          return instanceId;
        }
        function buildMessageIdentity(entry, bodyElement, { textCache } = {}) {
          const dataMessageId = getMessageDataId(entry);
          const detailsFingerprint = getMessageDetailsFingerprint(entry.messageNode);
          const fromText = normalizeIdentityValue(
            entry.card?.querySelector(".v-MessageCard-from")?.getAttribute("title") || entry.card?.querySelector(".v-MessageCard-from")?.textContent || ""
          );
          const toText = normalizeIdentityValue(
            entry.card?.querySelector(".v-Message-toName")?.getAttribute("title") || entry.card?.querySelector(".v-Message-toName")?.textContent || ""
          );
          const exactDateText = normalizeIdentityValue(entry.card?.querySelector(".v-MessageCard-time")?.getAttribute("title") || "");
          const bodyTextSource = bodyElement instanceof globalThis.HTMLElement ? bodyElement : entry.messageNode instanceof globalThis.HTMLElement ? entry.messageNode : null;
          const bodyExcerpt = !dataMessageId && !detailsFingerprint && bodyTextSource ? normalizeIdentityValue(segmenter.extractBodyText(bodyTextSource, { textCache }).slice(0, 200), 200) : "";
          return buildIdentity("message", [
            dataMessageId,
            detailsFingerprint,
            fromText,
            toText,
            exactDateText,
            bodyExcerpt
          ], entry.card?.id || entry.messageNode?.id || "message");
        }
        function collectMessageDescriptors(threadRoot, { textCache } = {}) {
          return getThreadMessageEntries(threadRoot).map((entry) => {
            const instanceId = getOrAssignMessageInstanceId(entry);
            const body = findBodyElement(entry.messageNode, { textCache });
            const contentRoot = body instanceof globalThis.HTMLElement ? findPrimaryContentRoot(body, { textCache }) || body : null;
            const identity = buildMessageIdentity(entry, body, { textCache });
            return {
              instanceId,
              identity,
              card: entry.card,
              header: entry.header,
              messageNode: entry.messageNode,
              body,
              contentRoot
            };
          });
        }
        function reconcileMessageStates(threadState, descriptors) {
          const liveKeys = /* @__PURE__ */ new Set();
          for (const descriptor of descriptors) {
            descriptor.key = `${threadState.key}::${descriptor.instanceId}`;
            liveKeys.add(descriptor.key);
            let messageState = threadState.messages.get(descriptor.key);
            if (!messageState) {
              messageState = runtimeState.createMessageState({
                key: descriptor.key,
                identity: descriptor.identity,
                instanceId: descriptor.instanceId
              });
              threadState.messages.set(descriptor.key, messageState);
            }
            const identityChanged = messageState.identity && messageState.identity !== descriptor.identity;
            if (identityChanged) {
              messageState.status = "idle";
              messageState.segmentSignature = "";
              messageState.translatedSegments = null;
              messageState.error = "";
              messageState.requestSerial += 1;
            }
            messageState.identity = descriptor.identity;
            messageState.instanceId = descriptor.instanceId;
            descriptor.state = messageState;
          }
          for (const key of threadState.messages.keys()) {
            if (!liveKeys.has(key)) {
              threadState.messages.delete(key);
            }
          }
        }
        function findMessageEntryByInstanceId(threadRoot, instanceId) {
          if (!(threadRoot instanceof globalThis.HTMLElement) || !instanceId) {
            return null;
          }
          return getThreadMessageEntries(threadRoot).find((entry) => {
            return getMessageInstanceId(entry) === instanceId;
          }) || null;
        }
        function findLiveMessageElements(threadRoot, descriptor, { textCache } = {}) {
          const directBody = findBodyElement(descriptor.messageNode, { textCache });
          if (directBody instanceof globalThis.HTMLElement && directBody.isConnected) {
            return {
              body: directBody,
              contentRoot: findPrimaryContentRoot(directBody, { textCache }) || directBody
            };
          }
          const liveEntry = findMessageEntryByInstanceId(threadRoot, descriptor.state?.instanceId || descriptor.instanceId);
          if (!liveEntry) {
            return {
              body: null,
              contentRoot: null
            };
          }
          const liveBody = findBodyElement(liveEntry.messageNode, { textCache });
          return {
            body: liveBody instanceof globalThis.HTMLElement ? liveBody : null,
            contentRoot: liveBody instanceof globalThis.HTMLElement ? findPrimaryContentRoot(liveBody, { textCache }) || liveBody : null
          };
        }
        function findBodyElement(messageNode, { textCache } = {}) {
          if (!(messageNode instanceof globalThis.HTMLElement)) {
            return null;
          }
          const explicitSelectors = [
            ".v-Message-body",
            ".v-Message-bodyContents",
            ".v-MessageBody",
            ".u-containSelection.v-Message-body"
          ];
          for (const selector of explicitSelectors) {
            const found = messageNode.matches(selector) ? messageNode : messageNode.querySelector(selector);
            if (found instanceof globalThis.HTMLElement && policies.isExplicitBodyText(segmenter.extractBodyText(found, { textCache }))) {
              return found;
            }
          }
          let bestCandidate = null;
          let bestScore = 0;
          for (const candidate of messageNode.querySelectorAll(".u-article, article, section, pre, blockquote, div")) {
            if (!(candidate instanceof globalThis.HTMLElement)) {
              continue;
            }
            if (candidate.closest(`.${constants.INLINE_TRANSLATION_CLASS}`)) {
              continue;
            }
            const text = segmenter.extractBodyText(candidate, { textCache });
            if (!policies.isCandidateBodyText(text)) {
              continue;
            }
            const rect = candidate.getBoundingClientRect();
            if (rect.width === 0 || rect.height === 0) {
              continue;
            }
            const score = text.length - candidate.querySelectorAll("button, input, svg").length * 20;
            if (score > bestScore) {
              bestScore = score;
              bestCandidate = candidate;
            }
          }
          if (!(bestCandidate instanceof globalThis.HTMLElement)) {
            return null;
          }
          return bestCandidate.closest(".v-Message-body, .v-Message-bodyContents, .v-MessageBody") || bestCandidate;
        }
        function isMessageBodyDeferred(descriptor) {
          const card = descriptor?.card;
          if (!(card instanceof globalThis.HTMLElement)) {
            return false;
          }
          if (card.classList.contains("is-collapsed")) {
            return true;
          }
          return Boolean(card.querySelector(".v-MessageCard-loadingBody"));
        }
        function findPrimaryContentRoot(container, { textCache } = {}) {
          if (!(container instanceof globalThis.HTMLElement)) {
            return null;
          }
          const preferredSelectors = [".u-article", "article", "pre", "blockquote", "section", "div"];
          let bestCandidate = null;
          let bestScore = 0;
          for (const selector of preferredSelectors) {
            for (const candidate of container.querySelectorAll(selector)) {
              if (!(candidate instanceof globalThis.HTMLElement)) {
                continue;
              }
              if (candidate.closest(`.${constants.INLINE_TRANSLATION_CLASS}`)) {
                continue;
              }
              const text = segmenter.extractBodyText(candidate, { textCache });
              if (text.length < 20) {
                continue;
              }
              if (text.length > bestScore) {
                bestScore = text.length;
                bestCandidate = candidate;
              }
            }
          }
          return bestCandidate;
        }
        function syncThreadButtons(threadRoot, isActive) {
          if (!(threadRoot instanceof globalThis.HTMLElement)) {
            return;
          }
          threadRoot.querySelectorAll(`.${constants.BUTTON_CLASS}`).forEach((button) => {
            if (button instanceof globalThis.HTMLElement) {
              updateButtonState(button, isActive);
            }
          });
          const pageRoot = pageLocator2.findPageRoot(threadRoot);
          const toolbar = findToolbarForPageRoot(pageRoot, threadRoot);
          if (!(toolbar instanceof globalThis.HTMLElement)) {
            return;
          }
          const candidates = toolbar.querySelectorAll(`.${constants.BUTTON_CLASS}`);
          for (const candidate of candidates) {
            if (!(candidate instanceof globalThis.HTMLElement)) {
              continue;
            }
            if (toolbarButtonThreadRoots.get(candidate) === threadRoot) {
              updateButtonState(candidate, isActive);
            }
          }
        }
        function updateButtonState(button, isActive) {
          runtimeState.withObserverMuted(() => {
            toolbarButtonApi2.updateToolbarButtonState(button, isActive, { constants, i18n });
          });
        }
        function pruneDetachedThreadStates() {
          for (const threadState of Array.from(runtimeState.state.activeThreadStates)) {
            if (threadState.root instanceof globalThis.HTMLElement && threadState.root.isConnected) {
              continue;
            }
            runtimeState.deactivateThreadState(threadState);
            if (threadState.root instanceof globalThis.HTMLElement) {
              runtimeState.state.threadStates.delete(threadState.root);
            }
          }
        }
        function collectDetachedActiveThreadRoots() {
          const roots = [];
          for (const threadState of Array.from(runtimeState.state.activeThreadStates)) {
            if (threadState.root instanceof globalThis.HTMLElement && !threadState.root.isConnected) {
              roots.push(threadState.root);
            }
          }
          return roots;
        }
        function clearThreadDomState(root) {
          if (!root || typeof root.querySelectorAll !== "function") {
            return;
          }
          runtimeState.withObserverMuted(() => {
            root.querySelectorAll(`[${constants.MESSAGE_INSTANCE_ATTRIBUTE}], [${constants.SEGMENT_ATTRIBUTE}]`).forEach((node) => {
              if (!(node instanceof globalThis.HTMLElement)) {
                return;
              }
              node.removeAttribute(constants.MESSAGE_INSTANCE_ATTRIBUTE);
              node.removeAttribute(constants.SEGMENT_ATTRIBUTE);
            });
          });
        }
        return {
          findThreadRoot,
          findPrimaryThreadRoot,
          findToolbarForPageRoot,
          findTitleElement,
          getTitleText,
          ensureThreadState,
          getExistingThreadState,
          resetThreadState,
          isThreadActive,
          injectButtons,
          getThreadMessageEntries,
          collectMessageDescriptors,
          reconcileMessageStates,
          findLiveMessageElements,
          findBodyElement,
          findPrimaryContentRoot,
          isMessageBodyDeferred,
          syncThreadButtons,
          updateButtonState,
          isManagedTranslateButton,
          collectDetachedActiveThreadRoots,
          pruneDetachedThreadStates,
          clearThreadDomState
        };
      }
      module.exports = { createThreadDom: createThreadDom2 };
    }
  });

  // src/fastmail/text-policies.js
  var require_text_policies = __commonJS({
    "src/fastmail/text-policies.js"(exports, module) {
      var CANDIDATE_BODY_MIN_LENGTH = 3;
      function normalizeText(text) {
        return String(text || "").replace(/\s+/g, " ").trim();
      }
      function hasTranslatableText(text) {
        const compact = normalizeText(text);
        if (!compact) {
          return false;
        }
        if (/^https?:\/\/\S+$/i.test(compact)) {
          return false;
        }
        return /\p{Letter}/u.test(compact);
      }
      function isExplicitBodyText(text) {
        return hasTranslatableText(text);
      }
      function isCandidateBodyText(text) {
        const compact = normalizeText(text);
        if (compact.length < CANDIDATE_BODY_MIN_LENGTH) {
          return false;
        }
        return hasTranslatableText(compact);
      }
      module.exports = {
        hasTranslatableText,
        isExplicitBodyText,
        isCandidateBodyText,
        normalizeText
      };
    }
  });

  // src/platform/tm-api.js
  var require_tm_api = __commonJS({
    "src/platform/tm-api.js"(exports, module) {
      var REQUIRED_CAPABILITIES = Object.freeze([
        "GM_getValue",
        "GM_setValue",
        "GM_registerMenuCommand",
        "GM_xmlhttpRequest"
      ]);
      function validateCapability(scope, capability) {
        if (typeof scope?.[capability] !== "function") {
          throw new Error(`Missing Tampermonkey capability: ${capability}`);
        }
      }
      function createTmApi2(scope = globalThis) {
        for (const capability of REQUIRED_CAPABILITIES) {
          validateCapability(scope, capability);
        }
        return {
          getValue(key, fallback) {
            return scope.GM_getValue(key, fallback);
          },
          setValue(key, value) {
            return scope.GM_setValue(key, value);
          },
          registerMenuCommand(label, handler) {
            return scope.GM_registerMenuCommand(label, handler);
          },
          xmlHttpRequest(details) {
            return scope.GM_xmlhttpRequest(details);
          }
        };
      }
      module.exports = { createTmApi: createTmApi2 };
    }
  });

  // src/platform/xhr.js
  var require_xhr = __commonJS({
    "src/platform/xhr.js"(exports, module) {
      function createXhr2({ tmApi }) {
        function isSuccessStatus(status) {
          return typeof status === "number" && status >= 200 && status < 300;
        }
        function createHttpStatusError(response) {
          const error = new Error(`HTTP ${response?.status || 0}`);
          error.status = response?.status;
          error.statusText = response?.statusText;
          error.responseText = response?.responseText;
          error.finalUrl = response?.finalUrl;
          error.context = response?.context;
          error.response = response;
          return error;
        }
        return {
          request(details) {
            let requestHandle = null;
            const promise = new Promise((resolve, reject) => {
              requestHandle = tmApi.xmlHttpRequest({
                ...details,
                onload(response) {
                  if (isSuccessStatus(response?.status)) {
                    resolve(response);
                    return;
                  }
                  reject(createHttpStatusError(response));
                },
                onerror: reject,
                ontimeout: reject,
                onabort: reject
              });
            });
            promise.abort = () => {
              if (requestHandle && typeof requestHandle.abort === "function") {
                requestHandle.abort();
              }
            };
            return promise;
          }
        };
      }
      module.exports = { createXhr: createXhr2 };
    }
  });

  // src/storage/settings-store.js
  var require_settings_store = __commonJS({
    "src/storage/settings-store.js"(exports, module) {
      function assertConfig(condition, message) {
        if (!condition) {
          throw new Error(message);
        }
      }
      function createSettingsStore2({ tmApi, defaults, languages, providers }) {
        const SETTINGS_KEY = "settings";
        const TARGET_LANGUAGE_KEY = "targetLanguage";
        const PREFERRED_PROVIDER_KEY = "preferredProvider";
        assertConfig(tmApi && typeof tmApi.getValue === "function", "tmApi.getValue must be a function");
        assertConfig(tmApi && typeof tmApi.setValue === "function", "tmApi.setValue must be a function");
        assertConfig(defaults && typeof defaults.targetLanguage === "string", "defaults.targetLanguage must be a string");
        assertConfig(defaults && typeof defaults.preferredProvider === "string", "defaults.preferredProvider must be a string");
        assertConfig(Array.isArray(languages) && languages.length > 0, "languages must be a non-empty array");
        assertConfig(Array.isArray(providers) && providers.length > 0, "providers must be a non-empty array");
        const supportedLanguages = new Set(languages.map((language) => language.id));
        const supportedProviders = new Set(providers.map((provider) => provider.id));
        assertConfig(supportedLanguages.has(defaults.targetLanguage), "defaults.targetLanguage must exist in languages");
        assertConfig(supportedProviders.has(defaults.preferredProvider), "defaults.preferredProvider must exist in providers");
        function normalizeTargetLanguage(value) {
          return supportedLanguages.has(value) ? value : defaults.targetLanguage;
        }
        function normalizePreferredProvider(value) {
          return supportedProviders.has(value) ? value : defaults.preferredProvider;
        }
        function normalizeSettingsSnapshot(value) {
          return {
            targetLanguage: normalizeTargetLanguage(value?.targetLanguage),
            preferredProvider: normalizePreferredProvider(value?.preferredProvider)
          };
        }
        function isSettingsSnapshot(value) {
          return Boolean(value) && typeof value === "object" && !Array.isArray(value);
        }
        async function writeSettingsSnapshot(value) {
          const normalizedValue = normalizeSettingsSnapshot(value);
          await tmApi.setValue(SETTINGS_KEY, normalizedValue);
          return normalizedValue;
        }
        async function readLegacySettingsSnapshot() {
          const [targetLanguage, preferredProvider] = await Promise.all([
            tmApi.getValue(TARGET_LANGUAGE_KEY, defaults.targetLanguage),
            tmApi.getValue(PREFERRED_PROVIDER_KEY, defaults.preferredProvider)
          ]);
          return normalizeSettingsSnapshot({ targetLanguage, preferredProvider });
        }
        async function resolveSettingsSnapshot({ repair }) {
          const storedSnapshot = await tmApi.getValue(SETTINGS_KEY, void 0);
          if (isSettingsSnapshot(storedSnapshot)) {
            const normalizedSnapshot = normalizeSettingsSnapshot(storedSnapshot);
            const needsRepair = normalizedSnapshot.targetLanguage !== storedSnapshot.targetLanguage || normalizedSnapshot.preferredProvider !== storedSnapshot.preferredProvider;
            if (repair && needsRepair) {
              await writeSettingsSnapshot(normalizedSnapshot);
            }
            return normalizedSnapshot;
          }
          if (typeof storedSnapshot !== "undefined") {
            const defaultSnapshot = {
              targetLanguage: defaults.targetLanguage,
              preferredProvider: defaults.preferredProvider
            };
            if (repair) {
              await writeSettingsSnapshot(defaultSnapshot);
            }
            return defaultSnapshot;
          }
          const legacySnapshot = await readLegacySettingsSnapshot();
          if (repair) {
            await writeSettingsSnapshot(legacySnapshot);
          }
          return legacySnapshot;
        }
        async function readSettingsSnapshot() {
          return resolveSettingsSnapshot({ repair: true });
        }
        return {
          async getTargetLanguage() {
            return (await readSettingsSnapshot()).targetLanguage;
          },
          async setTargetLanguage(value) {
            const settings = await resolveSettingsSnapshot({ repair: false });
            return (await writeSettingsSnapshot({
              ...settings,
              targetLanguage: value
            })).targetLanguage;
          },
          async getPreferredProvider() {
            return (await readSettingsSnapshot()).preferredProvider;
          },
          async setPreferredProvider(value) {
            const settings = await resolveSettingsSnapshot({ repair: false });
            return (await writeSettingsSnapshot({
              ...settings,
              preferredProvider: value
            })).preferredProvider;
          },
          async getSettings() {
            return readSettingsSnapshot();
          },
          async setSettings(nextSettings) {
            return writeSettingsSnapshot(nextSettings);
          }
        };
      }
      module.exports = { createSettingsStore: createSettingsStore2 };
    }
  });

  // src/core/errors.js
  var require_errors = __commonJS({
    "src/core/errors.js"(exports, module) {
      var ERROR_CODES = Object.freeze({
        EDGE_AUTH_FAILED: "edge_auth_failed",
        EDGE_TRANSLATE_FAILED: "edge_translate_failed",
        GOOGLE_TRANSLATE_FAILED: "google_translate_failed",
        UNSUPPORTED_TARGET_LANGUAGE: "unsupported_target_language",
        NO_TRANSLATOR_AVAILABLE: "no_translator_available",
        TRANSLATOR_CONTRACT_VIOLATION: "translator_contract_violation",
        TRANSLATION_CANCELLED: "translation_cancelled"
      });
      function createError(code, message, metadata = {}, cause) {
        const error = new Error(message || code);
        error.code = code;
        error.metadata = metadata;
        if (cause !== void 0) {
          error.cause = cause;
        }
        return error;
      }
      function isStableError(error) {
        return Boolean(error && typeof error.code === "string" && Object.values(ERROR_CODES).includes(error.code));
      }
      function isAbortLikeError(error) {
        return Boolean(
          error && (error.code === ERROR_CODES.TRANSLATION_CANCELLED || error.name === "AbortError" || error.type === "abort" || error.error === "abort")
        );
      }
      function normalizeError(code, message, cause, metadata = {}) {
        if (isStableError(cause)) {
          return cause;
        }
        if (isAbortLikeError(cause)) {
          return createError(
            ERROR_CODES.TRANSLATION_CANCELLED,
            "Translation request was cancelled.",
            metadata,
            cause
          );
        }
        return createError(
          code,
          message,
          {
            ...metadata,
            status: typeof cause?.status === "number" ? cause.status : metadata.status,
            statusText: typeof cause?.statusText === "string" ? cause.statusText : metadata.statusText,
            causeMessage: typeof cause?.message === "string" ? cause.message : void 0
          },
          cause
        );
      }
      function attachAbort(promise, abort) {
        promise.abort = typeof abort === "function" ? abort : () => {
        };
        return promise;
      }
      module.exports = { ERROR_CODES, attachAbort, createError, normalizeError };
    }
  });

  // src/translation/edge-auth.js
  var require_edge_auth = __commonJS({
    "src/translation/edge-auth.js"(exports, module) {
      var {
        ERROR_CODES,
        attachAbort,
        createError,
        normalizeError
      } = require_errors();
      var { createNoopDiagnostics: createNoopDiagnostics2 } = require_diagnostics();
      var EDGE_FALLBACK_HEADERS = Object.freeze({
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0",
        "sec-ch-ua": '"Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"',
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": '"Windows"'
      });
      function createEdgeAuth2({ xhr, now = Date.now, diagnostics = createNoopDiagnostics2() }) {
        let cache = null;
        let inflight = null;
        function isJwtLike(token) {
          return typeof token === "string" && /^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+$/.test(token);
        }
        function decodeBase64Url(value) {
          const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
          const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
          if (typeof globalThis.atob !== "function") {
            throw new Error("base64 decoder is unavailable");
          }
          const binary = globalThis.atob(padded);
          let percentEncoded = "";
          for (let index = 0; index < binary.length; index += 1) {
            percentEncoded += `%${binary.charCodeAt(index).toString(16).padStart(2, "0")}`;
          }
          return decodeURIComponent(percentEncoded);
        }
        function getExpiry(token) {
          const [, payload = ""] = token.split(".");
          try {
            const parsed = JSON.parse(decodeBase64Url(payload));
            return typeof parsed.exp === "number" ? parsed.exp * 1e3 : now() + 6e5;
          } catch {
            return now() + 6e5;
          }
        }
        function getFallbackHeaders() {
          return { ...EDGE_FALLBACK_HEADERS };
        }
        return {
          getToken() {
            if (cache && cache.expiresAt > now() + 6e4) {
              return attachAbort(Promise.resolve(cache.token), () => {
              });
            }
            if (!inflight) {
              let activeAbort = () => {
              };
              const operation = (async () => {
                let request = xhr.request({
                  method: "GET",
                  url: "https://edge.microsoft.com/translate/auth",
                  nocache: true,
                  timeout: 8e3,
                  headers: {
                    Accept: "*/*"
                  }
                });
                diagnostics.record("edge-auth.request.start", {
                  attempt: 1,
                  timeoutMs: 8e3,
                  usedFallbackHeaders: false
                });
                activeAbort = () => request.abort?.();
                let response = await request;
                let token = String(response?.responseText || "").trim();
                diagnostics.record("edge-auth.request.result", {
                  attempt: 1,
                  usedFallbackHeaders: false,
                  status: response?.status || 0,
                  responseTextLength: token.length,
                  isJwtLike: isJwtLike(token)
                });
                if (!isJwtLike(token)) {
                  request = xhr.request({
                    method: "GET",
                    url: "https://edge.microsoft.com/translate/auth",
                    nocache: true,
                    timeout: 8e3,
                    headers: {
                      Accept: "*/*",
                      ...getFallbackHeaders()
                    }
                  });
                  diagnostics.record("edge-auth.request.start", {
                    attempt: 2,
                    timeoutMs: 8e3,
                    usedFallbackHeaders: true
                  });
                  activeAbort = () => request.abort?.();
                  response = await request;
                  token = String(response?.responseText || "").trim();
                  diagnostics.record("edge-auth.request.result", {
                    attempt: 2,
                    usedFallbackHeaders: true,
                    status: response?.status || 0,
                    responseTextLength: token.length,
                    isJwtLike: isJwtLike(token)
                  });
                }
                if (!isJwtLike(token)) {
                  diagnostics.record("edge-auth.token.rejected", {
                    reason: "token-not-jwt-like",
                    tokenLength: token.length
                  });
                  throw createError(ERROR_CODES.EDGE_AUTH_FAILED, "Edge auth token is invalid.");
                }
                cache = {
                  token,
                  expiresAt: getExpiry(token)
                };
                diagnostics.record("edge-auth.token.accepted", {
                  tokenLength: token.length,
                  expiresAt: cache.expiresAt
                });
                return token;
              })().catch((error) => {
                diagnostics.recordError("edge-auth.request.error", error, {
                  requestStage: "edge-auth"
                });
                throw normalizeError(
                  ERROR_CODES.EDGE_AUTH_FAILED,
                  "Edge auth request failed.",
                  error,
                  {
                    requestStage: "edge-auth"
                  }
                );
              }).finally(() => {
                inflight = null;
              });
              inflight = {
                consumerCount: 0,
                abortTransport() {
                  activeAbort();
                },
                promise: operation
              };
            }
            inflight.consumerCount += 1;
            let isSettled = false;
            let isCancelled = false;
            let rejectConsumer = null;
            const sharedState = inflight;
            const consumerPromise = new Promise((resolve, reject) => {
              rejectConsumer = reject;
              sharedState.promise.then(
                (token) => {
                  if (isCancelled) {
                    return;
                  }
                  isSettled = true;
                  resolve(token);
                },
                (error) => {
                  if (isCancelled) {
                    return;
                  }
                  isSettled = true;
                  reject(error);
                }
              );
            });
            return attachAbort(consumerPromise, () => {
              if (isSettled || isCancelled) {
                return;
              }
              isCancelled = true;
              sharedState.consumerCount -= 1;
              if (sharedState.consumerCount === 0) {
                sharedState.abortTransport();
              }
              rejectConsumer(
                createError(ERROR_CODES.TRANSLATION_CANCELLED, "Translation request was cancelled.")
              );
            });
          }
        };
      }
      module.exports = { createEdgeAuth: createEdgeAuth2 };
    }
  });

  // src/translation/edge-translator.js
  var require_edge_translator = __commonJS({
    "src/translation/edge-translator.js"(exports, module) {
      var {
        ERROR_CODES,
        attachAbort,
        createError,
        normalizeError
      } = require_errors();
      var { createNoopDiagnostics: createNoopDiagnostics2 } = require_diagnostics();
      var MAX_SEGMENTS_PER_EDGE_BATCH = 20;
      var MAX_CHARS_PER_EDGE_SEGMENT = 1e4;
      var MAX_CHARS_PER_EDGE_BATCH = 2e4;
      function createSegmentBatches(segments) {
        const batches = [];
        let currentBatch = [];
        let currentBatchChars = 0;
        segments.forEach((text, segmentIndex) => {
          const segmentLength = String(text || "").length;
          if (segmentLength > MAX_CHARS_PER_EDGE_SEGMENT) {
            throw createError(
              ERROR_CODES.EDGE_TRANSLATE_FAILED,
              "Edge translate segment was too long.",
              {
                reason: "edge-segment-too-long",
                segmentIndex,
                segmentLength,
                maxSegmentLength: MAX_CHARS_PER_EDGE_SEGMENT
              }
            );
          }
          const wouldExceedSegmentCount = currentBatch.length >= MAX_SEGMENTS_PER_EDGE_BATCH;
          const wouldExceedCharBudget = currentBatch.length > 0 && currentBatchChars + segmentLength > MAX_CHARS_PER_EDGE_BATCH;
          if (wouldExceedSegmentCount || wouldExceedCharBudget) {
            batches.push(currentBatch);
            currentBatch = [];
            currentBatchChars = 0;
          }
          currentBatch.push(text);
          currentBatchChars += segmentLength;
        });
        if (currentBatch.length > 0) {
          batches.push(currentBatch);
        }
        return batches;
      }
      function createEdgeTranslator2({ edgeAuth, xhr, diagnostics = createNoopDiagnostics2() }) {
        function getTranslatedText(item) {
          const text = item?.translations?.[0]?.text;
          if (typeof text !== "string") {
            throw createError(ERROR_CODES.EDGE_TRANSLATE_FAILED, "Edge translate response was invalid.");
          }
          return text;
        }
        return {
          translateSegments(segments, language) {
            let activeAbort = () => {
            };
            let wasAborted = false;
            const operation = (async () => {
              const batches = createSegmentBatches(segments);
              diagnostics.record("edge-translate.request.start", {
                batchCount: batches.length,
                segmentCount: segments.length,
                targetLanguage: language.id
              });
              const tokenRequest = edgeAuth.getToken();
              activeAbort = () => tokenRequest.abort?.();
              const token = await tokenRequest;
              if (wasAborted) {
                throw createError(ERROR_CODES.TRANSLATION_CANCELLED, "Translation request was cancelled.");
              }
              const translatedSegments = [];
              for (let batchIndex = 0; batchIndex < batches.length; batchIndex += 1) {
                if (wasAborted) {
                  throw createError(ERROR_CODES.TRANSLATION_CANCELLED, "Translation request was cancelled.");
                }
                const batch = batches[batchIndex];
                const translateRequest = xhr.request({
                  method: "POST",
                  url: `https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=${encodeURIComponent(language.microsoft)}`,
                  nocache: true,
                  timeout: 15e3,
                  headers: {
                    "Content-Type": "application/json",
                    Authorization: `Bearer ${token}`
                  },
                  data: JSON.stringify(batch.map((text) => ({ text })))
                });
                activeAbort = () => translateRequest.abort?.();
                if (wasAborted) {
                  activeAbort();
                }
                const response = await translateRequest;
                diagnostics.record("edge-translate.response.received", {
                  batchIndex,
                  status: response?.status || 0,
                  responseTextLength: String(response?.responseText || "").length
                });
                let payload = null;
                try {
                  payload = JSON.parse(response?.responseText || "[]");
                } catch (error) {
                  throw createError(
                    ERROR_CODES.EDGE_TRANSLATE_FAILED,
                    "Edge translate response was invalid.",
                    { batchIndex },
                    error
                  );
                }
                const batchTranslations = Array.isArray(payload) ? payload.map((item) => getTranslatedText(item)) : [];
                diagnostics.record("edge-translate.response.parsed", {
                  batchIndex,
                  translatedSegmentCount: batchTranslations.length
                });
                if (batchTranslations.length !== batch.length) {
                  throw createError(
                    ERROR_CODES.EDGE_TRANSLATE_FAILED,
                    "Edge translated segment count mismatch.",
                    {
                      batchIndex,
                      expectedSegmentCount: batch.length,
                      actualSegmentCount: batchTranslations.length
                    }
                  );
                }
                translatedSegments.push(...batchTranslations);
              }
              return {
                provider: "edge-web",
                providerLabel: "Microsoft Edge(免 Key)",
                targetLanguage: language.id,
                targetLanguageLabel: language.label,
                translatedSegments
              };
            })().catch((error) => {
              diagnostics.recordError("edge-translate.request.error", error, {
                requestStage: "edge-translate",
                segmentCount: segments.length
              });
              throw normalizeError(
                ERROR_CODES.EDGE_TRANSLATE_FAILED,
                "Edge translate request failed.",
                error,
                {
                  requestStage: "edge-translate",
                  segmentCount: segments.length
                }
              );
            });
            return attachAbort(operation, () => {
              wasAborted = true;
              activeAbort();
            });
          }
        };
      }
      module.exports = { createEdgeTranslator: createEdgeTranslator2 };
    }
  });

  // src/translation/google-translator.js
  var require_google_translator = __commonJS({
    "src/translation/google-translator.js"(exports, module) {
      var {
        ERROR_CODES,
        attachAbort,
        createError,
        normalizeError
      } = require_errors();
      var { createNoopDiagnostics: createNoopDiagnostics2 } = require_diagnostics();
      var SEGMENT_CONCURRENCY = 4;
      var MAX_SEGMENTS_PER_REQUEST = 20;
      var MAX_URL_LENGTH = 1800;
      var HTML_ENTITIES = Object.freeze({
        amp: "&",
        lt: "<",
        gt: ">",
        quot: '"',
        apos: "'"
      });
      function decodeHtmlEntities(value) {
        return String(value || "").replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (match, entity) => {
          if (entity[0] === "#") {
            const isHex = entity[1]?.toLowerCase() === "x";
            const digits = isHex ? entity.slice(2) : entity.slice(1);
            const codePoint = Number.parseInt(digits, isHex ? 16 : 10);
            return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match;
          }
          return HTML_ENTITIES[entity.toLowerCase()] || match;
        });
      }
      function extractTranslatedText(payload) {
        const translatedText = Array.isArray(payload?.[0]) ? payload[0].map((item) => Array.isArray(item) && typeof item[0] === "string" ? item[0] : "").join("").trim() : "";
        if (!translatedText) {
          throw createError(ERROR_CODES.GOOGLE_TRANSLATE_FAILED, "Google translate response was invalid.");
        }
        return decodeHtmlEntities(translatedText);
      }
      function mapWithConcurrency(items, limit, mapper) {
        const results = new Array(items.length);
        let nextIndex = 0;
        async function worker() {
          while (nextIndex < items.length) {
            const currentIndex = nextIndex;
            nextIndex += 1;
            results[currentIndex] = await mapper(items[currentIndex], currentIndex);
          }
        }
        return Promise.all(
          Array.from({ length: Math.min(limit, items.length) }, () => worker())
        ).then(() => results);
      }
      function createGoogleTranslateUrl(segment, language) {
        const endpoint = new URL("https://translate.googleapis.com/translate_a/single");
        endpoint.searchParams.set("client", "gtx");
        endpoint.searchParams.set("sl", "auto");
        endpoint.searchParams.set("tl", language.google || language.id);
        endpoint.searchParams.set("dt", "t");
        endpoint.searchParams.set("q", segment);
        return endpoint.toString();
      }
      function createGoogleWebTranslator2({ xhr, diagnostics = createNoopDiagnostics2() }) {
        return {
          translateSegments(segments, language) {
            let wasAborted = false;
            const activeRequests = /* @__PURE__ */ new Set();
            const operation = (async () => {
              diagnostics.record("google-translate.request.start", {
                segmentCount: segments.length,
                targetLanguage: language.id
              });
              if (segments.length > MAX_SEGMENTS_PER_REQUEST) {
                diagnostics.record("google-translate.request.rejected", {
                  reason: "too-many-segments",
                  segmentCount: segments.length,
                  segmentLimit: MAX_SEGMENTS_PER_REQUEST
                });
                throw createError(
                  ERROR_CODES.GOOGLE_TRANSLATE_FAILED,
                  "Google translate request was rejected before transport.",
                  {
                    reason: "too-many-segments",
                    segmentCount: segments.length,
                    segmentLimit: MAX_SEGMENTS_PER_REQUEST
                  }
                );
              }
              const urls = segments.map((segment) => createGoogleTranslateUrl(segment, language));
              const oversizedUrl = urls.find((url) => url.length > MAX_URL_LENGTH);
              if (oversizedUrl) {
                diagnostics.record("google-translate.request.rejected", {
                  reason: "url-too-long",
                  urlLength: oversizedUrl.length,
                  urlLengthLimit: MAX_URL_LENGTH
                });
                throw createError(
                  ERROR_CODES.GOOGLE_TRANSLATE_FAILED,
                  "Google translate request was rejected before transport.",
                  {
                    reason: "url-too-long",
                    urlLength: oversizedUrl.length,
                    urlLengthLimit: MAX_URL_LENGTH
                  }
                );
              }
              const translatedSegments = await mapWithConcurrency(
                urls,
                SEGMENT_CONCURRENCY,
                async (url) => {
                  if (wasAborted) {
                    throw createError(ERROR_CODES.TRANSLATION_CANCELLED, "Translation request was cancelled.");
                  }
                  const request = xhr.request({
                    method: "GET",
                    url,
                    nocache: true,
                    timeout: 15e3
                  });
                  activeRequests.add(request);
                  try {
                    const response = await request;
                    diagnostics.record("google-translate.response.received", {
                      status: response?.status || 0,
                      responseTextLength: String(response?.responseText || "").length
                    });
                    let payload = null;
                    try {
                      payload = JSON.parse(response?.responseText || "null");
                    } catch (error) {
                      throw createError(ERROR_CODES.GOOGLE_TRANSLATE_FAILED, "Google translate response was invalid.", {}, error);
                    }
                    return extractTranslatedText(payload);
                  } finally {
                    activeRequests.delete(request);
                  }
                }
              );
              diagnostics.record("google-translate.response.parsed", {
                translatedSegmentCount: translatedSegments.length
              });
              if (translatedSegments.length !== segments.length) {
                throw createError(ERROR_CODES.GOOGLE_TRANSLATE_FAILED, "Google translated segment count mismatch.");
              }
              return {
                provider: "google-web",
                providerLabel: "Google Web(实验性 / 免费)",
                targetLanguage: language.id,
                targetLanguageLabel: language.label,
                translatedSegments
              };
            })().catch((error) => {
              diagnostics.recordError("google-translate.request.error", error, {
                requestStage: "google-translate",
                segmentCount: segments.length
              });
              throw normalizeError(
                ERROR_CODES.GOOGLE_TRANSLATE_FAILED,
                "Google translate request failed.",
                error,
                {
                  requestStage: "google-translate",
                  segmentCount: segments.length
                }
              );
            });
            return attachAbort(operation, () => {
              wasAborted = true;
              for (const request of Array.from(activeRequests)) {
                request.abort?.();
              }
            });
          }
        };
      }
      module.exports = { createGoogleWebTranslator: createGoogleWebTranslator2 };
    }
  });

  // src/translation/request-registry.js
  var require_request_registry = __commonJS({
    "src/translation/request-registry.js"(exports, module) {
      function createRequestRegistry2() {
        const activeRequests = /* @__PURE__ */ new Map();
        function register(requestId, request) {
          if (!requestId) {
            return request;
          }
          if (request && typeof request.abort === "function") {
            activeRequests.set(requestId, request);
          } else {
            activeRequests.delete(requestId);
          }
          return request;
        }
        function release(requestId) {
          if (requestId) {
            activeRequests.delete(requestId);
          }
        }
        function cancel(requestId) {
          const request = requestId ? activeRequests.get(requestId) : null;
          activeRequests.delete(requestId);
          if (request && typeof request.abort === "function") {
            request.abort();
          }
        }
        function cancelMany(requestIds) {
          for (const requestId of requestIds || []) {
            cancel(requestId);
          }
        }
        function cancelAll() {
          cancelMany(Array.from(activeRequests.keys()));
        }
        return {
          register,
          release,
          cancel,
          cancelMany,
          cancelAll
        };
      }
      module.exports = { createRequestRegistry: createRequestRegistry2 };
    }
  });

  // src/translation/translate-service.js
  var require_translate_service = __commonJS({
    "src/translation/translate-service.js"(exports, module) {
      var {
        ERROR_CODES,
        attachAbort,
        createError
      } = require_errors();
      var { createNoopDiagnostics: createNoopDiagnostics2 } = require_diagnostics();
      function createTranslateService2({
        diagnostics = createNoopDiagnostics2(),
        edgeTranslator,
        languages,
        providers,
        translators
      }) {
        const translatorMap = translators || {
          "edge-web": edgeTranslator
        };
        const providerIds = Array.isArray(providers) && providers.length > 0 ? providers.map((provider) => provider.id) : Object.keys(translatorMap);
        function getProviderOrder(preferredProvider) {
          const primaryProvider = providerIds.includes(preferredProvider) ? preferredProvider : providerIds[0];
          return [
            primaryProvider,
            ...providerIds.filter((providerId) => providerId !== primaryProvider)
          ];
        }
        function isRecoverableProviderError(error) {
          return error?.code === ERROR_CODES.EDGE_AUTH_FAILED || error?.code === ERROR_CODES.EDGE_TRANSLATE_FAILED || error?.code === ERROR_CODES.GOOGLE_TRANSLATE_FAILED;
        }
        function shouldSkipFallbackProvider(fallbackProviderId, preferredProvider) {
          return fallbackProviderId === "google-web" && preferredProvider !== "google-web";
        }
        function assertTranslatorContract(response, providerId, expectedSegmentCount) {
          if (!Array.isArray(response?.translatedSegments)) {
            throw createError(
              ERROR_CODES.TRANSLATOR_CONTRACT_VIOLATION,
              "Translator response did not match the request.",
              {
                providerId,
                expectedSegmentCount,
                actualSegmentCount: null,
                reason: "translated-segments-not-array"
              }
            );
          }
          if (response.translatedSegments.length !== expectedSegmentCount) {
            throw createError(
              ERROR_CODES.TRANSLATOR_CONTRACT_VIOLATION,
              "Translator response did not match the request.",
              {
                providerId,
                expectedSegmentCount,
                actualSegmentCount: response.translatedSegments.length,
                reason: "translated-segment-count-mismatch"
              }
            );
          }
          const invalidSegmentIndex = response.translatedSegments.findIndex((item) => typeof item !== "string");
          if (invalidSegmentIndex !== -1) {
            throw createError(
              ERROR_CODES.TRANSLATOR_CONTRACT_VIOLATION,
              "Translator response did not match the request.",
              {
                providerId,
                expectedSegmentCount,
                actualSegmentCount: response.translatedSegments.length,
                invalidSegmentIndex,
                reason: "translated-segment-not-string"
              }
            );
          }
          return response;
        }
        return {
          getLanguageDefinition(languageId) {
            const language = languages.find((item) => item.id === languageId) || null;
            if (!language) {
              throw createError(
                ERROR_CODES.UNSUPPORTED_TARGET_LANGUAGE,
                "Unsupported target language."
              );
            }
            return language;
          },
          translateSegments(segments, language, options = {}) {
            const normalizedSegments = segments.map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
            let activeAbort = () => {
            };
            let wasAborted = false;
            const operation = (async () => {
              let lastError = null;
              const providerOrder = getProviderOrder(options.preferredProvider);
              for (let index = 0; index < providerOrder.length; index += 1) {
                if (wasAborted) {
                  throw createError(ERROR_CODES.TRANSLATION_CANCELLED, "Translation request was cancelled.");
                }
                const providerId = providerOrder[index];
                const translator = translatorMap[providerId];
                if (!translator || typeof translator.translateSegments !== "function") {
                  continue;
                }
                const request = translator.translateSegments(normalizedSegments, language);
                activeAbort = () => request.abort?.();
                try {
                  const response = await request;
                  return assertTranslatorContract(response, providerId, normalizedSegments.length);
                } catch (error) {
                  lastError = error;
                  const fallbackProviderId = providerOrder[index + 1];
                  if (!fallbackProviderId || !isRecoverableProviderError(error)) {
                    throw error;
                  }
                  if (shouldSkipFallbackProvider(fallbackProviderId, options.preferredProvider)) {
                    diagnostics.record("translate-service.provider.fallback.skipped", {
                      fromProvider: providerId,
                      toProvider: fallbackProviderId,
                      errorCode: error.code || "",
                      reason: "google-web-explicit-only"
                    });
                    throw error;
                  }
                  diagnostics.record("translate-service.provider.fallback", {
                    fromProvider: providerId,
                    toProvider: fallbackProviderId,
                    errorCode: error.code || ""
                  });
                }
              }
              throw lastError || createError(ERROR_CODES.NO_TRANSLATOR_AVAILABLE, "No translator provider was available.");
            })();
            return attachAbort(operation, () => {
              wasAborted = true;
              activeAbort();
            });
          }
        };
      }
      module.exports = { createTranslateService: createTranslateService2 };
    }
  });

  // src/ui/i18n.js
  var require_i18n = __commonJS({
    "src/ui/i18n.js"(exports, module) {
      var MESSAGES = Object.freeze({
        zh: Object.freeze({
          "content.translate": "翻译",
          "content.restoreOriginal": "恢复原文",
          "content.loading": "翻译中…",
          "content.bodyNotFound": "邮件正文尚未加载完成。",
          "content.noSegments": "没有可翻译的内容。",
          "content.translationFailed": "翻译失败。",
          "content.titleTranslationFailed": "标题翻译失败。",
          "content.titleTranslationEmpty": "标题翻译结果为空。",
          "diagnostics.toggleMenu": "FastTrMail 切换诊断日志",
          "settings.menu": "FastTrMail 设置",
          "settings.title": "FastTrMail 设置",
          "settings.targetLanguage": "目标语言",
          "settings.preferredProvider": "优先翻译工具",
          "settings.save": "保存",
          "settings.cancel": "取消",
          "settings.saved": "设置已保存。",
          "settings.saveFailed": "保存失败。",
          "settings.providerNotice": "免 Key 翻译接口可能变更或失效;上线使用时建议保留备用 provider。",
          "providers.edge-web": "Microsoft Edge(免 Key)",
          "providers.google-web": "Google Web(实验性 / 免费)"
        }),
        en: Object.freeze({
          "content.translate": "Translate",
          "content.restoreOriginal": "Restore",
          "content.loading": "Translating…",
          "content.bodyNotFound": "Message body has not loaded yet.",
          "content.noSegments": "No translatable content was found.",
          "content.translationFailed": "Translation failed.",
          "content.titleTranslationFailed": "Subject translation failed.",
          "content.titleTranslationEmpty": "Subject translation was empty.",
          "diagnostics.toggleMenu": "Toggle FastTrMail Diagnostics",
          "settings.menu": "FastTrMail Settings",
          "settings.title": "FastTrMail Settings",
          "settings.targetLanguage": "Target language",
          "settings.preferredProvider": "Preferred translator",
          "settings.save": "Save",
          "settings.cancel": "Cancel",
          "settings.saved": "Settings saved.",
          "settings.saveFailed": "Failed to save settings.",
          "settings.providerNotice": "No-key translation endpoints may change or stop working; keep a fallback provider available for production use.",
          "providers.edge-web": "Microsoft Edge (No Key)",
          "providers.google-web": "Google Web (Experimental / Free)"
        })
      });
      function normalizeLocale(language) {
        return String(language || "").toLowerCase().startsWith("zh") ? "zh" : "en";
      }
      function createI18n2({ navigator = globalThis.navigator } = {}) {
        const locale = normalizeLocale(navigator?.language);
        return {
          locale,
          t(key, replacements = null, overrideLocale = locale) {
            const dictionary = MESSAGES[normalizeLocale(overrideLocale)] || MESSAGES.en;
            let message = dictionary[key] || MESSAGES.en[key] || key;
            if (replacements && typeof replacements === "object") {
              for (const [name, value] of Object.entries(replacements)) {
                message = message.replaceAll(`{${name}}`, String(value));
              }
            }
            return message;
          }
        };
      }
      module.exports = { createI18n: createI18n2, normalizeLocale };
    }
  });

  // src/ui/menu-command.js
  var require_menu_command = __commonJS({
    "src/ui/menu-command.js"(exports, module) {
      var MENU_COMMAND_REGISTRY_KEY = "__fastTrMailTampermonkeyMenuCommands__";
      var DEFAULT_SCRIPT_KEY = "fasttrmail-tampermonkey";
      function getRegistryScope(scope) {
        if (scope && (typeof scope === "object" || typeof scope === "function")) {
          return scope;
        }
        return globalThis;
      }
      function getRegisteredMenuCommands(scope, scriptKey) {
        const registryScope = getRegistryScope(scope);
        if (!registryScope[MENU_COMMAND_REGISTRY_KEY]) {
          registryScope[MENU_COMMAND_REGISTRY_KEY] = /* @__PURE__ */ new Map();
        }
        const registriesByScript = registryScope[MENU_COMMAND_REGISTRY_KEY];
        const resolvedScriptKey = scriptKey || DEFAULT_SCRIPT_KEY;
        if (!registriesByScript.has(resolvedScriptKey)) {
          registriesByScript.set(resolvedScriptKey, /* @__PURE__ */ new Map());
        }
        return registriesByScript.get(resolvedScriptKey);
      }
      function wrapAsyncMenuHandler(handler, { diagnostics, event }) {
        return (...args) => {
          try {
            const result = handler(...args);
            if (result && typeof result.then === "function") {
              return result.catch((error) => {
                diagnostics?.recordError?.(event, error, {});
              });
            }
            return result;
          } catch (error) {
            diagnostics?.recordError?.(event, error, {});
            return void 0;
          }
        };
      }
      function registerSettingsMenuCommand2({ tmApi, i18n, onClick, diagnostics }) {
        return tmApi.registerMenuCommand(
          i18n.t("settings.menu"),
          wrapAsyncMenuHandler(onClick, {
            diagnostics,
            event: "ui.settings.menu.unexpected-error"
          })
        );
      }
      function registerDiagnosticsMenuCommand2({ tmApi, i18n, diagnosticsStore, diagnostics }) {
        const menuLabel = i18n.t("diagnostics.toggleMenu");
        return tmApi.registerMenuCommand(
          menuLabel,
          wrapAsyncMenuHandler(async () => {
            const nextEnabled = await diagnosticsStore.toggleEnabled();
            diagnostics?.setEnabled?.(nextEnabled);
          }, {
            diagnostics,
            event: "ui.diagnostics.menu.unexpected-error"
          })
        );
      }
      function createMenuCommandRegistry2({ tmApi, scope = globalThis, scriptKey = DEFAULT_SCRIPT_KEY }) {
        let activeKey = null;
        const registeredMenuCommands = getRegisteredMenuCommands(scope, scriptKey);
        function registerMenuCommand(label, handler) {
          if (!activeKey) {
            return tmApi.registerMenuCommand(label, handler);
          }
          const existing = registeredMenuCommands.get(activeKey);
          if (existing) {
            existing.label = label;
            existing.handler = handler;
            return existing.commandId;
          }
          const entry = {
            label,
            handler,
            commandId: 0
          };
          entry.commandId = tmApi.registerMenuCommand(label, (...args) => entry.handler(...args));
          registeredMenuCommands.set(activeKey, entry);
          return entry.commandId;
        }
        return {
          tmApi: {
            ...tmApi,
            registerMenuCommand
          },
          ensure(key, register) {
            activeKey = key;
            try {
              return register();
            } finally {
              activeKey = null;
            }
          }
        };
      }
      module.exports = {
        createMenuCommandRegistry: createMenuCommandRegistry2,
        wrapAsyncMenuHandler,
        registerSettingsMenuCommand: registerSettingsMenuCommand2,
        registerDiagnosticsMenuCommand: registerDiagnosticsMenuCommand2
      };
    }
  });

  // src/ui/settings-modal.js
  var require_settings_modal = __commonJS({
    "src/ui/settings-modal.js"(exports, module) {
      function createSettingsModal2({
        constants,
        i18n,
        languages,
        providers,
        runtimeState,
        document = globalThis.document
      }) {
        let overlay = null;
        let restoreFocusTarget = null;
        function isElement(node) {
          const HTMLElementCtor = document.defaultView?.HTMLElement || globalThis.HTMLElement;
          return typeof HTMLElementCtor === "function" && node instanceof HTMLElementCtor;
        }
        function getFocusableElements(container) {
          return Array.from(container.querySelectorAll(
            'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
          )).filter((node) => isElement(node) && !node.disabled);
        }
        function close() {
          if (!overlay) {
            return;
          }
          const node = overlay;
          const focusTarget = restoreFocusTarget;
          overlay = null;
          restoreFocusTarget = null;
          runtimeState.withObserverMuted(() => {
            node.remove();
          });
          if (isElement(focusTarget) && focusTarget.isConnected) {
            focusTarget.focus();
          }
        }
        function open({ currentSettings, onSave, currentTarget }) {
          close();
          let isSaving = false;
          restoreFocusTarget = isElement(currentTarget) ? currentTarget : document.activeElement;
          overlay = document.createElement("div");
          overlay.className = constants.MODAL_OVERLAY_CLASS;
          overlay.innerHTML = `
      <div class="${constants.MODAL_CLASS}" role="dialog" aria-modal="true" aria-label="${i18n.t("settings.title")}">
        <h2>${i18n.t("settings.title")}</h2>
        <label for="fmt-target-language">${i18n.t("settings.targetLanguage")}</label>
        <select id="fmt-target-language"></select>
        <label for="fmt-preferred-provider">${i18n.t("settings.preferredProvider")}</label>
        <select id="fmt-preferred-provider"></select>
        <p class="fmt-settings-modal-provider-notice">${i18n.t("settings.providerNotice")}</p>
        <div class="fmt-settings-modal-error" aria-live="polite"></div>
        <footer>
          <button type="button" data-variant="secondary">${i18n.t("settings.cancel")}</button>
          <button type="button" data-variant="primary">${i18n.t("settings.save")}</button>
        </footer>
      </div>
    `;
          const select = overlay.querySelector("#fmt-target-language");
          const providerSelect = overlay.querySelector("#fmt-preferred-provider");
          const errorNode = overlay.querySelector(".fmt-settings-modal-error");
          const cancelButton = overlay.querySelector('button[data-variant="secondary"]');
          const saveButton = overlay.querySelector('button[data-variant="primary"]');
          const syncPendingState = () => {
            select.disabled = isSaving;
            providerSelect.disabled = isSaving;
            cancelButton.disabled = isSaving;
            saveButton.disabled = isSaving;
          };
          const handleKeyDown = (event) => {
            if (event.key === "Escape") {
              if (!isSaving) {
                event.preventDefault();
                close();
              }
              return;
            }
            if (event.key !== "Tab") {
              return;
            }
            const focusableElements = getFocusableElements(overlay);
            if (focusableElements.length === 0) {
              event.preventDefault();
              return;
            }
            const firstElement = focusableElements[0];
            const lastElement = focusableElements[focusableElements.length - 1];
            const activeElement = document.activeElement;
            if (event.shiftKey && activeElement === firstElement) {
              event.preventDefault();
              lastElement.focus();
            } else if (!event.shiftKey && activeElement === lastElement) {
              event.preventDefault();
              firstElement.focus();
            }
          };
          for (const language of languages) {
            const option = document.createElement("option");
            option.value = language.id;
            option.textContent = language.label;
            option.selected = language.id === currentSettings?.targetLanguage;
            select.appendChild(option);
          }
          for (const provider of providers) {
            const option = document.createElement("option");
            option.value = provider.id;
            option.textContent = provider.label;
            option.selected = provider.id === currentSettings?.preferredProvider;
            providerSelect.appendChild(option);
          }
          overlay.addEventListener("click", (event) => {
            if (isSaving) {
              return;
            }
            if (event.target === overlay) {
              close();
            }
          });
          overlay.addEventListener("keydown", handleKeyDown);
          cancelButton.addEventListener("click", () => {
            if (isSaving) {
              return;
            }
            close();
          });
          saveButton.addEventListener("click", async () => {
            if (isSaving) {
              return;
            }
            isSaving = true;
            syncPendingState();
            if (errorNode instanceof globalThis.HTMLElement) {
              errorNode.textContent = "";
            }
            try {
              await onSave({
                targetLanguage: select.value,
                preferredProvider: providerSelect.value
              });
              close();
            } catch (error) {
              if (errorNode instanceof globalThis.HTMLElement) {
                errorNode.textContent = typeof error?.message === "string" && error.message.trim() ? error.message.trim() : i18n.t("settings.saveFailed");
              }
            } finally {
              isSaving = false;
              if (overlay) {
                syncPendingState();
              }
            }
          });
          runtimeState.withObserverMuted(() => {
            document.body.appendChild(overlay);
          });
          select.focus();
        }
        return {
          close,
          open
        };
      }
      module.exports = { createSettingsModal: createSettingsModal2 };
    }
  });

  // src/ui/styles.js
  var require_styles = __commonJS({
    "src/ui/styles.js"(exports, module) {
      function createStyles2(constants) {
        return `
    .${constants.BUTTON_CLASS}.${constants.BUTTON_ACTIVE_CLASS} {
      color: #467dde;
      font-weight: 600;
    }

    .${constants.TITLE_TRANSLATION_CLASS},
    .${constants.INLINE_TRANSLATION_CLASS},
    .${constants.MESSAGE_STATUS_CLASS} {
      margin-top: 8px;
      padding: 8px 10px;
      border-radius: 8px;
      line-height: 1.5;
      white-space: pre-wrap;
      word-break: break-word;
    }

    .${constants.TITLE_TRANSLATION_CLASS},
    .${constants.INLINE_TRANSLATION_CLASS},
    .${constants.TITLE_TRANSLATION_CLASS} .fmt-title-translation-content,
    .${constants.INLINE_TRANSLATION_CLASS} .fmt-inline-translation-content {
      user-select: text;
      -webkit-user-select: text;
      cursor: text;
    }

    .${constants.TITLE_TRANSLATION_CLASS}[data-state="done"],
    .${constants.INLINE_TRANSLATION_CLASS}[data-state="done"] {
      color: #467dde;
      background: rgba(70, 125, 222, 0.09);
    }

    .${constants.TITLE_TRANSLATION_CLASS}[data-state="loading"],
    .${constants.INLINE_TRANSLATION_CLASS}[data-state="loading"],
    .${constants.MESSAGE_STATUS_CLASS}[data-state="info"] {
      color: #76859c;
      background: rgba(170, 182, 200, 0.1);
    }

    .${constants.TITLE_TRANSLATION_CLASS}[data-state="error"],
    .${constants.INLINE_TRANSLATION_CLASS}[data-state="error"],
    .${constants.MESSAGE_STATUS_CLASS}[data-state="error"] {
      color: #c94b5b;
      background: rgba(255, 122, 122, 0.08);
    }

    .${constants.MODAL_OVERLAY_CLASS} {
      position: fixed;
      inset: 0;
      z-index: 2147483646;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 24px;
      background: rgba(17, 24, 39, 0.38);
      backdrop-filter: blur(6px);
    }

    .${constants.MODAL_CLASS} {
      width: min(420px, 100%);
      padding: 20px;
      border-radius: 16px;
      background: #ffffff;
      color: #1f2937;
      box-shadow: 0 24px 64px rgba(15, 23, 42, 0.22);
      font-family: "SF Pro Text", "PingFang SC", "Microsoft YaHei", sans-serif;
    }

    .${constants.MODAL_CLASS} h2 {
      margin: 0 0 16px;
      font-size: 18px;
      line-height: 1.3;
    }

    .${constants.MODAL_CLASS} label {
      display: block;
      margin-bottom: 8px;
      font-size: 13px;
      font-weight: 600;
      color: #4b5563;
    }

    .${constants.MODAL_CLASS} select {
      width: 100%;
      height: 40px;
      padding: 0 12px;
      border: 1px solid #d1d5db;
      border-radius: 10px;
      background: #ffffff;
      color: #111827;
      font-size: 14px;
    }

    .${constants.MODAL_CLASS} .fmt-settings-modal-provider-notice {
      margin: 8px 0 0;
      color: #6b7280;
      font-size: 12px;
      line-height: 1.45;
    }

    .${constants.MODAL_CLASS} .fmt-settings-modal-error {
      min-height: 20px;
      margin-top: 10px;
      color: #c94b5b;
      font-size: 13px;
      line-height: 1.4;
    }

    .${constants.MODAL_CLASS} footer {
      display: flex;
      justify-content: center;
      gap: 10px;
      margin-top: 20px;
    }

    .${constants.MODAL_CLASS} button {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      min-width: 88px;
      height: 38px;
      padding: 0 14px;
      border: 0;
      border-radius: 10px;
      font-size: 14px;
      cursor: pointer;
    }

    .${constants.MODAL_CLASS} button[data-variant="secondary"] {
      background: #e5e7eb;
      color: #1f2937;
    }

    .${constants.MODAL_CLASS} button[data-variant="primary"] {
      background: #2563eb;
      color: #ffffff;
    }
  `;
      }
      module.exports = { createStyles: createStyles2 };
    }
  });

  // src/ui/toolbar-button.js
  var require_toolbar_button = __commonJS({
    "src/ui/toolbar-button.js"(exports, module) {
      function createToolbarButton({ document, constants, i18n }) {
        const button = document.createElement("button");
        button.type = "button";
        button.className = `v-Button v-Button--subtleStandard v-Button--sizeM ${constants.BUTTON_CLASS}`;
        button.innerHTML = '<span class="label"></span>';
        updateToolbarButtonState(button, false, { constants, i18n });
        return button;
      }
      function normalizeActionLabel(text) {
        return String(text || "").replace(/\s+/g, " ").trim().toLowerCase();
      }
      function isMoreActionLabel(text) {
        const normalized = normalizeActionLabel(text);
        return normalized === "more" || normalized === "更多";
      }
      function getButtonLabel(button) {
        if (!button) {
          return "";
        }
        if (typeof button.textContent === "string" && button.classList?.contains("label")) {
          return button.textContent;
        }
        if (typeof button.querySelector === "function") {
          return button.querySelector(".label")?.textContent || button.textContent || "";
        }
        return button.textContent || "";
      }
      function placeToolbarButton(toolbar, button, divider) {
        const actionButtons = Array.from(toolbar.querySelectorAll(":scope > button"));
        const anchorButton = actionButtons.find((candidate) => {
          return candidate !== button && isMoreActionLabel(getButtonLabel(candidate));
        });
        const spacer = toolbar.querySelector(".v-Toolbar-flex");
        if (anchorButton?.nextElementSibling) {
          toolbar.insertBefore(divider, anchorButton.nextElementSibling);
          toolbar.insertBefore(button, divider.nextElementSibling);
          return;
        }
        if (spacer instanceof HTMLElement) {
          toolbar.insertBefore(divider, spacer);
          toolbar.insertBefore(button, spacer);
          return;
        }
        toolbar.appendChild(divider);
        toolbar.appendChild(button);
      }
      function updateToolbarButtonState(button, isActive, { constants, i18n }) {
        const label = i18n.t(isActive ? "content.restoreOriginal" : "content.translate");
        button.setAttribute("title", label);
        button.setAttribute("aria-label", label);
        button.classList.toggle(constants.BUTTON_ACTIVE_CLASS, isActive);
        const labelNode = button.querySelector(".label");
        if (labelNode) {
          labelNode.textContent = label;
        }
      }
      module.exports = {
        createToolbarButton,
        getButtonLabel,
        isMoreActionLabel,
        placeToolbarButton,
        updateToolbarButtonState
      };
    }
  });

  // src/ui/translation-renderer.js
  var require_translation_renderer = __commonJS({
    "src/ui/translation-renderer.js"(exports, module) {
      function createTranslationRenderer2({
        constants,
        i18n,
        runtimeState,
        segmenter,
        document = globalThis.document
      }) {
        function getMessageTranslationScope(descriptor) {
          if (descriptor?.contentRoot instanceof globalThis.HTMLElement && descriptor.contentRoot.isConnected) {
            return descriptor.contentRoot;
          }
          if (descriptor?.body instanceof globalThis.HTMLElement && descriptor.body.isConnected) {
            return descriptor.body;
          }
          if (descriptor?.messageNode instanceof globalThis.HTMLElement && descriptor.messageNode.isConnected) {
            return descriptor.messageNode;
          }
          if (descriptor?.card instanceof globalThis.HTMLElement && descriptor.card.isConnected) {
            return descriptor.card;
          }
          return null;
        }
        function removeInlineTranslations(bodyElement) {
          if (!(bodyElement instanceof globalThis.HTMLElement)) {
            return;
          }
          runtimeState.withObserverMuted(() => {
            bodyElement.querySelectorAll(`.${constants.INLINE_TRANSLATION_CLASS}, .${constants.SEGMENT_ANCHOR_CLASS}, .${constants.TRANSLATION_WRAPPER_CLASS}`).forEach((node) => node.remove());
          });
        }
        function clearMessageTranslations(descriptor) {
          const scope = getMessageTranslationScope(descriptor);
          if (!(scope instanceof globalThis.HTMLElement)) {
            return;
          }
          runtimeState.withObserverMuted(() => {
            scope.querySelectorAll(`.${constants.INLINE_TRANSLATION_CLASS}, .${constants.SEGMENT_ANCHOR_CLASS}, .${constants.TRANSLATION_WRAPPER_CLASS}`).forEach((node) => node.remove());
          });
        }
        function pruneMessageTranslations(descriptor, currentSegments) {
          const scope = getMessageTranslationScope(descriptor);
          if (!(scope instanceof globalThis.HTMLElement)) {
            return;
          }
          const currentSegmentIds = new Set(
            Array.isArray(currentSegments) ? currentSegments.map((segment) => segment?.id).filter(Boolean) : []
          );
          if (currentSegmentIds.size === 0) {
            clearMessageTranslations(descriptor);
            return;
          }
          runtimeState.withObserverMuted(() => {
            scope.querySelectorAll(`.${constants.INLINE_TRANSLATION_CLASS}`).forEach((node) => {
              const segmentId = node.getAttribute(constants.SEGMENT_ATTRIBUTE);
              if (segmentId && currentSegmentIds.has(segmentId)) {
                return;
              }
              const rowWrapper = node.closest(`tr.${constants.TRANSLATION_WRAPPER_CLASS}`);
              if (rowWrapper instanceof globalThis.HTMLElement) {
                rowWrapper.remove();
                return;
              }
              const cellWrapper = node.closest(`td.${constants.TRANSLATION_WRAPPER_CLASS}`);
              if (cellWrapper instanceof globalThis.HTMLElement) {
                cellWrapper.remove();
                return;
              }
              node.remove();
            });
            scope.querySelectorAll(`.${constants.SEGMENT_ANCHOR_CLASS}`).forEach((node) => {
              const translationNode = node.nextElementSibling;
              if (translationNode instanceof globalThis.HTMLElement && translationNode.classList.contains(constants.INLINE_TRANSLATION_CLASS) && currentSegmentIds.has(translationNode.getAttribute(constants.SEGMENT_ATTRIBUTE) || "")) {
                return;
              }
              node.remove();
            });
          });
        }
        function clearThreadRenderArtifacts(threadRoot) {
          if (!(threadRoot instanceof globalThis.HTMLElement)) {
            return;
          }
          runtimeState.withObserverMuted(() => {
            threadRoot.querySelectorAll(`.${constants.INLINE_TRANSLATION_CLASS}, .${constants.SEGMENT_ANCHOR_CLASS}, .${constants.MESSAGE_STATUS_CLASS}, .${constants.TITLE_TRANSLATION_CLASS}, .${constants.TRANSLATION_WRAPPER_CLASS}`).forEach((node) => node.remove());
          });
        }
        function hasThreadRenderArtifacts(threadRoot) {
          if (!(threadRoot instanceof globalThis.HTMLElement)) {
            return false;
          }
          return threadRoot.querySelector(
            `.${constants.INLINE_TRANSLATION_CLASS}, .${constants.SEGMENT_ANCHOR_CLASS}, .${constants.MESSAGE_STATUS_CLASS}, .${constants.TITLE_TRANSLATION_CLASS}, .${constants.TRANSLATION_WRAPPER_CLASS}`
          ) !== null;
        }
        function getThreadRenderArtifactCounts(threadRoot) {
          if (!(threadRoot instanceof globalThis.HTMLElement)) {
            return {
              inlineTranslations: 0,
              titleTranslations: 0,
              messageStatuses: 0,
              segmentAnchors: 0,
              translationWrappers: 0
            };
          }
          return {
            inlineTranslations: threadRoot.querySelectorAll(`.${constants.INLINE_TRANSLATION_CLASS}`).length,
            titleTranslations: threadRoot.querySelectorAll(`.${constants.TITLE_TRANSLATION_CLASS}`).length,
            messageStatuses: threadRoot.querySelectorAll(`.${constants.MESSAGE_STATUS_CLASS}`).length,
            segmentAnchors: threadRoot.querySelectorAll(`.${constants.SEGMENT_ANCHOR_CLASS}`).length,
            translationWrappers: threadRoot.querySelectorAll(`.${constants.TRANSLATION_WRAPPER_CLASS}`).length
          };
        }
        function clearTitleTranslation(threadRoot) {
          const titleNode = findTitleTranslationNode(threadRoot);
          if (!titleNode) {
            return;
          }
          runtimeState.withObserverMuted(() => {
            titleNode.remove();
          });
        }
        function renderTitleTranslation(threadRoot, sourceElement, content, state = "done") {
          if (!(threadRoot instanceof globalThis.HTMLElement) || !(sourceElement instanceof globalThis.HTMLElement)) {
            return false;
          }
          const normalizedContent = normalizeTranslationText(content);
          const existingNode = findTitleTranslationNode(threadRoot);
          if (existingNode) {
            return setTitleTranslationState(existingNode, { state, content: normalizedContent });
          }
          const node = document.createElement("div");
          node.className = constants.TITLE_TRANSLATION_CLASS;
          node.dataset.state = state;
          node.innerHTML = '<div class="fmt-title-translation-content"></div>';
          if (!setTitleTranslationState(node, { state, content: normalizedContent })) {
            return false;
          }
          runtimeState.withObserverMuted(() => {
            sourceElement.insertAdjacentElement("afterend", node);
          });
          return node.isConnected;
        }
        function clearMessageStatus(descriptor) {
          const statusNode = findMessageStatusNode(descriptor);
          if (statusNode) {
            runtimeState.withObserverMuted(() => {
              statusNode.remove();
            });
          }
        }
        function renderMessageStatus(descriptor, message, state = "info") {
          clearMessageStatus(descriptor);
          const mountTarget = getMessageStatusMountTarget(descriptor);
          if (!(mountTarget instanceof globalThis.HTMLElement)) {
            return false;
          }
          const node = document.createElement("div");
          node.className = constants.MESSAGE_STATUS_CLASS;
          node.dataset.state = state;
          node.dataset.messageKey = descriptor.key || "";
          node.textContent = normalizeTranslationText(message);
          runtimeState.withObserverMuted(() => {
            if (mountTarget.matches(".v-Message-body, .u-containSelection, .u-article, article, pre")) {
              mountTarget.insertAdjacentElement("afterbegin", node);
            } else {
              mountTarget.insertAdjacentElement("afterend", node);
            }
          });
          return node.isConnected;
        }
        function createRenderResult(expectedCount) {
          return {
            ok: true,
            expectedCount,
            renderedCount: 0,
            failedSegments: []
          };
        }
        function renderLoadingTranslations(segments) {
          return renderSegmentNodes(segments, segments.map(() => i18n.t("content.loading")), "loading");
        }
        function renderTranslatedSegments(segments, translatedSegments) {
          return renderSegmentNodes(segments, translatedSegments, "done");
        }
        function renderSegmentError(segments, message) {
          return renderSegmentNodes(segments, segments.map(() => message), "error");
        }
        function renderSegmentNodes(segments, contents, state) {
          const result = createRenderResult(segments.length);
          segments.forEach((segment, index) => {
            let translationNode = findInlineTranslationNode(segment);
            if (!translationNode) {
              translationNode = insertTranslationNode(segment, {
                state,
                content: contents[index]
              });
            }
            if (!translationNode) {
              result.ok = false;
              result.failedSegments.push({
                segmentId: segment.id,
                reason: "insert-failed"
              });
              return;
            }
            const didUpdate = setInlineTranslationState(translationNode, {
              state,
              content: contents[index]
            });
            if (!didUpdate) {
              result.ok = false;
              result.failedSegments.push({
                segmentId: segment.id,
                reason: "update-failed"
              });
              return;
            }
            result.renderedCount += 1;
          });
          return result;
        }
        function insertTranslationNode(segment, { state, content }) {
          const node = document.createElement("div");
          node.className = constants.INLINE_TRANSLATION_CLASS;
          node.dataset.state = state;
          node.setAttribute(constants.SEGMENT_ATTRIBUTE, segment.id || "");
          node.innerHTML = '<div class="fmt-inline-translation-content"></div>';
          setInlineTranslationState(node, { state, content });
          const inserted = runtimeState.withObserverMuted(() => insertTranslationAtValidPosition(segment, node));
          return inserted ? node : null;
        }
        function setInlineTranslationState(node, { state, content }) {
          if (!(node instanceof globalThis.HTMLElement)) {
            return false;
          }
          const normalizedContent = normalizeTranslationText(content);
          const contentNode = node.querySelector(".fmt-inline-translation-content");
          if (!(contentNode instanceof globalThis.HTMLElement)) {
            return false;
          }
          const normalizedState = String(state);
          if (node.dataset.state === normalizedState && contentNode.textContent === normalizedContent) {
            return true;
          }
          return runtimeState.withObserverMuted(() => {
            if (node.dataset.state !== normalizedState) {
              node.dataset.state = normalizedState;
            }
            if (contentNode.textContent !== normalizedContent) {
              contentNode.textContent = normalizedContent;
            }
            return true;
          });
        }
        function setTitleTranslationState(node, { state, content }) {
          if (!(node instanceof globalThis.HTMLElement)) {
            return false;
          }
          const normalizedContent = normalizeTranslationText(content);
          const contentNode = node.querySelector(".fmt-title-translation-content");
          if (!(contentNode instanceof globalThis.HTMLElement)) {
            return false;
          }
          const normalizedState = String(state);
          if (node.dataset.state === normalizedState && contentNode.textContent === normalizedContent) {
            return true;
          }
          return runtimeState.withObserverMuted(() => {
            if (node.dataset.state !== normalizedState) {
              node.dataset.state = normalizedState;
            }
            if (contentNode.textContent !== normalizedContent) {
              contentNode.textContent = normalizedContent;
            }
            return true;
          });
        }
        function findInlineTranslationNode(segmentOrId) {
          const segmentId = typeof segmentOrId === "string" ? segmentOrId : segmentOrId?.id;
          if (!segmentId) {
            return null;
          }
          if (segmentOrId && typeof segmentOrId === "object") {
            const localNode = findInlineTranslationNodeNearSegment(segmentOrId, segmentId);
            if (localNode) {
              return localNode;
            }
          }
          return document.querySelector(`.${constants.INLINE_TRANSLATION_CLASS}[${constants.SEGMENT_ATTRIBUTE}="${segmentId}"]`);
        }
        function findInlineTranslationNodeNearSegment(segment, segmentId) {
          if (segment.referenceNode) {
            const anchor = segmenter.getExistingSegmentAnchor(segment.referenceNode);
            const sibling = anchor?.nextElementSibling || null;
            if (matchesInlineTranslationNode(sibling, segmentId)) {
              return sibling;
            }
          }
          const element = segment.element;
          if (!(element instanceof globalThis.HTMLElement)) {
            return null;
          }
          if (matchesInlineTranslationNode(element.lastElementChild, segmentId)) {
            return element.lastElementChild;
          }
          if (matchesInlineTranslationNode(element.nextElementSibling, segmentId)) {
            return element.nextElementSibling;
          }
          const rowSibling = element.parentElement?.nextElementSibling;
          if (rowSibling instanceof globalThis.HTMLElement) {
            const rowNode = rowSibling.querySelector(`.${constants.INLINE_TRANSLATION_CLASS}[${constants.SEGMENT_ATTRIBUTE}="${segmentId}"]`);
            if (rowNode instanceof globalThis.HTMLElement) {
              return rowNode;
            }
          }
          return null;
        }
        function matchesInlineTranslationNode(node, segmentId) {
          return node instanceof globalThis.HTMLElement && node.classList.contains(constants.INLINE_TRANSLATION_CLASS) && node.getAttribute(constants.SEGMENT_ATTRIBUTE) === segmentId;
        }
        function insertTranslationAtValidPosition(segment, node) {
          if (segment.referenceNode) {
            const anchor = segmenter.ensureSegmentAnchor(segment.referenceNode);
            if (!(anchor instanceof globalThis.HTMLElement) || !anchor.isConnected) {
              return false;
            }
            anchor.insertAdjacentElement("afterend", node);
            return node.isConnected;
          }
          const element = segment.element;
          if (!(element instanceof globalThis.HTMLElement)) {
            return false;
          }
          const tagName = element.tagName.toLowerCase();
          if (tagName === "td" || tagName === "th") {
            element.appendChild(node);
            return true;
          }
          const parentTag = element.parentElement?.tagName?.toLowerCase();
          if (parentTag === "tr") {
            const wrapper = document.createElement("td");
            wrapper.className = constants.TRANSLATION_WRAPPER_CLASS;
            const colSpan = element.parentElement.children.length;
            if (colSpan > 1) {
              wrapper.colSpan = colSpan;
            }
            wrapper.appendChild(node);
            const row = document.createElement("tr");
            row.className = constants.TRANSLATION_WRAPPER_CLASS;
            row.appendChild(wrapper);
            element.parentElement.insertAdjacentElement("afterend", row);
            return true;
          }
          element.insertAdjacentElement("afterend", node);
          return true;
        }
        function getMessageStatusMountTarget(descriptor) {
          if (descriptor?.body instanceof globalThis.HTMLElement && descriptor.body.isConnected) {
            return descriptor.body;
          }
          if (descriptor?.messageNode instanceof globalThis.HTMLElement && descriptor.messageNode.isConnected) {
            return descriptor.messageNode;
          }
          if (descriptor?.card instanceof globalThis.HTMLElement && descriptor.card.isConnected) {
            return descriptor.card;
          }
          return null;
        }
        function findMessageStatusNode(descriptor) {
          const key = descriptor?.key;
          if (!key) {
            return null;
          }
          return document.querySelector(`.${constants.MESSAGE_STATUS_CLASS}[data-message-key="${key}"]`);
        }
        function findTitleTranslationNode(threadRoot) {
          if (!(threadRoot instanceof globalThis.HTMLElement)) {
            return null;
          }
          return threadRoot.querySelector(`.${constants.TITLE_TRANSLATION_CLASS}`);
        }
        function normalizeTranslationText(text) {
          return String(text || "").replace(/\r\n/g, "\n").split("\n").map((line) => line.trim()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
        }
        return {
          clearMessageTranslations,
          clearMessageStatus,
          clearThreadRenderArtifacts,
          clearTitleTranslation,
          findInlineTranslationNode,
          findTitleTranslationNode,
          getMessageStatusMountTarget,
          getThreadRenderArtifactCounts,
          hasThreadRenderArtifacts,
          pruneMessageTranslations,
          removeInlineTranslations,
          renderLoadingTranslations,
          renderMessageStatus,
          renderSegmentError,
          renderTitleTranslation,
          renderTranslatedSegments
        };
      }
      module.exports = { createTranslationRenderer: createTranslationRenderer2 };
    }
  });

  // src/entry/main.js
  var { DEFAULT_SETTINGS, DOM_CONSTANTS } = require_defaults();
  var { LANGUAGE_DEFINITIONS } = require_languages();
  var { PROVIDER_DEFINITIONS } = require_providers();
  var { createRuntimeState } = require_runtime_state();
  var { createApp } = require_app();
  var { createLifecycle } = require_lifecycle();
  var { createDiagnostics, createNoopDiagnostics } = require_diagnostics();
  var { createDiagnosticsStore } = require_store();
  var { createSegmenter } = require_segmenter();
  var pageLocator = require_page_locator();
  var { createThreadDom } = require_thread_dom();
  var textPolicies = require_text_policies();
  var { createTmApi } = require_tm_api();
  var { createXhr } = require_xhr();
  var { createSettingsStore } = require_settings_store();
  var { createEdgeAuth } = require_edge_auth();
  var { createEdgeTranslator } = require_edge_translator();
  var { createGoogleWebTranslator } = require_google_translator();
  var { createRequestRegistry } = require_request_registry();
  var { createTranslateService } = require_translate_service();
  var { createI18n } = require_i18n();
  var {
    createMenuCommandRegistry,
    registerDiagnosticsMenuCommand,
    registerSettingsMenuCommand
  } = require_menu_command();
  var { createSettingsModal } = require_settings_modal();
  var { createStyles } = require_styles();
  var toolbarButtonApi = require_toolbar_button();
  var { createTranslationRenderer } = require_translation_renderer();
  var BOOTSTRAP_KEY = "__fastTrMailTampermonkeyBootstrap__";
  var BOOTSTRAP_RETRY_KEY = "__fastTrMailTampermonkeyBootstrapRetries__";
  var MAX_BOOTSTRAP_RETRIES = 3;
  function ensureStyles(document, constants) {
    if (document.head.querySelector(`style[${constants.STYLES_ATTRIBUTE}]`)) {
      return;
    }
    const style = document.createElement("style");
    style.setAttribute(constants.STYLES_ATTRIBUTE, "true");
    style.textContent = createStyles(constants);
    document.head.appendChild(style);
  }
  async function bootstrap() {
    let diagnostics = createNoopDiagnostics();
    if (globalThis[BOOTSTRAP_KEY] === "ready" || globalThis[BOOTSTRAP_KEY] === "booting") {
      return;
    }
    if (!pageLocator.isFastmailUrl(globalThis.location)) {
      return;
    }
    globalThis[BOOTSTRAP_KEY] = "booting";
    try {
      const tmApi = createTmApi(globalThis);
      const menuCommands = createMenuCommandRegistry({ tmApi });
      const diagnosticsStore = createDiagnosticsStore({ tmApi });
      diagnostics = createDiagnostics({
        enabled: await diagnosticsStore.getEnabled()
      });
      const xhr = createXhr({ tmApi });
      const settingsStore = createSettingsStore({
        tmApi,
        defaults: DEFAULT_SETTINGS,
        languages: LANGUAGE_DEFINITIONS,
        providers: PROVIDER_DEFINITIONS
      });
      const edgeAuth = createEdgeAuth({ diagnostics, xhr });
      const edgeTranslator = createEdgeTranslator({ diagnostics, edgeAuth, xhr });
      const googleTranslator = createGoogleWebTranslator({ diagnostics, xhr });
      const translateService = createTranslateService({
        diagnostics,
        languages: LANGUAGE_DEFINITIONS,
        providers: PROVIDER_DEFINITIONS,
        translators: {
          "edge-web": edgeTranslator,
          "google-web": googleTranslator
        }
      });
      const i18n = createI18n();
      const runtimeState = createRuntimeState({
        getLocationKey: () => pageLocator.getLocationKey(globalThis.location)
      });
      const requestRegistry = createRequestRegistry();
      const segmenter = createSegmenter({
        constants: DOM_CONSTANTS,
        policies: textPolicies,
        runtimeState
      });
      const threadDom = createThreadDom({
        constants: DOM_CONSTANTS,
        i18n,
        pageLocator,
        policies: textPolicies,
        runtimeState,
        segmenter,
        toolbarButtonApi
      });
      const renderer = createTranslationRenderer({
        constants: DOM_CONSTANTS,
        i18n,
        runtimeState,
        segmenter
      });
      const settingsModal = createSettingsModal({
        constants: DOM_CONSTANTS,
        i18n,
        languages: LANGUAGE_DEFINITIONS,
        providers: PROVIDER_DEFINITIONS.map((provider) => ({
          id: provider.id,
          label: i18n.t(`providers.${provider.id}`)
        })),
        runtimeState
      });
      const app = createApp({
        defaults: DEFAULT_SETTINGS,
        document: globalThis.document,
        diagnostics,
        i18n,
        policies: textPolicies,
        renderer,
        requestRegistry,
        runtimeState,
        segmenter,
        settingsModal,
        settingsStore,
        threadDom,
        translateService
      });
      const lifecycle = createLifecycle({
        app,
        constants: DOM_CONSTANTS,
        document: globalThis.document,
        runtimeState,
        threadDom
      });
      ensureStyles(globalThis.document, DOM_CONSTANTS);
      menuCommands.ensure("settings", () => registerSettingsMenuCommand({
        tmApi: menuCommands.tmApi,
        i18n,
        diagnostics,
        onClick: app.openSettings
      }));
      menuCommands.ensure("diagnostics", () => registerDiagnosticsMenuCommand({
        tmApi: menuCommands.tmApi,
        i18n,
        diagnostics,
        diagnosticsStore
      }));
      await app.ensureTargetLanguageId();
      lifecycle.initialize();
      diagnostics.record("bootstrap.ready", {
        locationKey: pageLocator.getLocationKey(globalThis.location)
      });
      globalThis[BOOTSTRAP_KEY] = "ready";
      globalThis[BOOTSTRAP_RETRY_KEY] = 0;
    } catch (error) {
      globalThis[BOOTSTRAP_KEY] = "idle";
      const retryCount = Number(globalThis[BOOTSTRAP_RETRY_KEY] || 0) + 1;
      globalThis[BOOTSTRAP_RETRY_KEY] = retryCount;
      diagnostics.recordError("bootstrap.failed", error, {
        retryCount
      });
      console.error("[FastTrMail] bootstrap failed", error);
      if (retryCount < MAX_BOOTSTRAP_RETRIES) {
        globalThis.setTimeout(() => {
          void bootstrap();
        }, retryCount * 1e3);
      }
    }
  }
  if (globalThis.document?.readyState === "loading") {
    globalThis.document.addEventListener("DOMContentLoaded", () => {
      void bootstrap();
    }, { once: true });
  } else {
    void bootstrap();
  }
})();