(function () {
  "use strict";

  const webview = window.chrome && window.chrome.webview;
  const searchInput = document.getElementById("stock-search");
  const searchButton = document.getElementById("stock-search-button");
  const stockSearchMeta = document.getElementById("stock-search-meta");
  const stockResults = document.getElementById("stock-results");
  const cutList = document.getElementById("cut-list");
  const cutDragFeedback = document.getElementById("cut-drag-feedback");
  const playlistRows = document.getElementById("playlist-rows");
  const playlistHeadingCount = document.getElementById("playlist-heading-count");
  const playlistDropZone = document.getElementById("playlist-drop-zone");
  const playlistMoveUp = document.getElementById("playlist-move-up");
  const playlistMoveDown = document.getElementById("playlist-move-down");
  const playlistDeleteSelected = document.getElementById("playlist-delete-selected");
  const playlistDeleteAll = document.getElementById("playlist-delete-all");
  const playlistEnableAll = document.getElementById("playlist-enable-all");
  const marketTabs = document.getElementById("market-tabs");
  const categoryTree = document.getElementById("category-tree");
  const catalogExpandAll = document.getElementById("catalog-expand-all");
  const movingAverage5 = document.getElementById("moving-average-5");
  const movingAverage20 = document.getElementById("moving-average-20");
  const status = document.getElementById("operator-status");
  const operatorSettingsMessage = document.getElementById("operator-settings-message");
  const operatorSettingsRestart = document.getElementById("operator-settings-restart");
  const operatorNavigationExpanded = document.getElementById("operator-navigation-expanded");
  const operatorAppearanceControls = Object.freeze({
    colorTheme: Object.freeze({
      element: document.getElementById("operator-color-theme"),
      values: Object.freeze(["system", "light", "dark"]),
      fallback: "system"
    }),
    viewMode: Object.freeze({
      element: document.getElementById("operator-view-mode"),
      values: Object.freeze(["automatic", "compact", "cards"]),
      fallback: "automatic"
    }),
    startWorkspace: Object.freeze({
      element: document.getElementById("operator-start-workspace"),
      values: Object.freeze(["stockCut", "lastWorkspace"]),
      fallback: "stockCut"
    })
  });
  const operatorSettingsAppVersion = document.getElementById("operator-settings-app-version");
  const operatorSettingsPlayoutMode = document.getElementById("operator-settings-playout-mode");
  const operatorSettingsPlayoutConnection = document.getElementById(
    "operator-settings-playout-connection");
  const operatorSettingsOracleState = document.getElementById("operator-settings-oracle-state");
  const operatorSettingsMariaState = document.getElementById("operator-settings-maria-state");
  const operatorSettingsDbMonitor = document.getElementById("operator-settings-db-monitor");
  const operatorFolderControls = Object.freeze({
    design: Object.freeze({
      name: document.getElementById("operator-design-folder-name"),
      status: document.getElementById("operator-design-folder-status"),
      source: document.getElementById("operator-design-folder-source"),
      select: document.getElementById("operator-design-folder-select"),
      reset: document.getElementById("operator-design-folder-reset")
    }),
    resource: Object.freeze({
      name: document.getElementById("operator-resource-folder-name"),
      status: document.getElementById("operator-resource-folder-status"),
      source: document.getElementById("operator-resource-folder-source"),
      select: document.getElementById("operator-resource-folder-select"),
      reset: document.getElementById("operator-resource-folder-reset")
    }),
    background: Object.freeze({
      name: document.getElementById("operator-background-folder-name"),
      status: document.getElementById("operator-background-folder-status"),
      source: document.getElementById("operator-background-folder-source"),
      select: document.getElementById("operator-background-folder-select"),
      reset: document.getElementById("operator-background-folder-reset")
    })
  });
  const dialog = document.getElementById("legacy-dialog");
  const dialogCaption = document.getElementById("legacy-dialog-caption");
  const dialogMessage = document.getElementById("legacy-dialog-message");
  const dialogOk = document.getElementById("legacy-dialog-ok");
  const dialogNo = document.getElementById("legacy-dialog-no");
  const dbSaveButton = document.getElementById("db-save-button");
  const dbLoadButton = document.getElementById("db-load-button");
  const namedPlaylistModal = document.getElementById("named-playlist-modal");
  const namedPlaylistTitle = document.getElementById("named-playlist-title");
  const namedPlaylistClose = document.getElementById("named-playlist-close");
  const namedPlaylistStatus = document.getElementById("named-playlist-status");
  const namedPlaylistDefinitions = document.getElementById("named-playlist-definitions");
  const namedPlaylistNewTitle = document.getElementById("named-playlist-new-title");
  const namedPlaylistCreateButton = document.getElementById("named-playlist-create-button");
  const namedPlaylistDeleteButton = document.getElementById("named-playlist-delete-button");
  const namedPlaylistCancelButton = document.getElementById("named-playlist-cancel-button");
  const namedPlaylistConfirmButton = document.getElementById("named-playlist-confirm-button");
  const namedPlaylistConfirmationModal = document.getElementById(
    "named-playlist-confirmation");
  const namedPlaylistConfirmationMessage = document.getElementById(
    "named-playlist-confirmation-message");
  const namedPlaylistConfirmationYes = document.getElementById(
    "named-playlist-confirmation-yes");
  const namedPlaylistConfirmationNo = document.getElementById(
    "named-playlist-confirmation-no");
  const manualButtons = Array.from(document.querySelectorAll("button[data-manual-screen]"));
  const manualModal = document.getElementById("manual-financial-modal");
  const manualTitle = document.getElementById("manual-financial-title");
  const manualClose = document.getElementById("manual-financial-close");
  const manualExit = document.getElementById("manual-financial-exit");
  const manualWorkspace = document.getElementById("manual-financial-workspace");
  const manualListButtons = Array.from(
    document.querySelectorAll("button[data-manual-list-screen]"));
  const manualListModal = document.getElementById("manual-list-modal");
  const manualListTitle = document.getElementById("manual-list-title");
  const manualListClose = document.getElementById("manual-list-close");
  const manualListCancel = document.getElementById("manual-list-cancel");
  const manualListWorkspace = document.getElementById("manual-list-workspace");
  const operatorCatalogModal = document.getElementById("operator-catalog-modal");
  const operatorCatalogTitle = document.getElementById("operator-catalog-title");
  const operatorCatalogClose = document.getElementById("operator-catalog-close");
  const operatorCatalogStatus = document.getElementById("operator-catalog-status");
  const operatorCatalogToolbar = document.getElementById("operator-catalog-toolbar");
  const operatorCatalogBody = document.getElementById("operator-catalog-body");
  const operatorCatalogListPanel = document.getElementById("operator-catalog-list-panel");
  const operatorCatalogThemeTab = document.getElementById("operator-catalog-theme-tab");
  const operatorCatalogExpertTab = document.getElementById("operator-catalog-expert-tab");
  const operatorCatalogQuery = document.getElementById("operator-catalog-query");
  const operatorCatalogSession = document.getElementById("operator-catalog-session");
  const operatorCatalogSearch = document.getElementById("operator-catalog-search");
  const operatorCatalogNewMarket = document.getElementById("operator-catalog-new-market");
  const operatorCatalogNew = document.getElementById("operator-catalog-new");
  const operatorCatalogResults = document.getElementById("operator-catalog-results");
  const operatorCatalogEditor = document.getElementById("operator-catalog-editor");
  const operatorCatalogEditorMarketField = document.getElementById(
    "operator-catalog-editor-market-field");
  const operatorCatalogEditorMarket = document.getElementById(
    "operator-catalog-editor-market");
  const operatorCatalogCode = document.getElementById("operator-catalog-code");
  const operatorCatalogNameLabel = document.getElementById("operator-catalog-name-label");
  const operatorCatalogName = document.getElementById("operator-catalog-name");
  const operatorCatalogNameCheck = document.getElementById("operator-catalog-name-check");
  const operatorCatalogNameStatus = document.getElementById("operator-catalog-name-status");
  const operatorCatalogStockSearchLine = document.getElementById("operator-catalog-stock-search-line");
  const operatorCatalogStockQuery = document.getElementById("operator-catalog-stock-query");
  const operatorCatalogStockSearch = document.getElementById("operator-catalog-stock-search");
  const operatorCatalogStockResults = document.getElementById("operator-catalog-stock-results");
  const operatorCatalogAmountLabel = document.getElementById("operator-catalog-amount-label");
  const operatorCatalogAmount = document.getElementById("operator-catalog-amount");
  const operatorCatalogAddStock = document.getElementById("operator-catalog-add-stock");
  const operatorCatalogAddLine = document.getElementById("operator-catalog-add-line");
  const operatorCatalogDraftHeader = document.getElementById("operator-catalog-draft-header");
  const operatorCatalogDraftRows = document.getElementById("operator-catalog-draft-rows");
  const operatorCatalogDeleteAll = document.getElementById("operator-catalog-delete-all");
  const operatorCatalogCancel = document.getElementById("operator-catalog-cancel");
  const operatorCatalogSave = document.getElementById("operator-catalog-save");
  const namedPlaylistDialog = namedPlaylistModal.querySelector("[role='dialog']");
  const namedPlaylistConfirmationDialog = namedPlaylistConfirmationModal.querySelector(
    "[role='alertdialog']");
  const manualDialog = manualModal.querySelector("[role='dialog']");
  const manualListDialog = manualListModal.querySelector("[role='dialog']");
  const operatorCatalogDialog = operatorCatalogModal.querySelector("[role='dialog']");
  const legacyAlertDialog = dialog.querySelector("[role='alertdialog']");
  if (!window.LegacyModalFocus || typeof window.LegacyModalFocus.create !== "function") {
    throw new Error("Legacy modal focus support was not loaded.");
  }
  if (!window.LegacyCutDrag ||
      typeof window.LegacyCutDrag.createDragRectangle !== "function" ||
      typeof window.LegacyCutDrag.shouldStart !== "function" ||
      typeof window.LegacyCutDrag.toCssDragSize !== "function") {
    throw new Error("Legacy cut drag support was not loaded.");
  }
  if (!window.LegacyNamedPlaylistSelection ||
      typeof window.LegacyNamedPlaylistSelection.create !== "function") {
    throw new Error("Legacy named-playlist selection support was not loaded.");
  }
  const modalFocusManager = window.LegacyModalFocus.create({ document: document });
  const namedPlaylistSelection = window.LegacyNamedPlaylistSelection.create();
  modalFocusManager.start();
  let uiBusy = false;
  const searchDraftInputIds = new Set([
    "stock-search",
    "overseas-industry-search",
    "overseas-stock-search",
    "comparison-domestic-search",
    "comparison-world-search",
    "theme-search",
    "expert-search",
    "halt-search",
    "manual-financial-list-query",
    "manual-financial-find-query",
    "manual-vi-search",
    "operator-catalog-query",
    "operator-catalog-stock-query"
  ]);
  const searchDrafts = new Map();
  let namedPlaylistMode = null;
  let namedPlaylistState = null;
  let namedPlaylistConfirmation = null;
  let manualDraft = null;
  let manualDraftKey = "";
  let manualDraftResetPending = false;
  let operatorCatalogState = null;
  let operatorCatalogNameDraft = "";
  let operatorCatalogDraftKey = "";
  let manualResultClickTimer = null;
  let comparisonTargetClickTimer = null;
  let themeResultClickTimer = null;
  let manualViResultClickTimer = null;
  let manualResultPendingSelection = null;
  let comparisonTargetPendingSelection = null;
  let themeResultPendingSelection = null;
  let manualViResultPendingSelection = null;
  let pendingNamedPlaylistCommand = null;
  let nextNamedPlaylistRequestId = 1;
  let lastCommandSequence = 0;
  let operatorMutationLocked = true;
  const operatorCatalogDragMime = "text/x-mbn-operator-catalog-row";
  let deferredPlaylistPlainSelectionRowId = null;
  let playlistDragGesture = null;
  let playlistMutationLocked = false;
  let playlistStructureMutationLocked = true;
  let playlistProgramEnableMutationLocked = true;
  let playlistDeleteSelectedLocked = true;
  let cutSelectionEnabled = false;
  let cutDragSize = { width: 4, height: 4 };
  let cutDragGesture = null;
  let lastPresentedPlayoutRowKey = "";
  let draggedTabId = null;
  let tabDragLastTargetId = null;
  let pendingTabHoverSwap = null;
  let draggedOperatorCatalogRowId = null;
  let operatorCatalogDragBlockedRowId = null;
  let operatorCatalogMutationLocked = false;
  let legacyDialogIsConfirmation = false;
  let industryRenderStructureKey = "";
  let overseasRenderStructureKey = "";
  let themeRenderStructureKey = "";
  let comparisonRenderStructureKey = "";
  let expertRenderStructureKey = "";
  let tradingHaltRenderStructureKey = "";
  let manualFinancialRenderStateKey = "";
  let manualListRenderStateKey = "";
  let operatorCatalogRenderStateKey = "";
  let manualFinancialSearchFocus = null;
  let manualListSearchFocus = null;
  let operatorCatalogSearchFocus = null;
  let operatorSettingsRenderRevision = null;
  let operatorSettingsRevisionInitialized = false;
  let operatorAppearanceSnapshot = Object.freeze({
    colorTheme: "system",
    viewMode: "automatic",
    startWorkspace: "stockCut"
  });
  let pendingOperatorSettingsFocusId = null;
  let pendingOperatorSettingsFallbackFocusId = null;
  let operatorSettingsWasBusy = false;
  let renderedTreeTabId = null;
  let treeInteractionLocked = false;
  let treeFocusOwnedBeforeRender = false;
  const treeUiStateByTab = new Map();
  const pendingTreeResetTabs = new Set();

  function preferredModalFocus(modal) {
    if (modal === legacyAlertDialog) return dialogOk;
    if (modal === namedPlaylistConfirmationDialog) return namedPlaylistConfirmationYes;
    if (modal === namedPlaylistDialog) {
      return namedPlaylistDefinitions.querySelector("button[aria-selected='true']") ||
        namedPlaylistDefinitions.querySelector("button") || namedPlaylistNewTitle;
    }
    if (modal === manualDialog) {
      return manualWorkspace.querySelector(
        "button.selected[data-manual-stock-result-id]") ||
        manualWorkspace.querySelector("button.selected[data-manual-result-id]") ||
        manualWorkspace.querySelector("button[data-manual-result-id]") ||
        manualWorkspace.querySelector(".manual-financial-list-header");
    }
    if (modal === manualListDialog) {
      return manualListWorkspace.querySelector(
        "button.selected[data-manual-vi-result-id]") ||
        manualListWorkspace.querySelector("input:not([disabled])") ||
        manualListWorkspace.querySelector("button:not([disabled])");
    }
    if (modal === operatorCatalogDialog) {
      if (!operatorCatalogEditor.hidden && !operatorCatalogName.disabled) {
        return operatorCatalogName;
      }
      return operatorCatalogResults.querySelector("button[aria-selected='true']") ||
        operatorCatalogResults.querySelector("button") || operatorCatalogQuery;
    }
    return null;
  }

  function createModalOpenerReference() {
    const opener = document.activeElement;
    if (!renderedTreeTabId || !categoryTree.contains(opener)) return opener;

    captureTreeUiState(renderedTreeTabId);
    const node = opener.closest("[data-tree-node-key]");
    const descriptor = {
      tabId: renderedTreeTabId,
      controlId: opener.id || null,
      nodeKey: node ? node.dataset.treeNodeKey : null,
      selectionStart: Number.isInteger(opener.selectionStart) ? opener.selectionStart : null,
      selectionEnd: Number.isInteger(opener.selectionEnd) ? opener.selectionEnd : null
    };
    return function resolveTreeModalOpener() {
      if (descriptor.tabId !== renderedTreeTabId) return null;
      let resolved = descriptor.controlId
        ? document.getElementById(descriptor.controlId) : null;
      if (!resolved && descriptor.nodeKey) {
        resolved = findTreeNodeByKey(descriptor.nodeKey);
      }
      if (!resolved || !categoryTree.contains(resolved) || resolved.disabled === true) {
        return null;
      }
      if (Number.isInteger(descriptor.selectionStart) &&
          Number.isInteger(descriptor.selectionEnd) &&
          typeof resolved.setSelectionRange === "function") {
        try {
          resolved.setSelectionRange(descriptor.selectionStart, descriptor.selectionEnd);
        } catch (_) {
          // A recreated non-text control may expose, but reject, selection ranges.
        }
      }
      return resolved;
    };
  }

  function rememberModalOpener(modal) {
    if (modal) modalFocusManager.rememberOpener(modal, createModalOpenerReference());
  }

  function syncModalFocus() {
    modalFocusManager.sync(preferredModalFocus);
  }

  function activeTreeTabId(state) {
    if (!state || !Array.isArray(state.tabs)) return null;
    const active = state.tabs.find(function (tab) { return tab.isActive === true; });
    return active ? active.id : null;
  }

  function treeSectionIdentity(summary, index) {
    if (summary.dataset.sectionIndex != null) {
      return "fixed:" + summary.dataset.sectionIndex;
    }
    if (summary.dataset.industrySectionIndex != null) {
      return "industry:" + summary.dataset.industrySectionIndex;
    }
    if (summary.dataset.comparisonSectionIndex != null) {
      return "comparison:" + summary.dataset.comparisonSectionIndex;
    }
    return "section:" + index + ":" + (summary.textContent || "").trim();
  }

  function markVisualCardList(element, context) {
    if (!element) return element;
    element.dataset.visualCardList = "true";
    if (context) element.dataset.visualCardContext = context;
    return element;
  }

  function setVisualCardContent(button, options) {
    if (!button) return;
    const content = options || {};
    const titleText = content.title == null ? "" : String(content.title);
    const metaText = content.meta == null ? "" : String(content.meta);

    button.classList.add("visual-card");
    button.dataset.visualCardKind = content.kind || "graphic";
    if (titleText) button.setAttribute("aria-label", titleText);

    const copy = document.createElement("span");
    copy.className = "visual-card-copy";
    const title = document.createElement("span");
    title.className = "visual-card-title";
    title.textContent = titleText;
    copy.appendChild(title);
    if (metaText) {
      const meta = document.createElement("span");
      meta.className = "visual-card-meta";
      meta.textContent = metaText;
      copy.appendChild(meta);
    }

    button.replaceChildren(copy);
  }

  function treeLeafIdentity(button, index) {
    return button.dataset.actionId ||
      button.dataset.industryActionId ||
      button.dataset.comparisonActionId ||
      button.dataset.overseasActionId ||
      button.dataset.themeActionId ||
      button.dataset.expertActionId ||
      button.dataset.haltActionId ||
      ("leaf:" + index + ":" + (button.textContent || "").trim());
  }

  function syncTreeSectionExpanded(details) {
    const summary = details.querySelector(":scope > summary[data-tree-root]");
    if (summary) summary.setAttribute("aria-expanded", details.open ? "true" : "false");
  }

  function clearTreeRovingLists() {
    const lists = [];
    if (categoryTree.dataset.rovingTree === "true") lists.push(categoryTree);
    categoryTree.querySelectorAll("[data-roving-tree=\"true\"]")
      .forEach(function (list) { lists.push(list); });
    lists.forEach(function (list) {
      list.querySelectorAll("[data-tree-node-key]").forEach(function (node) {
        delete node.dataset.rovingRow;
        node.removeAttribute("tabindex");
      });
      delete list.dataset.rovingTree;
      delete list.dataset.rovingList;
      delete list.dataset.rovingPageSize;
      delete list.dataset.rovingSelectionFollowsFocus;
      delete list.dataset.rovingActivation;
      list.removeAttribute("tabindex");
      list.removeAttribute("role");
    });
  }

  function configureTreeRovingLists() {
    const containers = new Set();
    categoryTree.querySelectorAll("details[data-tree-section-key]")
      .forEach(function (details) {
        if (details.parentElement) containers.add(details.parentElement);
      });
    containers.forEach(function (list) {
      const rows = Array.from(list.querySelectorAll("[data-tree-node-key]"))
        .filter(function (row) {
          const details = row.closest("details[data-tree-section-key]");
          return details && details.parentElement === list;
        });
      const selected = rows.find(function (row) {
        return row.classList.contains("tree-node-selected");
      }) || null;
      configureRovingList(list, rows, selected, {
        listRole: "tree",
        rowRole: "treeitem",
        pageSize: 9,
        selectionFollowsFocus: false
      });
      list.dataset.rovingTree = "true";
    });
  }

  function decorateTreeNodes(tabId) {
    if (!tabId) return;
    clearTreeRovingLists();
    categoryTree.querySelectorAll("details").forEach(function (details, sectionIndex) {
      const summary = details.querySelector(":scope > summary");
      if (!summary) return;
      const sectionKey = tabId + "|" + treeSectionIdentity(summary, sectionIndex);
      details.dataset.treeSectionKey = sectionKey;
      summary.dataset.treeRoot = "true";
      summary.dataset.treeNodeKey = "root|" + sectionKey;
      summary.setAttribute("role", "treeitem");
      summary.setAttribute("aria-selected", "false");
      summary.setAttribute("aria-disabled", treeInteractionLocked ? "true" : "false");

      if (!summary.querySelector("[data-tree-disclosure]")) {
        const labelText = summary.textContent || "";
        const disclosure = document.createElement("span");
        disclosure.className = "tree-disclosure";
        disclosure.dataset.treeDisclosure = "true";
        disclosure.setAttribute("aria-hidden", "true");
        const label = document.createElement("span");
        label.className = "tree-root-label";
        label.dataset.treeRootLabel = "true";
        label.textContent = labelText;
        summary.replaceChildren(disclosure, label);
      }

      details.querySelectorAll(".tree-item").forEach(function (button, leafIndex) {
        button.dataset.treeNodeKey = "leaf|" + sectionKey + "|" +
          treeLeafIdentity(button, leafIndex);
        button.setAttribute("role", "treeitem");
        if (!button.classList.contains("tree-node-selected")) {
          button.setAttribute("aria-selected", "false");
        }
      });
      syncTreeSectionExpanded(details);
    });
    configureTreeRovingLists();
  }

  function findTreeNodeByKey(key) {
    if (!key) return null;
    return Array.from(categoryTree.querySelectorAll("[data-tree-node-key]")).find(
      function (node) { return node.dataset.treeNodeKey === key; }) || null;
  }

  function selectTreeNode(node, focus) {
    if (!node || !renderedTreeTabId) return;
    categoryTree.querySelectorAll("[data-tree-node-key].tree-node-selected")
      .forEach(function (selected) {
        selected.classList.remove("tree-node-selected");
        selected.setAttribute("aria-selected", "false");
      });
    node.classList.add("tree-node-selected");
    node.setAttribute("aria-selected", "true");
    const previous = treeUiStateByTab.get(renderedTreeTabId) || {};
    treeUiStateByTab.set(renderedTreeTabId, Object.assign({}, previous, {
      selectedKey: node.dataset.treeNodeKey
    }));
    const list = node.closest("[data-roving-tree=\"true\"]");
    if (list) setRovingListTabStop(list, node);
    if (focus !== false && typeof node.focus === "function") {
      node.focus({ preventScroll: true });
    }
  }

  function captureTreeUiState(tabId) {
    if (!tabId) return;
    decorateTreeNodes(tabId);
    const previous = treeUiStateByTab.get(tabId) || {};
    const openSections = new Map();
    categoryTree.querySelectorAll("details[data-tree-section-key]")
      .forEach(function (details) {
        openSections.set(details.dataset.treeSectionKey, details.open === true);
      });
    const selected = categoryTree.querySelector(
      "[data-tree-node-key].tree-node-selected");
    const activeElement = categoryTree.contains(document.activeElement)
      ? document.activeElement
      : null;
    const active = activeElement
      ? activeElement.closest("[data-tree-node-key]")
      : null;
    const controlFocus = activeElement && activeElement.id
      ? {
          id: activeElement.id,
          value: typeof activeElement.value === "string" ? activeElement.value : null,
          selectionStart: Number.isInteger(activeElement.selectionStart)
            ? activeElement.selectionStart : null,
          selectionEnd: Number.isInteger(activeElement.selectionEnd)
            ? activeElement.selectionEnd : null
        }
      : null;
    treeUiStateByTab.set(tabId, {
      openSections: openSections,
      selectedKey: selected ? selected.dataset.treeNodeKey : previous.selectedKey || null,
      focusKey: active ? active.dataset.treeNodeKey : previous.focusKey || null,
      controlFocus: activeElement ? controlFocus : previous.controlFocus || null,
      scrollTop: categoryTree.scrollTop
    });
  }

  function beginTreeRender(state) {
    const nextTabId = activeTreeTabId(state);
    treeFocusOwnedBeforeRender = categoryTree.contains(document.activeElement);
    if (renderedTreeTabId) captureTreeUiState(renderedTreeTabId);
    if (nextTabId !== renderedTreeTabId) {
      clearThemeResultPendingSelection(false);
      clearComparisonTargetPendingSelection(false);
      renderedTreeTabId = nextTabId;
      pendingTreeResetTabs.clear();
      if (nextTabId) pendingTreeResetTabs.add(nextTabId);
      if (nextTabId) treeUiStateByTab.delete(nextTabId);
      catalogExpandAll.checked = true;
    }
    treeInteractionLocked = state.isBusy === true || state.fixedSectionBatch != null;
    return nextTabId;
  }

  function finishTreeRender(tabId) {
    if (!tabId) return;
    const keyboardMarketFocusPending = typeof window !== "undefined" &&
      window.LegacyWorkspaceNavigation?.hasPendingKeyboardMarketFocus?.() === true;
    decorateTreeNodes(tabId);
    const details = Array.from(categoryTree.querySelectorAll("details[data-tree-section-key]"));
    const roots = Array.from(categoryTree.querySelectorAll("summary[data-tree-root]"));
    if (pendingTreeResetTabs.has(tabId) && roots.length === 0) {
      const originalFocusedControl = tabId === "expert"
        ? categoryTree.querySelector("#expert-preview")
        : null;
      if (originalFocusedControl && !modalFocusManager.getActiveModal() &&
          !keyboardMarketFocusPending) {
        originalFocusedControl.focus({ preventScroll: true });
      }
      pendingTreeResetTabs.delete(tabId);
      return;
    }
    if (pendingTreeResetTabs.has(tabId) && roots.length > 0) {
      details.forEach(function (section) {
        section.open = true;
        syncTreeSectionExpanded(section);
      });
      const firstRoot = roots[0];
      selectTreeNode(firstRoot,
        !modalFocusManager.getActiveModal() && !keyboardMarketFocusPending);
      if (typeof firstRoot.scrollIntoView === "function") {
        firstRoot.scrollIntoView({ block: "nearest", inline: "nearest" });
      }
      treeUiStateByTab.set(tabId, {
        openSections: new Map(details.map(function (section) {
          return [section.dataset.treeSectionKey, true];
        })),
        selectedKey: firstRoot.dataset.treeNodeKey,
        focusKey: firstRoot.dataset.treeNodeKey,
        scrollTop: categoryTree.scrollTop
      });
      pendingTreeResetTabs.delete(tabId);
      return;
    }

    const saved = treeUiStateByTab.get(tabId);
    if (!saved) return;
    if (saved.openSections instanceof Map) {
      details.forEach(function (section) {
        if (saved.openSections.has(section.dataset.treeSectionKey)) {
          section.open = saved.openSections.get(section.dataset.treeSectionKey) === true;
        }
        syncTreeSectionExpanded(section);
      });
    }
    const selected = findTreeNodeByKey(saved.selectedKey);
    if (selected) selectTreeNode(selected, false);
    const canRestoreFocus = treeFocusOwnedBeforeRender &&
      !keyboardMarketFocusPending &&
      !modalFocusManager.getActiveModal();
    const focused = findTreeNodeByKey(saved.focusKey);
    const control = canRestoreFocus && saved.controlFocus && saved.controlFocus.id
      ? document.getElementById(saved.controlFocus.id)
      : null;
    const canRestoreControl = control && categoryTree.contains(control) &&
      control.disabled !== true && typeof control.focus === "function";
    // A focused editor must never be blurred through the previously selected
    // tree node. Apart from losing the local draft, that blur commits/cancels
    // an in-progress Korean IME composition on every native status refresh.
    if (canRestoreFocus && !canRestoreControl && focused &&
        typeof focused.focus === "function") {
      focused.focus({ preventScroll: true });
    }
    if (canRestoreControl) {
      const controlWasRecreated = document.activeElement !== control;
      if (controlWasRecreated && typeof saved.controlFocus.value === "string" &&
          typeof control.value === "string") {
        control.value = saved.controlFocus.value;
      }
      if (controlWasRecreated) control.focus({ preventScroll: true });
      if (Number.isInteger(saved.controlFocus.selectionStart) &&
          Number.isInteger(saved.controlFocus.selectionEnd) &&
          typeof control.setSelectionRange === "function" && controlWasRecreated) {
          try {
            control.setSelectionRange(
              saved.controlFocus.selectionStart,
              saved.controlFocus.selectionEnd);
          } catch (_) {
            // Non-text controls can expose selection properties without accepting a range.
          }
      }
    }
    if (Number.isFinite(saved.scrollTop)) categoryTree.scrollTop = saved.scrollTop;
  }

  function send(type, payload) {
    if (!webview || uiBusy) return false;
    try {
      webview.postMessage({ type: type, payload: payload || {} });
      return true;
    } catch (_) {
      return false;
    }
  }

  function rememberSearchDraft(input) {
    if (!input || !searchDraftInputIds.has(input.id)) return;
    const previous = searchDrafts.get(input.id);
    const stateValue = previous ? previous.stateValue : "";
    searchDrafts.set(input.id, {
      value: input.value,
      stateValue: stateValue,
      dirty: input.value !== stateValue,
      scope: previous ? previous.scope : null
    });
  }

  function syncSearchDraft(input, stateValue, scope) {
    if (!input) return;
    const canonicalStateValue = stateValue || "";
    const canonicalScope = scope === undefined || scope === null ? null : String(scope);
    const remembered = searchDrafts.get(input.id);
    const previous = remembered && remembered.scope === canonicalScope
      ? remembered : null;
    let next;
    if (!previous) {
      next = {
        value: canonicalStateValue,
        stateValue: canonicalStateValue,
        dirty: false,
        scope: canonicalScope
      };
    } else if (previous.value === canonicalStateValue) {
      next = {
        value: canonicalStateValue,
        stateValue: canonicalStateValue,
        dirty: false,
        scope: canonicalScope
      };
    } else if (previous.dirty) {
      next = {
        value: previous.value,
        stateValue: canonicalStateValue,
        dirty: true,
        scope: canonicalScope
      };
    } else {
      next = {
        value: canonicalStateValue,
        stateValue: canonicalStateValue,
        dirty: false,
        scope: canonicalScope
      };
    }
    searchDrafts.set(input.id, next);
    if (document.activeElement !== input) input.value = next.value;
  }

  function forgetSearchDraft() {
    Array.from(arguments).forEach(function (inputId) {
      searchDrafts.delete(inputId);
    });
  }

  function captureSearchDraftFocus(root) {
    const input = document.activeElement;
    if (!root || !input || !searchDraftInputIds.has(input.id) ||
        typeof root.contains !== "function" || !root.contains(input)) return null;
    rememberSearchDraft(input);
    return {
      inputId: input.id,
      selectionStart: Number.isInteger(input.selectionStart) ? input.selectionStart : null,
      selectionEnd: Number.isInteger(input.selectionEnd) ? input.selectionEnd : null,
      selectionDirection: typeof input.selectionDirection === "string"
        ? input.selectionDirection : null
    };
  }

  function restoreSearchDraftFocus(root, saved) {
    if (!root || !saved || !saved.inputId) return false;
    const input = document.getElementById(saved.inputId);
    if (!input || input.disabled === true || typeof root.contains !== "function" ||
        !root.contains(input) || typeof input.focus !== "function") return false;
    if (document.activeElement !== input) input.focus({ preventScroll: true });
    if (Number.isInteger(saved.selectionStart) && Number.isInteger(saved.selectionEnd) &&
        typeof input.setSelectionRange === "function") {
      try {
        input.setSelectionRange(
          saved.selectionStart,
          saved.selectionEnd,
          saved.selectionDirection || "none");
      } catch (_) {
        // Text-like modal inputs support ranges; retain focus if a host rejects one.
      }
    }
    return true;
  }

  function isImeComposing(event) {
    return event.isComposing === true || event.keyCode === 229;
  }

  function setTransientControlsBusy(root, busy) {
    if (!root) return;
    root.querySelectorAll("button, input, select, textarea").forEach(function (control) {
      if (busy) {
        if (control.dataset.busyDisabled === undefined) {
          control.dataset.busyDisabled = control.disabled === true ? "true" : "false";
        }
        control.disabled = true;
      } else if (control.dataset.busyDisabled !== undefined) {
        control.disabled = control.dataset.busyDisabled === "true";
        delete control.dataset.busyDisabled;
      }
    });
  }

  function isOperatorMutationLocked(state) {
    const playout = state && state.playout;
    return !state || state.isBusy === true || state.fixedSectionBatch != null ||
      (playout && (playout.isBusy === true || playout.outcomeUnknown === true ||
        playout.isPlayCompletionPending === true ||
        playout.isTakeOutCompletionPending === true));
  }

  function isPlaylistStructureMutationLocked(state) {
    const playout = state && state.playout;
    return isOperatorMutationLocked(state) ||
      (playout && playout.phase !== "idle");
  }

  function isPlaylistProgramEnableMutationLocked(state) {
    const playout = state && state.playout;
    return isOperatorMutationLocked(state) || !playout ||
      (playout.phase !== "idle" && playout.phase !== "program");
  }

  function canDeleteSelectedPlaylistRows(state) {
    if (isOperatorMutationLocked(state) || !state || !Array.isArray(state.playlist)) {
      return false;
    }
    const selectedIndices = [];
    state.playlist.forEach(function (row, index) {
      if (row.isSelected === true) selectedIndices.push(index);
    });
    if (selectedIndices.length === 0) return false;
    const playout = state.playout;
    if (!playout || playout.phase === "idle") return true;
    const onAirIndex = Number(playout.onAirCueIndexZeroBased);
    const protectedIndex = Number.isSafeInteger(onAirIndex) && onAirIndex >= 0
      ? onAirIndex
      : Number(playout.currentCueIndexZeroBased);
    return Number.isSafeInteger(protectedIndex) && protectedIndex >= 0 &&
      selectedIndices.every(function (index) { return index > protectedIndex; });
  }

  function isCutSelectionLocked(state) {
    const playout = state && state.playout;
    return !state || state.isBusy === true || state.fixedSectionBatch != null ||
      (playout && (playout.isBusy === true || playout.outcomeUnknown === true ||
        playout.isPlayCompletionPending === true ||
        playout.isTakeOutCompletionPending === true));
  }

  function dragDropPosition(event, element) {
    const bounds = element.getBoundingClientRect();
    return event.clientY < bounds.top + (bounds.height / 2) ? "before" : "after";
  }

  function playlistPlayoutPresentation(state, row, position) {
    const playout = state && state.playout;
    const phase = playout && typeof playout.phase === "string"
      ? playout.phase : "idle";
    const hasActiveScene = phase === "prepared" || phase === "program";
    const currentEntryId = playout && typeof playout.currentEntryId === "string" &&
      playout.currentEntryId.length > 0 ? playout.currentEntryId : null;
    const currentIndex = playout && Number.isInteger(playout.currentCueIndexZeroBased)
      ? playout.currentCueIndexZeroBased : -1;
    const hasExplicitOnAirIdentity = playout && (
      Object.prototype.hasOwnProperty.call(playout, "onAirEntryId") ||
      Object.prototype.hasOwnProperty.call(playout, "onAirCueIndexZeroBased"));
    const onAirEntryId = playout && typeof playout.onAirEntryId === "string" &&
      playout.onAirEntryId.length > 0 ? playout.onAirEntryId : null;
    const onAirIndex = playout && Number.isInteger(playout.onAirCueIndexZeroBased)
      ? playout.onAirCueIndexZeroBased : -1;
    const isCurrent = hasActiveScene && (currentEntryId !== null
      ? currentEntryId === row.rowId
      : currentIndex === position);
    const isOnAir = phase === "program" && (hasExplicitOnAirIdentity
      ? (onAirEntryId !== null ? onAirEntryId === row.rowId : onAirIndex === position)
      : isCurrent);
    const ownsPage = phase === "program" ? isOnAir : isCurrent;
    const pageCount = ownsPage && Number.isInteger(playout.pageCount) &&
      playout.pageCount > 0 ? playout.pageCount : 0;
    const pageIndex = ownsPage && Number.isInteger(playout.pageIndexZeroBased) &&
      playout.pageIndexZeroBased >= 0 && playout.pageIndexZeroBased < pageCount
      ? playout.pageIndexZeroBased : -1;

    return {
      isSelected: row.isSelected === true,
      isCurrent: isCurrent,
      isOnAir: isOnAir,
      pageText: pageIndex >= 0
        ? String(pageIndex + 1) + "/" + String(pageCount)
        : row.pageText,
      focusKey: isCurrent
        ? [phase, currentEntryId || row.rowId || currentIndex, pageIndex, pageCount].join(":")
        : ""
    };
  }

  function presentCurrentPlaylistRow(element, focusKey) {
    if (!element || !focusKey) {
      lastPresentedPlayoutRowKey = "";
      return;
    }
    if (lastPresentedPlayoutRowKey === focusKey) return;

    lastPresentedPlayoutRowKey = focusKey;
    element.focus({ preventScroll: true });
    if (typeof element.scrollIntoView === "function") {
      element.scrollIntoView({ block: "nearest", inline: "nearest" });
    }
  }

  function selectPlaylistBoundary(boundary) {
    const rows = playlistRows.querySelectorAll(".playlist-data-row");
    const element = boundary === "last"
      ? rows.item(rows.length - 1)
      : rows.item(0);
    if (!element) return;

    // MainForm's KeyPreview changed the playlist selection but returned the
    // base key result. Keep the textbox/ListBox caret and focus where the
    // operator pressed Home/End; only mirror the playlist scroll/selection.
    if (typeof element.scrollIntoView === "function") {
      element.scrollIntoView({ block: "nearest", inline: "nearest" });
    }
    send("select-playlist-boundary", { boundary: boundary });
  }

  function movePlaylistKeyboardSelection(element, event) {
    if (!element || uiBusy) return false;
    const rows = Array.from(playlistRows.querySelectorAll(".playlist-data-row"));
    const currentIndex = rows.indexOf(element);
    if (currentIndex < 0 || rows.length === 0) return false;
    const offset = event.key === "ArrowUp" ? -1 : 1;
    const nextIndex = Math.max(0, Math.min(rows.length - 1, currentIndex + offset));
    const next = rows[nextIndex];
    next.focus({ preventScroll: true });
    if (typeof next.scrollIntoView === "function") {
      next.scrollIntoView({ block: "nearest", inline: "nearest" });
    }
    if (nextIndex !== currentIndex) {
      send("select-playlist-row", {
        rowId: next.dataset.rowId,
        control: event.ctrlKey,
        shift: event.shiftKey
      });
    }
    return true;
  }

  const fallbackDoubleClickTimeMilliseconds = 500;
  const maximumDoubleClickTimeMilliseconds = 5000;
  const doubleClickTimerGraceMilliseconds = 50;
  let doubleClickTimeMilliseconds = fallbackDoubleClickTimeMilliseconds;

  function updateDoubleClickTime(state) {
    const candidate = state && state.interactionMetrics &&
      state.interactionMetrics.doubleClickTimeMilliseconds;
    doubleClickTimeMilliseconds = Number.isInteger(candidate) && candidate > 0 &&
      candidate <= maximumDoubleClickTimeMilliseconds
      ? candidate
      : fallbackDoubleClickTimeMilliseconds;
  }

  function deferLegacySingleClick(callback) {
    return window.setTimeout(
      callback,
      doubleClickTimeMilliseconds + doubleClickTimerGraceMilliseconds);
  }

  function clearDelayedClick(timer) {
    if (timer !== null) window.clearTimeout(timer);
  }

  function pendingSelectionContainer(row) {
    if (!row || typeof row.closest !== "function") return null;
    return row.closest("[role=\"listbox\"]") || row.parentElement;
  }

  function applyPendingRowSelection(pending, focus) {
    if (!pending || !pending.container || !pending.row ||
        pending.container.isConnected === false || pending.row.isConnected === false) {
      return false;
    }
    pending.rows.forEach(function (entry) {
      if (!entry.element || entry.element.isConnected === false) return;
      const selected = entry.element === pending.row;
      entry.element.classList.toggle("selected", selected);
      entry.element.setAttribute("aria-selected", selected ? "true" : "false");
      entry.element.tabIndex = selected ? 0 : -1;
    });
    if (pending.row.id) {
      pending.container.setAttribute("aria-activedescendant", pending.row.id);
    }
    if (focus !== false && typeof pending.row.focus === "function") {
      pending.row.focus({ preventScroll: true });
    }
    return true;
  }

  function createPendingRowSelection(row, rowSelector, key) {
    const container = pendingSelectionContainer(row);
    if (!container) return null;
    const rows = Array.from(container.querySelectorAll(rowSelector)).map(function (element) {
      return {
        element: element,
        selected: element.classList.contains("selected"),
        ariaSelected: element.getAttribute("aria-selected"),
        tabIndex: element.tabIndex
      };
    });
    const pending = {
      key: key,
      row: row,
      container: container,
      rows: rows,
      activeDescendant: container.getAttribute("aria-activedescendant"),
      sent: false,
      lockedControls: []
    };
    applyPendingRowSelection(pending, true);
    return pending;
  }

  function restorePendingRowSelection(pending) {
    if (!pending || !pending.container || pending.container.isConnected === false) return;
    pending.rows.forEach(function (entry) {
      if (!entry.element || entry.element.isConnected === false) return;
      entry.element.classList.toggle("selected", entry.selected);
      entry.element.setAttribute(
        "aria-selected",
        entry.ariaSelected === null ? "false" : entry.ariaSelected);
      entry.element.tabIndex = entry.tabIndex;
    });
    if (pending.activeDescendant) {
      pending.container.setAttribute("aria-activedescendant", pending.activeDescendant);
    } else if (typeof pending.container.removeAttribute === "function") {
      pending.container.removeAttribute("aria-activedescendant");
    }
  }

  function lockPendingControls(root, selector) {
    if (!root) return [];
    const controls = Array.from(root.querySelectorAll(selector));
    const locked = controls.map(function (control) {
      return { control: control, disabled: control.disabled === true };
    });
    controls.forEach(function (control) { control.disabled = true; });
    return locked;
  }

  function applyPendingControlLocks(lockedControls) {
    (lockedControls || []).forEach(function (entry) {
      if (entry.control && entry.control.isConnected !== false) {
        entry.control.disabled = true;
      }
    });
  }

  function releasePendingControlLocks(lockedControls) {
    (lockedControls || []).forEach(function (entry) {
      const control = entry.control;
      if (!control || control.isConnected === false) return;
      if (control.dataset && control.dataset.busyDisabled !== undefined) {
        // Keep a host busy snapshot authoritative while correcting the semantic
        // value that setTransientControlsBusy will restore when that snapshot ends.
        control.dataset.busyDisabled = entry.disabled ? "true" : "false";
      } else {
        control.disabled = entry.disabled;
      }
    });
  }

  function finishPendingRowSelection(pending, restoreVisual) {
    if (!pending) return;
    releasePendingControlLocks(pending.lockedControls);
    if (restoreVisual === true) restorePendingRowSelection(pending);
  }

  function clearManualResultPendingSelection(restoreVisual) {
    clearDelayedClick(manualResultClickTimer);
    manualResultClickTimer = null;
    const pending = manualResultPendingSelection;
    manualResultPendingSelection = null;
    finishPendingRowSelection(pending, restoreVisual);
  }

  function clearThemeResultPendingSelection(restoreVisual) {
    clearDelayedClick(themeResultClickTimer);
    themeResultClickTimer = null;
    const pending = themeResultPendingSelection;
    themeResultPendingSelection = null;
    finishPendingRowSelection(pending, restoreVisual);
  }

  function clearComparisonTargetPendingSelection(restoreVisual) {
    clearDelayedClick(comparisonTargetClickTimer);
    comparisonTargetClickTimer = null;
    const pending = comparisonTargetPendingSelection;
    comparisonTargetPendingSelection = null;
    finishPendingRowSelection(pending, restoreVisual);
  }

  function clearManualViResultPendingSelection(restoreVisual) {
    clearDelayedClick(manualViResultClickTimer);
    manualViResultClickTimer = null;
    const pending = manualViResultPendingSelection;
    manualViResultPendingSelection = null;
    finishPendingRowSelection(pending, restoreVisual);
  }

  function beginManualResultPendingSelection(row) {
    const key = row.dataset.manualResultId;
    if (manualResultPendingSelection && !manualResultPendingSelection.sent &&
        manualResultPendingSelection.key === key) {
      applyPendingRowSelection(manualResultPendingSelection, true);
      applyPendingControlLocks(manualResultPendingSelection.lockedControls);
      return manualResultPendingSelection;
    }
    clearManualResultPendingSelection(true);
    manualResultPendingSelection = createPendingRowSelection(
      row,
      "button[data-manual-result-id]",
      key);
    if (manualResultPendingSelection) {
      manualResultPendingSelection.lockedControls = lockPendingControls(
        manualWorkspace,
        ".manual-financial-editor input, .manual-financial-editor select, " +
          ".manual-financial-editor textarea, button[data-manual-stock-search], " +
          "button[data-manual-stock-result-id], button[data-manual-save], " +
          "button[data-manual-delete], button[data-manual-delete-all], " +
          "button[data-manual-action-id]");
    }
    return manualResultPendingSelection;
  }

  function beginThemeResultPendingSelection(row) {
    const key = row.dataset.themeResultId;
    if (themeResultPendingSelection && !themeResultPendingSelection.sent &&
        themeResultPendingSelection.key === key) {
      applyPendingRowSelection(themeResultPendingSelection, true);
      applyPendingControlLocks(themeResultPendingSelection.lockedControls);
      return themeResultPendingSelection;
    }
    clearThemeResultPendingSelection(true);
    themeResultPendingSelection = createPendingRowSelection(
      row,
      "button[data-theme-result-id]",
      key);
    if (themeResultPendingSelection) {
      themeResultPendingSelection.lockedControls = lockPendingControls(
        categoryTree,
        "button[data-theme-action-id], #uc4-theme-edit, #uc4-theme-delete");
    }
    return themeResultPendingSelection;
  }

  function beginComparisonTargetPendingSelection(row, key) {
    if (comparisonTargetPendingSelection && !comparisonTargetPendingSelection.sent &&
        comparisonTargetPendingSelection.key === key) {
      applyPendingRowSelection(comparisonTargetPendingSelection, true);
      return comparisonTargetPendingSelection;
    }
    clearComparisonTargetPendingSelection(true);
    comparisonTargetPendingSelection = createPendingRowSelection(
      row,
      row.dataset.comparisonTargetId
        ? "button[data-comparison-target-id]"
        : "button[data-comparison-selection-id]",
      key);
    if (comparisonTargetPendingSelection) {
      comparisonTargetPendingSelection.kind = row.dataset.comparisonTargetId
        ? "fixed" : row.dataset.comparisonChoice;
    }
    return comparisonTargetPendingSelection;
  }

  function beginManualViResultPendingSelection(row, key) {
    if (manualViResultPendingSelection && !manualViResultPendingSelection.sent &&
        manualViResultPendingSelection.key === key) {
      applyPendingRowSelection(manualViResultPendingSelection, true);
      return manualViResultPendingSelection;
    }
    clearManualViResultPendingSelection(true);
    manualViResultPendingSelection = createPendingRowSelection(
      row,
      "button[data-manual-vi-result-id]",
      key);
    return manualViResultPendingSelection;
  }

  function isRovingListNavigationKey(key) {
    return key === "ArrowUp" || key === "ArrowDown" ||
      key === "Home" || key === "End" ||
      key === "PageUp" || key === "PageDown";
  }

  function rovingListRows(list) {
    return list
      ? Array.from(list.querySelectorAll("[data-roving-row]"))
      : [];
  }

  function configureRovingList(list, rows, selectedRow, options) {
    if (!list) return;
    const settings = options || {};
    list.dataset.rovingList = "true";
    list.dataset.rovingPageSize = String(
      Number.isSafeInteger(settings.pageSize) && settings.pageSize > 0
        ? settings.pageSize : 9);
    list.dataset.rovingSelectionFollowsFocus =
      settings.selectionFollowsFocus === false ? "false" : "true";
    list.dataset.rovingActivation = settings.activation === "focus"
      ? "focus" : "select";
    list.setAttribute("role", settings.listRole || "listbox");
    const enabledRows = rows.filter(isRovingListRowAvailable);
    const tabStop = selectedRow && isRovingListRowAvailable(selectedRow)
      ? selectedRow
      : (enabledRows[0] || null);
    rows.forEach(function (row) {
      row.dataset.rovingRow = "true";
      row.setAttribute("role", settings.rowRole || "option");
      row.tabIndex = row === tabStop ? 0 : -1;
    });
    // An empty WinForms ListBox is still one tab stop. Once it has rows, the
    // selected (or first selectable) row carries that one stop for WebView2.
    list.tabIndex = tabStop ? -1 : 0;
  }

  function isRovingListRowAvailable(row) {
    if (!row || row.disabled === true ||
        row.getAttribute("aria-disabled") === "true") return false;
    if (row.dataset && row.dataset.treeNodeKey &&
        row.dataset.treeRoot !== "true" && typeof row.closest === "function") {
      const details = row.closest("details");
      if (details && details.open !== true) return false;
    }
    return true;
  }

  function rovingListPageSize(list) {
    const value = list && list.dataset
      ? Number(list.dataset.rovingPageSize) : NaN;
    return Number.isSafeInteger(value) && value > 0 ? value : 9;
  }

  function rovingListKeyTarget(list, currentRow, key) {
    if (!list || !isRovingListNavigationKey(key)) return null;
    const rows = rovingListRows(list).filter(isRovingListRowAvailable);
    if (rows.length === 0) return null;
    let index = rows.indexOf(currentRow);
    if (index < 0) {
      index = rows.findIndex(function (row) {
        return row.getAttribute("aria-selected") === "true";
      });
    }
    if (key === "Home") return rows[0];
    if (key === "End") return rows[rows.length - 1];
    if (index < 0) {
      return key === "PageDown"
        ? rows[Math.min(rows.length - 1, rovingListPageSize(list))]
        : rows[0];
    }
    const offset = key === "PageUp" || key === "PageDown"
      ? rovingListPageSize(list) : 1;
    return key === "ArrowUp" || key === "PageUp"
      ? rows[Math.max(0, index - offset)]
      : rows[Math.min(rows.length - 1, index + offset)];
  }

  function setRovingListTabStop(list, row) {
    if (!list || !row || !isRovingListRowAvailable(row)) return false;
    rovingListRows(list).forEach(function (candidate) {
      candidate.tabIndex = candidate === row ? 0 : -1;
    });
    list.tabIndex = -1;
    return true;
  }

  function focusRovingListRow(list, row, select) {
    if (!setRovingListTabStop(list, row)) return false;
    rovingListRows(list).forEach(function (candidate) {
      const isTarget = candidate === row;
      if (select === true) {
        candidate.classList.toggle("selected", isTarget);
        candidate.setAttribute("aria-selected", isTarget ? "true" : "false");
      }
    });
    if (document.activeElement !== row) row.focus({ preventScroll: true });
    if (typeof row.scrollIntoView === "function") {
      row.scrollIntoView({ block: "nearest", inline: "nearest" });
    }
    return true;
  }

  function moveRovingListSelection(list, currentRow, key, activate) {
    const target = rovingListKeyTarget(list, currentRow, key);
    if (!target) return false;
    const selectionChanged = target.getAttribute("aria-selected") !== "true";
    const focusOnly = list.dataset.rovingActivation === "focus";
    if (!focusOnly && selectionChanged && activate(target) === false) return false;
    return focusRovingListRow(
      list,
      target,
      list.dataset.rovingSelectionFollowsFocus !== "false");
  }

  function stockResultPageOffset(button) {
    const itemHeight = button && Number.isFinite(button.offsetHeight)
      ? button.offsetHeight
      : 0;
    const viewportHeight = Number.isFinite(stockResults.clientHeight)
      ? stockResults.clientHeight
      : 0;
    if (itemHeight <= 0 || viewportHeight <= 0) return 9;

    // The native 174 px ListBox exposes ten 17 px rows and PageUp/PageDown
    // retain one boundary row, so its selection moves by visibleRows - 1.
    const visibleRows = Math.max(1, Math.floor(viewportHeight / itemHeight));
    return Math.max(1, visibleRows - 1);
  }

  function stockResultKeyTarget(results, key) {
    if (!results || results.length === 0) return null;

    let selectedPosition = -1;
    for (let position = 0; position < results.length; position += 1) {
      if (results.item(position).getAttribute("aria-selected") === "true") {
        selectedPosition = position;
        break;
      }
    }

    const pageOffset = stockResultPageOffset(results.item(0));
    let targetPosition;
    if (key === "End") {
      targetPosition = results.length - 1;
    } else if (key === "Home") {
      targetPosition = 0;
    } else if (key === "ArrowUp" || key === "PageUp") {
      const offset = key === "ArrowUp" ? 1 : pageOffset;
      targetPosition = selectedPosition < 0 ? 0 : selectedPosition - offset;
    } else if (key === "ArrowDown" || key === "PageDown") {
      const offset = key === "ArrowDown" ? 1 : pageOffset;
      targetPosition = selectedPosition < 0
        ? (key === "PageDown" ? offset : 0)
        : selectedPosition + offset;
    } else {
      return null;
    }

    return results.item(Math.max(0, Math.min(results.length - 1, targetPosition)));
  }

  function selectStockResult(button, focus) {
    if (!button || button.disabled || uiBusy) return;
    const results = stockResults.querySelectorAll("button[data-result-index]");
    const selectionChanged = button.getAttribute("aria-selected") !== "true";
    for (let position = 0; position < results.length; position += 1) {
      const result = results.item(position);
      const isSelected = result === button;
      result.tabIndex = isSelected ? 0 : -1;
      result.classList.toggle("selected", isSelected);
      result.setAttribute("aria-selected", isSelected ? "true" : "false");
    }
    if (focus === true) {
      button.focus({ preventScroll: true });
      if (typeof button.scrollIntoView === "function") {
        button.scrollIntoView({ block: "nearest", inline: "nearest" });
      }
    }
    if (selectionChanged) {
      send("select-stock", { resultIndex: Number(button.dataset.resultIndex) });
    }
  }

  function createStockResultElement(row) {
    const button = document.createElement("button");
    button.type = "button";
    button.dataset.resultIndex = String(row.index);
    button.setAttribute("role", "option");
    button.addEventListener("click", function () {
      selectStockResult(button, false);
    });
    button.addEventListener("keydown", function (event) {
      const results = stockResults.querySelectorAll("button[data-result-index]");
      const target = stockResultKeyTarget(results, event.key);
      if (!target) return;
      // Consume only the native ListBox navigation behavior. Home/End continue
      // bubbling to MainForm's KeyPreview-equivalent playlist boundary handler.
      event.preventDefault();
      selectStockResult(target, true);
    });
    return button;
  }

  function renderStockResults(state) {
    let hasSelectedResult = false;
    state.searchResults.forEach(function (row, position) {
      let button = stockResults.children.item(position);
      if (!button || button.dataset.resultIndex !== String(row.index)) {
        button = createStockResultElement(row);
        if (position < stockResults.children.length) {
          stockResults.replaceChild(button, stockResults.children.item(position));
        } else {
          stockResults.appendChild(button);
        }
      }

      const isSelected = state.selectedStockIndex === row.index;
      if (isSelected) hasSelectedResult = true;
      button.className = "stock-result" + (isSelected ? " selected" : "");
      button.setAttribute("aria-disabled", state.isBusy === true ? "true" : "false");
      button.disabled = state.isBusy === true;
      button.textContent = row.displayName;
      button.setAttribute("aria-selected", isSelected ? "true" : "false");
      button.tabIndex = isSelected ? 0 : -1;
    });

    while (stockResults.children.length > state.searchResults.length) {
      stockResults.lastElementChild.remove();
    }

    // A WinForms ListBox is one tab stop even before it has a selection.
    if (!hasSelectedResult && stockResults.children.length > 0) {
      stockResults.children.item(0).tabIndex = 0;
    }
  }

  function renderStockSearchMeta(state) {
    const results = Array.isArray(state.searchResults) ? state.searchResults : [];
    const selected = results.find(function (row) {
      return row.index === state.selectedStockIndex;
    });
    let label = "";

    if (state.isBusy === true) {
      label = "검색 중";
    } else if (results.length > 0 || String(state.searchText || "").trim().length > 0) {
      label = String(results.length) + "건";
      if (selected && selected.displayName) {
        label += " · " + selected.displayName;
      }
    }

    stockSearchMeta.textContent = label;
    stockSearchMeta.hidden = label.length === 0;
    if (state.isBusy !== true && results.length === 0) {
      stockResults.dataset.emptyLabel = "결과 없음";
    } else {
      delete stockResults.dataset.emptyLabel;
    }
  }

  function pointerPosition(event) {
    const bounds = cutList.getBoundingClientRect();
    return {
      x: event.clientX - bounds.left,
      y: event.clientY - bounds.top
    };
  }

  function pointInsideElement(element, event) {
    if (!element || typeof element.getBoundingClientRect !== "function") return false;
    const bounds = element.getBoundingClientRect();
    return event.clientX >= bounds.left && event.clientX < bounds.right &&
      event.clientY >= bounds.top && event.clientY < bounds.bottom;
  }

  function clearCutDragGesture(releaseCapture) {
    const gesture = cutDragGesture;
    cutDragGesture = null;
    playlistDropZone.classList.remove("cut-drag-over");
    cutDragFeedback.hidden = true;
    cutDragFeedback.classList.remove("allowed");
    delete cutDragFeedback.dataset.dropAllowed;
    cutDragFeedback.style.removeProperty("left");
    cutDragFeedback.style.removeProperty("top");
    if (!gesture) return;
    gesture.element.classList.remove("dragging", "drag-allowed");
    if (releaseCapture !== false &&
        typeof gesture.element.hasPointerCapture === "function" &&
        gesture.element.hasPointerCapture(gesture.pointerId) &&
        typeof gesture.element.releasePointerCapture === "function") {
      try {
        gesture.element.releasePointerCapture(gesture.pointerId);
      } catch (_) {
        // The browser may release capture between the key event and cleanup.
      }
    }
  }

  function showCutDragFeedback(event, allowed) {
    cutDragFeedback.hidden = false;
    cutDragFeedback.classList.toggle("allowed", allowed);
    cutDragFeedback.dataset.dropAllowed = allowed ? "true" : "false";
    const gap = 14;
    const viewportWidth = document.documentElement.clientWidth;
    const viewportHeight = document.documentElement.clientHeight;
    const width = cutDragFeedback.offsetWidth;
    const height = cutDragFeedback.offsetHeight;
    const left = Math.max(4, Math.min(event.clientX + gap, viewportWidth - width - 4));
    const top = Math.max(4, Math.min(event.clientY + gap, viewportHeight - height - 4));
    cutDragFeedback.style.left = Math.round(left) + "px";
    cutDragFeedback.style.top = Math.round(top) + "px";
  }

  function beginCutDragGesture(element, event, position) {
    clearCutDragGesture();
    if (event.button !== 0) return;
    cutDragGesture = {
      element: element,
      pointerId: event.pointerId,
      down: position,
      rectangle: window.LegacyCutDrag.createDragRectangle(position, cutDragSize),
      started: false
    };
    if (typeof element.setPointerCapture === "function") {
      try {
        element.setPointerCapture(event.pointerId);
      } catch (_) {
        // Pointer capture can fail if the WebView has already released it.
      }
    }
  }

  function onCutPointerMove(event) {
    const gesture = cutDragGesture;
    if (!gesture || gesture.element !== event.currentTarget ||
        gesture.pointerId !== event.pointerId) return;
    if ((event.buttons & 1) !== 1) {
      clearCutDragGesture();
      return;
    }
    if (!gesture.started &&
        window.LegacyCutDrag.shouldStart(gesture.rectangle, pointerPosition(event))) {
      gesture.started = true;
      gesture.element.classList.add("dragging");
    }
    if (gesture.started) {
      event.preventDefault();
      const dropAllowed = !playlistMutationLocked &&
        pointInsideElement(playlistDropZone, event);
      gesture.element.classList.toggle("drag-allowed", dropAllowed);
      playlistDropZone.classList.toggle("cut-drag-over", dropAllowed);
      showCutDragFeedback(event, dropAllowed);
    }
  }

  function onCutPointerUp(event) {
    const gesture = cutDragGesture;
    if (!gesture || gesture.element !== event.currentTarget ||
        gesture.pointerId !== event.pointerId) return;
    const shouldDrop = gesture.started && !playlistMutationLocked &&
      pointInsideElement(playlistDropZone, event);
    clearCutDragGesture();
    if (shouldDrop) send("drop-selected-cuts", {});
  }

  function onCutPointerCancel(event) {
    const gesture = cutDragGesture;
    if (gesture && gesture.element === event.currentTarget &&
        gesture.pointerId === event.pointerId) {
      clearCutDragGesture(event.type !== "lostpointercapture");
    }
  }

  function legacyCutKey(key) {
    return {
      ArrowUp: "arrow-up",
      ArrowDown: "arrow-down",
      Home: "home",
      End: "end",
      " ": "space"
    }[key] || null;
  }

  function focusCutElement(element, scroll) {
    if (!element) return;
    Array.from(cutList.children).forEach(function (row) {
      row.tabIndex = row === element ? 0 : -1;
    });
    element.focus({ preventScroll: true });
    if (scroll && typeof element.scrollIntoView === "function") {
      element.scrollIntoView({ block: "nearest", inline: "nearest" });
    }
  }

  function cutKeyboardTarget(element, key) {
    const rows = Array.from(cutList.children);
    const index = rows.indexOf(element);
    if (index < 0 || rows.length === 0) return null;
    if (key === "ArrowUp") return rows[Math.max(0, index - 1)];
    if (key === "ArrowDown") return rows[Math.min(rows.length - 1, index + 1)];
    if (key === "Home") return rows[0];
    if (key === "End") return rows[rows.length - 1];
    return element;
  }

  function createCutElement(row) {
    const element = document.createElement("div");
    element.setAttribute("role", "option");
    element.dataset.physicalIndex = String(row.physicalIndex);
    element.tabIndex = -1;
    element.draggable = false;

    const number = document.createElement("span");
    number.className = "cut-number";
    number.textContent = row.displayOrdinal === null ? "" : String(row.displayOrdinal);
    const label = document.createElement("span");
    label.textContent = row.rawLabel;
    element.append(number, label);

    element.addEventListener("pointerdown", function (event) {
      if (!cutSelectionEnabled) return event.preventDefault();
      focusCutElement(element, false);
      const position = pointerPosition(event);
      send("cut-pointer-down", {
        physicalIndex: row.physicalIndex,
        control: event.ctrlKey,
        shift: event.shiftKey,
        // C# retains integer coordinates only for the native double-click
        // equality contract. Drag geometry keeps the raw fractional CSS point.
        x: Math.round(position.x),
        y: Math.round(position.y),
        timestampMilliseconds: Math.max(0, Math.round(event.timeStamp)),
        allowAppend: !playlistMutationLocked
      });
      if (!playlistMutationLocked) {
        beginCutDragGesture(element, event, position);
      }
    });
    element.addEventListener("pointermove", onCutPointerMove);
    element.addEventListener("pointerup", onCutPointerUp);
    element.addEventListener("pointercancel", onCutPointerCancel);
    element.addEventListener("lostpointercapture", onCutPointerCancel);
    element.addEventListener("keydown", function (event) {
      const key = legacyCutKey(event.key);
      if (!key) return;
      // The native ListView consumes these keys. Home/End still bubble to the
      // document-level MainForm KeyPreview equivalent for its playlist side effect.
      event.preventDefault();
      if (!cutSelectionEnabled) return;
      const target = cutKeyboardTarget(element, event.key);
      focusCutElement(target, true);
      send("cut-key-down", {
        key: key,
        control: event.ctrlKey,
        shift: event.shiftKey
      });
    });
    return element;
  }

  function renderCuts(state) {
    const cutDragLocked = isOperatorMutationLocked(state);
    cutSelectionEnabled = Number.isInteger(state.selectedStockIndex) &&
      !isCutSelectionLocked(state);
    const focusedCutIndex = state.cutRows.findIndex(function (row) {
      return row.isFocused === true;
    });
    // renderCuts precedes renderPlaylist, so publish the same lock before a
    // cut row can initiate an append drag for this state revision.
    playlistMutationLocked = cutDragLocked;
    if (!cutSelectionEnabled || cutDragLocked ||
        (cutDragGesture && !cutList.contains(cutDragGesture.element))) {
      clearCutDragGesture();
    }
    state.cutRows.forEach(function (row, index) {
      let element = cutList.children.item(index);
      if (!element || element.dataset.physicalIndex !== String(row.physicalIndex)) {
        element = createCutElement(row);
        if (index < cutList.children.length) {
          cutList.replaceChild(element, cutList.children.item(index));
        } else {
          cutList.appendChild(element);
        }
      }

      element.className = "cut-row" +
        (row.isSelected ? " selected" : "") +
        (row.isSeparator ? " separator" : "") +
        (cutDragGesture && cutDragGesture.element === element && cutDragGesture.started
          ? " dragging" : "");
      element.setAttribute("aria-selected", row.isSelected ? "true" : "false");
      element.setAttribute("aria-disabled", state.isBusy === true ? "true" : "false");
      element.tabIndex = row.isFocused === true || (focusedCutIndex < 0 && index === 0)
        ? 0
        : -1;
      element.draggable = false;
      element.dataset.cutDragEnabled = cutSelectionEnabled && !cutDragLocked
        ? "true"
        : "false";
    });

    while (cutList.children.length > state.cutRows.length) {
      cutList.lastElementChild.remove();
    }
    if (cutDragGesture && !cutList.contains(cutDragGesture.element)) {
      clearCutDragGesture();
    }

    if (state.cutRows.length === 0) {
      cutList.dataset.emptyLabel = Number.isInteger(state.selectedStockIndex)
        ? "컷 없음"
        : "종목을 선택하세요";
    } else {
      delete cutList.dataset.emptyLabel;
    }
  }

  cutList.addEventListener("pointerdown", function (event) {
    if (event.target !== cutList || !cutSelectionEnabled) return;
    send("clear-cut-selection", {});
  });

  function clearPlaylistDragVisuals() {
    playlistRows.querySelectorAll(".dragging, .drag-allowed, .drag-before, .drag-after")
      .forEach(function (row) {
        row.classList.remove("dragging", "drag-allowed", "drag-before", "drag-after");
      });
  }

  function playlistPointerPosition(event) {
    const bounds = playlistRows.getBoundingClientRect();
    return {
      x: event.clientX - bounds.left,
      y: event.clientY - bounds.top
    };
  }

  function playlistRowHeaderAtPoint(event) {
    if (typeof document.elementFromPoint !== "function") return null;
    const pointed = document.elementFromPoint(event.clientX, event.clientY);
    const header = pointed && typeof pointed.closest === "function"
      ? pointed.closest(".playlist-row-header") : null;
    return header && playlistRows.contains(header) ? header : null;
  }

  function clearPlaylistDragGesture(releaseCapture) {
    const gesture = playlistDragGesture;
    if (!gesture) return;
    playlistDragGesture = null;
    clearPlaylistDragVisuals();
    if (releaseCapture !== false &&
        typeof gesture.handle.hasPointerCapture === "function" &&
        gesture.handle.hasPointerCapture(gesture.pointerId) &&
        typeof gesture.handle.releasePointerCapture === "function") {
      try {
        gesture.handle.releasePointerCapture(gesture.pointerId);
      } catch (_) {
        // Chromium can release capture before the final cleanup callback.
      }
    }
  }

  function beginPlaylistDragGesture(handle, row, event) {
    clearPlaylistDragGesture();
    if (playlistStructureMutationLocked || event.button !== 0 || !row.dataset.rowId) return;
    const position = playlistPointerPosition(event);
    playlistDragGesture = {
      handle: handle,
      row: row,
      rowId: row.dataset.rowId,
      pointerId: event.pointerId,
      down: position,
      rectangle: window.LegacyCutDrag.createDragRectangle(position, cutDragSize),
      started: false,
      targetRowId: null,
      position: null
    };
    if (typeof handle.setPointerCapture === "function") {
      try {
        handle.setPointerCapture(event.pointerId);
      } catch (_) {
        // Pointer capture can fail if WebView2 has already released the pointer.
      }
    }
  }

  function onPlaylistPointerMove(event) {
    const gesture = playlistDragGesture;
    if (!gesture || gesture.handle !== event.currentTarget ||
        gesture.pointerId !== event.pointerId) return;
    if ((event.buttons & 1) !== 1) {
      deferredPlaylistPlainSelectionRowId = null;
      clearPlaylistDragGesture();
      return;
    }
    if (!gesture.started && window.LegacyCutDrag.shouldStart(
        gesture.rectangle, playlistPointerPosition(event))) {
      gesture.started = true;
      deferredPlaylistPlainSelectionRowId = null;
      gesture.row.classList.add("dragging");
    }
    if (!gesture.started) return;

    event.preventDefault();
    playlistRows.querySelectorAll(".drag-before, .drag-after").forEach(function (row) {
      row.classList.remove("drag-before", "drag-after");
    });
    const targetHeader = playlistRowHeaderAtPoint(event);
    const targetRow = targetHeader ? targetHeader.closest(".playlist-data-row") : null;
    if (!targetRow || targetRow === gesture.row) {
      gesture.targetRowId = null;
      gesture.position = null;
      gesture.row.classList.remove("drag-allowed");
      return;
    }

    gesture.targetRowId = targetRow.dataset.rowId || null;
    gesture.position = dragDropPosition(event, targetRow);
    gesture.row.classList.toggle("drag-allowed", gesture.targetRowId !== null);
    targetRow.classList.toggle("drag-before", gesture.position === "before");
    targetRow.classList.toggle("drag-after", gesture.position === "after");
  }

  function onPlaylistPointerUp(event) {
    const gesture = playlistDragGesture;
    if (!gesture || gesture.handle !== event.currentTarget ||
        gesture.pointerId !== event.pointerId) return;
    const targetHeader = gesture.started ? playlistRowHeaderAtPoint(event) : null;
    const targetRow = targetHeader ? targetHeader.closest(".playlist-data-row") : null;
    const targetRowId = targetRow && targetRow !== gesture.row
      ? targetRow.dataset.rowId || null : null;
    const shouldReorder = gesture.started && !playlistStructureMutationLocked &&
      targetRowId && targetRowId !== gesture.rowId;
    const sourceRowId = gesture.rowId;
    const position = targetRow ? dragDropPosition(event, targetRow) : null;
    const wasStarted = gesture.started;
    clearPlaylistDragGesture();
    if (!wasStarted) return;

    deferredPlaylistPlainSelectionRowId = null;
    event.preventDefault();
    if (shouldReorder) {
      send("reorder-playlist-rows", {
        sourceRowId: sourceRowId,
        targetRowId: targetRowId,
        position: position
      });
    }
  }

  function onPlaylistPointerCancel(event) {
    const gesture = playlistDragGesture;
    if (!gesture || gesture.handle !== event.currentTarget ||
        gesture.pointerId !== event.pointerId) return;
    deferredPlaylistPlainSelectionRowId = null;
    clearPlaylistDragGesture(event.type !== "lostpointercapture");
  }

  function createPlaylistElement(row) {
    const element = document.createElement("div");
    element.className = "playlist-row playlist-data-row";
    element.dataset.rowId = row.rowId;
    element.setAttribute("role", "option");
    element.tabIndex = -1;
    element.draggable = false;

    const rowHeader = document.createElement("div");
    rowHeader.className = "playlist-row-header";
    rowHeader.setAttribute("role", "presentation");
    rowHeader.draggable = false;
    rowHeader.addEventListener("pointerdown", function (event) {
      // FarPoint reports a row-header press separately from normal data-cell
      // selection. It changes the active row used by F8, but it does not
      // rewrite MainForm's Ctrl/Shift deletion set.
      event.stopPropagation();
      element.focus({ preventScroll: true });
      send("activate-playlist-row", { rowId: element.dataset.rowId });
      beginPlaylistDragGesture(rowHeader, element, event);
    });
    rowHeader.addEventListener("pointermove", onPlaylistPointerMove);
    rowHeader.addEventListener("pointerup", onPlaylistPointerUp);
    rowHeader.addEventListener("pointercancel", onPlaylistPointerCancel);
    rowHeader.addEventListener("lostpointercapture", onPlaylistPointerCancel);
    element.appendChild(rowHeader);

    const enabledCell = document.createElement("div");
    enabledCell.className = "enabled-cell";
    const enabled = document.createElement("input");
    enabled.type = "checkbox";
    enabled.tabIndex = -1;
    enabled.draggable = false;
    enabled.addEventListener("click", function (event) {
      if (playlistStructureMutationLocked) event.preventDefault();
    });
    enabled.addEventListener("change", function () {
      if (playlistStructureMutationLocked) return;
      send("set-playlist-enabled", {
        rowId: element.dataset.rowId,
        enabled: enabled.checked
      });
    });
    enabledCell.appendChild(enabled);
    element.appendChild(enabledCell);

    for (let index = 0; index < 5; index += 1) {
      element.appendChild(document.createElement("div"));
    }

    element.addEventListener("pointerdown", function (event) {
      if (event.button !== 0) return;
      if (enabledCell.contains(event.target)) {
        element.focus({ preventScroll: true });
        send("activate-playlist-row", { rowId: element.dataset.rowId });
        return;
      }
      element.focus({ preventScroll: true });
      const plainSelection = !event.ctrlKey && !event.shiftKey;
      const deferSelectedRowPlainClickForDrag = plainSelection &&
        element.getAttribute("aria-selected") === "true" &&
        playlistRows.querySelectorAll('.playlist-data-row[aria-selected="true"]').length > 1;
      if (deferSelectedRowPlainClickForDrag) {
        deferredPlaylistPlainSelectionRowId = element.dataset.rowId;
        return;
      }

      deferredPlaylistPlainSelectionRowId = null;
      send("select-playlist-row", {
        rowId: element.dataset.rowId,
        control: event.ctrlKey,
        shift: event.shiftKey
      });
    });
    element.addEventListener("pointerup", function (event) {
      if (enabledCell.contains(event.target)) {
        return;
      }
      if (event.button !== 0 ||
          deferredPlaylistPlainSelectionRowId !== element.dataset.rowId) return;
      deferredPlaylistPlainSelectionRowId = null;
      send("select-playlist-row", {
        rowId: element.dataset.rowId,
        control: false,
        shift: false
      });
    });
    element.addEventListener("pointercancel", function () {
      if (deferredPlaylistPlainSelectionRowId === element.dataset.rowId) {
        deferredPlaylistPlainSelectionRowId = null;
      }
    });
    element.addEventListener("dragstart", function (event) {
      // Only the 30 px row header owns the pointer-capture reorder gesture.
      // Suppress Chromium's native text/checkbox/row drag path completely.
      event.preventDefault();
    });
    return element;
  }

  function renderPlaylist(state) {
    playlistMutationLocked = isOperatorMutationLocked(state);
    playlistStructureMutationLocked = isPlaylistStructureMutationLocked(state);
    playlistProgramEnableMutationLocked =
      isPlaylistProgramEnableMutationLocked(state);
    playlistDeleteSelectedLocked = !canDeleteSelectedPlaylistRows(state);
    if (playlistStructureMutationLocked) {
      deferredPlaylistPlainSelectionRowId = null;
      clearPlaylistDragGesture();
    }
    const existingRows = new Map();
    let currentPlayoutRow = null;
    let currentPlayoutFocusKey = "";
    Array.from(playlistRows.children).forEach(function (element) {
      existingRows.set(element.dataset.rowId, element);
    });

    state.playlist.forEach(function (row, position) {
      const presentation = playlistPlayoutPresentation(state, row, position);
      let element = existingRows.get(row.rowId);
      if (!element) {
        element = createPlaylistElement(row);
      }

      const elementAtPosition = playlistRows.children.item(position);
      if (elementAtPosition !== element) {
        playlistRows.insertBefore(element, elementAtPosition);
      }

      const enabled = element.querySelector("input");
      const rowHeader = element.querySelector(".playlist-row-header");
      rowHeader.textContent = String(position + 1);
      rowHeader.title = String(position + 1);
      rowHeader.setAttribute("aria-label", String(position + 1) + "번 행 이동 핸들");
      rowHeader.dataset.dragEnabled = playlistStructureMutationLocked ? "false" : "true";
      enabled.checked = row.isEnabled;
      enabled.disabled = false;
      enabled.setAttribute("aria-disabled", playlistStructureMutationLocked ? "true" : "false");
      const isCandidate = row.isActive === true;
      const isDragSource = playlistDragGesture?.started === true &&
        playlistDragGesture.rowId === row.rowId;
      const dragTargetPosition = playlistDragGesture?.started === true &&
        playlistDragGesture.targetRowId === row.rowId
        ? playlistDragGesture.position
        : null;
      element.className = "playlist-row playlist-data-row" +
        (presentation.isOnAir ? " on-air" : "") +
        (presentation.isCurrent ? " playout-current" : "") +
        (presentation.isSelected || isCandidate ? " selected" : "") +
        (row.isSceneResolved === false ? " scene-unresolved" : "") +
        (isDragSource ? " dragging" : "") +
        (dragTargetPosition === "before" ? " drag-before" : "") +
        (dragTargetPosition === "after" ? " drag-after" : "");
      element.dataset.active = isCandidate ? "true" : "false";
      element.setAttribute(
        "aria-selected",
        presentation.isSelected ? "true" : "false");
      element.setAttribute(
        "aria-current",
        presentation.isCurrent ? "true" : "false");
      const accessibleStates = [];
      if (presentation.isOnAir) accessibleStates.push("현재 PGM");
      if (isCandidate) accessibleStates.push("송출 후보");
      if (presentation.isSelected) accessibleStates.push("삭제 선택됨");
      if (row.isSceneResolved === false) accessibleStates.push("장면 확인 필요");
      element.title = row.isSceneResolved === false
        ? "기존 데이터는 정상 로드되었습니다. 이 행은 송출할 때 장면 이름을 다시 확인합니다."
        : "";
      element.setAttribute(
        "aria-label",
        accessibleStates.concat([
          row.marketText,
          row.stockName,
          row.graphicType,
          row.subtype,
          presentation.pageText
        ]).filter(Boolean).join(", "));
      element.draggable = false;
      element.setAttribute("aria-disabled", playlistMutationLocked ? "true" : "false");
      [row.marketText, row.stockName, row.graphicType, row.subtype, presentation.pageText]
        .forEach(function (text, cellIndex) {
          const cell = element.children.item(cellIndex + 2);
          cell.textContent = text;
          cell.title = text;
        });
      if (presentation.isCurrent) {
        currentPlayoutRow = element;
        currentPlayoutFocusKey = presentation.focusKey;
      }
    });

    const retainedRowIds = new Set(state.playlist.map(function (row) { return row.rowId; }));
    Array.from(playlistRows.children).forEach(function (element) {
      if (!retainedRowIds.has(element.dataset.rowId)) element.remove();
    });
    if (playlistDragGesture && !retainedRowIds.has(playlistDragGesture.rowId)) {
      deferredPlaylistPlainSelectionRowId = null;
      clearPlaylistDragGesture();
    }
    presentCurrentPlaylistRow(currentPlayoutRow, currentPlayoutFocusKey);

    const interactionDisabled = playlistStructureMutationLocked;
    const enabledCount = state.playlist.filter(function (row) { return row.isEnabled; }).length;
    const nextHeadingCount = `총 ${state.playlist.length}개 · 송출 ${enabledCount}개`;
    if (playlistHeadingCount.textContent !== nextHeadingCount) {
      playlistHeadingCount.textContent = nextHeadingCount;
    }
    playlistEnableAll.checked = state.playlist.length === 0 || enabledCount === state.playlist.length;
    playlistEnableAll.indeterminate = enabledCount > 0 && enabledCount < state.playlist.length;
    playlistEnableAll.disabled = playlistProgramEnableMutationLocked;
    playlistMoveUp.disabled = interactionDisabled;
    playlistMoveDown.disabled = interactionDisabled;
    playlistDeleteSelected.disabled = playlistDeleteSelectedLocked;
    playlistDeleteAll.disabled = interactionDisabled;
  }

  function tabOrderFromDom() {
    return Array.from(marketTabs.querySelectorAll("button[data-tab-id]"))
      .map(function (button) { return button.dataset.tabId; });
  }

  function clearPendingTabHoverSwap() {
    if (pendingTabHoverSwap && pendingTabHoverSwap.timeoutId !== null) {
      window.clearTimeout(pendingTabHoverSwap.timeoutId);
    }
    pendingTabHoverSwap = null;
  }

  function acknowledgePendingTabHoverSwap(order) {
    if (!pendingTabHoverSwap) return;
    const orderKey = order.join("|");
    if (orderKey === pendingTabHoverSwap.expectedOrderKey ||
        orderKey !== pendingTabHoverSwap.initialOrderKey) {
      clearPendingTabHoverSwap();
    }
  }

  function hasCrossedLegacyTabSwapThreshold(
    source,
    target,
    clientX,
    sourceIndex,
    targetIndex
  ) {
    if (!source || !target || !Number.isFinite(clientX) ||
        sourceIndex < 0 || targetIndex < 0 || sourceIndex === targetIndex) return false;

    const sourceRect = source.getBoundingClientRect();
    const targetRect = target.getBoundingClientRect();
    if (sourceRect.width < targetRect.width) {
      // MainForm compensates for the narrow source width so merely entering a
      // wider target cannot immediately swap it. Its comparisons are strict.
      return sourceIndex < targetIndex
        ? clientX > targetRect.left + targetRect.width - sourceRect.width
        : clientX < targetRect.left + sourceRect.width;
    }

    // An equal-width or wider source swaps as soon as it enters another tab.
    return true;
  }

  function onMarketTabDragOver(event) {
    const target = event.target.closest("button[data-tab-id]");
    if (!target || !draggedTabId) return;
    event.preventDefault();
    if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
    const targetId = target.dataset.tabId;
    if (targetId === draggedTabId) {
      tabDragLastTargetId = null;
      return;
    }
    if (tabDragLastTargetId === targetId || pendingTabHoverSwap) return;

    const buttons = Array.from(
      marketTabs.querySelectorAll("button[data-tab-id]"));
    const source = buttons.find(function (button) {
      return button.dataset.tabId === draggedTabId;
    });
    const sourceIndex = buttons.indexOf(source);
    const targetIndex = buttons.indexOf(target);
    if (!hasCrossedLegacyTabSwapThreshold(
        source,
        target,
        event.clientX,
        sourceIndex,
        targetIndex)) return;

    // Record the crossing only when the busy/pending gates actually accepted
    // the opaque native intent. Repeated dragover events then cannot toggle it.
    if (requestTabHoverSwap(targetId)) tabDragLastTargetId = targetId;
  }

  function requestTabHoverSwap(targetId) {
    if (uiBusy || !draggedTabId || !targetId || draggedTabId === targetId ||
        pendingTabHoverSwap) return false;

    const order = tabOrderFromDom();
    const sourceIndex = order.indexOf(draggedTabId);
    const targetIndex = order.indexOf(targetId);
    if (sourceIndex < 0 || targetIndex < 0 || sourceIndex === targetIndex) return false;

    const expectedOrder = order.slice();
    expectedOrder[sourceIndex] = targetId;
    expectedOrder[targetIndex] = draggedTabId;
    const pending = {
      initialOrderKey: order.join("|"),
      expectedOrderKey: expectedOrder.join("|"),
      timeoutId: null
    };
    pending.timeoutId = window.setTimeout(function () {
      if (pendingTabHoverSwap === pending) {
        pendingTabHoverSwap = null;
        tabDragLastTargetId = null;
      }
    }, 500);
    pendingTabHoverSwap = pending;
    send("hover-swap-tabs", {
      source: draggedTabId,
      target: targetId,
      expectedSourceIndex: sourceIndex,
      expectedTargetIndex: targetIndex
    });
    return true;
  }

  function renderTabs(state) {
    if (!Array.isArray(state.tabs)) return;
    const existingTabs = new Map();
    Array.from(marketTabs.children).forEach(function (button) {
      existingTabs.set(button.dataset.tabId, button);
    });
    state.tabs.forEach(function (tab, position) {
      const button = existingTabs.get(tab.id);
      if (!button) return;
      const buttonAtPosition = marketTabs.children.item(position);
      if (buttonAtPosition !== button) marketTabs.insertBefore(button, buttonAtPosition);
      const label = button.querySelector(".workspace-nav-label");
      if (label) label.textContent = tab.label;
      else button.textContent = tab.label;
      button.setAttribute("aria-label", tab.label);
      button.title = tab.label;
      button.classList.toggle("active", tab.isActive === true);
      button.disabled = state.isBusy === true || state.fixedSectionBatch != null;
      button.draggable = state.isBusy !== true && state.fixedSectionBatch == null;
    });
    const activeTab = state.tabs.find(function (tab) { return tab.isActive === true; });
    // Keep the clicked menu highlighted while native tab loading is in flight.
    // The busy snapshot still carries the previous active tab; only reconcile
    // once the authoritative operation has completed.
    if (activeTab && state.isBusy !== true &&
        window.LegacyWorkspaceNavigation?.syncMarket) {
      window.LegacyWorkspaceNavigation.syncMarket(activeTab.id, {
        authoritativeNativeRender: true
      });
    }
    acknowledgePendingTabHoverSwap(
      state.tabs.map(function (tab) { return tab.id; }));
  }

  function renderFixedCatalog(state) {
    const catalog = state.fixedCatalog;
    const batch = state.fixedSectionBatch;
    if (!catalog || !Array.isArray(catalog.sections)) {
      return;
    }

    const fragment = document.createDocumentFragment();
    catalog.sections.forEach(function (section) {
      const details = document.createElement("details");
      details.className = "catalog-section";
      details.open = catalogExpandAll.checked;
      const summary = document.createElement("summary");
      summary.dataset.sectionIndex = String(section.index);
      summary.dataset.cardCount = String(section.actions.length);
      summary.textContent = section.name;
      summary.title = batch
        ? "현재 수동 입력 단계를 완료하거나 취소하세요."
        : "하위 항목을 더블클릭해 편성표에 추가합니다.";
      summary.setAttribute("aria-disabled", batch ? "true" : "false");
      const children = document.createElement("div");
      children.className = "catalog-actions";
      markVisualCardList(children, "fixed-actions");
      section.actions.forEach(function (action) {
        const button = document.createElement("button");
        button.type = "button";
        button.className = "tree-item" + (action.isAvailable ? "" : " unavailable");
        button.dataset.actionId = action.id;
        setVisualCardContent(button, {
          title: action.label,
          meta: action.isAvailable
            ? (action.detail === section.name ? "" : action.detail)
            : action.prerequisite,
          kind: "fixed"
        });
        button.title = action.isAvailable ? action.detail : action.prerequisite;
        button.disabled = state.isBusy === true || batch != null;
        children.appendChild(button);
      });
      details.append(summary, children);
      fragment.appendChild(details);
    });
    categoryTree.replaceChildren(fragment);
  }

  function markIndustryRowSelected(row) {
    categoryTree.querySelectorAll("button[data-industry-index]").forEach(function (candidate) {
      const selected = candidate === row;
      candidate.classList.toggle("selected", selected);
      candidate.setAttribute("aria-selected", selected ? "true" : "false");
    });
    const list = row.closest(".industry-list");
    if (list) list.setAttribute("aria-activedescendant", row.id);
  }

  function selectIndustryRow(row) {
    markIndustryRowSelected(row);
    const list = row.closest(".industry-list");
    if (list) list.focus({ preventScroll: true });
    send("select-industry", { index: Number(row.dataset.industryIndex) });
  }

  function activateIndustryRow(row) {
    markIndustryRowSelected(row);
    const list = row.closest(".industry-list");
    if (list) list.focus({ preventScroll: true });
    send("double-click-industry", { index: Number(row.dataset.industryIndex) });
  }

  function renderIndustry(state) {
    const industry = state.industry;
    if (!industry || !Array.isArray(industry.industries) || !Array.isArray(industry.actions)) {
      return;
    }

    const structureKey = JSON.stringify({
      market: industry.market,
      rows: industry.industries.map(function (row) {
        return [row.index, row.name];
      }),
      actions: industry.actions.map(function (action) {
        return [action.actionId, action.section, action.label];
      })
    });
    const existingWorkspace = categoryTree.querySelector(".industry-workspace");
    if (existingWorkspace && structureKey === industryRenderStructureKey) {
      const firstInput = existingWorkspace.querySelector('input[data-industry-slot="first"]');
      const secondInput = existingWorkspace.querySelector('input[data-industry-slot="second"]');
      if (firstInput && document.activeElement !== firstInput) {
        firstInput.value = industry.firstName || "";
      }
      if (secondInput && document.activeElement !== secondInput) {
        secondInput.value = industry.secondName || "";
      }
      const list = existingWorkspace.querySelector(".industry-list");
      let selectedRow = null;
      existingWorkspace.querySelectorAll("button[data-industry-index]")
        .forEach(function (row) {
          const selected = Number(row.dataset.industryIndex) === industry.selectedIndex;
          row.classList.toggle("selected", selected);
          row.setAttribute("aria-selected", selected ? "true" : "false");
          if (selected) selectedRow = row;
        });
      if (list && selectedRow) {
        list.setAttribute("aria-activedescendant", selectedRow.id);
      } else if (list) {
        list.removeAttribute("aria-activedescendant");
      }
      return;
    }

    const previousList = categoryTree.querySelector(".industry-list");
    const previousScrollTop = previousList ? previousList.scrollTop : 0;
    const focusedIndustry = document.activeElement &&
      document.activeElement.closest("button[data-industry-index]");
    const focusedIndustryIndex = focusedIndustry
      ? focusedIndustry.dataset.industryIndex : null;

    const workspace = document.createElement("div");
    workspace.className = "industry-workspace";
    const selector = document.createElement("section");
    selector.className = "industry-selector";
    const slots = document.createElement("div");
    slots.className = "industry-slots";
    const first = document.createElement("input");
    first.id = "industry-comparison-first";
    first.dataset.industrySlot = "first";
    first.maxLength = 128;
    first.autocomplete = "off";
    first.value = industry.firstName || "";
    first.setAttribute("aria-label", "첫 번째 업종");
    const swap = document.createElement("button");
    swap.type = "button";
    swap.dataset.industrySwap = "true";
    swap.textContent = "◀▶";
    const second = document.createElement("input");
    second.id = "industry-comparison-second";
    second.dataset.industrySlot = "second";
    second.maxLength = 128;
    second.autocomplete = "off";
    second.value = industry.secondName || "";
    second.setAttribute("aria-label", "두 번째 업종");
    slots.append(first, swap, second);
    const list = document.createElement("div");
    list.className = "industry-list";
    list.setAttribute("role", "listbox");
    list.tabIndex = 0;
    industry.industries.forEach(function (row) {
      const button = document.createElement("button");
      button.type = "button";
      button.id = "industry-row-" + row.index;
      button.dataset.industryIndex = String(row.index);
      button.tabIndex = -1;
      button.setAttribute("role", "option");
      button.textContent = row.name;
      button.className = industry.selectedIndex === row.index ? "selected" : "";
      button.setAttribute("aria-selected", industry.selectedIndex === row.index ? "true" : "false");
      if (industry.selectedIndex === row.index) {
        list.setAttribute("aria-activedescendant", button.id);
      }
      list.appendChild(button);
    });
    const comparisonTitle = document.createElement("strong");
    comparisonTitle.textContent = "업종비교";
    selector.append(list, comparisonTitle, slots);

    const actionTree = document.createElement("section");
    actionTree.className = "industry-action-tree";
    const group = document.createElement("details");
    group.open = catalogExpandAll.checked;
    const summary = document.createElement("summary");
    summary.textContent = industry.market === "kospi" ? "코스피" : "코스닥";
    summary.dataset.industrySectionIndex = "0";
    summary.dataset.cardCount = String(industry.actions.length);
    const actionCards = document.createElement("div");
    actionCards.className = "catalog-actions";
    markVisualCardList(actionCards, "industry-actions");
    group.append(summary, actionCards);
    industry.actions.forEach(function (action) {
      const button = document.createElement("button");
      button.type = "button";
      button.className = "tree-item";
      button.dataset.industryActionId = action.actionId;
      setVisualCardContent(button, {
        title: action.label,
        kind: "industry"
      });
      actionCards.appendChild(button);
    });
    actionTree.appendChild(group);
    workspace.append(selector, actionTree);
    categoryTree.replaceChildren(workspace);
    list.scrollTop = previousScrollTop;
    if (focusedIndustryIndex !== null) {
      const replacement = Array.from(
        list.querySelectorAll("button[data-industry-index]"))
        .find(function (row) {
          return row.dataset.industryIndex === focusedIndustryIndex;
        });
      if (replacement) replacement.focus({ preventScroll: true });
    }
    industryRenderStructureKey = structureKey;
  }

  function updateOverseasWorkspace(workspace, overseas, isBusy) {
    [
      ["fixed", overseas.selectedFixedIndexId],
      ["industry", overseas.industry.selectedId],
      ["stock", overseas.stock.selectedId]
    ].forEach(function (selection) {
      const kind = selection[0];
      const selectedId = selection[1];
      const list = workspace.querySelector('[data-overseas-list="' + kind + '"]');
      if (!list) return;
      list.setAttribute("aria-disabled", isBusy ? "true" : "false");
      let selected = null;
      list.querySelectorAll("button").forEach(function (button) {
        const id = kind === "fixed"
          ? button.dataset.overseasTargetId : button.dataset.overseasSelectionId;
        const isSelected = id === selectedId;
        button.classList.toggle("selected", isSelected);
        button.setAttribute("aria-selected", isSelected ? "true" : "false");
        button.disabled = isBusy;
        if (isSelected) selected = button;
      });
      if (selected) list.setAttribute("aria-activedescendant", selected.id);
      else list.removeAttribute("aria-activedescendant");
    });

    ["industry", "stock"].forEach(function (kind) {
      const input = workspace.querySelector("#overseas-" + kind + "-search");
      syncSearchDraft(input, overseas[kind].query);
      if (input) input.readOnly = isBusy;
      const search = workspace.querySelector('button[data-overseas-search="' + kind + '"]');
      if (search) search.disabled = isBusy;
    });

    const actionStates = new Map(overseas.actions.map(function (action) {
      return [action.actionId, action];
    }));
    workspace.querySelectorAll("button[data-overseas-action-id]").forEach(function (button) {
      const action = actionStates.get(button.dataset.overseasActionId);
      if (!action) return;
      button.disabled = isBusy || !action.isAvailable;
      button.title = action.isAvailable ? "Add to playlist" : action.reason || "Select an item first.";
    });
  }

  function markOverseasRowSelected(row) {
    const list = row.closest("[data-overseas-list]");
    if (!list) return;
    list.querySelectorAll("button").forEach(function (candidate) {
      const selected = candidate === row;
      candidate.classList.toggle("selected", selected);
      candidate.setAttribute("aria-selected", selected ? "true" : "false");
    });
    list.setAttribute("aria-activedescendant", row.id);
  }

  function selectOverseasRow(row) {
    markOverseasRowSelected(row);
    const list = row.closest("[data-overseas-list]");
    if (list) list.focus({ preventScroll: true });
    if (row.dataset.overseasTargetId) {
      send("select-overseas-fixed-index", { targetId: row.dataset.overseasTargetId });
      return;
    }
    send(row.dataset.overseasResultKind === "stock"
      ? "select-overseas-stock" : "select-overseas-industry", {
      selectionId: row.dataset.overseasSelectionId
    });
  }

  function moveOverseasListSelection(list, key) {
    if (!list || list.getAttribute("aria-disabled") === "true") return false;
    const rows = Array.from(list.querySelectorAll("button"));
    if (rows.length === 0) return false;
    const selectedIndex = rows.findIndex(function (row) {
      return row.getAttribute("aria-selected") === "true";
    });
    let nextIndex;
    if (key === "Home") nextIndex = 0;
    else if (key === "End") nextIndex = rows.length - 1;
    else if (selectedIndex < 0) nextIndex = 0;
    else if (key === "ArrowUp") nextIndex = Math.max(0, selectedIndex - 1);
    else nextIndex = Math.min(rows.length - 1, selectedIndex + 1);
    if (nextIndex === selectedIndex) return false;
    const nextRow = rows[nextIndex];
    selectOverseasRow(nextRow);
    nextRow.scrollIntoView({ block: "nearest", inline: "nearest" });
    return true;
  }

  function renderOverseas(state) {
    const overseas = state.overseas;
    if (!overseas || !Array.isArray(overseas.fixedIndexTargets) ||
        !overseas.industry || !overseas.stock || !Array.isArray(overseas.actions)) return;

    const structureKey = JSON.stringify({
      fixed: overseas.fixedIndexTargets.map(function (row) {
        return [row.targetId, row.displayName];
      }),
      industry: (overseas.industry.results || []).map(function (row) {
        return [row.selectionId, row.displayName];
      }),
      industryTruncated: overseas.industry.isTruncated === true,
      stock: (overseas.stock.results || []).map(function (row) {
        return [row.selectionId, row.displayName];
      }),
      stockTruncated: overseas.stock.isTruncated === true,
      actions: overseas.actions.map(function (action) {
        return [action.actionId, action.source, action.periodDays];
      })
    });
    const existingWorkspace = categoryTree.querySelector(".overseas-workspace");
    if (existingWorkspace && structureKey === overseasRenderStructureKey) {
      updateOverseasWorkspace(existingWorkspace, overseas, state.isBusy === true);
      return;
    }

    function actionButton(action, label) {
      const button = document.createElement("button");
      button.type = "button";
      button.dataset.overseasActionId = action.actionId;
      button.textContent = label;
      button.disabled = state.isBusy === true || !action.isAvailable;
      button.title = action.isAvailable ? "Add to playlist" : action.reason || "Select an item first.";
      return button;
    }

    function periodButtons(source) {
      const buttons = document.createElement("div");
      buttons.className = "overseas-period-actions";
      overseas.actions.filter(function (action) {
        return action.source === source && action.periodDays;
      }).forEach(function (action) {
        buttons.appendChild(actionButton(action, action.periodDays + "일"));
      });
      return buttons;
    }

    function resultList(kind, model) {
      const list = document.createElement("div");
      list.className = "overseas-results";
      list.dataset.overseasList = kind;
      list.setAttribute("role", "listbox");
      list.tabIndex = 0;
      list.setAttribute("aria-disabled", state.isBusy === true ? "true" : "false");
      (model.results || []).forEach(function (row, index) {
        const button = document.createElement("button");
        button.type = "button";
        button.id = "overseas-" + kind + "-row-" + index;
        button.dataset.overseasResultKind = kind;
        button.dataset.overseasSelectionId = row.selectionId;
        button.tabIndex = -1;
        button.textContent = row.displayName;
        const selected = model.selectedId === row.selectionId;
        button.className = selected ? "selected" : "";
        button.setAttribute("aria-selected", selected ? "true" : "false");
        button.disabled = state.isBusy === true;
        if (selected) list.setAttribute("aria-activedescendant", button.id);
        list.appendChild(button);
      });
      return list;
    }

    function searchGroup(kind, title, searchLabel, model, includePeriods) {
      const group = document.createElement("section");
      group.className = "overseas-group overseas-search-group";
      const heading = document.createElement("h3");
      heading.textContent = title;
      if (model.isTruncated === true) {
        const truncation = document.createElement("span");
        truncation.className = "overseas-truncation";
        const isolatedCount = Number.isSafeInteger(model.isolatedInvalidRowCount)
          ? model.isolatedInvalidRowCount : 0;
        truncation.textContent = isolatedCount > 0
          ? "검색 결과 일부가 안전 상한 또는 불완전 데이터 때문에 제외되었습니다. " +
            "(표시명 없음 " + isolatedCount + "건)"
          : "검색 결과가 안전 상한을 초과해 일부만 표시됩니다.";
        heading.appendChild(truncation);
      }
      const searchLine = document.createElement("div");
      searchLine.className = "overseas-search-line";
      const input = document.createElement("input");
      input.id = "overseas-" + kind + "-search";
      input.value = model.query || "";
      input.maxLength = 64;
      input.setAttribute("aria-label", searchLabel);
      input.readOnly = state.isBusy === true;
      const search = document.createElement("button");
      search.type = "button";
      search.dataset.overseasSearch = kind;
      search.textContent = searchLabel;
      search.disabled = state.isBusy === true;
      searchLine.append(input, search);
      const resultArea = document.createElement("div");
      resultArea.className = "overseas-result-area";
      resultArea.appendChild(resultList(kind, model));
      const actionColumn = periodButtons(kind);
      const current = overseas.actions.find(function (action) {
        return action.source === kind && !action.periodDays;
      });
      if (current) actionColumn.prepend(actionButton(current, "1열판"));
      if (includePeriods || current) resultArea.appendChild(actionColumn);
      group.append(heading, searchLine, resultArea);
      return group;
    }

    const workspace = document.createElement("div");
    workspace.className = "overseas-workspace";
    const fixedGroup = document.createElement("section");
    fixedGroup.className = "overseas-group overseas-fixed-group";
    const fixedHeading = document.createElement("h3");
    fixedHeading.textContent = "해외지수";
    const fixedBody = document.createElement("div");
    fixedBody.className = "overseas-fixed-body";
    const fixedList = document.createElement("div");
    fixedList.className = "overseas-fixed-list";
    fixedList.dataset.overseasList = "fixed";
    fixedList.setAttribute("role", "listbox");
    fixedList.tabIndex = 0;
    fixedList.setAttribute("aria-disabled", state.isBusy === true ? "true" : "false");
    overseas.fixedIndexTargets.forEach(function (target, index) {
      const button = document.createElement("button");
      button.type = "button";
      button.id = "overseas-fixed-row-" + index;
      button.dataset.overseasTargetId = target.targetId;
      button.tabIndex = -1;
      button.textContent = target.displayName;
      const selected = overseas.selectedFixedIndexId === target.targetId;
      button.className = selected ? "selected" : "";
      button.setAttribute("aria-selected", selected ? "true" : "false");
      button.disabled = state.isBusy === true;
      if (selected) fixedList.setAttribute("aria-activedescendant", button.id);
      fixedList.appendChild(button);
    });
    fixedBody.append(fixedList, periodButtons("fixedIndex"));
    fixedGroup.append(fixedHeading, fixedBody);
    workspace.append(
      fixedGroup,
      searchGroup("industry", "해외 업종지수", "업종 검색", overseas.industry, false),
      searchGroup("stock", "해외 종목", "종목 검색", overseas.stock, true));
    categoryTree.replaceChildren(workspace);
    overseasRenderStructureKey = structureKey;
  }

  function updateComparisonWorkspace(workspace, comparison) {
    ["domestic", "world"].forEach(function (kind) {
      const input = workspace.querySelector("#comparison-" + kind + "-search");
      syncSearchDraft(input, comparison[kind + "Search"].query);
    });
  }

  function comparisonUnavailableText(reason) {
    switch (reason) {
      case "pair-incomplete-or-invalid":
        return "비교할 종목 두 개를 선택하세요.";
      case "deferred-unsafe-comparison-row":
        return "현재 데이터로는 안전하게 비교할 수 없습니다.";
      case "comparison-graphs-require-two-krx-stocks":
        return "국내 종목 두 개를 선택해야 합니다.";
      case "two-futures-targets-are-unsupported":
        return "선물 두 개는 서로 비교할 수 없습니다.";
      case "futures-requires-a-market-target-counterpart":
        return "선물은 시장 지수와 비교해야 합니다.";
      case "expected-mode-supports-only-krx-stocks-and-four-domestic-indices":
        return "예상가는 국내 종목과 지원 지수에서만 사용할 수 있습니다.";
      case "mixed-market-and-world-is-unsupported":
        return "시장 지수와 해외 종목은 함께 비교할 수 없습니다.";
      default:
        if (!reason) return "";
        return /^[a-z0-9-]+$/.test(reason)
          ? "현재 선택으로는 사용할 수 없습니다."
          : reason;
    }
  }

  function renderComparison(state) {
    const comparison = state.comparison;
    if (!comparison || !comparison.domesticSearch || !comparison.worldSearch) {
      clearComparisonTargetPendingSelection(false);
      return;
    }

    const structureKey = JSON.stringify({
      domestic: {
        results: comparison.domesticSearch.results,
        selectedResultId: comparison.domesticSearch.selectedResultId,
        isTruncated: comparison.domesticSearch.isTruncated,
        isolatedInvalidRowCount: comparison.domesticSearch.isolatedInvalidRowCount,
        deferredUnsafeRowCount: comparison.domesticSearch.deferredUnsafeRowCount
      },
      world: {
        results: comparison.worldSearch.results,
        selectedResultId: comparison.worldSearch.selectedResultId,
        isTruncated: comparison.worldSearch.isTruncated,
        isolatedInvalidRowCount: comparison.worldSearch.isolatedInvalidRowCount,
        deferredUnsafeRowCount: comparison.worldSearch.deferredUnsafeRowCount
      },
      fixedTargets: comparison.fixedTargets,
      selectedFixedTargetId: comparison.selectedFixedTargetId,
      firstTarget: comparison.firstTarget,
      secondTarget: comparison.secondTarget,
      savedPairs: comparison.savedPairs,
      actions: comparison.actions,
      canImportLegacyPairs: comparison.canImportLegacyPairs,
      lastImport: comparison.lastImport,
      statusMessage: comparison.statusMessage,
      statusKind: comparison.statusKind
    });
    if (comparisonTargetPendingSelection) {
      if (comparisonTargetPendingSelection.sent && state.isBusy !== true) {
        const selectedId = comparisonTargetPendingSelection.kind === "fixed"
          ? comparison.selectedFixedTargetId
          : comparison[comparisonTargetPendingSelection.kind + "Search"].selectedResultId;
        clearComparisonTargetPendingSelection(
          selectedId !== comparisonTargetPendingSelection.key);
      } else if (!comparisonTargetPendingSelection.sent &&
          comparisonRenderStructureKey &&
          structureKey !== comparisonRenderStructureKey) {
        clearComparisonTargetPendingSelection(false);
      } else {
        applyPendingRowSelection(comparisonTargetPendingSelection, false);
      }
    }
    const existingWorkspace = categoryTree.querySelector(".comparison-workspace");
    if (existingWorkspace && structureKey === comparisonRenderStructureKey) {
      updateComparisonWorkspace(existingWorkspace, comparison);
      return;
    }

    const workspace = document.createElement("div");
    workspace.className = "comparison-workspace";

    const searches = document.createElement("section");
    searches.className = "comparison-searches";
    [
      ["domestic", "국내·NXT 종목", comparison.domesticSearch],
      ["world", "미국·대만 종목", comparison.worldSearch]
    ].forEach(function (definition) {
      const kind = definition[0];
      const panel = document.createElement("div");
      panel.className = "comparison-search-panel";
      const line = document.createElement("div");
      line.className = "comparison-search-line";
      const input = document.createElement("input");
      input.id = "comparison-" + kind + "-search";
      input.maxLength = 64;
      input.value = definition[2].query || "";
      input.placeholder = definition[1];
      const button = document.createElement("button");
      button.type = "button";
      button.dataset.comparisonSearch = kind;
      button.textContent = "검색";
      line.append(input, button);
      const results = document.createElement("div");
      results.className = "comparison-results";
      results.setAttribute("aria-label", definition[1] + " 검색 결과");
      let selectedResult = null;
      definition[2].results.forEach(function (row) {
        const result = document.createElement("button");
        result.type = "button";
        result.id = "comparison-" + kind + "-result-" + row.selectionId;
        result.dataset.comparisonChoice = kind;
        result.dataset.comparisonSelectionId = row.selectionId;
        const isSelected = definition[2].selectedResultId === row.selectionId;
        result.className = isSelected
          ? "selected" : "";
        result.setAttribute(
          "aria-selected",
          isSelected ? "true" : "false");
        result.disabled = row.canSelect === false;
        if (row.unavailableReason) {
          result.title = comparisonUnavailableText(row.unavailableReason);
        }
        result.textContent = row.displayName + " · " + row.metadata;
        if (isSelected) selectedResult = result;
        results.appendChild(result);
      });
      configureRovingList(
        results,
        Array.from(results.querySelectorAll("button[data-comparison-selection-id]")),
        selectedResult);
      panel.append(line, results);
      if (definition[2].isTruncated === true ||
          Number(definition[2].deferredUnsafeRowCount) > 0) {
        const isolated = Number(definition[2].isolatedInvalidRowCount) || 0;
        const deferred = Number(definition[2].deferredUnsafeRowCount) || 0;
        const messages = [];
        if (isolated > 0) {
          messages.push("식별할 수 없는 기존 데이터 " + String(isolated) +
            "행은 제외하고 나머지 결과를 표시합니다.");
        } else if (definition[2].isTruncated === true) {
          messages.push("안전한 표시 상한을 초과해 일부 결과만 표시합니다.");
        }
        if (deferred > 0) {
          messages.push("대상 구분이 모호한 " + String(deferred) +
            "행은 목록에 표시하되 선택과 송출을 차단합니다.");
        }
        const warning = document.createElement("div");
        warning.className = "comparison-search-warning";
        warning.setAttribute("role", "status");
        warning.textContent = messages.join(" ");
        panel.appendChild(warning);
      }
      searches.appendChild(panel);
    });

    const fixed = document.createElement("section");
    fixed.className = "comparison-fixed";
    const fixedTitle = document.createElement("strong");
    fixedTitle.textContent = "지수·환율 고정 대상";
    const fixedList = document.createElement("div");
    fixedList.className = "comparison-fixed-list";
    fixedList.setAttribute("aria-label", "지수·환율 고정 대상");
    let selectedFixedTarget = null;
    comparison.fixedTargets.forEach(function (row) {
      const button = document.createElement("button");
      button.type = "button";
      button.id = "comparison-fixed-result-" + row.targetId;
      button.dataset.comparisonTargetId = row.targetId;
      const isSelected = comparison.selectedFixedTargetId === row.targetId;
      button.className = isSelected
        ? "selected" : "";
      button.setAttribute(
        "aria-selected",
        isSelected ? "true" : "false");
      button.textContent = row.displayName;
      if (isSelected) selectedFixedTarget = button;
      fixedList.appendChild(button);
    });
    configureRovingList(
      fixedList,
      Array.from(fixedList.querySelectorAll("button[data-comparison-target-id]")),
      selectedFixedTarget);
    fixed.append(fixedTitle, fixedList);

    const pairPanel = document.createElement("section");
    pairPanel.className = "comparison-pair-panel";
    const draft = document.createElement("div");
    draft.className = "comparison-draft";
    const first = document.createElement("input");
    first.readOnly = true;
    first.value = comparison.firstTarget ? comparison.firstTarget.displayName : "";
    first.setAttribute("aria-label", "첫 번째 비교 대상");
    const second = document.createElement("input");
    second.readOnly = true;
    second.value = comparison.secondTarget ? comparison.secondTarget.displayName : "";
    second.setAttribute("aria-label", "두 번째 비교 대상");
    const swap = document.createElement("button");
    swap.type = "button";
    swap.dataset.comparisonSwap = "true";
    swap.textContent = "◀▶";
    const add = document.createElement("button");
    add.type = "button";
    add.dataset.comparisonAdd = "true";
    add.textContent = "추가";
    add.disabled = !comparison.firstTarget || !comparison.secondTarget;
    draft.append(first, swap, second, add);

    const saved = document.createElement("div");
    saved.className = "comparison-saved";
    saved.setAttribute("aria-label", "저장된 종목 비교");
    let selectedSavedPair = null;
    comparison.savedPairs.forEach(function (row) {
      const button = document.createElement("button");
      button.type = "button";
      button.id = "comparison-saved-result-" + row.rowId;
      button.dataset.comparisonPairId = row.rowId;
      button.textContent = row.rowNumber + ". " + row.displayLabel;
      button.className = row.isSelected ? "selected" : "";
      button.setAttribute("aria-selected", row.isSelected ? "true" : "false");
      if (row.isSelected) selectedSavedPair = button;
      saved.appendChild(button);
    });
    configureRovingList(
      saved,
      Array.from(saved.querySelectorAll("button[data-comparison-pair-id]")),
      selectedSavedPair);
    const savedTools = document.createElement("div");
    savedTools.className = "comparison-saved-tools";
    const importLegacy = document.createElement("button");
    importLegacy.type = "button";
    importLegacy.dataset.comparisonImport = "true";
    importLegacy.textContent = "원본 비교쌍 가져오기";
    importLegacy.title =
      "현재 저장 순서는 유지하고 원본 프로그램에만 있는 비교쌍을 뒤에 추가합니다.";
    importLegacy.disabled = comparison.canImportLegacyPairs !== true ||
      comparison.isBusy === true;
    savedTools.appendChild(importLegacy);
    [["up", "UP"], ["down", "DOWN"]].forEach(function (item) {
      const button = document.createElement("button");
      button.type = "button";
      button.dataset.comparisonMove = item[0];
      button.textContent = item[1];
      savedTools.appendChild(button);
    });
    const deleteSelected = document.createElement("button");
    deleteSelected.type = "button";
    deleteSelected.dataset.comparisonDelete = "selected";
    deleteSelected.textContent = "DEL";
    const deleteAll = document.createElement("button");
    deleteAll.type = "button";
    deleteAll.dataset.comparisonDelete = "all";
    deleteAll.textContent = "DEL ALL";
    savedTools.append(deleteSelected, deleteAll);
    const importStatus = document.createElement("div");
    importStatus.className = "comparison-import-status";
    importStatus.setAttribute("role", "status");
    importStatus.textContent = comparison.statusMessage || "";
    if (comparison.lastImport) {
      importStatus.title =
        "원본 " + String(comparison.lastImport.sourceRowCount) + "개 · 추가 " +
        String(comparison.lastImport.addedPairCount) + "개 · 중복 " +
        String(comparison.lastImport.skippedDuplicateCount) + "개 · 현재 " +
        String(comparison.lastImport.totalPairCount) + "개";
    }
    pairPanel.append(draft, saved, savedTools, importStatus);

    const actions = document.createElement("section");
    actions.className = "comparison-actions";
    const comparisonSections = new Map();
    comparison.actions.forEach(function (action) {
      let group = comparisonSections.get(action.section);
      let actionCards;
      if (!group) {
        group = document.createElement("details");
        group.open = catalogExpandAll.checked;
        const summary = document.createElement("summary");
        summary.textContent = action.section;
        summary.dataset.comparisonSectionIndex = String(comparisonSections.size);
        summary.dataset.cardCount = String(comparison.actions.filter(function (candidate) {
          return candidate.section === action.section;
        }).length);
        summary.title = "더블클릭하면 직계 자식 전체를 원본 순서로 추가합니다.";
        actionCards = document.createElement("div");
        actionCards.className = "catalog-actions";
        markVisualCardList(actionCards, "comparison-actions");
        group.append(summary, actionCards);
        comparisonSections.set(action.section, group);
        actions.appendChild(group);
      } else {
        actionCards = group.querySelector(":scope > .catalog-actions");
      }
      const button = document.createElement("button");
      button.type = "button";
      button.className = "tree-item" + (action.isAvailable ? "" : " unavailable");
      button.dataset.comparisonActionId = action.actionId;
      setVisualCardContent(button, {
        title: action.label,
        kind: "comparison"
      });
      button.disabled = !action.isAvailable;
      button.title = action.isAvailable
        ? "더블클릭하여 플레이리스트에 추가"
        : comparisonUnavailableText(action.unavailableReason);
      actionCards.appendChild(button);
    });

    workspace.append(searches, fixed, pairPanel, actions);
    categoryTree.replaceChildren(workspace);
    comparisonRenderStructureKey = structureKey;
    updateComparisonWorkspace(workspace, comparison);
  }

  function themeActionIsClickable(action) {
    return action.isAvailable === true ||
      (action.actionId === "five-row-expected" &&
       action.unavailableReason === "expected-theme-is-krx-only");
  }

  function updateThemeWorkspace(workspace, theme) {
    const input = workspace.querySelector("#theme-search");
    syncSearchDraft(input, theme.query);

    const searchTruncation = workspace.querySelector(".theme-search-truncation");
    if (searchTruncation) {
      searchTruncation.hidden = theme.searchIsTruncated !== true;
      searchTruncation.textContent = theme.searchIsTruncated === true
        ? "검색 결과가 최대 5,000건으로 제한되었습니다. 검색어를 좁혀 주세요."
        : "";
    }

    const results = workspace.querySelector(".theme-results");
    let selectedResult = null;
    results.querySelectorAll("button[data-theme-result-id]").forEach(function (button) {
      const selected = button.dataset.themeResultId === theme.selectedResultId;
      button.classList.toggle("selected", selected);
      button.setAttribute("aria-selected", selected ? "true" : "false");
      if (selected) selectedResult = button;
    });
    configureRovingList(
      results,
      Array.from(results.querySelectorAll("button[data-theme-result-id]")),
      selectedResult);
    if (selectedResult) results.setAttribute("aria-activedescendant", selectedResult.id);
    else results.removeAttribute("aria-activedescendant");

    workspace.querySelectorAll("input[data-theme-sort]").forEach(function (radio) {
      radio.checked = radio.value === theme.sortId;
    });

    const preview = workspace.querySelector(".theme-preview-list");
    const previewFragment = document.createDocumentFragment();
    theme.previewItems.forEach(function (row) {
      const item = document.createElement("li");
      item.textContent = row.itemName;
      previewFragment.appendChild(item);
    });
    preview.replaceChildren(previewFragment);

    const actionStates = new Map(theme.actions.map(function (action) {
      return [action.actionId, action];
    }));
    workspace.querySelectorAll("button[data-theme-action-id]").forEach(function (button) {
      const action = actionStates.get(button.dataset.themeActionId);
      if (!action) return;
      const warningHandledByNative = action.actionId === "five-row-expected" &&
        action.unavailableReason === "expected-theme-is-krx-only";
      button.disabled = !themeActionIsClickable(action);
      button.title = warningHandledByNative
        ? "NXT는 데이터가 없습니다."
        : action.unavailableReason || "클릭하여 플레이리스트에 추가";
    });

    const editTheme = workspace.querySelector("#uc4-theme-edit");
    const deleteTheme = workspace.querySelector("#uc4-theme-delete");
    editTheme.disabled = !theme.selectedTheme;
    deleteTheme.disabled = !theme.selectedTheme;
  }

  function renderTheme(state) {
    const theme = state.theme;
    if (!theme || !Array.isArray(theme.searchResults) || !Array.isArray(theme.actions)) {
      clearThemeResultPendingSelection(false);
      return;
    }

    const structureKey = JSON.stringify({
      query: theme.query || "",
      results: theme.searchResults.map(function (row) {
        return [row.resultId, row.displayTitle];
      }),
      sorts: theme.sorts.map(function (row) {
        return [row.id, row.label];
      }),
      actions: theme.actions.map(function (action) {
        return [action.actionId, action.label];
      })
    });
    const existingWorkspace = categoryTree.querySelector(".theme-workspace");
    if (themeResultPendingSelection) {
      if (themeResultPendingSelection.sent && state.isBusy !== true) {
        clearThemeResultPendingSelection(
          theme.selectedResultId !== themeResultPendingSelection.key);
      } else if (themeRenderStructureKey && structureKey !== themeRenderStructureKey) {
        clearThemeResultPendingSelection(false);
      }
    }
    if (existingWorkspace && structureKey === themeRenderStructureKey) {
      updateThemeWorkspace(existingWorkspace, theme);
      if (themeResultPendingSelection) {
        applyPendingRowSelection(themeResultPendingSelection, false);
        applyPendingControlLocks(themeResultPendingSelection.lockedControls);
      }
      return;
    }

    const workspace = document.createElement("div");
    workspace.className = "theme-workspace";
    const search = document.createElement("div");
    search.className = "theme-search-line";
    const input = document.createElement("input");
    input.id = "theme-search";
    input.value = theme.query || "";
    input.maxLength = 64;
    input.placeholder = "테마명";
    input.setAttribute("aria-label", "테마명");
    const searchButton = document.createElement("button");
    searchButton.type = "button";
    searchButton.dataset.themeSearch = "true";
    searchButton.textContent = "테마검색";
    const addTheme = document.createElement("button");
    addTheme.type = "button";
    addTheme.id = "uc4-theme-add";
    addTheme.dataset.uc4ThemeCatalog = "create";
    addTheme.textContent = "테마추가";
    const editTheme = document.createElement("button");
    editTheme.type = "button";
    editTheme.id = "uc4-theme-edit";
    editTheme.dataset.uc4ThemeCatalog = "edit";
    editTheme.textContent = "테마편집";
    editTheme.disabled = !theme.selectedTheme;
    const deleteTheme = document.createElement("button");
    deleteTheme.type = "button";
    deleteTheme.id = "uc4-theme-delete";
    deleteTheme.dataset.uc4ThemeCatalog = "delete";
    deleteTheme.textContent = "테마삭제";
    deleteTheme.disabled = !theme.selectedTheme;
    search.append(input, searchButton, addTheme, editTheme, deleteTheme);

    const resultsPanel = document.createElement("section");
    resultsPanel.className = "theme-results-panel";
    const resultsTitle = document.createElement("strong");
    resultsTitle.className = "theme-column-title";
    resultsTitle.textContent = "테마명";
    const searchTruncation = document.createElement("span");
    searchTruncation.className = "theme-search-truncation";
    searchTruncation.hidden = theme.searchIsTruncated !== true;
    searchTruncation.textContent = theme.searchIsTruncated === true
      ? "검색 결과가 최대 5,000건으로 제한되었습니다. 검색어를 좁혀 주세요."
      : "";
    resultsTitle.appendChild(searchTruncation);
    const results = document.createElement("div");
    results.className = "theme-results";
    results.setAttribute("role", "listbox");
    results.setAttribute("aria-label", "테마명");
    theme.searchResults.forEach(function (row, index) {
      const button = document.createElement("button");
      button.type = "button";
      button.id = "theme-result-row-" + index;
      button.dataset.themeResultId = row.resultId;
      button.textContent = row.displayTitle;
      button.className = theme.selectedResultId === row.resultId ? "selected" : "";
      button.setAttribute("aria-selected",
        theme.selectedResultId === row.resultId ? "true" : "false");
      results.appendChild(button);
    });
    resultsPanel.append(resultsTitle, results);

    const sortControls = document.createElement("fieldset");
    sortControls.className = "theme-sort-controls";
    const sortLegend = document.createElement("legend");
    sortLegend.textContent = "정렬";
    sortControls.appendChild(sortLegend);
    theme.sorts.forEach(function (row) {
      const label = document.createElement("label");
      const radio = document.createElement("input");
      radio.type = "radio";
      radio.name = "theme-sort";
      radio.value = row.id;
      radio.dataset.themeSort = "true";
      radio.checked = row.id === theme.sortId;
      label.append(radio, document.createTextNode(row.label));
      sortControls.appendChild(label);
    });

    const previewPanel = document.createElement("section");
    previewPanel.className = "theme-preview-panel";
    const previewTitle = document.createElement("strong");
    previewTitle.className = "theme-column-title";
    previewTitle.textContent = "종목명";
    const preview = document.createElement("ol");
    preview.className = "theme-preview-list";
    theme.previewItems.forEach(function (row) {
      const item = document.createElement("li");
      item.textContent = row.itemName;
      preview.appendChild(item);
    });
    previewPanel.append(previewTitle, preview);

    const actions = document.createElement("div");
    actions.className = "theme-actions";
    markVisualCardList(actions, "theme-actions");
    const originalActionOrder = new Map([
      ["six-row-current", 0],
      ["twelve-row-current", 1],
      ["five-row-current", 2],
      ["five-row-expected", 3]
    ]);
    const originalActionLabels = new Map([
      ["six-row-current", "6종목 현재가"],
      ["twelve-row-current", "12종목 현재가"],
      ["five-row-current", "현재가"],
      ["five-row-expected", "예상체결가"]
    ]);
    theme.actions.slice().sort(function (left, right) {
      return (originalActionOrder.get(left.actionId) ?? Number.MAX_SAFE_INTEGER) -
        (originalActionOrder.get(right.actionId) ?? Number.MAX_SAFE_INTEGER);
    }).forEach(function (action) {
      const button = document.createElement("button");
      button.type = "button";
      button.dataset.themeActionId = action.actionId;
      setVisualCardContent(button, {
        title: originalActionLabels.get(action.actionId) || action.label,
        kind: "theme"
      });
      actions.appendChild(button);
    });
    // UC4 is a vertical native panel: search, theme rows, sort radios,
    // action buttons, then the selected theme's item preview.
    workspace.append(search, resultsPanel, sortControls, actions, previewPanel);
    categoryTree.replaceChildren(workspace);
    themeRenderStructureKey = structureKey;
    updateThemeWorkspace(workspace, theme);
  }

  function updateExpertWorkspace(workspace, expert) {
    const input = workspace.querySelector("#expert-search");
    syncSearchDraft(input, expert.search.query);
  }

  function expertRecommendationText(row) {
    const buyAmount = row.buyAmount == null ? "" : String(row.buyAmount);
    return row.stockName + " · " + buyAmount;
  }

  function renderExpert(state) {
    const expert = state.expert;
    if (!expert || !expert.search || !expert.preview) return;
    const structureKey = JSON.stringify({
      results: expert.search.results,
      isTruncated: expert.search.isTruncated,
      selectedExpert: expert.selectedExpert,
      preview: expert.preview,
      actions: expert.actions
    });
    const existingWorkspace = categoryTree.querySelector(".expert-workspace");
    if (existingWorkspace && structureKey === expertRenderStructureKey) {
      updateExpertWorkspace(existingWorkspace, expert);
      return;
    }
    const workspace = document.createElement("div");
    workspace.className = "expert-workspace";
    const search = document.createElement("div");
    search.className = "expert-search-line";
    const input = document.createElement("input");
    input.id = "expert-search";
    input.maxLength = 64;
    input.value = expert.search.query || "";
    input.placeholder = "전문가명";
    const searchButton = document.createElement("button");
    searchButton.type = "button";
    searchButton.dataset.expertSearch = "true";
    searchButton.textContent = "조회";
    const addExpert = document.createElement("button");
    addExpert.type = "button";
    addExpert.id = "uc6-expert-add";
    addExpert.dataset.uc6ExpertCatalog = "create";
    addExpert.textContent = "전문가 추가";
    const deleteExpert = document.createElement("button");
    deleteExpert.type = "button";
    deleteExpert.id = "uc6-expert-delete";
    deleteExpert.dataset.uc6ExpertCatalog = "delete";
    deleteExpert.textContent = "전문가 삭제";
    deleteExpert.disabled = !expert.selectedExpert;
    search.append(input, searchButton, addExpert, deleteExpert);

    const body = document.createElement("div");
    body.className = "expert-body";
    const results = document.createElement("div");
    results.className = "expert-results";
    results.setAttribute("aria-label", "전문가 검색 결과");
    let selectedExpertResult = null;
    expert.search.results.forEach(function (row) {
      const button = document.createElement("button");
      button.type = "button";
      button.id = "expert-result-" + row.selectionId;
      button.dataset.expertSelectionId = row.selectionId;
      button.textContent = row.expertName;
      const isSelected = !!expert.selectedExpert &&
        expert.selectedExpert.selectionId === row.selectionId;
      button.className = isSelected ? "selected" : "";
      button.setAttribute("aria-selected", isSelected ? "true" : "false");
      if (isSelected) selectedExpertResult = button;
      results.appendChild(button);
    });
    configureRovingList(
      results,
      Array.from(results.querySelectorAll("button[data-expert-selection-id]")),
      selectedExpertResult);
    const preview = document.createElement("div");
    preview.id = "expert-preview";
    preview.className = "expert-preview";
    preview.tabIndex = 0;
    const heading = document.createElement("strong");
    heading.textContent = expert.selectedExpert
      ? expert.selectedExpert.expertName + " 추천 종목"
      : "전문가를 선택하세요.";
    const list = document.createElement("ol");
    expert.preview.items.forEach(function (row) {
      const item = document.createElement("li");
      item.textContent = expertRecommendationText(row);
      if (row.buyAmount == null) {
        item.setAttribute("aria-label", row.stockName + " · 매수가 미입력");
      }
      list.appendChild(item);
    });
    preview.append(heading, list);
    body.append(results, preview);

    const actions = document.createElement("div");
    actions.className = "expert-actions";
    markVisualCardList(actions, "expert-actions");
    expert.actions.forEach(function (action) {
      const button = document.createElement("button");
      button.type = "button";
      button.dataset.expertActionId = action.actionId;
      setVisualCardContent(button, {
        title: action.label,
        kind: "expert"
      });
      button.disabled = !action.isAvailable;
      actions.appendChild(button);
    });
    const editRecommendations = document.createElement("button");
    editRecommendations.type = "button";
    editRecommendations.id = "uc6-expert-edit";
    editRecommendations.dataset.uc6ExpertCatalog = "edit";
    editRecommendations.textContent = "추천행 편집·저장·삭제";
    editRecommendations.disabled = !expert.selectedExpert;
    actions.appendChild(editRecommendations);
    workspace.append(search, body, actions);
    categoryTree.replaceChildren(workspace);
    expertRenderStructureKey = structureKey;
    updateExpertWorkspace(workspace, expert);
  }

  function updateTradingHaltWorkspace(workspace, halt) {
    const input = workspace.querySelector("#halt-search");
    syncSearchDraft(input, halt.query);
  }

  function renderTradingHalt(state) {
    const halt = state.tradingHalt;
    if (!halt || !Array.isArray(halt.results)) return;
    const structureKey = JSON.stringify({
      results: halt.results,
      nameSort: halt.nameSort,
      actions: halt.actions
    });
    const existingWorkspace = categoryTree.querySelector(".halt-workspace");
    if (existingWorkspace && structureKey === tradingHaltRenderStructureKey) {
      updateTradingHaltWorkspace(existingWorkspace, halt);
      return;
    }
    const workspace = document.createElement("div");
    workspace.className = "halt-workspace";
    const search = document.createElement("div");
    search.className = "halt-search-line";
    const input = document.createElement("input");
    input.id = "halt-search";
    input.maxLength = 64;
    input.value = halt.query || "";
    input.placeholder = "거래정지 종목";
    const searchButton = document.createElement("button");
    searchButton.type = "button";
    searchButton.dataset.haltSearch = "true";
    searchButton.textContent = "검색";
    const sortButton = document.createElement("button");
    sortButton.type = "button";
    sortButton.dataset.haltSort = "true";
    sortButton.textContent = halt.nameSort === "ascending" ? "종목명 ↑" : "종목명 ↓";
    search.append(input, searchButton, sortButton);
    const results = document.createElement("div");
    results.className = "halt-results";
    results.setAttribute("aria-label", "거래정지 종목 검색 결과");
    let selectedHaltResult = null;
    halt.results.forEach(function (row) {
      const button = document.createElement("button");
      button.type = "button";
      button.id = "halt-result-" + row.selectionId;
      button.dataset.haltSelectionId = row.selectionId;
      button.textContent = (row.market === "kospi" ? "코스피 · " : "코스닥 · ") + row.displayName;
      button.className = row.isSelected ? "selected" : "";
      button.setAttribute("aria-selected", row.isSelected ? "true" : "false");
      if (row.isSelected) selectedHaltResult = button;
      results.appendChild(button);
    });
    configureRovingList(
      results,
      Array.from(results.querySelectorAll("button[data-halt-selection-id]")),
      selectedHaltResult);
    const actions = document.createElement("div");
    actions.className = "halt-actions";
    markVisualCardList(actions, "halt-actions");
    halt.actions.forEach(function (action) {
      const button = document.createElement("button");
      button.type = "button";
      button.dataset.haltActionId = action.actionId;
      setVisualCardContent(button, {
        title: action.label,
        kind: "halt"
      });
      button.disabled = !action.isAvailable;
      actions.appendChild(button);
    });
    workspace.append(search, results, actions);
    categoryTree.replaceChildren(workspace);
    tradingHaltRenderStructureKey = structureKey;
    updateTradingHaltWorkspace(workspace, halt);
  }

  const manualFinancialReferenceImages = Object.freeze({
    "revenue-composition": "./Previews/pie.bmp",
    "growth-metrics": "./Previews/Grow.bmp",
    sales: "./Previews/sell.bmp",
    "operating-profit": "./Previews/profit.bmp"
  });

  function manualFinancialPreviewColumns(screen) {
    if (screen === "revenue-composition") {
      return ["종목명", "기준일", "구성 1", "구성 2", "구성 3", "구성 4", "구성 5"];
    }
    if (screen === "growth-metrics") {
      return ["종목명", "매출 성장", "영업익 성장", "순자산 성장", "순이익 성장", "기간"];
    }
    return ["종목명", "분기 1", "분기 2", "분기 3", "분기 4", "분기 5", "분기 6"];
  }

  function manualFinancialPreviewValues(row, count) {
    const projected = Array.isArray(row.previewValues)
      ? row.previewValues.slice(0, count)
      : [];
    while (projected.length < count) projected.push("");
    projected[0] = projected[0] == null || String(projected[0]).trim() === ""
      ? (row.stockName || "")
      : projected[0];
    return projected.map(function (value) {
      return value == null ? "" : String(value);
    });
  }

  function manualFinancialReferenceFigure(screen, label) {
    const source = manualFinancialReferenceImages[screen];
    if (!source) return null;
    const figure = manualElement("figure", "manual-financial-reference");
    const image = document.createElement("img");
    image.src = source;
    image.width = 480;
    image.height = 270;
    image.loading = "eager";
    image.decoding = "async";
    image.alt = label + " 기존 송출 화면 참고 이미지";
    const caption = manualElement(
      "figcaption",
      "",
      "기존 송출 화면 참고 · 입력 위치와 구성 확인용");
    figure.append(image, caption);
    return figure;
  }

  function blankManualRecord(screen) {
    if (screen === "revenue-composition") {
      return {
        screen: screen,
        stockName: "",
        baseDate: "",
        slices: Array.from({ length: 5 }, function () {
          return { label: "", percentageText: "" };
        })
      };
    }
    if (screen === "growth-metrics") {
      return {
        screen: screen,
        stockName: "",
        salesGrowth: [null, null, null, null],
        operatingProfitGrowth: [null, null, null, null],
        netAssetGrowth: [null, null, null, null],
        netIncomeGrowth: [null, null, null, null],
        periods: ["", "", "", ""]
      };
    }
    return {
      screen: screen,
      stockName: "",
      quarters: Array.from({ length: 6 }, function () {
        return { quarter: "", value: 0 };
      })
    };
  }

  function splitLegacyRawValue(value, count) {
    const tokens = String(value === null || value === undefined ? "" : value).split("_");
    while (tokens.length < count) tokens.push("");
    if (tokens.length > count) {
      tokens[count - 1] = tokens.slice(count - 1).join("_");
      tokens.length = count;
    }
    return tokens;
  }

  function manualRecordFromRaw(raw) {
    if (!raw) return null;
    const record = blankManualRecord(raw.screen);
    record.stockName = raw.stockName || "";
    record.rawMode = true;
    const values = Array.isArray(raw.storageValues) ? raw.storageValues : [];
    record.rawOriginalStorageValues = values.map(function (value) {
      return String(value === null || value === undefined ? "" : value);
    });
    record.rawDirtyStorageIndices = [];
    if (record.screen === "revenue-composition") {
      record.baseDate = values[0] || "";
      record.slices = record.slices.map(function (_, index) {
        const tokens = splitLegacyRawValue(values[index + 1] || "", 2);
        return { label: tokens[0], percentageText: tokens[1] };
      });
    } else if (record.screen === "growth-metrics") {
      ["salesGrowth", "operatingProfitGrowth", "netAssetGrowth", "netIncomeGrowth"]
        .forEach(function (seriesName, index) {
          record[seriesName] = splitLegacyRawValue(values[index] || "", 4);
        });
      record.periods = splitLegacyRawValue(values[4] || "", 4);
    } else {
      record.quarters = record.quarters.map(function (_, index) {
        const tokens = splitLegacyRawValue(values[index] || "", 2);
        return { quarter: tokens[0], value: tokens[1] };
      });
    }
    return record;
  }

  function preserveManualRawStorageValue(record, index, rebuiltValue) {
    const dirty = Array.isArray(record.rawDirtyStorageIndices) &&
      record.rawDirtyStorageIndices.includes(index);
    const originals = Array.isArray(record.rawOriginalStorageValues)
      ? record.rawOriginalStorageValues : [];
    return !dirty && index < originals.length
      ? originals[index]
      : rebuiltValue;
  }

  function cloneManualRecord(record) {
    return record ? JSON.parse(JSON.stringify(record)) : null;
  }

  function shouldResetManualStockDraft(stockIsSelected, stockNameReadOnly, draft) {
    return stockIsSelected !== true && stockNameReadOnly !== true &&
      !!draft && draft.rawMode !== true;
  }

  function manualElement(tagName, className, text) {
    const element = document.createElement(tagName);
    if (className) element.className = className;
    if (text !== undefined) element.textContent = text;
    return element;
  }

  function manualInput(value, dataName, dataValue, type) {
    const input = document.createElement("input");
    input.type = type || "text";
    input.value = value === null || value === undefined ? "" : String(value);
    if (dataName) input.dataset[dataName] = String(dataValue);
    return input;
  }

  function appendManualLabel(parent, labelText, input) {
    const label = manualElement("label", "manual-financial-field");
    label.append(manualElement("span", "", labelText), input);
    parent.appendChild(label);
  }

  function renderManualEditorFields(editor, record) {
    const fields = manualElement("div", "manual-financial-fields");
    if (record.screen === "revenue-composition") {
      appendManualLabel(
        fields,
        "기준일",
        manualInput(record.baseDate, "manualBaseDate", "true"));
      const slices = manualElement("div", "manual-financial-grid manual-revenue-grid");
      slices.append(
        manualElement("strong", "", "구성"),
        manualElement("strong", "", "비율"));
      record.slices.forEach(function (slice, index) {
        slices.append(
          manualInput(slice ? slice.label : "", "manualRevenueLabel", index),
          manualInput(
            slice ? slice.percentageText : "",
            "manualRevenuePercentage",
            index));
      });
      fields.appendChild(slices);
    } else if (record.screen === "growth-metrics") {
      const grid = manualElement("div", "manual-financial-grid manual-growth-grid");
      grid.appendChild(manualElement("strong", "", "구분"));
      record.periods.forEach(function (period, index) {
        grid.appendChild(manualInput(period, "manualGrowthPeriod", index));
      });
      [
        ["매출 성장", "salesGrowth"],
        ["영업이익 성장", "operatingProfitGrowth"],
        ["순자산 성장", "netAssetGrowth"],
        ["순이익 성장", "netIncomeGrowth"]
      ].forEach(function (series) {
        grid.appendChild(manualElement("span", "", series[0]));
        record[series[1]].forEach(function (value, index) {
          const input = manualInput(
            value,
            "manualGrowthValue",
            index,
            record.rawMode ? "text" : "number");
          input.dataset.manualGrowthSeries = series[1];
          input.step = "any";
          grid.appendChild(input);
        });
      });
      fields.appendChild(grid);
    } else {
      const grid = manualElement("div", "manual-financial-grid manual-quarter-grid");
      grid.append(
        manualElement("strong", "", "분기"),
        manualElement("strong", "", "값"));
      record.quarters.forEach(function (quarter, index) {
        grid.append(
          manualInput(quarter.quarter, "manualQuarterLabel", index),
          manualInput(
            quarter.value,
            "manualQuarterValue",
            index,
            record.rawMode ? "text" : "number"));
      });
      fields.appendChild(grid);
    }
    editor.appendChild(fields);
  }

  function bindManualRawStorageInputs(editor, record) {
    if (!record || record.rawMode !== true) return;
    const bind = function (selector, indexFactory) {
      editor.querySelectorAll(selector).forEach(function (input) {
        input.dataset.manualRawStorageIndex = String(indexFactory(input));
      });
    };
    bind("[data-manual-base-date]", function () { return 0; });
    bind("[data-manual-revenue-label], [data-manual-revenue-percentage]",
      function (input) {
        return Number(input.dataset.manualRevenueLabel !== undefined
          ? input.dataset.manualRevenueLabel
          : input.dataset.manualRevenuePercentage) + 1;
      });
    const growthSeriesIndices = {
      salesGrowth: 0,
      operatingProfitGrowth: 1,
      netAssetGrowth: 2,
      netIncomeGrowth: 3
    };
    bind("[data-manual-growth-value]", function (input) {
      return growthSeriesIndices[input.dataset.manualGrowthSeries];
    });
    bind("[data-manual-growth-period]", function () { return 4; });
    bind("[data-manual-quarter-label], [data-manual-quarter-value]",
      function (input) {
        return Number(input.dataset.manualQuarterLabel !== undefined
          ? input.dataset.manualQuarterLabel
          : input.dataset.manualQuarterValue);
      });
  }

  function readManualRecord() {
    if (!manualDraft || !manualWorkspace) return null;
    const stockName = manualWorkspace.querySelector("#manual-financial-stock-name");
    const record = blankManualRecord(manualDraft.screen);
    record.stockName = stockName ? stockName.value.trim() : "";
    if (record.screen === "revenue-composition") {
      const baseDate = manualWorkspace.querySelector("[data-manual-base-date]");
      record.baseDate = baseDate ? baseDate.value : "";
      record.slices = record.slices.map(function (_, index) {
        const label = manualWorkspace.querySelector(
          "[data-manual-revenue-label=\"" + index + "\"]");
        const percentage = manualWorkspace.querySelector(
          "[data-manual-revenue-percentage=\"" + index + "\"]");
        const labelValue = label ? label.value.trim() : "";
        const percentageValue = percentage ? percentage.value.trim() : "";
        return labelValue === "" && percentageValue === "" ? null : {
          label: labelValue,
          percentageText: percentageValue
        };
      });
    } else if (record.screen === "growth-metrics") {
      record.periods = record.periods.map(function (_, index) {
        const input = manualWorkspace.querySelector(
          "[data-manual-growth-period=\"" + index + "\"]");
        return input ? input.value : "";
      });
      ["salesGrowth", "operatingProfitGrowth", "netAssetGrowth", "netIncomeGrowth"]
        .forEach(function (seriesName) {
          record[seriesName] = record[seriesName].map(function (_, index) {
            const input = manualWorkspace.querySelector(
              "[data-manual-growth-series=\"" + seriesName + "\"]" +
              "[data-manual-growth-value=\"" + index + "\"]");
            if (!input || input.value.trim() === "") return null;
            const value = Number(input.value);
            return Number.isFinite(value) ? value : null;
          });
        });
    } else {
      record.quarters = record.quarters.map(function (_, index) {
        const quarter = manualWorkspace.querySelector(
          "[data-manual-quarter-label=\"" + index + "\"]");
        const value = manualWorkspace.querySelector(
          "[data-manual-quarter-value=\"" + index + "\"]");
        const parsed = value && /^-?\d+$/.test(value.value.trim())
          ? Number(value.value) : null;
        return {
          quarter: quarter ? quarter.value.trim() : "",
          value: parsed
        };
      });
    }
    return record;
  }

  function readManualRawRecord() {
    if (!manualDraft || !manualWorkspace || manualDraft.rawMode !== true) return null;
    const stockName = manualWorkspace.querySelector("#manual-financial-stock-name");
    const record = {
      screen: manualDraft.screen,
      stockName: stockName ? stockName.value : "",
      storageValues: []
    };
    if (record.screen === "revenue-composition") {
      const baseDate = manualWorkspace.querySelector("[data-manual-base-date]");
      record.storageValues.push(preserveManualRawStorageValue(
        manualDraft,
        0,
        baseDate ? baseDate.value : ""));
      for (let index = 0; index < 5; index += 1) {
        const label = manualWorkspace.querySelector(
          "[data-manual-revenue-label=\"" + index + "\"]");
        const percentage = manualWorkspace.querySelector(
          "[data-manual-revenue-percentage=\"" + index + "\"]");
        record.storageValues.push(preserveManualRawStorageValue(
          manualDraft,
          index + 1,
          (label ? label.value : "") + "_" +
            (percentage ? percentage.value : "")));
      }
    } else if (record.screen === "growth-metrics") {
      ["salesGrowth", "operatingProfitGrowth", "netAssetGrowth", "netIncomeGrowth"]
        .forEach(function (seriesName) {
          const values = [];
          for (let index = 0; index < 4; index += 1) {
            const input = manualWorkspace.querySelector(
              "[data-manual-growth-series=\"" + seriesName + "\"]" +
              "[data-manual-growth-value=\"" + index + "\"]");
            values.push(input ? input.value : "");
          }
          record.storageValues.push(preserveManualRawStorageValue(
            manualDraft,
            record.storageValues.length,
            values.join("_")));
        });
      const periods = [];
      for (let index = 0; index < 4; index += 1) {
        const input = manualWorkspace.querySelector(
          "[data-manual-growth-period=\"" + index + "\"]");
        periods.push(input ? input.value : "");
      }
      record.storageValues.push(preserveManualRawStorageValue(
        manualDraft,
        4,
        periods.join("_")));
    } else {
      for (let index = 0; index < 6; index += 1) {
        const quarter = manualWorkspace.querySelector(
          "[data-manual-quarter-label=\"" + index + "\"]");
        const value = manualWorkspace.querySelector(
          "[data-manual-quarter-value=\"" + index + "\"]");
        record.storageValues.push(preserveManualRawStorageValue(
          manualDraft,
          index,
          (quarter ? quarter.value : "") + "_" + (value ? value.value : "")));
      }
    }
    return record;
  }

  function rememberManualDraft() {
    if (manualDraft && manualDraft.rawMode === true) {
      const raw = readManualRawRecord();
      if (raw) manualDraft = manualRecordFromRaw(raw);
      return;
    }
    const record = readManualRecord();
    if (record) manualDraft = record;
  }

  function focusManualFinancialStock(stock) {
    if (!stock || stock.disabled === true || !manualWorkspace.contains(stock)) return false;
    if (document.activeElement !== stock) stock.focus({ preventScroll: true });
    return true;
  }

  function selectManualFinancialStock(stock) {
    if (!focusManualFinancialStock(stock)) return false;
    rememberManualDraft();
    const previousDraft = manualDraft ? cloneManualRecord(manualDraft) : null;
    const previousDraftKey = manualDraftKey;
    const previousDraftResetPending = manualDraftResetPending;
    const stockName = manualWorkspace.querySelector("#manual-financial-stock-name");
    const stockIsSelected = stock.getAttribute("aria-selected") === "true";
    if (stockName && shouldResetManualStockDraft(
        stockIsSelected,
        stockName.readOnly,
        manualDraft)) {
      // The original GraphE forms cleared the other editor cells whenever the
      // operator deliberately chose another stock. Do this only for that
      // selection transition; ordinary native rerenders keep the live draft.
      manualDraft = blankManualRecord(manualDraft.screen);
      manualDraftKey = manualDraft.screen + "|new";
      manualDraftResetPending = true;
    }
    if (send("select-manual-financial-stock", {
      resultId: stock.dataset.manualStockResultId
    })) return true;

    manualDraft = previousDraft;
    manualDraftKey = previousDraftKey;
    manualDraftResetPending = previousDraftResetPending;
    return false;
  }

  function captureManualDraftForChangedModel() {
    if (!manualModal.hidden && !manualDraftResetPending) rememberManualDraft();
    manualDraftResetPending = false;
  }

  function renderManualFinancial(state) {
    const manual = state.manualFinancial;
    manualButtons.forEach(function (button) {
      button.disabled = state.isBusy === true;
    });
    if (!manual) {
      clearManualResultPendingSelection(false);
      setTransientControlsBusy(manualDialog, false);
      manualModal.hidden = true;
      manualWorkspace.replaceChildren();
      forgetSearchDraft(
        "manual-financial-list-query",
        "manual-financial-find-query");
      manualFinancialSearchFocus = null;
      manualDraft = null;
      manualDraftKey = "";
      manualDraftResetPending = false;
      manualFinancialRenderStateKey = "";
      return;
    }

    const capturedSearchFocus = captureSearchDraftFocus(manualWorkspace);
    if (capturedSearchFocus) manualFinancialSearchFocus = capturedSearchFocus;
    const manualSearchScope = "manual-financial:" + manual.screen;
    const renderStateKey = JSON.stringify(manual);
    if (manualResultPendingSelection) {
      if (manualResultPendingSelection.sent && state.isBusy !== true) {
        const accepted = manual.selectedRowId === manualResultPendingSelection.key;
        clearManualResultPendingSelection(!accepted);
      } else if (!manualResultPendingSelection.sent &&
          manualFinancialRenderStateKey &&
          renderStateKey !== manualFinancialRenderStateKey) {
        clearManualResultPendingSelection(false);
      } else {
        applyPendingRowSelection(manualResultPendingSelection, false);
        applyPendingControlLocks(manualResultPendingSelection.lockedControls);
      }
    }
    if (!manualModal.hidden && renderStateKey === manualFinancialRenderStateKey) {
      setTransientControlsBusy(manualDialog, state.isBusy === true);
      if (state.isBusy !== true &&
          restoreSearchDraftFocus(manualWorkspace, manualFinancialSearchFocus)) {
        manualFinancialSearchFocus = null;
      }
      return;
    }

    captureManualDraftForChangedModel();
    // Clear the previous transient snapshot before applying a changed model.
    // Otherwise a busy -> new-model transition can restore the old screen's
    // semantic disabled states over the controls rendered below.
    setTransientControlsBusy(manualDialog, false);
    if (manualModal.hidden) rememberModalOpener(manualDialog);
    manualModal.hidden = false;
    manualTitle.textContent = manual.label + " · GraphE 수동 입력";
    const selectedEditorRecord = manual.selectedRawRecord
      ? manualRecordFromRaw(manual.selectedRawRecord)
      : manual.selectedRecord;
    const detailKey = manual.screen + "|" + (manual.selectedRowId || "new") + "|" +
      JSON.stringify(manual.selectedRawRecord || manual.selectedRecord || null);
    if (selectedEditorRecord && detailKey !== manualDraftKey) {
      manualDraft = cloneManualRecord(selectedEditorRecord);
      manualDraftKey = detailKey;
    } else if (!manualDraft || manualDraft.screen !== manual.screen) {
      manualDraft = blankManualRecord(manual.screen);
      manualDraftKey = manual.screen + "|new";
    }
    if (!manual.selectedRecord && manual.verifiedStock) {
      manualDraft.stockName = manual.verifiedStock.name;
    }

    const toolbar = manualElement("div", "manual-financial-toolbar");
    const listQuery = manualInput(manual.query || "", "manualListQuery", "true");
    listQuery.id = "manual-financial-list-query";
    syncSearchDraft(listQuery, manual.query, manualSearchScope);
    const listSearch = manualElement("button", "", "목록 검색");
    listSearch.type = "button";
    listSearch.dataset.manualListSearch = "true";
    const findQuery = manualInput(manual.findQuery || "", "manualFindQuery", "true");
    findQuery.id = "manual-financial-find-query";
    findQuery.placeholder = "종목명 찾기";
    syncSearchDraft(findQuery, manual.findQuery, manualSearchScope);
    const findUp = manualElement("button", "", "▲ 위로 찾기");
    findUp.type = "button";
    findUp.dataset.manualFindDirection = "up";
    findUp.dataset.manualGeneration = String(manual.generation);
    const findDown = manualElement("button", "", "▼ 아래로 찾기");
    findDown.type = "button";
    findDown.dataset.manualFindDirection = "down";
    findDown.dataset.manualGeneration = String(manual.generation);
    const create = manualElement("button", "", "새 입력");
    create.type = "button";
    create.dataset.manualCreate = "true";
    toolbar.append(listQuery, listSearch, create, findQuery, findUp, findDown);

    const body = manualElement("div", "manual-financial-body");
    const list = manualElement("div", "manual-financial-list");
    list.setAttribute("aria-label", manual.label + " 저장 목록");
    const previewColumns = manualFinancialPreviewColumns(manual.screen);
    list.classList.add("manual-financial-preview-columns-" + String(previewColumns.length));
    const nameHeader = manualElement(
      "button",
      "manual-financial-list-header manual-financial-list-row");
    nameHeader.type = "button";
    nameHeader.dataset.manualNameSort = "true";
    nameHeader.dataset.manualGeneration = String(manual.generation);
    nameHeader.title = "종목명 정렬 방향 전환";
    previewColumns.forEach(function (label, index) {
      nameHeader.appendChild(manualElement(
        "span",
        "manual-financial-preview-cell",
        index === 0
          ? label + " " + (manual.nameSortDirection === "descending" ? "▼" : "▲")
          : label));
    });
    list.appendChild(nameHeader);
    let selectedManualRow = null;
    manual.rows.forEach(function (row) {
      const button = manualElement("button", "manual-financial-list-row");
      button.type = "button";
      button.id = "manual-financial-result-" + row.resultId;
      button.dataset.manualResultId = row.resultId;
      const isSelected = manual.selectedRowId === row.resultId;
      button.classList.toggle("selected", isSelected);
      button.setAttribute("aria-selected", isSelected ? "true" : "false");
      const previewValues = manualFinancialPreviewValues(row, previewColumns.length);
      previewValues.forEach(function (value) {
        const cell = manualElement("span", "manual-financial-preview-cell", value);
        if (value) cell.title = value;
        button.appendChild(cell);
      });
      button.setAttribute("aria-label", previewColumns.map(function (label, index) {
        return label + " " + (previewValues[index] || "없음");
      }).join(", "));
      if (row.isDataReadable === false) {
        button.classList.add("manual-financial-row-unreadable");
        button.title = "종목 이름을 읽을 수 없어 이 행을 사용할 수 없습니다.";
        button.setAttribute(
          "aria-label",
          button.getAttribute("aria-label") + " · 읽기 불가");
      } else if (row.isPlayoutReady === false) {
        button.classList.add("manual-financial-row-legacy");
        button.title = "기존 GraphE 원문 형식입니다. 선택해 원문 그대로 확인하고 편집할 수 있습니다.";
        button.setAttribute(
          "aria-label",
          button.getAttribute("aria-label") + " · 기존 원문 형식");
      }
      if (isSelected) selectedManualRow = button;
      list.appendChild(button);
    });
    configureRovingList(
      list,
      Array.from(list.querySelectorAll("button[data-manual-result-id]")),
      selectedManualRow);

    const editor = manualElement("section", "manual-financial-editor");
    const stockLine = manualElement("div", "manual-financial-stock-line");
    const stockInput = manualInput(manualDraft.stockName || "");
    stockInput.id = "manual-financial-stock-name";
    stockInput.readOnly = !!manual.selectedRecord || !!manual.selectedRawRecord;
    const stockSearch = manualElement("button", "", "종목 확인");
    stockSearch.type = "button";
    stockSearch.dataset.manualStockSearch = "true";
    stockLine.append(stockInput, stockSearch);
    editor.appendChild(stockLine);

    const candidates = manualElement("div", "manual-financial-stock-results");
    let selectedManualStock = null;
    manual.stockCandidates.forEach(function (row) {
      const isSelected = manual.selectedStockResultId === row.resultId;
      const button = manualElement(
        "button",
        isSelected ? "selected" : "",
        row.name + " · " + row.market);
      button.type = "button";
      button.id = "manual-financial-stock-result-" + row.resultId;
      button.dataset.manualStockResultId = row.resultId;
      button.setAttribute(
        "aria-selected",
        isSelected ? "true" : "false");
      if (isSelected) selectedManualStock = button;
      candidates.appendChild(button);
    });
    // A single click on the legacy GraphE candidate ListBox only moved its
    // focus/highlight. Keep arrow/page navigation equally non-destructive;
    // double-click or Enter remains the explicit stock-selection command.
    configureRovingList(
      candidates,
      Array.from(candidates.querySelectorAll("button[data-manual-stock-result-id]")),
      selectedManualStock,
      { activation: "focus", selectionFollowsFocus: false });
    editor.appendChild(candidates);
    const verification = manualElement(
      "div",
      "manual-financial-verification",
      manual.verifiedStock
        ? "확인됨: " + manual.verifiedStock.name + " · " + manual.verifiedStock.market
        : "정확한 종목 마스터를 확인하세요.");
    editor.appendChild(verification);
    const referenceFigure = manualFinancialReferenceFigure(manual.screen, manual.label);
    if (referenceFigure) editor.appendChild(referenceFigure);
    renderManualEditorFields(editor, manualDraft);
    bindManualRawStorageInputs(editor, manualDraft);
    if (manual.selectedRawRecord && manual.isPlayoutReady !== true) {
      editor.appendChild(manualElement(
        "div",
        "manual-financial-warning",
        "기존 GraphE 원문 값으로 불러왔습니다. 편집·삭제할 수 있고, 송출 전에는 장면 데이터 검증을 다시 수행합니다."));
    }

    const actions = manualElement("div", "manual-financial-actions");
    const save = manualElement("button", "manual-primary", "저장");
    save.type = "button";
    save.dataset.manualSave = "true";
    save.disabled = !manual.canSave;
    const remove = manualElement("button", "", "선택 삭제");
    remove.type = "button";
    remove.dataset.manualDelete = "true";
    remove.disabled = !manual.canDelete;
    const removeAll = manualElement("button", "", "전체 삭제");
    removeAll.type = "button";
    removeAll.dataset.manualDeleteAll = "true";
    removeAll.disabled = manual.writesQuarantined === true;
    const add = manualElement("button", "manual-primary", "편성표에 추가");
    add.type = "button";
    add.dataset.manualActionId = manual.action.actionId;
    add.disabled = !manual.canMaterializePlaylist;
    actions.append(save, remove, removeAll, add);
    editor.appendChild(actions);
    if (manual.lastMessage) {
      editor.appendChild(manualElement(
        "div",
        manual.writesQuarantined || manual.listRejectedItemCount > 0 ||
          manual.listIsTruncated === true
          ? "manual-financial-warning"
          : "manual-financial-message",
        manual.lastMessage));
    }

    body.append(list, editor);
    manualWorkspace.replaceChildren(toolbar, body);
    manualClose.disabled = false;
    manualExit.disabled = manualClose.disabled;
    setTransientControlsBusy(manualDialog, state.isBusy === true);
    manualFinancialRenderStateKey = renderStateKey;
    if (state.isBusy !== true &&
        restoreSearchDraftFocus(manualWorkspace, manualFinancialSearchFocus)) {
      manualFinancialSearchFocus = null;
    }
  }

  function isCanonicalNamedPlaylistTitle(value) {
    return value.length > 0 && value.length <= 128 && value === value.trim() &&
      !/[\u0000-\u001f\u007f-\u009f\ud800-\udfff\ufffd]/.test(value);
  }

  function renderManualLists(state) {
    const manual = state.manualLists;
    const fixedBatch = state.fixedSectionBatch;
    manualListButtons.forEach(function (button) {
      button.disabled = state.isBusy === true || fixedBatch != null;
    });
    if (!manual || manual.isOpen !== true) {
      clearManualViResultPendingSelection(false);
      setTransientControlsBusy(manualListDialog, false);
      manualListModal.hidden = true;
      manualListWorkspace.replaceChildren();
      forgetSearchDraft("manual-vi-search");
      manualListSearchFocus = null;
      manualListRenderStateKey = "";
      return;
    }

    if (manual.screen === "vi") {
      const capturedSearchFocus = captureSearchDraftFocus(manualListWorkspace);
      if (capturedSearchFocus) manualListSearchFocus = capturedSearchFocus;
    } else {
      clearManualViResultPendingSelection(false);
      forgetSearchDraft("manual-vi-search");
      manualListSearchFocus = null;
    }
    const renderStateKey = JSON.stringify({
      manual: manual,
      fixedBatch: fixedBatch
    });
    if (manualViResultPendingSelection) {
      if (manualViResultPendingSelection.sent && state.isBusy !== true) {
        const accepted = manual.selectedSearchResultId ===
          manualViResultPendingSelection.key;
        clearManualViResultPendingSelection(!accepted);
      } else if (!manualViResultPendingSelection.sent && manualListRenderStateKey &&
          renderStateKey !== manualListRenderStateKey) {
        clearManualViResultPendingSelection(false);
      } else {
        applyPendingRowSelection(manualViResultPendingSelection, false);
      }
    }
    if (!manualListModal.hidden && renderStateKey === manualListRenderStateKey) {
      setTransientControlsBusy(manualListDialog, state.isBusy === true);
      if (state.isBusy !== true && manual.screen === "vi" &&
          restoreSearchDraftFocus(manualListWorkspace, manualListSearchFocus)) {
        manualListSearchFocus = null;
      }
      return;
    }

    setTransientControlsBusy(manualListDialog, false);
    if (manualListModal.hidden) rememberModalOpener(manualListDialog);
    manualListModal.hidden = false;
    const busy = state.isBusy === true;
    if (manual.screen !== "vi") {
      clearManualViResultPendingSelection(false);
    }
    const audienceLabels = {
      individual: "개인",
      foreign: "외국인",
      institution: "기관"
    };
    const baseTitle = manual.screen === "vi"
      ? "VI 발동 수동 목록"
      : (audienceLabels[manual.audience] || "수동") + " 순매도 상위";
    manualListTitle.textContent = fixedBatch
      ? baseTitle + " · " + fixedBatch.sectionName + " " +
        String(fixedBatch.manualStepNumber) + "/" + String(fixedBatch.manualStepCount)
      : baseTitle;

    const fragment = document.createDocumentFragment();
    if (fixedBatch) {
      const progress = manualElement(
        "div",
        "manual-list-status",
        "섹션 전체 " + String(fixedBatch.totalActionCount) + "개는 아직 편성표에 추가되지 않았습니다. " +
          "현재 항목을 확인하면 C#이 다음 수동 항목으로 진행하며, 닫으면 배치를 취소합니다.");
      progress.setAttribute("role", "status");
      fragment.appendChild(progress);
    }
    if (manual.writesQuarantined || manual.isAvailable !== true) {
      const warning = manualElement(
        "div",
        "manual-list-status error",
        manual.statusMessage || manual.availabilityMessage ||
          "수동 목록 저장소를 사용할 수 없습니다.");
      warning.setAttribute("role", "alert");
      fragment.appendChild(warning);
    }

    if (manual.screen === "net-sell") {
      const tabs = manualElement("div", "manual-list-tabs");
      Object.keys(audienceLabels).forEach(function (audience) {
        const button = manualElement(
          "button",
          audience === manual.audience ? "active" : "",
          audienceLabels[audience]);
        button.type = "button";
        button.disabled = fixedBatch != null;
        button.addEventListener("click", function () {
          send("open-manual-list", { screen: audience });
        });
        tabs.appendChild(button);
      });
      fragment.appendChild(tabs);

      const wrap = manualElement("div", "manual-list-table-wrap");
      const table = manualElement("table", "manual-list-table");
      const head = document.createElement("thead");
      const headRow = document.createElement("tr");
      ["#", "좌측 종목", "좌측 금액", "우측 종목", "우측 금액"].forEach(function (label) {
        headRow.appendChild(manualElement("th", "", label));
      });
      head.appendChild(headRow);
      table.appendChild(head);
      const body = document.createElement("tbody");
      const fields = ["leftName", "leftAmount", "rightName", "rightAmount"];
      manual.netRows.forEach(function (row, index) {
        const tr = document.createElement("tr");
        tr.appendChild(manualElement("th", "", String(index + 1)));
        fields.forEach(function (field) {
          const td = document.createElement("td");
          const input = document.createElement("input");
          input.type = "text";
          input.maxLength = 256;
          input.value = row[field] || "";
          input.disabled = manual.isAvailable !== true || manual.writesQuarantined;
          input.addEventListener("change", function () {
            setTransientControlsBusy(manualListDialog, true);
            const sent = send("set-manual-net-sell-cell", {
              rowId: row.rowId,
              field: field,
              value: input.value
            });
            if (!sent) setTransientControlsBusy(manualListDialog, false);
          });
          td.appendChild(input);
          tr.appendChild(td);
        });
        body.appendChild(tr);
      });
      table.appendChild(body);
      wrap.appendChild(table);
      fragment.appendChild(wrap);
    } else {
      const search = manualElement("div", "manual-list-search");
      const input = document.createElement("input");
      input.type = "search";
      input.id = "manual-vi-search";
      input.maxLength = 100;
      input.placeholder = "종목명 검색";
      syncSearchDraft(input, manual.searchQuery, "manual-list:vi");
      const searchButton = manualElement("button", "", "검색");
      searchButton.type = "button";
      searchButton.disabled = manual.isAvailable !== true;
      function submitSearch() {
        send("search-manual-vi", { query: input.value });
      }
      searchButton.addEventListener("click", submitSearch);
      input.addEventListener("keydown", function (event) {
        if (event.key === "Enter" && !isImeComposing(event)) {
          event.preventDefault();
          submitSearch();
        }
      });
      search.append(input, searchButton);
      fragment.appendChild(search);

      const results = manualElement("div", "manual-list-search-results");
      results.setAttribute("aria-label", "VI 종목 검색 결과");
      let selectedViResult = null;
      manual.searchResults.forEach(function (row) {
        const isSelected = manual.selectedSearchResultId === row.resultId;
        const button = manualElement(
          "button",
          isSelected ? "selected" : "",
          row.displayName + " · " + row.marketText);
        button.type = "button";
        button.id = "manual-vi-result-" + row.resultId;
        button.dataset.manualViResultId = row.resultId;
        button.disabled = false;
        button.setAttribute("aria-selected", isSelected ? "true" : "false");
        if (isSelected) selectedViResult = button;
        button.addEventListener("click", function () {
          clearDelayedClick(manualViResultClickTimer);
          const pending = beginManualViResultPendingSelection(button, row.resultId);
          manualViResultClickTimer = deferLegacySingleClick(function () {
            manualViResultClickTimer = null;
            if (!pending || manualViResultPendingSelection !== pending) return;
            if (send("select-manual-vi-result", { resultId: row.resultId })) {
              pending.sent = true;
            } else {
              clearManualViResultPendingSelection(true);
            }
          });
        });
        button.addEventListener("dblclick", function (event) {
          event.preventDefault();
          clearDelayedClick(manualViResultClickTimer);
          manualViResultClickTimer = null;
          const pending = manualViResultPendingSelection &&
            manualViResultPendingSelection.key === row.resultId
              ? manualViResultPendingSelection
              : beginManualViResultPendingSelection(button, row.resultId);
          if (send("add-manual-vi-result", { resultId: row.resultId })) {
            if (pending) pending.sent = true;
          } else {
            clearManualViResultPendingSelection(true);
          }
        });
        results.appendChild(button);
      });
      configureRovingList(
        results,
        Array.from(results.querySelectorAll("button[data-manual-vi-result-id]")),
        selectedViResult);
      fragment.appendChild(results);

      if (manual.searchIsTruncated === true) {
        const truncationWarning = manualElement(
          "div",
          "manual-list-status warning",
          "검색 결과 일부만 표시 중입니다. 검색어를 더 구체적으로 입력해 주세요.");
        truncationWarning.setAttribute("role", "status");
        fragment.appendChild(truncationWarning);
      }

      const summary = manualElement(
        "div",
        "manual-list-status",
        String(manual.viItems.length) + "/100종목 · " +
          String(manual.viPageCount) + "페이지");
      fragment.appendChild(summary);
      const wrap = manualElement("div", "manual-list-table-wrap");
      const table = manualElement("table", "manual-list-table");
      const body = document.createElement("tbody");
      manual.viItems.forEach(function (row, index) {
        const tr = document.createElement("tr");
        tr.append(
          manualElement("td", "", String(row.index)),
          manualElement("td", "", row.name));
        const controls = manualElement("td", "manual-list-row-actions");
        [["↑", "up"], ["↓", "down"]].forEach(function (move) {
          const button = manualElement("button", "", move[0]);
          button.type = "button";
          button.disabled = move[1] === "up"
            ? index === 0
            : index === manual.viItems.length - 1;
          button.addEventListener("click", function () {
            send("move-manual-vi-item", { itemId: row.itemId, direction: move[1] });
          });
          controls.appendChild(button);
        });
        const remove = manualElement("button", "", "삭제");
        remove.type = "button";
        remove.disabled = false;
        remove.addEventListener("click", function () {
          send("delete-manual-vi-item", { itemId: row.itemId });
        });
        controls.appendChild(remove);
        tr.appendChild(controls);
        body.appendChild(tr);
      });
      table.appendChild(body);
      wrap.appendChild(table);
      fragment.appendChild(wrap);
    }

    if (manual.statusMessage) {
      fragment.appendChild(manualElement(
        "div",
        "manual-list-status" +
          (manual.statusKind === "error" || manual.writesQuarantined ? " error" : ""),
        manual.statusMessage));
    }
    const actions = manualElement("div", "manual-list-actions-row");
    const importButton = manualElement("button", "", "원본 1회 가져오기");
    importButton.type = "button";
    importButton.disabled = manual.isAvailable !== true || manual.writesQuarantined;
    importButton.addEventListener("click", function () {
      if (window.confirm("원본 파일을 현재 빈 저장소로 한 번만 가져옵니다. 기존 파일은 덮어쓰지 않습니다.")) {
        send("import-manual-lists", {});
      }
    });
    const refresh = manualElement("button", "", "재조회");
    refresh.type = "button";
    refresh.disabled = manual.isAvailable !== true;
    refresh.addEventListener("click", function () { send("refresh-manual-list", {}); });
    if (manual.screen === "vi") {
      const clear = manualElement("button", "", "전체 삭제");
      clear.type = "button";
      clear.addEventListener("click", function () {
        send("delete-all-manual-vi-items", {});
      });
      actions.appendChild(clear);
    }
    const confirm = manualElement("button", "primary", "확인");
    confirm.type = "button";
    if (fixedBatch) {
      confirm.textContent = fixedBatch.manualStepNumber < fixedBatch.manualStepCount
        ? "확인 후 다음"
        : "확인 후 섹션 추가";
    }
    confirm.disabled = manual.canSave !== true;
    confirm.addEventListener("click", function () {
      send("confirm-manual-list", {});
    });
    actions.append(importButton, refresh, confirm);
    fragment.appendChild(actions);
    manualListWorkspace.replaceChildren(fragment);
    manualListClose.disabled = false;
    manualListCancel.disabled = manualListClose.disabled;
    setTransientControlsBusy(manualListDialog, busy);
    manualListRenderStateKey = renderStateKey;
    if (!busy && manual.screen === "vi" &&
        restoreSearchDraftFocus(manualListWorkspace, manualListSearchFocus)) {
      manualListSearchFocus = null;
    }
  }

  function openNamedPlaylist(mode) {
    if (operatorMutationLocked ||
        (mode === "load" && playlistStructureMutationLocked)) return false;
    namedPlaylistSelection.cancel();
    namedPlaylistMode = mode;
    namedPlaylistTitle.textContent = mode === "save"
      ? "DB 재생목록 저장" : "DB 재생목록 불러오기";
    namedPlaylistConfirmButton.textContent = mode === "save" ? "현재 목록 저장" : "불러오기";
    namedPlaylistNewTitle.value = "";
    if (namedPlaylistModal.hidden) rememberModalOpener(namedPlaylistDialog);
    namedPlaylistModal.hidden = false;
    if (namedPlaylistState) {
      renderNamedPlaylist({ namedPlaylist: namedPlaylistState, isBusy: uiBusy });
    }
    syncModalFocus();
    beginNamedPlaylistBusyCycle("refresh-named-playlists", {});
    return true;
  }

  function closeNamedPlaylist() {
    if (isNamedPlaylistModalLocked() || namedPlaylistSelection.isPending()) return false;
    namedPlaylistMode = null;
    namedPlaylistModal.hidden = true;
    syncModalFocus();
    return true;
  }

  function isNamedPlaylistModalLocked() {
    return uiBusy || pendingNamedPlaylistCommand !== null ||
      namedPlaylistConfirmation !== null ||
      !!(namedPlaylistState && namedPlaylistState.isWriteInProgress === true);
  }

  function isNamedPlaylistActionLocked() {
    return isNamedPlaylistModalLocked() || operatorMutationLocked ||
      namedPlaylistSelection.isPending();
  }

  function isNamedPlaylistDefinitionLocked() {
    return operatorMutationLocked || pendingNamedPlaylistCommand !== null ||
      namedPlaylistConfirmation !== null ||
      !!(namedPlaylistState && namedPlaylistState.isWriteInProgress === true) ||
      (uiBusy && !namedPlaylistSelection.isPending());
  }

  function markNamedPlaylistVisualSelection(definitionId) {
    namedPlaylistDefinitions
      .querySelectorAll("button[data-named-playlist-definition-id]")
      .forEach(function (button) {
        const selected = button.dataset.namedPlaylistDefinitionId === definitionId;
        button.setAttribute("aria-selected", selected ? "true" : "false");
        button.classList.toggle("selected", selected);
      });
  }

  function lockNamedPlaylistSelectionActions() {
    namedPlaylistClose.disabled = true;
    namedPlaylistCancelButton.disabled = true;
    namedPlaylistNewTitle.disabled = true;
    namedPlaylistCreateButton.disabled = true;
    namedPlaylistDeleteButton.disabled = true;
    namedPlaylistConfirmButton.disabled = true;
  }

  function beginNamedPlaylistSelection(definitionId) {
    if (namedPlaylistSelection.isPending()) {
      const pending = namedPlaylistSelection.begin(
        definitionId,
        namedPlaylistState && namedPlaylistState.selectedDefinitionId);
      if (pending.accepted) markNamedPlaylistVisualSelection(definitionId);
      return pending.accepted;
    }
    if (isNamedPlaylistModalLocked() || operatorMutationLocked) return false;

    const requestId = "named-ui-" + String(nextNamedPlaylistRequestId++);
    const started = namedPlaylistSelection.begin(
      definitionId,
      namedPlaylistState && namedPlaylistState.selectedDefinitionId,
      requestId,
      lastCommandSequence + 1);
    if (!started.accepted) return false;
    markNamedPlaylistVisualSelection(definitionId);
    if (!started.shouldSend) return true;

    lockNamedPlaylistSelectionActions();
    if (!send("select-named-playlist", {
      definitionId: definitionId,
      requestId: started.requestId
    })) {
      namedPlaylistSelection.cancel();
      restoreNamedPlaylistAfterConfirmation();
      return false;
    }
    return true;
  }

  function runNamedPlaylistDoubleClick(definitionId, mode) {
    if (mode === "save") {
      return openNamedPlaylistConfirmation({
        kind: "save",
        definitionId: definitionId,
        message: "저장하겠습니까?",
        showSavedAlert: false,
        closeOnNo: true
      });
    }
    return beginNamedPlaylistCommand("load-named-playlist-by-id", definitionId, false);
  }

  function lockNamedPlaylistControls() {
    namedPlaylistModal.querySelectorAll("button, input").forEach(function (control) {
      control.disabled = true;
    });
  }

  function beginNamedPlaylistBusyCycle(type, payload) {
    if (isNamedPlaylistActionLocked()) return false;
    const requestId = "named-ui-" + String(nextNamedPlaylistRequestId++);
    pendingNamedPlaylistCommand = {
      command: type,
      targetId: requestId,
      minimumSequence: lastCommandSequence + 1,
      closeOnSuccess: false,
      showSavedAlert: false
    };
    const correlatedPayload = Object.assign({}, payload || {}, { requestId: requestId });
    if (!send(type, correlatedPayload)) {
      pendingNamedPlaylistCommand = null;
      return false;
    }
    lockNamedPlaylistControls();
    return true;
  }

  function beginNamedPlaylistCommand(command, definitionId, showSavedAlert) {
    if (isNamedPlaylistActionLocked() || !definitionId) return false;
    const payload = { definitionId: definitionId };
    if (command === "save-current-named-playlist-to") {
      payload.showSavedAlert = showSavedAlert === true;
    }
    pendingNamedPlaylistCommand = {
      command: command,
      targetId: definitionId,
      definitionId: definitionId,
      minimumSequence: lastCommandSequence + 1,
      closeOnSuccess: true
    };
    if (!send(command, payload)) {
      pendingNamedPlaylistCommand = null;
      return false;
    }
    lockNamedPlaylistControls();
    return true;
  }

  function isMatchingNamedPlaylistReceipt(busy, pending, commandResult) {
    const exact = !!pending && !!commandResult &&
      Number.isSafeInteger(commandResult.sequence) &&
      commandResult.sequence >= pending.minimumSequence &&
      commandResult.command === pending.command &&
      commandResult.targetId === pending.targetId;
    return exact && (commandResult.succeeded === false ||
      (busy !== true && commandResult.succeeded === true));
  }

  function openNamedPlaylistConfirmation(options) {
    if (!options || isNamedPlaylistActionLocked()) return false;
    namedPlaylistConfirmation = options;
    namedPlaylistConfirmationMessage.textContent = options.message;
    if (namedPlaylistConfirmationModal.hidden) {
      rememberModalOpener(namedPlaylistConfirmationDialog);
    }
    namedPlaylistConfirmationModal.hidden = false;
    lockNamedPlaylistControls();
    // The confirmation can open immediately after a correlated DB refresh or
    // selection receipt. Re-establish its own controls here so a transient
    // busy render cannot leave the affirmative action stale-disabled.
    namedPlaylistConfirmationYes.disabled = false;
    namedPlaylistConfirmationNo.disabled = false;
    syncModalFocus();
    return true;
  }

  function restoreNamedPlaylistAfterConfirmation() {
    if (!namedPlaylistState || namedPlaylistModal.hidden) return;
    renderNamedPlaylist({
      namedPlaylist: namedPlaylistState,
      isBusy: uiBusy,
      commandResult: null,
      statusKind: null
    });
  }

  function resolveNamedPlaylistConfirmation(confirmed) {
    const confirmation = namedPlaylistConfirmation;
    if (!confirmation) return false;
    namedPlaylistConfirmation = null;
    namedPlaylistConfirmationModal.hidden = true;
    syncModalFocus();

    if (!confirmed) {
      if (confirmation.closeOnNo === true) closeNamedPlaylist();
      else restoreNamedPlaylistAfterConfirmation();
      return true;
    }

    const started = confirmation.kind === "delete"
      ? beginNamedPlaylistBusyCycle("delete-selected-named-playlist", {})
      : beginNamedPlaylistCommand(
          "save-current-named-playlist-to",
          confirmation.definitionId,
          confirmation.showSavedAlert === true);
    if (!started) restoreNamedPlaylistAfterConfirmation();
    return started;
  }

  function renderNamedPlaylist(state) {
    const named = state.namedPlaylist;
    namedPlaylistState = named || null;
    const busy = state.isBusy === true;
    const commandResult = state.commandResult;
    const selectionResult = namedPlaylistSelection.reconcile(
      commandResult,
      named && named.selectedDefinitionId);
    const deferredSelectionAction = selectionResult.deferredAction;
    if (commandResult && Number.isSafeInteger(commandResult.sequence)) {
      lastCommandSequence = Math.max(lastCommandSequence, commandResult.sequence);
    }
    if (isMatchingNamedPlaylistReceipt(
      busy,
      pendingNamedPlaylistCommand,
      commandResult
    )) {
      const completed = pendingNamedPlaylistCommand;
      const succeeded = commandResult.succeeded === true;
      pendingNamedPlaylistCommand = null;
      if (succeeded && completed.closeOnSuccess) {
        closeNamedPlaylist();
      }
    }
    dbLoadButton.disabled = playlistStructureMutationLocked || busy || !named;
    dbSaveButton.disabled = operatorMutationLocked || busy || !named ||
      named.canMutate !== true;
    if (!named || namedPlaylistModal.hidden) return;

    const modalLocked = isNamedPlaylistActionLocked();
    const definitionLocked = isNamedPlaylistDefinitionLocked();
    const existingDefinitions = new Map();
    namedPlaylistDefinitions
      .querySelectorAll("button[data-named-playlist-definition-id]")
      .forEach(function (button) {
        existingDefinitions.set(button.dataset.namedPlaylistDefinitionId, button);
      });
    const fragment = document.createDocumentFragment();
    let selectedDefinition = null;
    named.definitions.forEach(function (definition) {
      const button = existingDefinitions.get(definition.definitionId) ||
        document.createElement("button");
      button.type = "button";
      button.dataset.namedPlaylistDefinitionId = definition.definitionId;
      button.setAttribute("role", "option");
      button.setAttribute("aria-selected", definition.isSelected ? "true" : "false");
      button.className = definition.isSelected ? "selected" : "";
      button.disabled = definitionLocked;
      button.textContent = definition.title;
      if (definition.isSelected) selectedDefinition = button;
      fragment.appendChild(button);
    });
    namedPlaylistDefinitions.replaceChildren(fragment);
    configureRovingList(
      namedPlaylistDefinitions,
      Array.from(namedPlaylistDefinitions.querySelectorAll(
        "button[data-named-playlist-definition-id]")),
      selectedDefinition);

    const selectedDescription = named.selectedTitle
      ? "선택: " + named.selectedTitle + " · " + String(named.rowCount) + "행"
      : "DB 재생목록을 선택하세요.";
    const quarantine = named.isWriteQuarantined === true
      ? " 결과 불명확 상태로 이번 실행의 추가 쓰기가 차단되었습니다." : "";
    const truncated = named.isListTruncated === true
      ? " 목록 일부만 표시 중입니다." : "";
    namedPlaylistStatus.textContent = (named.statusMessage || selectedDescription) +
      quarantine + truncated;
    namedPlaylistStatus.classList.toggle("error", named.isWriteQuarantined === true ||
      named.statusKind === "error");

    const closeLocked = isNamedPlaylistModalLocked() ||
      namedPlaylistSelection.isPending();
    namedPlaylistClose.disabled = closeLocked;
    namedPlaylistCancelButton.disabled = closeLocked;
    namedPlaylistNewTitle.disabled = modalLocked;
    namedPlaylistCreateButton.disabled = modalLocked || named.canCreate !== true ||
      !isCanonicalNamedPlaylistTitle(namedPlaylistNewTitle.value);
    namedPlaylistDeleteButton.disabled = modalLocked || named.canDelete !== true;
    namedPlaylistConfirmButton.disabled = modalLocked ||
      (namedPlaylistMode === "save" ? named.canSave !== true : named.canLoad !== true);
    // A PROGRAM auto-refresh can begin between pointer-down and pointer-up.
    // Keep the already-open confirmation clickable through unrelated busy
    // state; the native ordered gate serializes the request and performs the
    // authoritative mutation-safety check before any database write.
    namedPlaylistConfirmationYes.disabled = pendingNamedPlaylistCommand !== null ||
      !!(namedPlaylistState && namedPlaylistState.isWriteInProgress === true);
    if (deferredSelectionAction) {
      runNamedPlaylistDoubleClick(
        deferredSelectionAction.definitionId,
        deferredSelectionAction.mode);
    }
  }

  function clearOperatorCatalogDragVisuals() {
    operatorCatalogDraftRows.querySelectorAll(".dragging, .drag-before, .drag-after")
      .forEach(function (row) {
        row.classList.remove("dragging", "drag-before", "drag-after");
      });
  }

  function onOperatorCatalogPointerDown(event) {
    if (event.button !== 0) return;
    const rowId = event.currentTarget.dataset.operatorCatalogRowId;
    operatorCatalogDragBlockedRowId = event.target.closest("button, input")
      ? rowId
      : null;
  }

  function onOperatorCatalogPointerEnd(event) {
    if (operatorCatalogDragBlockedRowId ===
        event.currentTarget.dataset.operatorCatalogRowId) {
      operatorCatalogDragBlockedRowId = null;
    }
  }

  function onOperatorCatalogDragStart(event) {
    const row = event.currentTarget;
    const sourceRowId = row.dataset.operatorCatalogRowId;
    if (operatorCatalogMutationLocked || !sourceRowId || !event.dataTransfer ||
        operatorCatalogDragBlockedRowId === sourceRowId ||
        event.target.closest("button, input")) {
      operatorCatalogDragBlockedRowId = null;
      event.preventDefault();
      return;
    }

    operatorCatalogDragBlockedRowId = null;
    draggedOperatorCatalogRowId = sourceRowId;
    event.dataTransfer.effectAllowed = "move";
    event.dataTransfer.setData(operatorCatalogDragMime, sourceRowId);
    row.classList.add("dragging");
  }

  function onOperatorCatalogDragOver(event) {
    const target = event.currentTarget;
    if (operatorCatalogMutationLocked || !draggedOperatorCatalogRowId ||
        target.dataset.operatorCatalogRowId === draggedOperatorCatalogRowId) return;

    event.preventDefault();
    if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
    const position = dragDropPosition(event, target);
    target.classList.toggle("drag-before", position === "before");
    target.classList.toggle("drag-after", position === "after");
  }

  function onOperatorCatalogDragLeave(event) {
    event.currentTarget.classList.remove("drag-before", "drag-after");
  }

  function onOperatorCatalogDrop(event) {
    const target = event.currentTarget;
    const sourceRowId = draggedOperatorCatalogRowId;
    const targetRowId = target.dataset.operatorCatalogRowId;
    if (operatorCatalogMutationLocked || !sourceRowId || !targetRowId ||
        sourceRowId === targetRowId || !event.dataTransfer ||
        event.dataTransfer.getData(operatorCatalogDragMime) !== sourceRowId) {
      draggedOperatorCatalogRowId = null;
      clearOperatorCatalogDragVisuals();
      return;
    }

    event.preventDefault();
    const position = dragDropPosition(event, target);
    draggedOperatorCatalogRowId = null;
    clearOperatorCatalogDragVisuals();
    send("reorder-operator-catalog-draft-row", {
      sourceRowId: sourceRowId,
      targetRowId: targetRowId,
      position: position
    });
  }

  function onOperatorCatalogDragEnd() {
    draggedOperatorCatalogRowId = null;
    operatorCatalogDragBlockedRowId = null;
    clearOperatorCatalogDragVisuals();
  }

  function reconcileOperatorCatalogStockResults(rows) {
    const existing = new Map();
    Array.from(operatorCatalogStockResults.children).forEach(function (button) {
      existing.set(button.dataset.operatorCatalogStockResultId, button);
    });

    (rows || []).forEach(function (row, position) {
      let button = existing.get(row.resultId);
      if (!button) {
        button = document.createElement("button");
        button.type = "button";
        button.dataset.operatorCatalogStockResultId = row.resultId;
        button.title = "더블클릭하면 구성 종목에 추가합니다.";
        button.setAttribute("role", "option");
        const name = document.createElement("span");
        name.className = "operator-catalog-stock-result-name";
        const market = document.createElement("small");
        market.className = "operator-catalog-stock-result-market";
        button.append(name, market);
      }

      const buttonAtPosition = operatorCatalogStockResults.children.item(position);
      if (buttonAtPosition !== button) {
        operatorCatalogStockResults.insertBefore(button, buttonAtPosition);
      }
      existing.delete(row.resultId);

      button.className = row.isSelected ? "selected" : "";
      button.disabled = row.isCompatible !== true;
      button.setAttribute("aria-selected", row.isSelected ? "true" : "false");
      button.querySelector(".operator-catalog-stock-result-name").textContent = row.displayName;
      button.querySelector(".operator-catalog-stock-result-market").textContent = row.market;
    });

    existing.forEach(function (button) { button.remove(); });
    const stockRows = Array.from(operatorCatalogStockResults.querySelectorAll(
      "button[data-operator-catalog-stock-result-id]"));
    configureRovingList(
      operatorCatalogStockResults,
      stockRows,
      stockRows.find(function (button) {
        return button.getAttribute("aria-selected") === "true";
      }) || null);
  }

  function renderOperatorCatalog(state) {
    const catalog = state.operatorCatalog;
    const busy = state.isBusy === true;
    operatorCatalogMutationLocked = isOperatorMutationLocked(state);
    operatorCatalogState = catalog || null;
    if (!catalog || catalog.isOpen !== true) {
      setTransientControlsBusy(operatorCatalogDialog, false);
      draggedOperatorCatalogRowId = null;
      operatorCatalogDragBlockedRowId = null;
      clearOperatorCatalogDragVisuals();
      operatorCatalogModal.hidden = true;
      operatorCatalogDraftKey = "";
      operatorCatalogNameDraft = "";
      forgetSearchDraft(
        "operator-catalog-query",
        "operator-catalog-stock-query");
      operatorCatalogSearchFocus = null;
      operatorCatalogNameStatus.textContent = "";
      operatorCatalogNameStatus.className = "";
      operatorCatalogRenderStateKey = "";
      return;
    }

    const capturedSearchFocus = captureSearchDraftFocus(operatorCatalogDialog);
    if (capturedSearchFocus) operatorCatalogSearchFocus = capturedSearchFocus;
    if (operatorCatalogMutationLocked) {
      draggedOperatorCatalogRowId = null;
      operatorCatalogDragBlockedRowId = null;
      clearOperatorCatalogDragVisuals();
    }

    const renderStateKey = JSON.stringify(catalog);
    if (!operatorCatalogModal.hidden && renderStateKey === operatorCatalogRenderStateKey) {
      setTransientControlsBusy(operatorCatalogDialog, busy);
      operatorCatalogDraftRows.querySelectorAll("[data-operator-catalog-row-id]")
        .forEach(function (row) {
          row.draggable = !operatorCatalogMutationLocked;
          row.setAttribute(
            "aria-disabled",
            operatorCatalogMutationLocked ? "true" : "false");
        });
      if (!busy &&
          restoreSearchDraftFocus(operatorCatalogDialog, operatorCatalogSearchFocus)) {
        operatorCatalogSearchFocus = null;
      }
      return;
    }

    setTransientControlsBusy(operatorCatalogDialog, false);
    if (operatorCatalogModal.hidden) rememberModalOpener(operatorCatalogDialog);
    operatorCatalogModal.hidden = false;
    const isTheme = catalog.entity === "theme";
    const editing = catalog.mode === "create" || catalog.mode === "edit";
    operatorCatalogTitle.textContent = isTheme
      ? "테마추가"
      : catalog.mode === "create" ? "전문가 추가" : "전문가 추천 편집";
    operatorCatalogThemeTab.classList.toggle("selected", isTheme);
    operatorCatalogExpertTab.classList.toggle("selected", !isTheme);
    operatorCatalogThemeTab.disabled = editing;
    operatorCatalogExpertTab.disabled = editing;
    operatorCatalogToolbar.hidden = editing;
    operatorCatalogListPanel.hidden = editing;
    operatorCatalogBody.classList.toggle("editor-only", editing);
    operatorCatalogClose.disabled = false;
    operatorCatalogSearch.disabled = false;
    operatorCatalogSession.disabled = !isTheme;
    operatorCatalogSession.hidden = !isTheme;
    operatorCatalogNewMarket.disabled = !isTheme;
    operatorCatalogNewMarket.hidden = !isTheme;
    operatorCatalogNew.disabled = catalog.canBeginCreate !== true;
    operatorCatalogNew.textContent = isTheme ? "새 ThemeA" : "새 EList";
    syncSearchDraft(
      operatorCatalogQuery,
      catalog.query,
      "operator-catalog:" + catalog.entity);
    operatorCatalogSession.value = catalog.nxtSession === "afterMarket"
      ? "afterMarket" : "preMarket";
    operatorCatalogNewMarket.value = catalog.newThemeMarket === "nxt" ? "nxt" : "krx";

    const resultFragment = document.createDocumentFragment();
    (catalog.results || []).forEach(function (row) {
      const button = document.createElement("button");
      button.type = "button";
      button.dataset.operatorCatalogResultId = row.resultId;
      button.className = row.isSelected ? "selected" : "";
      button.disabled = false;
      button.setAttribute("role", "option");
      button.setAttribute("aria-selected", row.isSelected ? "true" : "false");
      const code = document.createElement("strong");
      code.textContent = row.codeLabel;
      const name = document.createElement("span");
      name.textContent = row.displayName;
      const scope = document.createElement("small");
      scope.textContent = row.scope;
      button.append(code, name, scope);
      resultFragment.appendChild(button);
    });
    operatorCatalogResults.replaceChildren(resultFragment);
    const catalogRows = Array.from(operatorCatalogResults.querySelectorAll(
      "button[data-operator-catalog-result-id]"));
    configureRovingList(
      operatorCatalogResults,
      catalogRows,
      catalogRows.find(function (button) {
        return button.getAttribute("aria-selected") === "true";
      }) || null);

    operatorCatalogEditor.hidden = !editing;
    const canChangeCreateThemeMarket = isTheme && catalog.mode === "create";
    operatorCatalogEditorMarketField.hidden = !canChangeCreateThemeMarket;
    operatorCatalogEditorMarket.disabled = !canChangeCreateThemeMarket;
    operatorCatalogEditorMarket.value = catalog.newThemeMarket === "nxt" ? "nxt" : "krx";
    const draftKey = catalog.entity + "|" + catalog.mode + "|" +
      (catalog.selectedResultId || "new") + "|" + (catalog.codeLabel || "");
    if (draftKey !== operatorCatalogDraftKey) {
      operatorCatalogDraftKey = draftKey;
      operatorCatalogNameDraft = catalog.editorName || "";
    }
    if (document.activeElement !== operatorCatalogName) {
      operatorCatalogName.value = operatorCatalogNameDraft;
    }
    operatorCatalogCode.textContent = catalog.codeLabel
      ? "코드 " + catalog.codeLabel : "";
    operatorCatalogNameLabel.textContent = isTheme ? "테마명" : "전문가명";
    operatorCatalogName.placeholder = isTheme ? "ThemeA 이름" : "전문가 이름";
    operatorCatalogName.disabled = !editing ||
      (!isTheme && catalog.mode === "edit");
    const currentThemeName = operatorCatalogNameDraft.trim();
    const checkedCurrentThemeName = isTheme && currentThemeName.length > 0 &&
      catalog.checkedThemeName === currentThemeName;
    const themeNameAvailability = checkedCurrentThemeName
      ? catalog.themeNameAvailability
      : "notChecked";
    operatorCatalogNameCheck.hidden = !isTheme;
    operatorCatalogNameCheck.disabled = !isTheme || !editing ||
      catalog.canCheckThemeName !== true || currentThemeName.length === 0;
    operatorCatalogNameStatus.hidden = !isTheme;
    operatorCatalogNameStatus.className = themeNameAvailability === "available" ||
      themeNameAvailability === "duplicate" || themeNameAvailability === "indeterminate"
      ? themeNameAvailability
      : "";
    operatorCatalogNameStatus.textContent = themeNameAvailability === "available"
      ? "사용 가능"
      : themeNameAvailability === "duplicate"
        ? "이미 사용 중"
        : themeNameAvailability === "indeterminate"
          ? "확인 불가"
          : "";
    syncSearchDraft(
      operatorCatalogStockQuery,
      catalog.stockQuery,
      "operator-catalog-stock:" + draftKey);
    operatorCatalogStockQuery.disabled = !editing;
    operatorCatalogStockSearch.disabled = !editing;
    const showCatalogRows = isTheme || catalog.mode === "edit";
    operatorCatalogStockSearchLine.hidden = !showCatalogRows;
    operatorCatalogStockResults.hidden = !showCatalogRows;
    operatorCatalogAddLine.hidden = !showCatalogRows;
    operatorCatalogDraftHeader.hidden = !showCatalogRows;
    operatorCatalogDraftRows.hidden = !showCatalogRows;
    // ThemeA and UC6 both add the selected stock immediately. UC6 then lets
    // the operator fill the buy amount in column 3 of the newly added row.
    operatorCatalogAmountLabel.hidden = true;
    operatorCatalogAmount.hidden = true;
    operatorCatalogAmount.disabled = true;
    operatorCatalogAddStock.disabled = catalog.canAddStock !== true;

    reconcileOperatorCatalogStockResults(catalog.stockResults);

    const focusedDraftRow = document.activeElement &&
      document.activeElement.closest("[data-operator-catalog-row-id]");
    const restoreDraftFocus = focusedDraftRow &&
      operatorCatalogDraftRows.contains(focusedDraftRow);
    const draftFragment = document.createDocumentFragment();
    (catalog.draftRows || []).forEach(function (row, index) {
      const line = document.createElement("div");
      const isActive = row.rowId === catalog.activeDraftRowId;
      line.className = "operator-catalog-draft-row" + (isActive ? " selected" : "");
      line.dataset.operatorCatalogRowId = row.rowId;
      line.tabIndex = isActive || (!catalog.activeDraftRowId && index === 0) ? 0 : -1;
      line.setAttribute("role", "row");
      line.setAttribute("aria-selected", isActive ? "true" : "false");
      line.draggable = !operatorCatalogMutationLocked;
      line.setAttribute(
        "aria-disabled",
        operatorCatalogMutationLocked ? "true" : "false");
      line.addEventListener("pointerdown", onOperatorCatalogPointerDown);
      line.addEventListener("pointerup", onOperatorCatalogPointerEnd);
      line.addEventListener("pointercancel", onOperatorCatalogPointerEnd);
      line.addEventListener("dragstart", onOperatorCatalogDragStart);
      line.addEventListener("dragover", onOperatorCatalogDragOver);
      line.addEventListener("dragleave", onOperatorCatalogDragLeave);
      line.addEventListener("drop", onOperatorCatalogDrop);
      line.addEventListener("dragend", onOperatorCatalogDragEnd);
      let selectionSent = false;
      function selectDraftRow() {
        if (!selectionSent && row.rowId !== catalog.activeDraftRowId) {
          selectionSent = true;
          send("select-operator-catalog-draft-row", { rowId: row.rowId });
        }
      }
      line.addEventListener("focus", selectDraftRow);
      line.addEventListener("click", function (event) {
        if (!event.target.closest("button, input")) {
          line.focus({ preventScroll: true });
          selectDraftRow();
        }
      });
      const order = document.createElement("span");
      order.textContent = String(row.position + 1);
      const name = document.createElement("strong");
      name.textContent = row.displayName;
      const detail = document.createElement(isTheme ? "small" : "input");
      if (isTheme) {
        detail.textContent = row.market;
      } else {
        detail.type = "number";
        detail.min = "1";
        detail.step = "1";
        detail.inputMode = "numeric";
        detail.value = row.buyAmount == null ? "" : String(row.buyAmount);
        detail.dataset.operatorCatalogBuyAmountRowId = row.rowId;
        detail.setAttribute("aria-label", row.displayName + " 매수가");
        detail.disabled = false;
      }
      const up = document.createElement("button");
      up.type = "button";
      up.textContent = "Up";
      up.dataset.operatorCatalogMoveRowId = row.rowId;
      up.dataset.operatorCatalogMoveDirection = "up";
      up.disabled = index === 0;
      const down = document.createElement("button");
      down.type = "button";
      down.textContent = "Down";
      down.dataset.operatorCatalogMoveRowId = row.rowId;
      down.dataset.operatorCatalogMoveDirection = "down";
      down.disabled = index + 1 === catalog.draftRows.length;
      const remove = document.createElement("button");
      remove.type = "button";
      remove.textContent = "Delete";
      remove.dataset.operatorCatalogRemoveRowId = row.rowId;
      remove.disabled = false;
      line.append(order, name, detail, up, down, remove);
      draftFragment.appendChild(line);
    });
    operatorCatalogDraftRows.replaceChildren(draftFragment);
    if (restoreDraftFocus && catalog.activeDraftRowId) {
      const activeRow = Array.from(
        operatorCatalogDraftRows.querySelectorAll("[data-operator-catalog-row-id]"))
        .find(function (row) {
          return row.dataset.operatorCatalogRowId === catalog.activeDraftRowId;
        });
      if (activeRow) activeRow.focus({ preventScroll: true });
    }

    operatorCatalogSave.disabled = catalog.canSave !== true;
    operatorCatalogDeleteAll.hidden = !isTheme || !editing;
    operatorCatalogDeleteAll.disabled = !isTheme || !editing ||
      (catalog.draftRows || []).length === 0;
    operatorCatalogCancel.disabled = !editing;
    const messages = [];
    if (catalog.statusMessage) messages.push(catalog.statusMessage);
    if (catalog.resultsAreTruncated) messages.push("목록 일부만 표시 중입니다.");
    if (catalog.stockResultsAreTruncated) messages.push("종목 검색 결과 일부만 표시 중입니다.");
    if (catalog.writesQuarantined) {
      messages.push("결과 불명확 상태로 현재 프로세스의 추가 DB 쓰기가 차단되었습니다.");
    }
    operatorCatalogStatus.textContent = messages.join(" ");
    operatorCatalogStatus.classList.toggle(
      "error",
      catalog.writesQuarantined === true || catalog.statusKind === "error");
    setTransientControlsBusy(operatorCatalogDialog, busy);
    operatorCatalogRenderStateKey = renderStateKey;
    if (!busy &&
        restoreSearchDraftFocus(operatorCatalogDialog, operatorCatalogSearchFocus)) {
      operatorCatalogSearchFocus = null;
    }
  }

  function renderDialog(state) {
    if (state.dialog && state.dialog.message) {
      legacyDialogIsConfirmation = state.dialog.isConfirmation === true;
      dialogCaption.textContent = state.dialog.caption || "MmoneyCoder";
      dialogMessage.textContent = state.dialog.message;
      dialogOk.textContent = legacyDialogIsConfirmation ? "예" : "확인";
      // A one-button result alert is safe to acknowledge while an unrelated
      // refresh is finishing. Disabling it between pointer-down and pointer-up
      // swallowed real operator clicks; native dispatch now queues one dismissal.
      dialogOk.disabled = legacyDialogIsConfirmation && state.isBusy === true;
      dialogNo.hidden = !legacyDialogIsConfirmation;
      dialogNo.disabled = state.isBusy === true;
      if (dialog.hidden) rememberModalOpener(legacyAlertDialog);
      dialog.hidden = false;
    } else {
      legacyDialogIsConfirmation = false;
      dialogOk.disabled = false;
      dialogNo.hidden = true;
      dialogNo.disabled = false;
      dialog.hidden = true;
    }
  }

  function updateCutDragSize(state) {
    const metrics = state && state.interactionMetrics;
    if (!metrics || metrics.coordinateSpace !== "host-dip" ||
        !Number.isInteger(metrics.cutDragWidthDips) ||
        !Number.isInteger(metrics.cutDragHeightDips) ||
        metrics.cutDragWidthDips <= 0 || metrics.cutDragHeightDips <= 0 ||
        typeof metrics.hostRasterizationScale !== "number") return;
    const nextDragSize = window.LegacyCutDrag.toCssDragSize({
      width: metrics.cutDragWidthDips,
      height: metrics.cutDragHeightDips
    }, metrics.hostRasterizationScale, window.devicePixelRatio);
    cutDragSize = nextDragSize;
    if (cutDragGesture && !cutDragGesture.started) {
      // The cut-pointer-down reply is also a native state boundary. If a
      // Windows preference change raced the previous rendered state, update
      // the still-pending gesture before its next pointermove decision.
      cutDragGesture.rectangle = window.LegacyCutDrag.createDragRectangle(
        cutDragGesture.down,
        nextDragSize);
    }
    if (playlistDragGesture && !playlistDragGesture.started) {
      playlistDragGesture.rectangle = window.LegacyCutDrag.createDragRectangle(
        playlistDragGesture.down,
        nextDragSize);
    }
  }

  function operatorSettingsText(value, fallback) {
    if (typeof value === "string" && value.trim()) return value.trim();
    if (value && typeof value === "object") {
      const candidate = value.displayName || value.label || value.status || value.message;
      if (typeof candidate === "string" && candidate.trim()) return candidate.trim();
    }
    return fallback;
  }

  function operatorDiagnosticTone(value, label) {
    if (value && typeof value === "object") {
      if (value.isConnected === true || value.isHealthy === true) return "good";
      if (value.isConnected === false || value.isHealthy === false) return "bad";
    }
    const normalized = String(label || "").toLowerCase();
    if (/연결됨|정상|사용 가능|ready|connected|healthy/.test(normalized)) return "good";
    if (/확인 중|재연결|대기|dry run|감지됨|unknown|waiting/.test(normalized)) {
      return "warning";
    }
    if (/끊김|미연결|오류|실패|불명확|disconnected|fault|error|failed/.test(normalized)) {
      return "bad";
    }
    return "neutral";
  }

  function setOperatorDiagnostic(element, value, fallback) {
    const label = operatorSettingsText(value, fallback);
    if (element.textContent !== label) element.textContent = label;
    const tone = operatorDiagnosticTone(value, label);
    if (tone === "neutral") element.removeAttribute("data-state");
    else element.dataset.state = tone;
  }

  function operatorPlayoutModeLabel(playout) {
    if (!playout) return "확인 중";
    if (playout.mode === "live") return "LIVE";
    if (playout.mode === "dryRun") return "DRY RUN";
    if (playout.mode === "disabled") return "송출 비활성";
    return operatorSettingsText(playout.mode, "확인 중");
  }

  function operatorPlayoutConnectionLabel(playout) {
    if (!playout) return "확인 중";
    if (playout.outcomeUnknown === true) return "결과 불명확";
    if (playout.mode === "disabled") return "송출 비활성";
    if (playout.mode === "dryRun") return "DRY RUN 준비됨";
    if (playout.isConnected === true) return "Tornado2 연결됨";
    if (playout.isProcessRunning === true) return "Tornado2 감지됨";
    return "미연결";
  }

  function renderOperatorFolder(kind, folder, isBusy) {
    const controls = operatorFolderControls[kind];
    const current = folder && typeof folder === "object" ? folder : {};
    const defaultNames = {
      design: "앱 기본값",
      resource: "앱 기본값",
      background: "자동"
    };
    const displayName = current.isCustom === true
      ? operatorSettingsText(current.displayName, defaultNames[kind])
      : defaultNames[kind];
    if (controls.name.textContent !== displayName) controls.name.textContent = displayName;
    controls.name.title = displayName;

    const valid = current.isValid === true;
    const invalid = current.isValid === false;
    const statusLabel = operatorSettingsText(
      current.status,
      valid ? "사용 가능" : (invalid ? "확인 필요" : "확인 중"));
    if (controls.status.textContent !== statusLabel) controls.status.textContent = statusLabel;
    controls.status.classList.toggle("valid", valid);
    controls.status.classList.toggle("invalid", invalid);
    controls.status.classList.toggle("neutral", !valid && !invalid);
    controls.source.textContent = current.isCustom === true ? "사용자 지정" : "앱 기본값";
    controls.select.disabled = isBusy;
    controls.reset.disabled = isBusy || current.isCustom !== true;
  }

  function normalizeOperatorAppearanceValue(key, value) {
    const definition = operatorAppearanceControls[key];
    return typeof value === "string" && definition.values.includes(value)
      ? value
      : definition.fallback;
  }

  function operatorAppearanceFromSettings(settings) {
    return {
      colorTheme: normalizeOperatorAppearanceValue(
        "colorTheme",
        settings && settings.colorTheme),
      viewMode: normalizeOperatorAppearanceValue("viewMode", settings && settings.viewMode),
      startWorkspace: normalizeOperatorAppearanceValue(
        "startWorkspace",
        settings && settings.startWorkspace)
    };
  }

  function operatorAppearanceFromControls() {
    return {
      colorTheme: normalizeOperatorAppearanceValue(
        "colorTheme",
        operatorAppearanceControls.colorTheme.element.value),
      viewMode: normalizeOperatorAppearanceValue(
        "viewMode",
        operatorAppearanceControls.viewMode.element.value),
      startWorkspace: normalizeOperatorAppearanceValue(
        "startWorkspace",
        operatorAppearanceControls.startWorkspace.element.value)
    };
  }

  function syncOperatorAppearanceControls(appearance) {
    Object.keys(operatorAppearanceControls).forEach(function (key) {
      const control = operatorAppearanceControls[key].element;
      const nextValue = appearance[key];
      if (control.value !== nextValue) control.value = nextValue;
    });
  }

  function applyOperatorAppearance(appearance) {
    [document.documentElement, document.body].forEach(function (element) {
      element.dataset.colorTheme = appearance.colorTheme;
      element.dataset.viewMode = appearance.viewMode;
      element.dataset.startWorkspace = appearance.startWorkspace;
    });
  }

  function renderOperatorSettings(state) {
    const settings = state && state.operatorSettings;
    const isBusy = !settings || state.isBusy === true;
    const wasBusy = operatorSettingsWasBusy;
    let revisionChanged = false;
    renderOperatorFolder("design", settings && settings.design, isBusy);
    renderOperatorFolder("resource", settings && settings.resource, isBusy);
    renderOperatorFolder("background", settings && settings.background, isBusy);

    operatorNavigationExpanded.disabled = isBusy;
    Object.keys(operatorAppearanceControls).forEach(function (key) {
      operatorAppearanceControls[key].element.disabled = isBusy;
    });
    if (settings) {
      const revision = settings.revision === undefined ? null : settings.revision;
      const appearance = operatorAppearanceFromSettings(settings);
      revisionChanged = !operatorSettingsRevisionInitialized ||
        revision !== operatorSettingsRenderRevision;
      if (revisionChanged) {
        operatorAppearanceSnapshot = Object.freeze(appearance);
        syncOperatorAppearanceControls(appearance);
        applyOperatorAppearance(appearance);
        const expanded = settings.navigationExpanded !== false;
        operatorNavigationExpanded.checked = expanded;
        if (window.LegacyWorkspaceNavigation &&
            window.LegacyWorkspaceNavigation.expanded() !== expanded) {
          window.LegacyWorkspaceNavigation.setExpanded(expanded);
        }
        operatorSettingsRenderRevision = revision;
        operatorSettingsRevisionInitialized = true;
      }
      if (window.LegacyWorkspaceNavigation &&
          typeof window.LegacyWorkspaceNavigation.applyStartPreference === "function") {
        window.LegacyWorkspaceNavigation.applyStartPreference(appearance.startWorkspace);
      }
    }

    const message = operatorSettingsText(settings && settings.message, "");
    operatorSettingsMessage.hidden = !message;
    if (operatorSettingsMessage.textContent !== message) {
      operatorSettingsMessage.textContent = message;
    }
    const messageKind = settings && typeof settings.messageKind === "string"
      ? settings.messageKind.toLowerCase()
      : "";
    operatorSettingsMessage.className = "settings-message" +
      (["information", "success", "warning", "error"].includes(messageKind)
        ? " " + messageKind
        : "");
    operatorSettingsRestart.hidden = !(settings && settings.restartRequired === true);

    setOperatorDiagnostic(
      operatorSettingsAppVersion,
      settings && settings.appVersion,
      "확인 중");
    const playout = state && state.playout;
    setOperatorDiagnostic(operatorSettingsPlayoutMode, operatorPlayoutModeLabel(playout), "확인 중");
    setOperatorDiagnostic(
      operatorSettingsPlayoutConnection,
      operatorPlayoutConnectionLabel(playout),
      "확인 중");
    setOperatorDiagnostic(
      operatorSettingsOracleState,
      settings && settings.oracleState,
      "확인 중");
    setOperatorDiagnostic(
      operatorSettingsMariaState,
      settings && settings.mariaState,
      "확인 중");
    const pollingSeconds = Number(settings && settings.databasePollingSeconds);
    operatorSettingsDbMonitor.textContent = Number.isFinite(pollingSeconds) && pollingSeconds > 0
      ? String(pollingSeconds) + "초마다"
      : "10초마다";

    operatorSettingsWasBusy = isBusy;
    if (pendingOperatorSettingsFocusId && !isBusy && (wasBusy || revisionChanged)) {
      const focusId = pendingOperatorSettingsFocusId;
      const fallbackFocusId = pendingOperatorSettingsFallbackFocusId;
      pendingOperatorSettingsFocusId = null;
      pendingOperatorSettingsFallbackFocusId = null;
      window.requestAnimationFrame(function () {
        const primary = document.getElementById(focusId);
        const fallback = fallbackFocusId
          ? document.getElementById(fallbackFocusId)
          : null;
        const target = primary && !primary.disabled ? primary : fallback;
        const active = document.activeElement;
        if (target && !target.disabled && !target.closest("[inert]") &&
            (active === document.body || active === null)) {
          target.focus({ preventScroll: true });
        }
      });
    }
  }

  function render(state) {
    const isBusy = state.isBusy === true;
    const treeTabId = beginTreeRender(state);
    uiBusy = isBusy;
    operatorMutationLocked = isOperatorMutationLocked(state);
    document.body.classList.toggle("busy", isBusy);
    document.body.setAttribute("aria-busy", isBusy ? "true" : "false");
    searchInput.readOnly = isBusy;
    searchButton.setAttribute("aria-disabled", isBusy ? "true" : "false");
    syncSearchDraft(searchInput, state.searchText);
    updateDoubleClickTime(state);
    updateCutDragSize(state);
    renderStockResults(state);
    renderStockSearchMeta(state);
    renderCuts(state);
    renderTabs(state);
    movingAverage5.checked = state.movingAverages ? state.movingAverages.ma5 : true;
    movingAverage20.checked = state.movingAverages ? state.movingAverages.ma20 : true;
    movingAverage5.disabled = isBusy;
    movingAverage20.disabled = isBusy;
    catalogExpandAll.disabled = treeInteractionLocked;
    renderFixedCatalog(state);
    renderIndustry(state);
    renderOverseas(state);
    renderComparison(state);
    renderTheme(state);
    renderExpert(state);
    renderTradingHalt(state);
    finishTreeRender(treeTabId);
    renderManualFinancial(state);
    renderManualLists(state);
    renderOperatorCatalog(state);
    renderOperatorSettings(state);
    renderPlaylist(state);
    renderNamedPlaylist(state);
    renderDialog(state);
    syncModalFocus();
    status.textContent = isBusy ? "처리 중" : (state.statusMessage || "");
  }

  Object.keys(operatorFolderControls).forEach(function (kind) {
    const controls = operatorFolderControls[kind];
    controls.select.addEventListener("click", function () {
      pendingOperatorSettingsFocusId = controls.select.id;
      pendingOperatorSettingsFallbackFocusId = null;
      if (!send("choose-operator-folder", { kind: kind })) {
        pendingOperatorSettingsFocusId = null;
        pendingOperatorSettingsFallbackFocusId = null;
      }
    });
    controls.reset.addEventListener("click", function () {
      pendingOperatorSettingsFocusId = controls.reset.id;
      pendingOperatorSettingsFallbackFocusId = controls.select.id;
      if (!send("reset-operator-folder", { kind: kind })) {
        pendingOperatorSettingsFocusId = null;
        pendingOperatorSettingsFallbackFocusId = null;
      }
    });
  });

  operatorNavigationExpanded.addEventListener("change", function () {
    const expanded = operatorNavigationExpanded.checked === true;
    pendingOperatorSettingsFocusId = operatorNavigationExpanded.id;
    pendingOperatorSettingsFallbackFocusId = null;
    if (!send("set-operator-navigation-expanded", { expanded: expanded })) {
      pendingOperatorSettingsFocusId = null;
      pendingOperatorSettingsFallbackFocusId = null;
      operatorNavigationExpanded.checked = !expanded;
      return;
    }
    if (window.LegacyWorkspaceNavigation &&
        window.LegacyWorkspaceNavigation.expanded() !== expanded) {
      window.LegacyWorkspaceNavigation.setExpanded(expanded);
    }
  });

  Object.keys(operatorAppearanceControls).forEach(function (key) {
    const control = operatorAppearanceControls[key].element;
    control.addEventListener("change", function () {
      const appearance = operatorAppearanceFromControls();
      pendingOperatorSettingsFocusId = control.id;
      pendingOperatorSettingsFallbackFocusId = null;
      applyOperatorAppearance(appearance);
      if (!send("set-operator-appearance", {
        colorTheme: appearance.colorTheme,
        viewMode: appearance.viewMode,
        startWorkspace: appearance.startWorkspace
      })) {
        pendingOperatorSettingsFocusId = null;
        pendingOperatorSettingsFallbackFocusId = null;
        syncOperatorAppearanceControls(operatorAppearanceSnapshot);
        applyOperatorAppearance(operatorAppearanceSnapshot);
      }
    });
  });

  document.addEventListener("input", function (event) {
    rememberSearchDraft(event.target);
  });

  searchButton.addEventListener("click", function () {
    send("search-stocks", { text: searchInput.value, trigger: "button" });
  });

  searchInput.addEventListener("keypress", function (event) {
    if (event.key === "Enter" && !isImeComposing(event)) {
      event.preventDefault();
      send("search-stocks", { text: searchInput.value, trigger: "enter" });
    }
  });

  dbSaveButton.addEventListener("click", function () {
    openNamedPlaylist("save");
  });
  dbLoadButton.addEventListener("click", function () {
    openNamedPlaylist("load");
  });
  namedPlaylistClose.addEventListener("click", closeNamedPlaylist);
  namedPlaylistCancelButton.addEventListener("click", closeNamedPlaylist);
  namedPlaylistDefinitions.addEventListener("dblclick", function (event) {
    const definition = event.target.closest("button[data-named-playlist-definition-id]");
    if (!definition || definition.disabled) return;
    event.preventDefault();
    const definitionId = definition.dataset.namedPlaylistDefinitionId;
    if (namedPlaylistSelection.isPending()) {
      namedPlaylistSelection.defer(definitionId, {
        definitionId: definitionId,
        mode: namedPlaylistMode
      });
      return;
    }
    runNamedPlaylistDoubleClick(definitionId, namedPlaylistMode);
  });
  namedPlaylistDefinitions.addEventListener("click", function (event) {
    const definition = event.target.closest("button[data-named-playlist-definition-id]");
    if (definition && !definition.disabled) {
      beginNamedPlaylistSelection(definition.dataset.namedPlaylistDefinitionId);
    }
  });
  namedPlaylistNewTitle.addEventListener("input", function () {
    namedPlaylistCreateButton.disabled = isNamedPlaylistActionLocked() ||
      !namedPlaylistState ||
      namedPlaylistState.canCreate !== true ||
      !isCanonicalNamedPlaylistTitle(namedPlaylistNewTitle.value);
  });
  namedPlaylistNewTitle.addEventListener("keydown", function (event) {
    if (event.key === "Enter" && !isImeComposing(event) &&
        !namedPlaylistCreateButton.disabled) {
      event.preventDefault();
      namedPlaylistCreateButton.click();
    }
  });
  namedPlaylistCreateButton.addEventListener("click", function () {
    const title = namedPlaylistNewTitle.value;
    if (isCanonicalNamedPlaylistTitle(title)) {
      beginNamedPlaylistBusyCycle("create-named-playlist", { title: title });
    }
  });
  namedPlaylistDeleteButton.addEventListener("click", function () {
    openNamedPlaylistConfirmation({
      kind: "delete",
      message: "프로그램이 삭제됩니다. 계속 하시겠습니까?",
      closeOnNo: false
    });
  });
  namedPlaylistConfirmButton.addEventListener("click", function () {
    const definitionId = namedPlaylistState && namedPlaylistState.selectedDefinitionId;
    if (!definitionId) return;
    if (namedPlaylistMode === "save") {
      openNamedPlaylistConfirmation({
        kind: "save",
        definitionId: definitionId,
        message: "저장하겠습니까?",
        showSavedAlert: true,
        closeOnNo: true
      });
    } else if (namedPlaylistMode === "load") {
      beginNamedPlaylistCommand(
        "load-named-playlist-by-id",
        definitionId,
        false);
    }
  });
  namedPlaylistConfirmationYes.addEventListener("click", function () {
    if (!namedPlaylistConfirmationYes.disabled) {
      resolveNamedPlaylistConfirmation(true);
    }
  });
  namedPlaylistConfirmationNo.addEventListener("click", function () {
    resolveNamedPlaylistConfirmation(false);
  });

  manualButtons.forEach(function (button) {
    button.addEventListener("click", function () {
      clearManualResultPendingSelection(true);
      manualDraft = null;
      manualDraftKey = "";
      manualDraftResetPending = false;
      send("open-manual-financial", { screen: button.dataset.manualScreen });
    });
  });
  function closeManualFinancial() {
    clearManualResultPendingSelection(true);
    send("close-manual-financial", {});
  }
  manualClose.addEventListener("click", closeManualFinancial);
  manualExit.addEventListener("click", closeManualFinancial);
  manualListButtons.forEach(function (button) {
    button.addEventListener("click", function () {
      clearManualViResultPendingSelection(true);
      send("open-manual-list", { screen: button.dataset.manualListScreen });
    });
  });
  function closeManualList() {
    clearManualViResultPendingSelection(true);
    send("close-manual-list", {});
  }
  manualListClose.addEventListener("click", closeManualList);
  manualListCancel.addEventListener("click", closeManualList);
  manualWorkspace.addEventListener("input", function (event) {
    if (!manualDraft || manualDraft.rawMode !== true ||
        !event.target || !event.target.dataset ||
        event.target.dataset.manualRawStorageIndex === undefined) {
      return;
    }
    const index = Number(event.target.dataset.manualRawStorageIndex);
    if (!Number.isSafeInteger(index) || index < 0) return;
    if (!Array.isArray(manualDraft.rawDirtyStorageIndices)) {
      manualDraft.rawDirtyStorageIndices = [];
    }
    if (!manualDraft.rawDirtyStorageIndices.includes(index)) {
      manualDraft.rawDirtyStorageIndices.push(index);
    }
  });

  manualWorkspace.addEventListener("dblclick", function (event) {
    const stock = event.target.closest("button[data-manual-stock-result-id]");
    if (stock && !stock.disabled) {
      event.preventDefault();
      selectManualFinancialStock(stock);
      return;
    }
    const row = event.target.closest("button[data-manual-result-id]");
    if (!row || row.disabled) return;
    event.preventDefault();
    clearDelayedClick(manualResultClickTimer);
    manualResultClickTimer = null;
    const pending = manualResultPendingSelection &&
      manualResultPendingSelection.key === row.dataset.manualResultId
        ? manualResultPendingSelection
        : beginManualResultPendingSelection(row);
    if (send("activate-manual-financial-result", {
      resultId: row.dataset.manualResultId
    })) {
      if (pending) pending.sent = true;
      manualDraft = null;
      manualDraftKey = "";
      manualDraftResetPending = false;
    } else {
      clearManualResultPendingSelection(true);
    }
  });
  manualWorkspace.addEventListener("click", function (event) {
    const row = event.target.closest("button[data-manual-result-id]");
    if (row) {
      clearDelayedClick(manualResultClickTimer);
      const resultId = row.dataset.manualResultId;
      const pending = beginManualResultPendingSelection(row);
      manualResultClickTimer = deferLegacySingleClick(function () {
        manualResultClickTimer = null;
        if (!pending || manualResultPendingSelection !== pending) return;
        if (send("load-manual-financial", { resultId: resultId })) {
          pending.sent = true;
          manualDraft = null;
          manualDraftKey = "";
          manualDraftResetPending = false;
        } else {
          clearManualResultPendingSelection(true);
        }
      });
      return;
    }
    const find = event.target.closest("button[data-manual-find-direction]");
    if (find && !find.disabled) {
      const query = manualWorkspace.querySelector("#manual-financial-find-query");
      const generation = Number(find.dataset.manualGeneration);
      if (query && query.value.trim() && Number.isSafeInteger(generation)) {
        send("find-manual-financial", {
          query: query.value,
          direction: find.dataset.manualFindDirection,
          generation: generation
        });
      }
      return;
    }
    const sort = event.target.closest("button[data-manual-name-sort]");
    if (sort && !sort.disabled) {
      const generation = Number(sort.dataset.manualGeneration);
      if (Number.isSafeInteger(generation)) {
        send("toggle-manual-financial-name-sort", { generation: generation });
      }
      return;
    }
    if (event.target.closest("button[data-manual-list-search]")) {
      const query = manualWorkspace.querySelector("#manual-financial-list-query");
      send("search-manual-financial", { query: query ? query.value : "" });
      return;
    }
    if (event.target.closest("button[data-manual-create]")) {
      const screen = manualDraft ? manualDraft.screen : "";
      manualDraft = blankManualRecord(screen);
      manualDraftKey = screen + "|new";
      manualDraftResetPending = true;
      if (!send("begin-manual-financial-create", {})) {
        manualDraftResetPending = false;
        rememberManualDraft();
      }
      return;
    }
    if (event.target.closest("button[data-manual-stock-search]")) {
      rememberManualDraft();
      const stockName = manualWorkspace.querySelector("#manual-financial-stock-name");
      send("search-manual-financial-stocks", {
        stockName: stockName ? stockName.value.trim() : ""
      });
      return;
    }
    const stock = event.target.closest("button[data-manual-stock-result-id]");
    if (stock && !stock.disabled) {
      // GraphE only committed a candidate from its DoubleClick handler. A
      // single click must remain a harmless focus/highlight gesture so it
      // cannot clear an editor draft after the operator resumes typing.
      focusManualFinancialStock(stock);
      return;
    }
    if (event.target.closest("button[data-manual-save]")) {
      if (manualDraft && manualDraft.rawMode === true) {
        const rawRecord = readManualRawRecord();
        if (rawRecord) {
          send("save-manual-financial-raw", { record: rawRecord });
        }
        return;
      }
      const record = readManualRecord();
      if (record) send("save-manual-financial", { record: record });
      return;
    }
    if (event.target.closest("button[data-manual-delete]")) {
      send("delete-manual-financial", {});
      return;
    }
    if (event.target.closest("button[data-manual-delete-all]")) {
      send("delete-all-manual-financial", {});
      return;
    }
    const action = event.target.closest("button[data-manual-action-id]");
    if (action && !action.disabled) {
      send("activate-manual-financial-action", {
        actionId: action.dataset.manualActionId
      });
    }
  });
  manualWorkspace.addEventListener("keydown", function (event) {
    if (event.key !== "Enter" || isImeComposing(event)) return;
    const stock = event.target.closest("button[data-manual-stock-result-id]");
    if (stock && !stock.disabled) {
      event.preventDefault();
      selectManualFinancialStock(stock);
    } else if (event.target.id === "manual-financial-list-query") {
      event.preventDefault();
      send("search-manual-financial", { query: event.target.value });
    } else if (event.target.id === "manual-financial-find-query") {
      event.preventDefault();
      const down = manualWorkspace.querySelector(
        "button[data-manual-find-direction=\"down\"]");
      if (down) down.click();
    } else if (event.target.id === "manual-financial-stock-name") {
      event.preventDefault();
      rememberManualDraft();
      send("search-manual-financial-stocks", {
        stockName: event.target.value.trim()
      });
    }
  });

  operatorCatalogClose.addEventListener("click", function () {
    send("close-operator-catalog", {});
  });
  operatorCatalogThemeTab.addEventListener("click", function () {
    send("open-operator-catalog", { entity: "theme" });
  });
  operatorCatalogExpertTab.addEventListener("click", function () {
    send("open-operator-catalog", { entity: "expert" });
  });
  function searchOperatorCatalog() {
    send("search-operator-catalog", {
      query: operatorCatalogQuery.value,
      nxtSession: operatorCatalogSession.value === "afterMarket"
        ? "afterMarket" : "preMarket"
    });
  }
  operatorCatalogSearch.addEventListener("click", searchOperatorCatalog);
  operatorCatalogQuery.addEventListener("keydown", function (event) {
    if (event.key === "Enter" && !isImeComposing(event)) {
      event.preventDefault();
      searchOperatorCatalog();
    }
  });
  operatorCatalogNew.addEventListener("click", function () {
    if (!operatorCatalogState) return;
    operatorCatalogDraftKey = "";
    operatorCatalogNameDraft = "";
    if (operatorCatalogState.entity === "theme") {
      send("begin-create-theme-catalog", {
        market: operatorCatalogNewMarket.value === "nxt" ? "nxt" : "krx"
      });
    } else {
      send("begin-create-expert-catalog", {});
    }
  });
  operatorCatalogEditorMarket.addEventListener("change", function () {
    if (!operatorCatalogState || operatorCatalogState.entity !== "theme" ||
        operatorCatalogState.mode !== "create" || operatorCatalogEditorMarket.disabled) return;

    operatorCatalogNameDraft = operatorCatalogName.value;
    send("change-create-theme-market", {
      market: operatorCatalogEditorMarket.value === "nxt" ? "nxt" : "krx",
      editorName: operatorCatalogName.value.trim()
    });
  });
  operatorCatalogResults.addEventListener("click", function (event) {
    const row = event.target.closest("button[data-operator-catalog-result-id]");
    if (!row || row.disabled) return;
    operatorCatalogDraftKey = "";
    send("select-operator-catalog-result", {
      resultId: row.dataset.operatorCatalogResultId
    });
  });
  operatorCatalogName.addEventListener("input", function () {
    operatorCatalogNameDraft = operatorCatalogName.value;
    operatorCatalogNameStatus.textContent = "";
    operatorCatalogNameStatus.className = "";
    operatorCatalogNameCheck.disabled = uiBusy || !operatorCatalogState ||
      operatorCatalogState.entity !== "theme" ||
      (operatorCatalogState.mode !== "create" && operatorCatalogState.mode !== "edit") ||
      operatorCatalogState.canCheckThemeName !== true ||
      operatorCatalogName.value.trim().length === 0;
  });
  operatorCatalogNameCheck.addEventListener("click", function () {
    const name = operatorCatalogName.value.trim();
    // ThemeA's duplicate-check button silently returned for an empty name.
    if (!name) return;

    operatorCatalogName.setCustomValidity("");
    operatorCatalogNameDraft = name;
    send("check-operator-catalog-theme-name", { name: name });
  });
  function searchOperatorCatalogStocks() {
    const query = operatorCatalogStockQuery.value.trim();
    send("search-operator-catalog-stocks", { query: query });
  }
  operatorCatalogStockSearch.addEventListener("click", searchOperatorCatalogStocks);
  operatorCatalogStockQuery.addEventListener("keydown", function (event) {
    if (event.key === "Enter" && !isImeComposing(event)) {
      event.preventDefault();
      searchOperatorCatalogStocks();
    }
  });
  operatorCatalogStockResults.addEventListener("dblclick", function (event) {
    const row = event.target.closest("button[data-operator-catalog-stock-result-id]");
    if (!row || row.disabled) return;
    event.preventDefault();
    send("activate-operator-catalog-stock", {
      resultId: row.dataset.operatorCatalogStockResultId,
      buyAmount: null
    });
  });
  operatorCatalogStockResults.addEventListener("click", function (event) {
    const row = event.target.closest("button[data-operator-catalog-stock-result-id]");
    if (!row || row.disabled) return;
    // A real double-click raises click(detail=1), click(detail=2), then dblclick.
    // Select immediately on the first click and ignore the synthetic second
    // click. Keyed rendering above keeps this exact button attached while the
    // selection snapshot returns, so slow Windows double-clicks retain a target.
    if ((event.detail || 1) > 1) return;
    send("select-operator-catalog-stock", {
      resultId: row.dataset.operatorCatalogStockResultId
    });
  });
  operatorCatalogAddStock.addEventListener("click", function () {
    send("add-operator-catalog-stock", { buyAmount: null });
  });
  operatorCatalogDraftRows.addEventListener("click", function (event) {
    const move = event.target.closest("button[data-operator-catalog-move-row-id]");
    if (move && !move.disabled) {
      send("move-operator-catalog-draft-row", {
        rowId: move.dataset.operatorCatalogMoveRowId,
        direction: move.dataset.operatorCatalogMoveDirection
      });
      return;
    }
    const remove = event.target.closest("button[data-operator-catalog-remove-row-id]");
    if (remove && !remove.disabled) {
      send("remove-operator-catalog-draft-row", {
        rowId: remove.dataset.operatorCatalogRemoveRowId
      });
    }
  });
  operatorCatalogDraftRows.addEventListener("change", function (event) {
    const input = event.target.closest("input[data-operator-catalog-buy-amount-row-id]");
    if (!input || input.disabled) return;
    const text = input.value.trim();
    if (!text) {
      input.setCustomValidity("");
      send("set-operator-catalog-draft-buy-amount", {
        rowId: input.dataset.operatorCatalogBuyAmountRowId,
        buyAmount: null
      });
      return;
    }
    const amount = Number(text);
    if (!Number.isSafeInteger(amount) || amount <= 0) {
      input.setCustomValidity("매수가는 1 이상의 정수여야 합니다.");
      input.reportValidity();
      return;
    }
    input.setCustomValidity("");
    send("set-operator-catalog-draft-buy-amount", {
      rowId: input.dataset.operatorCatalogBuyAmountRowId,
      buyAmount: amount
    });
  });
  operatorCatalogSave.addEventListener("click", function () {
    const rawName = operatorCatalogName.value;
    const canonicalName = rawName.trim();
    if (!canonicalName) {
      // EList showed an exact one-button message; ThemeA's empty-name path did
      // not own that message and is kept as a no-op here.
      if (operatorCatalogState && operatorCatalogState.entity === "expert") {
        send("save-operator-catalog", { name: "" });
      }
      return;
    }
    const preserveStoredThemeName = operatorCatalogState &&
      operatorCatalogState.entity === "theme" &&
      operatorCatalogState.mode === "edit";
    const name = preserveStoredThemeName ? rawName : canonicalName;
    operatorCatalogName.setCustomValidity("");
    operatorCatalogNameDraft = name;
    send("save-operator-catalog", { name: name });
  });
  operatorCatalogDeleteAll.addEventListener("click", function () {
    if (!operatorCatalogState || operatorCatalogState.entity !== "theme" ||
        (operatorCatalogState.mode !== "create" && operatorCatalogState.mode !== "edit")) {
      return;
    }
    send("clear-operator-catalog-draft-rows", {});
  });
  operatorCatalogCancel.addEventListener("click", function () {
    operatorCatalogDraftKey = "";
    send("close-operator-catalog", {});
  });

  playlistMoveUp.addEventListener("click", function () {
    send("move-selected-playlist-rows", { direction: "up" });
  });
  playlistMoveDown.addEventListener("click", function () {
    send("move-selected-playlist-rows", { direction: "down" });
  });
  playlistDeleteSelected.addEventListener("click", function () {
    send("delete-selected-playlist-rows", {});
  });
  playlistDeleteAll.addEventListener("click", function () {
    send("delete-all-playlist-rows", {});
  });
  playlistEnableAll.addEventListener("change", function () {
    if (!playlistProgramEnableMutationLocked) {
      send("set-all-playlist-enabled", { enabled: playlistEnableAll.checked });
    }
  });
  marketTabs.addEventListener("click", function (event) {
    const button = event.target.closest("button[data-tab-id]");
    if (button) {
      clearComparisonTargetPendingSelection(true);
      clearThemeResultPendingSelection(true);
      send("select-tab", { tabId: button.dataset.tabId });
    }
  });
  marketTabs.addEventListener("dragstart", function (event) {
    const button = event.target.closest("button[data-tab-id]");
    if (!button || !event.dataTransfer) return;
    clearPendingTabHoverSwap();
    draggedTabId = button.dataset.tabId;
    tabDragLastTargetId = null;
    event.dataTransfer.effectAllowed = "move";
    event.dataTransfer.setData("text/x-mbn-tab", draggedTabId);
    button.classList.add("dragging");
  });
  marketTabs.addEventListener("dragover", onMarketTabDragOver);
  marketTabs.addEventListener("drop", function (event) {
    const target = event.target.closest("button[data-tab-id]");
    if (!target || !event.dataTransfer || !draggedTabId) return;
    event.preventDefault();
    if (event.dataTransfer.getData("text/x-mbn-tab") !== draggedTabId) return;
    draggedTabId = null;
    tabDragLastTargetId = null;
    marketTabs.querySelectorAll("button.dragging")
      .forEach(function (button) { button.classList.remove("dragging"); });
  });
  marketTabs.addEventListener("dragend", function () {
    draggedTabId = null;
    tabDragLastTargetId = null;
    marketTabs.querySelectorAll("button.dragging")
      .forEach(function (button) { button.classList.remove("dragging"); });
  });
  function sendMovingAverages() {
    send("set-moving-averages", {
      ma5: movingAverage5.checked,
      ma20: movingAverage20.checked
    });
  }
  movingAverage5.addEventListener("change", sendMovingAverages);
  movingAverage20.addEventListener("change", sendMovingAverages);
  catalogExpandAll.addEventListener("change", function () {
    if (treeInteractionLocked) {
      const sections = Array.from(categoryTree.querySelectorAll("details"));
      catalogExpandAll.checked = sections.length > 0 && sections.every(function (details) {
        return details.open === true;
      });
      return;
    }
    categoryTree.querySelectorAll("details").forEach(function (details) {
      details.open = catalogExpandAll.checked;
      syncTreeSectionExpanded(details);
    });
  });
  categoryTree.addEventListener("dblclick", function (event) {
    const treeNode = event.target.closest("[data-tree-node-key]");
    if (treeNode) {
      event.preventDefault();
      if (treeInteractionLocked || treeNode.disabled === true ||
          treeNode.getAttribute("aria-disabled") === "true") return;
      selectTreeNode(treeNode, true);
      // The disclosure is a separate expand/collapse command, not a node action.
      if (event.target.closest("[data-tree-disclosure]")) return;
    }
    const themeResult = event.target.closest("button[data-theme-result-id]");
    if (themeResult && !themeResult.disabled) {
      event.preventDefault();
      clearDelayedClick(themeResultClickTimer);
      themeResultClickTimer = null;
      const pending = themeResultPendingSelection &&
        themeResultPendingSelection.key === themeResult.dataset.themeResultId
          ? themeResultPendingSelection
          : beginThemeResultPendingSelection(themeResult);
      if (send("activate-theme-result", {
        resultId: themeResult.dataset.themeResultId
      })) {
        if (pending) pending.sent = true;
      } else {
        clearThemeResultPendingSelection(true);
      }
      return;
    }
    const comparisonChoice = event.target.closest("button[data-comparison-choice]");
    if (comparisonChoice && !comparisonChoice.disabled) {
      event.preventDefault();
      clearDelayedClick(comparisonTargetClickTimer);
      comparisonTargetClickTimer = null;
      const selectionId = comparisonChoice.dataset.comparisonSelectionId;
      const pending = comparisonTargetPendingSelection &&
        comparisonTargetPendingSelection.key === selectionId
          ? comparisonTargetPendingSelection
          : beginComparisonTargetPendingSelection(comparisonChoice, selectionId);
      if (send(comparisonChoice.dataset.comparisonChoice === "world"
        ? "choose-comparison-world" : "choose-comparison-domestic", {
        selectionId: selectionId
      })) {
        if (pending) pending.sent = true;
      } else {
        clearComparisonTargetPendingSelection(true);
      }
      return;
    }
    const comparisonFixed = event.target.closest("button[data-comparison-target-id]");
    if (comparisonFixed && !comparisonFixed.disabled) {
      event.preventDefault();
      clearDelayedClick(comparisonTargetClickTimer);
      comparisonTargetClickTimer = null;
      const targetId = comparisonFixed.dataset.comparisonTargetId;
      const pending = comparisonTargetPendingSelection &&
        comparisonTargetPendingSelection.key === targetId
          ? comparisonTargetPendingSelection
          : beginComparisonTargetPendingSelection(comparisonFixed, targetId);
      if (send("choose-comparison-fixed", { targetId: targetId })) {
        if (pending) pending.sent = true;
      } else {
        clearComparisonTargetPendingSelection(true);
      }
      return;
    }
    const comparisonSection = event.target.closest("summary[data-comparison-section-index]");
    if (comparisonSection) {
      event.preventDefault();
      send("activate-comparison-section", {
        sectionIndex: Number(comparisonSection.dataset.comparisonSectionIndex)
      });
      return;
    }
    const industrySection = event.target.closest("summary[data-industry-section-index]");
    if (industrySection) {
      event.preventDefault();
      send("activate-industry-section", {
        sectionIndex: Number(industrySection.dataset.industrySectionIndex)
      });
      return;
    }
    const comparisonAction = event.target.closest("button[data-comparison-action-id]");
    if (comparisonAction && !comparisonAction.disabled) {
      send("activate-comparison-action", {
        actionId: comparisonAction.dataset.comparisonActionId
      });
      return;
    }
    const industryRow = event.target.closest("button[data-industry-index]");
    if (industryRow) {
      activateIndustryRow(industryRow);
      return;
    }
    const industryAction = event.target.closest("button[data-industry-action-id]");
    if (industryAction) {
      send("activate-industry-action", {
        actionId: industryAction.dataset.industryActionId
      });
      return;
    }
    const action = event.target.closest("button[data-action-id]");
    if (action) {
      send("activate-fixed-action", { actionId: action.dataset.actionId });
      return;
    }
    const section = event.target.closest("summary[data-section-index]");
    if (section && section.getAttribute("aria-disabled") !== "true") {
      event.preventDefault();
      // UC1.treeView_DoubleClick walks every direct child when the selected
      // node is a root. Keep the whole batch behind one native intent so C#
      // can materialize every child first and commit in original order.
      send("activate-fixed-section", {
        sectionIndex: Number(section.dataset.sectionIndex)
      });
      return;
    }
  });
  categoryTree.addEventListener("click", function (event) {
    const treeRoot = event.target.closest("summary[data-tree-root]");
    if (treeRoot) {
      // A native TreeView selects on its label and expands only from the glyph.
      // Cancel <summary>'s whole-row default toggle to preserve that split.
      event.preventDefault();
      if (treeInteractionLocked || treeRoot.getAttribute("aria-disabled") === "true") return;
      selectTreeNode(treeRoot, true);
      if (event.target.closest("[data-tree-disclosure]") && (event.detail || 1) < 2) {
        const details = treeRoot.parentElement;
        details.open = !details.open;
        syncTreeSectionExpanded(details);
      }
      return;
    }
    const treeLeaf = event.target.closest(".tree-item[data-tree-node-key]");
    if (treeLeaf) {
      if (treeInteractionLocked || treeLeaf.disabled === true) {
        event.preventDefault();
        return;
      }
      selectTreeNode(treeLeaf, true);
    }
    const uc4Catalog = event.target.closest("button[data-uc4-theme-catalog]");
    if (uc4Catalog && !uc4Catalog.disabled) {
      const action = uc4Catalog.dataset.uc4ThemeCatalog;
      if (action === "create") {
        send("begin-uc4-theme-catalog", {});
      } else if (action === "edit") {
        send("edit-uc4-selected-theme", {});
      } else if (action === "delete") {
        send("delete-uc4-selected-theme", {});
      }
      return;
    }
    const uc6Catalog = event.target.closest("button[data-uc6-expert-catalog]");
    if (uc6Catalog && !uc6Catalog.disabled) {
      const action = uc6Catalog.dataset.uc6ExpertCatalog;
      if (action === "create") {
        send("begin-uc6-expert-catalog", {});
      } else if (action === "edit") {
        send("edit-uc6-selected-expert", {});
      } else if (action === "delete") {
        send("delete-uc6-selected-expert", {});
      }
      return;
    }
    const catalogOpen = event.target.closest("button[data-operator-catalog-open]");
    if (catalogOpen && !catalogOpen.disabled) {
      send("open-operator-catalog", {
        entity: catalogOpen.dataset.operatorCatalogOpen
      });
      return;
    }
    // UC4/UC6/UC7 wire their scene action controls to WinForms Click.
    // Keep these actions out of the dblclick delegate above so one operator
    // click produces exactly one closed native intent.
    const themeAction = event.target.closest("button[data-theme-action-id]");
    if (themeAction && !themeAction.disabled) {
      send("activate-theme-action", { actionId: themeAction.dataset.themeActionId });
      return;
    }
    const expertAction = event.target.closest("button[data-expert-action-id]");
    if (expertAction && !expertAction.disabled) {
      send("activate-expert-action", { actionId: expertAction.dataset.expertActionId });
      return;
    }
    const haltAction = event.target.closest("button[data-halt-action-id]");
    if (haltAction && !haltAction.disabled) {
      send("activate-trading-halt-action", { actionId: haltAction.dataset.haltActionId });
      return;
    }
    const overseasAction = event.target.closest("button[data-overseas-action-id]");
    if (overseasAction && !overseasAction.disabled) {
      send("activate-overseas-action", {
        actionId: overseasAction.dataset.overseasActionId
      });
      return;
    }
    const overseasTarget = event.target.closest("button[data-overseas-target-id]");
    if (overseasTarget && !overseasTarget.disabled) {
      selectOverseasRow(overseasTarget);
      return;
    }
    const overseasResult = event.target.closest("button[data-overseas-selection-id]");
    if (overseasResult && !overseasResult.disabled) {
      selectOverseasRow(overseasResult);
      return;
    }
    const overseasSearch = event.target.closest("button[data-overseas-search]");
    if (overseasSearch) {
      const kind = overseasSearch.dataset.overseasSearch;
      const input = categoryTree.querySelector("#overseas-" + kind + "-search");
      send(kind === "stock" ? "search-overseas-stocks" : "search-overseas-industries", {
        query: input ? input.value : ""
      });
      return;
    }
    const comparisonChoice = event.target.closest("button[data-comparison-choice]");
    if (comparisonChoice) {
      clearDelayedClick(comparisonTargetClickTimer);
      const kind = comparisonChoice.dataset.comparisonChoice;
      const selectionId = comparisonChoice.dataset.comparisonSelectionId;
      const pending = beginComparisonTargetPendingSelection(
        comparisonChoice,
        selectionId);
      comparisonTargetClickTimer = deferLegacySingleClick(function () {
        comparisonTargetClickTimer = null;
        if (!pending || comparisonTargetPendingSelection !== pending) return;
        if (send(kind === "world"
          ? "select-comparison-world" : "select-comparison-domestic", {
          selectionId: selectionId
        })) {
          pending.sent = true;
        } else {
          clearComparisonTargetPendingSelection(true);
        }
      });
      return;
    }
    const comparisonFixed = event.target.closest("button[data-comparison-target-id]");
    if (comparisonFixed) {
      clearDelayedClick(comparisonTargetClickTimer);
      const targetId = comparisonFixed.dataset.comparisonTargetId;
      const pending = beginComparisonTargetPendingSelection(comparisonFixed, targetId);
      comparisonTargetClickTimer = deferLegacySingleClick(function () {
        comparisonTargetClickTimer = null;
        if (!pending || comparisonTargetPendingSelection !== pending) return;
        if (send("select-comparison-fixed", { targetId: targetId })) {
          pending.sent = true;
        } else {
          clearComparisonTargetPendingSelection(true);
        }
      });
      return;
    }
    const comparisonPair = event.target.closest("button[data-comparison-pair-id]");
    if (comparisonPair) {
      send("select-comparison-pair", { rowId: comparisonPair.dataset.comparisonPairId });
      return;
    }
    const comparisonSearch = event.target.closest("button[data-comparison-search]");
    if (comparisonSearch) {
      const kind = comparisonSearch.dataset.comparisonSearch;
      const input = categoryTree.querySelector("#comparison-" + kind + "-search");
      const searchIntent = kind === "world"
        ? "search-comparison-world" : "search-comparison-domestic";
      send(searchIntent, { query: input ? input.value : "" });
      return;
    }
    if (event.target.closest("button[data-comparison-swap]")) {
      send("swap-comparison-targets", {});
      return;
    }
    if (event.target.closest("button[data-comparison-clear]")) {
      send("clear-comparison-targets", {});
      return;
    }
    if (event.target.closest("button[data-comparison-add]")) {
      send("add-comparison-pair", {});
      return;
    }
    if (event.target.closest("button[data-comparison-import]")) {
      send("import-legacy-comparison-pairs", {});
      return;
    }
    const comparisonMove = event.target.closest("button[data-comparison-move]");
    if (comparisonMove) {
      send("move-comparison-pair", { direction: comparisonMove.dataset.comparisonMove });
      return;
    }
    const comparisonDelete = event.target.closest("button[data-comparison-delete]");
    if (comparisonDelete) {
      const all = comparisonDelete.dataset.comparisonDelete === "all";
      send(all ? "delete-all-comparison-pairs" :
        "delete-selected-comparison-pair", {});
      return;
    }
    const expertResult = event.target.closest("button[data-expert-selection-id]");
    if (expertResult) {
      send("select-expert", { selectionId: expertResult.dataset.expertSelectionId });
      return;
    }
    if (event.target.closest("button[data-expert-search]")) {
      const input = categoryTree.querySelector("#expert-search");
      send("search-experts", { query: input ? input.value : "" });
      return;
    }
    const haltResult = event.target.closest("button[data-halt-selection-id]");
    if (haltResult) {
      send("select-trading-halt", { selectionId: haltResult.dataset.haltSelectionId });
      return;
    }
    if (event.target.closest("button[data-halt-search]")) {
      const input = categoryTree.querySelector("#halt-search");
      send("search-trading-halts", { query: input ? input.value : "" });
      return;
    }
    if (event.target.closest("button[data-halt-sort]")) {
      send("toggle-trading-halt-sort", {});
      return;
    }
    const themeResult = event.target.closest("button[data-theme-result-id]");
    if (themeResult) {
      clearDelayedClick(themeResultClickTimer);
      const resultId = themeResult.dataset.themeResultId;
      const pending = beginThemeResultPendingSelection(themeResult);
      themeResultClickTimer = deferLegacySingleClick(function () {
        themeResultClickTimer = null;
        if (!pending || themeResultPendingSelection !== pending) return;
        if (send("select-theme", { resultId: resultId })) {
          pending.sent = true;
        } else {
          clearThemeResultPendingSelection(true);
        }
      });
      return;
    }
    if (event.target.closest("button[data-theme-search]")) {
      const input = categoryTree.querySelector("#theme-search");
      send("search-themes", { query: input ? input.value : "" });
      return;
    }
    const industryRow = event.target.closest("button[data-industry-index]");
    if (industryRow) {
      selectIndustryRow(industryRow);
      return;
    }
    if (event.target.closest("button[data-industry-swap]")) {
      send("swap-industries", {});
    }
  });
  categoryTree.addEventListener("change", function (event) {
    if (event.target.matches("input[data-industry-slot]")) {
      send("set-industry-comparison-text", {
        slot: event.target.dataset.industrySlot,
        value: event.target.value
      });
    } else if (event.target.matches("input[data-theme-sort]")) {
      send("set-theme-sort", { sortId: event.target.value });
    }
  });
  categoryTree.addEventListener("keydown", function (event) {
    if (isImeComposing(event)) return;
    const overseasList = event.target.closest("[data-overseas-list]");
    if (overseasList && ["ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) {
      event.preventDefault();
      event.stopPropagation();
      moveOverseasListSelection(overseasList, event.key);
      return;
    }
    const industryList = event.target.closest(".industry-list");
    if (industryList && ["ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) {
      event.preventDefault();
      const rows = Array.from(
        industryList.querySelectorAll("button[data-industry-index]"));
      if (rows.length === 0) return;
      const selectedIndex = rows.findIndex(function (row) {
        return row.getAttribute("aria-selected") === "true";
      });
      let nextIndex;
      if (event.key === "Home") nextIndex = 0;
      else if (event.key === "End") nextIndex = rows.length - 1;
      else if (selectedIndex < 0) nextIndex = 0;
      else if (event.key === "ArrowUp") nextIndex = Math.max(0, selectedIndex - 1);
      else nextIndex = Math.min(rows.length - 1, selectedIndex + 1);
      const nextRow = rows[nextIndex];
      selectIndustryRow(nextRow);
      nextRow.scrollIntoView({ block: "nearest", inline: "nearest" });
    } else {
      const treeRoot = event.target.closest("summary[data-tree-root]");
      if (treeRoot && ["Enter", " ", "ArrowLeft", "ArrowRight"].includes(event.key)) {
        event.preventDefault();
        if (treeInteractionLocked || treeRoot.getAttribute("aria-disabled") === "true") return;
        selectTreeNode(treeRoot, true);
        const details = treeRoot.parentElement;
        if (event.key === "ArrowLeft") details.open = false;
        else if (event.key === "ArrowRight") details.open = true;
        syncTreeSectionExpanded(details);
      } else if (event.key === "Enter" &&
          (event.target.id === "overseas-industry-search" ||
           event.target.id === "overseas-stock-search")) {
        event.preventDefault();
        const kind = event.target.id === "overseas-stock-search" ? "stock" : "industry";
        send(kind === "stock" ? "search-overseas-stocks" : "search-overseas-industries", {
          query: event.target.value
        });
      } else if (event.key === "Enter" &&
        (event.target.id === "comparison-domestic-search" ||
         event.target.id === "comparison-world-search")) {
        event.preventDefault();
        const kind = event.target.id === "comparison-world-search" ? "world" : "domestic";
        send(kind === "world" ? "search-comparison-world" :
          "search-comparison-domestic", { query: event.target.value });
      } else if (event.key === "Enter" && event.target.id === "theme-search") {
        event.preventDefault();
        send("search-themes", { query: event.target.value });
      } else if (event.key === "Enter" && event.target.id === "expert-search") {
        event.preventDefault();
        send("search-experts", { query: event.target.value });
      } else if (event.key === "Enter" && event.target.id === "halt-search") {
        event.preventDefault();
        send("search-trading-halts", { query: event.target.value });
      }
    }
  });
  window.addEventListener("pointerup", function () {
    deferredPlaylistPlainSelectionRowId = null;
    operatorCatalogDragBlockedRowId = null;
  });

  function closeActiveChildModalFromAltF4(activeModal) {
    if (!activeModal) return false;
    if (activeModal === namedPlaylistDialog) {
      closeNamedPlaylist();
    } else if (activeModal === manualDialog) {
      if (!manualClose.disabled) manualClose.click();
    } else if (activeModal === manualListDialog) {
      if (!manualListClose.disabled) manualListClose.click();
    } else if (activeModal === operatorCatalogDialog) {
      if (!operatorCatalogClose.disabled) operatorCatalogClose.click();
    } else if (activeModal === legacyAlertDialog && !legacyDialogIsConfirmation) {
      send("dismiss-dialog", {});
    }
    // Native Yes/No MessageBox windows do not expose an Alt+F4 close result.
    // Consume Alt+F4 for those and for the nested PList confirmation as well.
    return true;
  }

  function activateRovingListRow(row) {
    if (!row || row.disabled === true) return false;
    if (row.dataset.treeNodeKey) {
      if (treeInteractionLocked || row.getAttribute("aria-disabled") === "true") {
        return false;
      }
      selectTreeNode(row, false);
      return true;
    }
    if (row.dataset.themeResultId) {
      const pending = beginThemeResultPendingSelection(row);
      if (pending && send("select-theme", {
        resultId: row.dataset.themeResultId
      })) {
        pending.sent = true;
        return true;
      }
      clearThemeResultPendingSelection(true);
      return false;
    }
    if (row.dataset.namedPlaylistDefinitionId) {
      return beginNamedPlaylistSelection(row.dataset.namedPlaylistDefinitionId);
    }
    if (row.dataset.operatorCatalogResultId) {
      operatorCatalogDraftKey = "";
      return send("select-operator-catalog-result", {
        resultId: row.dataset.operatorCatalogResultId
      });
    }
    if (row.dataset.operatorCatalogStockResultId) {
      return send("select-operator-catalog-stock", {
        resultId: row.dataset.operatorCatalogStockResultId
      });
    }
    if (row.dataset.comparisonSelectionId) {
      clearComparisonTargetPendingSelection(true);
      return send(row.dataset.comparisonChoice === "world"
        ? "select-comparison-world" : "select-comparison-domestic", {
        selectionId: row.dataset.comparisonSelectionId
      });
    }
    if (row.dataset.comparisonTargetId) {
      clearComparisonTargetPendingSelection(true);
      return send("select-comparison-fixed", {
        targetId: row.dataset.comparisonTargetId
      });
    }
    if (row.dataset.comparisonPairId) {
      return send("select-comparison-pair", { rowId: row.dataset.comparisonPairId });
    }
    if (row.dataset.expertSelectionId) {
      return send("select-expert", { selectionId: row.dataset.expertSelectionId });
    }
    if (row.dataset.haltSelectionId) {
      return send("select-trading-halt", { selectionId: row.dataset.haltSelectionId });
    }
    if (row.dataset.manualResultId) {
      clearManualResultPendingSelection(true);
      manualDraft = null;
      manualDraftKey = "";
      manualDraftResetPending = false;
      return send("load-manual-financial", { resultId: row.dataset.manualResultId });
    }
    if (row.dataset.manualViResultId) {
      clearManualViResultPendingSelection(true);
      return send("select-manual-vi-result", { resultId: row.dataset.manualViResultId });
    }
    return false;
  }

  document.addEventListener("focusin", function (event) {
    const row = event.target && typeof event.target.closest === "function"
      ? event.target.closest("[data-roving-row]") : null;
    const list = row && row.closest("[data-roving-list]");
    if (row && list) focusRovingListRow(list, row, false);
  });

  document.addEventListener("keydown", function (event) {
    const activeModal = modalFocusManager.getActiveModal();
    if (event.altKey && event.key === "F4") {
      event.preventDefault();
      event.stopPropagation();
      // MainForm consumed Alt+F4 at the main workspace. Child modal behavior
      // remains explicit, while the main workspace shortcut is a no-op.
      if (activeModal && !event.repeat) closeActiveChildModalFromAltF4(activeModal);
      return;
    }
    const rovingRow = event.target && typeof event.target.closest === "function"
      ? event.target.closest("[data-roving-row]") : null;
    const rovingList = rovingRow && rovingRow.closest("[data-roving-list]");
    if (rovingList && isRovingListNavigationKey(event.key) &&
        !isImeComposing(event)) {
      if (moveRovingListSelection(
          rovingList,
          rovingRow,
          event.key,
          activateRovingListRow)) {
        event.preventDefault();
        // MainForm.KeyPreview still applied its playlist boundary side effect
        // behind non-modal ListBoxes. ShowDialog-owned GraphE/VI lists isolate it.
        if (!activeModal && !event.repeat &&
            (event.key === "Home" || event.key === "End")) {
          selectPlaylistBoundary(event.key === "Home" ? "first" : "last");
        }
      }
      return;
    }
    const dragLoopShortcut = event.key === "Escape" || event.key === "F2" ||
      event.key === "F3" || event.key === "F8";
    const pointerDragStarted = (cutDragGesture && cutDragGesture.started) ||
      (playlistDragGesture && playlistDragGesture.started);
    if (pointerDragStarted && dragLoopShortcut) {
      // Native DoDragDrop runs its own modal loop. MainForm shortcuts cannot run
      // until that loop ends. Match that behavior for both pointer-capture drag
      // surfaces; Escape cancels without issuing TAKE OUT.
      event.preventDefault();
      if (event.key === "Escape") {
        if (cutDragGesture) clearCutDragGesture();
        if (playlistDragGesture) {
          deferredPlaylistPlainSelectionRowId = null;
          clearPlaylistDragGesture();
        }
      }
    } else if (event.key === "Delete" && !event.repeat && dialog.hidden &&
        !operatorCatalogModal.hidden && operatorCatalogState &&
        (operatorCatalogState.mode === "create" || operatorCatalogState.mode === "edit")) {
      const draftRow = event.target.closest("[data-operator-catalog-row-id]");
      const textEditor = event.target.closest("input, textarea, [contenteditable='true']");
      if (draftRow && !textEditor && !operatorCatalogMutationLocked &&
          draftRow.dataset.operatorCatalogRowId === operatorCatalogState.activeDraftRowId) {
        event.preventDefault();
        event.stopPropagation();
        send("remove-active-operator-catalog-draft-row", {});
      }
    } else if (event.key === "Escape" && modalFocusManager.getActiveModal()) {
      // The legacy child forms did not assign a CancelButton and none handled
      // Escape. ShowDialog isolated MainForm, so Escape cannot fall through to
      // MainForm's TAKE OUT shortcut. The one-button MessageBox is the exception:
      // its Escape is equivalent to acknowledging OK.
      event.preventDefault();
      if (!event.repeat && modalFocusManager.getActiveModal() === legacyAlertDialog &&
          !legacyDialogIsConfirmation) {
        send("dismiss-dialog", {});
      }
    } else if ((event.key === "ArrowUp" || event.key === "ArrowDown") &&
        dialog.hidden && manualModal.hidden && manualListModal.hidden &&
        namedPlaylistModal.hidden && namedPlaylistConfirmationModal.hidden &&
        operatorCatalogModal.hidden && event.target.closest(".playlist-data-row")) {
      if (movePlaylistKeyboardSelection(
          event.target.closest(".playlist-data-row"), event)) {
        event.preventDefault();
      }
    } else if (event.key === "Delete" && !event.repeat && dialog.hidden && manualModal.hidden &&
        manualListModal.hidden && namedPlaylistModal.hidden &&
        namedPlaylistConfirmationModal.hidden && operatorCatalogModal.hidden) {
      // MainForm.KeyPreview invoked playlist deletion but did not suppress the key,
      // so an editor that owns focus must still perform its normal text deletion.
      if (!playlistDeleteSelectedLocked) send("delete-selected-playlist-rows", {});
    } else if (event.key === " " && !event.repeat && dialog.hidden && manualModal.hidden &&
        manualListModal.hidden && namedPlaylistModal.hidden &&
        namedPlaylistConfirmationModal.hidden && operatorCatalogModal.hidden &&
      event.target.closest(".playlist-data-row")) {
      event.preventDefault();
      if (!playlistProgramEnableMutationLocked) {
        send("toggle-active-playlist-enabled", {});
      }
    } else if ((event.key === "Home" || event.key === "End") &&
        !event.repeat && dialog.hidden && manualModal.hidden && manualListModal.hidden &&
        namedPlaylistModal.hidden && namedPlaylistConfirmationModal.hidden &&
        operatorCatalogModal.hidden) {
      // MainForm handled its playlist/timer side effect and then returned the
      // base key result, so a focused textbox/list control still receives Home/End.
      selectPlaylistBoundary(event.key === "Home" ? "first" : "last");
    }
  });
  dialogOk.addEventListener("click", function () {
    send(legacyDialogIsConfirmation
      ? "confirm-native-dialog"
      : "dismiss-dialog", {});
  });
  dialogNo.addEventListener("click", function () {
    if (legacyDialogIsConfirmation) {
      send("cancel-native-dialog", {});
    }
  });

  if (webview) {
    webview.addEventListener("message", function (event) {
      if (event.data && event.data.type === "state" && event.data.payload) {
        render(event.data.payload);
      }
    });
    send("ready", {});
  } else {
    status.textContent = "WebView2 브리지를 사용할 수 없습니다.";
  }
}());
