Brazen Framework - Download Manager

Cross-tab download queue, resolution pipeline, and immediate downloads for Brazen user scripts

ეს სკრიპტი არ უნდა იყოს პირდაპირ დაინსტალირებული. ეს ბიბლიოთეკაა, სხვა სკრიპტებისთვის უნდა ჩართეთ მეტა-დირექტივაში // @require https://update.greasyfork.org/scripts/587126/1909491/Brazen%20Framework%20-%20Download%20Manager.js.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Brazen Framework - Download Manager
// @namespace    brazenvoid
// @version      4.0.0
// @author       brazenvoid
// @license      GPL-3.0-only
// @description  Cross-tab download queue and resolution pipeline for Brazen user scripts
// @grant        GM_download
// ==/UserScript==

// @ts-nocheck

/** Build id for Tampermonkey Resource-override checks (must match local `base-scripts` file). */
const BRAZEN_DOWNLOAD_MANAGER_BUILD = 'local-3.0.0'

/** @typedef {'resolution'|'download'} BrazenDownloadManagerLaneId */
/** @typedef {{itemId: string|null, promptTabId: string|null, openUrl: string|null, at: number}} BrazenHumanInteractionLane */
/** @typedef {{resolution: BrazenHumanInteractionLane|null, download: BrazenHumanInteractionLane|null}} BrazenHumanInteractionMap */

const OPTION_ENABLE_DOWNLOAD_MANAGER = 'enable-download-manager'
/** Behaviours: search pages open with selection mode active (toggleable from the dock per visit). */
const OPTION_DOWNLOAD_SELECTION_MODE_DEFAULT = 'download-selection-mode-default'
/** Behaviours: reopen tag discovery when ignored tags empty filename pins. */
const OPTION_REVIEW_IGNORED_FILENAME_PINS = 'review-ignored-filename-pins'
/** Behaviours: skip batch downloads with no tags for any filename/subfolder tag-type pin. */
const OPTION_SKIP_EMPTY_FILENAME_PINS = 'skip-empty-filename-pins'
/** Behaviours: defer tag-discovery review until the resolution queue drains (unattended batches). */
const OPTION_DEFER_TAG_DISCOVERY = 'defer-tag-discovery-unattended'

/** Canonical config field keys for download-path settings (SSOT for DM registration). */
const BRAZEN_DOWNLOAD_PATH_FIELD_KEYS = Object.freeze({
  folder: 'download-folder',
  filenamePattern: 'filename-pattern',
  subfolderPattern: 'subfolder-pattern',
  substitutions: 'filename-tag-substitutions',
  tagIgnore: 'filename-tag-ignore-list',
  stripCharacterSeries: 'strip-series-from-character-tags',
})

const DOCK_TAG_DISCOVERY_MODE = 'dock-tag-discovery-mode'
const DOCK_SELECTION_MODE = 'dock-selection-mode'
const DOCK_SELECTION_SELECT_ALL = 'dock-selection-select-all'
const DOCK_ADD_TO_QUEUE = 'dock-add-to-queue'
const DOCK_DOWNLOAD_START_PAUSE = 'dock-download-start-pause'
const DOCK_CLEAR_DOWNLOAD_QUEUE = 'dock-clear-download-queue'

/** Max similar tags shown in the discovery panel. */
const SIMILAR_DISCOVERY_LIMIT = 10
/** Page size when scanning registry tags by type for similarity. */
const SIMILAR_DISCOVERY_PAGE_SIZE = 200

// Shared with BrazenIndexedDBStorage (loaded before this module).
const RESOLUTION_TERMINAL = RESOLUTION_QUEUE_TERMINAL_SET
const DOWNLOAD_TERMINAL = DOWNLOAD_QUEUE_TERMINAL_SET

/** Reactor bridge command — wake coordinator pipeline pump (BroadcastChannel). */
const REACTOR_PIPELINE_PUMP = 'pipeline-pump'
/** DM state keys published as coordinator patch stream for follower dock progress. */
const DM_STATE_PATCH_FIELDS = Object.freeze([
  'completedResolutionCount',
  'completedDownloadCount',
  'paused',
  'resolutionBlocked',
  'discoveryLanePhaseActive',
])
/** dm.state keys that drive follower HI / tag-discovery panel restore. */
const DM_STATE_GATE_PATCH_FIELDS = Object.freeze([
  'resolutionBlocked',
  'discoveryPanelTags',
  'discoveryPanelKnownTags',
  'discoveryReviewMode',
  'tagDiscoveryPanelTabId',
  'humanInteraction',
])
/** Coalesce bulk-enqueue pipeline-pump Commands on the Reactor bus. */
const PROCESSORS_WAKE_SIGNAL_DEBOUNCE_MS = 150
/** Coalesce stacked wake resumes on the receiving tab. */
const PROCESSORS_WAKE_RECEIVE_DEBOUNCE_MS = 150
/** Coalesce runtime pipeline store writes into one read-only DM UI refresh. */
const RUNTIME_UI_REFRESH_DEBOUNCE_MS = 50
/** Backoff before retrying follower snapshot handshake when coordinator is slow to answer. */
const FOLLOWER_SNAPSHOT_RETRY_MS = 500
/** DM runtime IDB stores — high-frequency pipeline writes; not config-domain UI reactions. */
if (!globalThis.__brazenRuntimePipelineConfigSources) {
  globalThis.__brazenRuntimePipelineConfigSources = new Set([
    'downloadResolutionQueue',
    'downloadQueue',
    'downloadManagerState',
  ])
}

/**
 * Default detailed help HTML for Download Manager settings and dock actions.
 * Consumer scripts may override per field via {@link ConfigurationField#setHelp}.
 *
 * @type {Readonly<Record<string, string>>}
 */
const DOWNLOAD_MANAGER_FIELD_DETAILED_HELP = Object.freeze({
  ENABLE_DOWNLOAD_MANAGER:
      '<p>Shows download-manager dock controls (selection mode, queue start/pause, tag discovery, and related actions). ' +
      'When off, batch enqueue and queue processing UI are hidden.</p>',

  SELECTION_MODE_DEFAULT:
      '<p>When enabled, search pages open with selection mode active so you can click posts to enqueue downloads.</p>' +
      '<p>Turn selection on/off from the dock for each visit without changing this default. Workflow:</p>' +
      '<ol><li>Click each media thumb you want — tiles show queue progress.</li>' +
      '<li>When items are Ready, use <strong>Download Queue Start/Pause</strong>.</li></ol>',

  REVIEW_IGNORED_FILENAME_PINS:
      '<p>When a post would download with an empty filename or folder segment because all matching tags are on the Filename Tag Ignore List, ' +
      'Tag Discovery opens with those tags and similar suggestions so you can substitute aliases or remove ignores before the save continues.</p>' +
      '<p>Runs after unknown-tag review, or alone when there are no unknown tags.</p>',

  SKIP_EMPTY_FILENAME_PINS:
      '<p>Skips enqueue/download when your filename or subfolder pattern expects tag-type tokens but the post provides none for those slots. ' +
      'The Filename Tag Ignore List does not affect this check. Patterns without tag slots are unaffected.</p>',

  DEFER_TAG_DISCOVERY:
      '<p>For unattended batches: posts needing tag review wait in a discovery lane until ordinary resolution finishes. ' +
      'The resolution progress bar counts both lanes. New enqueue items wait while discovery review is active. Requires Tag Discovery on the dock.</p>',

  DOCK_TAG_DISCOVERY_MODE:
      '<p>Pauses resolution when a post has tags not yet in your database. Review mappings in the discovery panel, then confirm to continue. ' +
      'Downloads already marked Ready keep going unless blocked by other review steps.</p>',

  DOCK_SELECTION_MODE:
      '<p>Toggle on search pages to click posts for batch enqueue. Selected items enter the resolution queue; click again to remove. ' +
      'Works with Start in Selection Mode under Behaviours.</p>',

  DOCK_SELECTION_SELECT_ALL:
      '<p>While selection mode is on, enqueue every visible search tile on this page that is not already in the download pipeline.</p>',

  DOCK_ADD_TO_QUEUE:
      '<p>On media pages, enqueue or dequeue the open post for resolution/download processing. ' +
      'The button state shows whether this post is already queued.</p>',

  DOCK_CLEAR_DOWNLOAD_QUEUE:
      '<p>Clears pending download saves only. Items still resolving, tag-discovery review, and selection-mode state are unchanged.</p>',

  DOCK_DOWNLOAD_START_PAUSE:
      '<p>Controls saving of Ready downloads only. Resolution and tag discovery continue unless rate-limited or blocked by review.</p>',

  DOWNLOAD_FOLDER:
      '<p>Root folder all downloads are saved under. It can be renamed but not removed — clearing it restores the default.</p>' +
      '<p>Subfolder and filename patterns build paths inside this folder under your browser download location.</p>',

  FILENAME_PATTERN:
      '<p>Build the saved file name from the pattern field below. The file extension is added automatically.</p>' +
      '<p>Tag substitutions and the Filename Tag Ignore List apply before the path is resolved. ' +
      'When a tag-type segment has nothing to use, that part becomes <code>unknown</code>.</p>',

  SUBFOLDER_PATTERN:
      '<p>Optional path segments inside the download folder. Use the pattern field and <code>/</code> between folder levels.</p>' +
      '<p>Leave empty to save directly under the download folder.</p>',

  TAG_SUBSTITUTIONS:
      '<p>Rename tags for filename and subfolder tokens (Subject → Alias). Use underscores, not spaces.</p>' +
      '<p>Add rules in the panel below. Scripts with sidebar tag actions may also add rules from there.</p>' +
      '<p>The same mappings can appear in Tag Discovery while resolution is paused for review.</p>',

  TAG_IGNORE:
      '<p>Tags excluded from tag-type tokens in file names. One tag per row (underscores, not spaces).</p>' +
      '<p>Ignored tags stay out of Artists, Characters, and other filename tokens.</p>',

  STRIP_CHARACTER_SERIES:
      '<p>Shortens disambiguated character tags in filename and subfolder tokens only — for example ' +
      '<code>sciel_(clair_obscur_expedition_33)</code> → <code>sciel</code>.</p>' +
      '<p>Does not change search tags or compliance rules.</p>',
})

class BrazenDownloadManager
{
  // -------------------------------------------------------------------------
  // Static public methods
  // -------------------------------------------------------------------------

  /**
   * Prefix-scoped localStorage / sessionStorage key for Download Manager cross-tab signals.
   * @param {string|null|undefined} scriptPrefix
   * @param {string} suffix
   * @return {string}
   */
  static storageKey(scriptPrefix, suffix)
  {
    return (scriptPrefix ?? 'brazen-') + suffix
  }

  /** Phase-1 HI static API. */
  /** Default soft-expiry TTL before coordinator clears an HI lane. */
  static SOFT_EXPIRE_MS = 120000
  /** Periodic panel restore / stale promptTabId reclaim interval. */
  static WATCHDOG_MS = 6000

  /**
   * Lane entry from DM state (`humanInteraction[ctx]`).
   * @param {object|null|undefined} state
   * @param {BrazenHumanInteractionLaneId} ctx
   * @return {BrazenHumanInteractionLane|null}
   */
  static _hiLaneState(state, ctx)
  {
    if (!state) {
      return null
    }
    let map = state.humanInteraction
    if (map && typeof map === 'object' && map[ctx]) {
      return map[ctx]
    }
    return null
  }

  /**
   * True when any per-lane human-interaction entry is set.
   * @param {object|null|undefined} state
   * @return {boolean}
   * @private
   */
  static _anyHumanInteractionState(state)
  {
    if (!state) {
      return false
    }
    let map = state.humanInteraction
    return !!(map && (map.resolution || map.download))
  }

  /**
   * True when this script's shared human-interaction block is mirrored in localStorage.
   * Usable in Phase-1 page ops before Download Manager initialize().
   * @param {string|null|undefined} scriptPrefix
   * @return {boolean}
   */
  static peekHumanInteractionBlockedMirror(scriptPrefix)
  {
    try {
      return localStorage.getItem(BrazenDownloadManager.storageKey(scriptPrefix, 'dm-human-interaction-blocked')) === '1'
    } catch (e) {
      return false
    }
  }

  /**
   * True when any per-lane human-interaction entry is set (or legacy single-slot block).
   * Usable in Phase-1 before Download Manager initialize() — survives CF stripping
   * `brazen_hi` and Chrome's default `noopener` on `window.open`.
   * @param {string|null|undefined} scriptPrefix
   * @return {Promise<boolean>}
   */
  static async peekHumanInteractionBlockedIdb(scriptPrefix)
  {
    let prefix = BrazenDownloadManager.storageKey(scriptPrefix, '')
    try {
      if (typeof BrazenStorageRepositories !== 'function') {
        return false
      }
      let repos = new BrazenStorageRepositories(prefix)
      if (!repos.storage.available) {
        return false
      }
      await repos.storage.open()
      let state = await repos.downloadManagerState.get()
      return BrazenDownloadManager._anyHumanInteractionState(state)
    } catch (e) {
      return false
    }
  }

  /**
   * Lane ids that currently have a human-interaction block.
   * @param {object|null|undefined} state
   * @return {BrazenHumanInteractionLaneId[]}
   * @private
   */
  static _hiContextsState(state)
  {
    /** @type {BrazenHumanInteractionLaneId[]} */
    let out = []
    if (BrazenDownloadManager._hiLaneState(state, 'resolution')) {
      out.push('resolution')
    }
    if (BrazenDownloadManager._hiLaneState(state, 'download')) {
      out.push('download')
    }
    return out
  }

  /**
   * Read `brazen_hi_ctx` from the live URL (query or hash).
   * @return {BrazenHumanInteractionLaneId|null}
   * @private
   */
  static _peekHumanInteractionContextFromLocation()
  {
    try {
      let liveParams = new URLSearchParams(location.search)
      let fromQuery = liveParams.get('brazen_hi_ctx')
      if (fromQuery === 'download' || fromQuery === 'resolution') {
        return fromQuery
      }
      let hash = location.hash || ''
      let match = /(?:^|[&#?])brazen_hi_ctx=(resolution|download)\b/.exec(hash)
      if (match) {
        return /** @type {BrazenHumanInteractionLaneId} */ (match[1])
      }
    } catch (e) {
      // ignore
    }
    return null
  }

  /**
   * When `brazen_hi_ctx` was stripped, pick the blocked lane that matches this challenge page.
   * @param {object|null|undefined} state
   * @return {BrazenHumanInteractionLaneId|null}
   * @private
   */
  static _resolveChallengeHumanInteractionContext(state)
  {
    let contexts = BrazenDownloadManager._hiContextsState(state)
    if (!contexts.length) {
      return null
    }
    if (contexts.length === 1) {
      return contexts[0]
    }
    // Prefer the lane whose openUrl points at this document (sans brazen_hi markers).
    let current = null
    try {
      current = new URL(location.href)
      current.searchParams.delete('brazen_hi')
      current.searchParams.delete('brazen_hi_ctx')
      current.hash = ''
    } catch (e) {
      current = null
    }
    if (current) {
      for (let ctx of contexts) {
        let openUrl = BrazenDownloadManager._hiLaneState(state, ctx)?.openUrl
        if (!openUrl) {
          continue
        }
        try {
          let marked = new URL(openUrl, location.href)
          marked.searchParams.delete('brazen_hi')
          marked.searchParams.delete('brazen_hi_ctx')
          marked.hash = ''
          if (marked.origin === current.origin && marked.pathname === current.pathname) {
            return ctx
          }
        } catch (e) {
          // ignore bad openUrl
        }
      }
    }
    return contexts[0]
  }

  /**
   * True when the URL still carries a queue-verification marker (`brazen_hi` query or hash).
   * @return {boolean}
   */
  static hasBrazenHumanInteractionMarker()
  {
    try {
      let liveParams = new URLSearchParams(location.search)
      if (liveParams.get('brazen_hi') === '1') {
        return true
      }
      let hash = location.hash || ''
      if (/(^|[&#?])brazen_hi=1\b/.test(hash) || hash === '#brazen_hi') {
        return true
      }
    } catch (e) {
      // ignore
    }
    return false
  }

  /**
   * True when this tab was opened by same-origin `window.open` / `<a rel="opener">`
   * (queue verification). Chrome's default `noopener` on `window.open(_blank)` breaks this
   * unless the opener used {@link BrazenDownloadManager#_openHumanInteractionTab}.
   * @return {boolean}
   */
  static isQueueOpenedVerificationTab()
  {
    try {
      let opener = window.opener
      if (!opener || opener.closed) {
        return false
      }
      void opener.location.href
      return true
    } catch (e) {
      return false
    }
  }

  /**
   * Whether a mediaCloudflare / challenge page should stay silent (queue owns the leader pane).
   * Fail closed toward silence. Usable in Phase-1 before `initialize()`.
   * @param {string|null|undefined} scriptPrefix
   * @param {BrazenDownloadManager|null|undefined} [instance] optional live instance for sync state
   * @return {Promise<boolean>}
   */
  static async shouldSilenceMediaCloudflarePrompt(scriptPrefix, instance = null)
  {
    if (BrazenDownloadManager.hasBrazenHumanInteractionMarker()) {
      return true
    }
    if (BrazenDownloadManager.peekHumanInteractionBlockedMirror(scriptPrefix)) {
      return true
    }
    if (instance?.isHumanInteractionBlockedSync?.()) {
      return true
    }
    if (BrazenDownloadManager.isQueueOpenedVerificationTab()) {
      return true
    }
    if (await BrazenDownloadManager.peekHumanInteractionBlockedIdb(scriptPrefix)) {
      return true
    }
    return false
  }

  // -------------------------------------------------------------------------
  // Constructor
  // -------------------------------------------------------------------------

  /**
   * @param {BrazenFramework} framework
   * @param {BrazenConfigurationManager} configurationManager
   * @param {object} config
   */
  constructor(framework, configurationManager, config)
  {
    this._framework = framework
    this._cm = configurationManager
    this._config = config
    this._tabId = Utilities.generateId('dm-tab-')
    /** @type {boolean} Reactor scheduler `linkQueues` mirrors `config.linkQueues`. */
    this._schedulerLinkQueues = config.linkQueues === true
    /** @type {boolean} Interleaved-policy serial dispatcher lock. */
    this._processing = false
    /** @type {boolean} Set when interleaved dispatcher is kicked while already running. */
    this._dispatcherWakeRequested = false
    /** @type {boolean} Resolution lane processor running. */
    this._resolutionProcessing = false
    /** @type {boolean} Download lane processor running. */
    this._downloadProcessing = false
    /** @type {boolean} Resolution lane wake while processor running. */
    this._resolutionWakeRequested = false
    /** @type {boolean} Download lane wake while processor running. */
    this._downloadWakeRequested = false
    /** @type {object|null} Lazy lane descriptors for the dispatcher. */
    this._lanes = null
    /** @type {boolean} Reactor Core modules wired (`BrazenKernel` / scheduler / registry / bus). */
    this._reactorReady = false
    /** @type {object|null} */
    this._kernel = null
    /** @type {object|null} */
    this._scheduler = null
    /** @type {object|null} */
    this._jobRegistry = null
    /** @type {object|null} */
    this._reactorBus = null
    /** @type {(() => void)|null} */
    this._reactorBusUnsubscribe = null
    /** @type {number} Last patch seq applied on this tab (follower reconciliation). */
    this._lastAppliedPatchSeq = 0
    /**
     * @type {{paths: string[], sinceSeq: number}[]}
     * Follower commands awaiting authoritative patch seq > sinceSeq.
     */
    this._followerPendingCommands = []
    /** @type {object|null} Reactor `reactor.localEditsDirty` atom */
    this._localEditsDirtyAtom = null
    /** @type {number|null} Debounced Reactor pipeline-pump command publish. */
    this._reactorPumpSignalTimer = null
    /** @type {number|null} Debounced read-only UI refresh after runtime pipeline store writes. */
    this._runtimeUiRefreshHandle = null
    /** @type {string|null} In-tab resolution item currently owned by this tab's processor. */
    this._activeResolutionItemId = null
    /** @type {string|null} In-tab download item currently owned by this tab's processor. */
    this._activeDownloadItemId = null
    /**
     * True after `pagehide`/`beforeunload` until a live resume clears it.
     * @type {boolean}
     */
    this._documentSuspended = false
    this._selectionClickHandler = null
    /** @type {((event: DragEvent) => void)|null} Capture-phase drag guard while selection mode is on. */
    this._selectionDragStartHandler = null
    this._crossTabVisibilityHandler = null
    this._crossTabPageshowHandler = null
    this._crossTabPagehideHandler = null
    this._selectionModeActive = false
    /** @type {boolean} Sync mirror for media-page Add/Remove queue dock chrome (signal-backed). */
    this._currentMediaQueued = false
    /** @type {object|null} Reactor `dm.currentMediaQueued` atom */
    this._currentMediaQueuedAtom = null
    /** @type {object|null} Reactor `dm.state.snapshot` atom */
    this._dmStateAtom = null
    /** @type {object|null} */
    this._pendingDownloadCountAtom = null
    /** @type {object|null} */
    this._pendingResolutionCountAtom = null
    /** @type {object|null} */
    this._dockProgressEpochAtom = null
    /** @type {object|null} */
    this._tagDiscoveryRefreshAtom = null
    /** @type {boolean} */
    this._dmUiEffectsReady = false
    /** @type {boolean} Framework dock mounted — coordinator pump must not run before this. */
    this._processorsUiReady = false
    /** @type {boolean} Coordinator acquired before dock; kick pump in {@link afterDockReady}. */
    this._processorsDeferredWake = false
    /** @type {number|null} Pending idle/setTimeout handle for coalesced processor kick. */
    this._processorsKickHandle = null
    /** @type {object[]} */
    this._dmUiEffectDisposers = []
    /** @type {boolean} Next dock progress paint should refresh all item tiles. */
    this._dockProgressRefreshAllItems = false
    /** @type {number|null} rAF/setTimeout handle for coalesced dock progress paint. */
    this._dockProgressPaintRaf = null
    /** @type {function(): void|null} Cancels a pending coalesced dock progress paint. */
    this._dockProgressPaintCancel = null
    /** @type {number|null} rAF/setTimeout handle for coalesced dock progress epoch bump. */
    this._dockProgressEpochBumpRaf = null
    /** @type {function(): void|null} Cancels a pending coalesced dock progress epoch bump. */
    this._dockProgressEpochBumpCancel = null
    /** @type {number|null} rAF/setTimeout handle for coalesced tag-discovery refresh bump. */
    this._tagDiscoveryRefreshBumpRaf = null
    /** @type {function(): void|null} Cancels a pending coalesced tag-discovery refresh bump. */
    this._tagDiscoveryRefreshBumpCancel = null
    /**
     * Pending onlyTags filter merged across coalesced tag-discovery refresh schedules.
     * `undefined` = none pending; `null` = unrestricted; `string[]` = filtered.
     * @type {string[]|null|undefined}
     */
    this._tagDiscoveryRefreshPendingOnlyTags = undefined
    /** @type {string|null} Last follower gate signature for panel-restore dedupe. */
    this._lastFollowerGateSignature = null
    /** @type {string|null} JSON of last state published to dm.state.snapshot atom. */
    this._lastPublishedStateJson = null
    /** @type {number|null} Last painted pending download count (slide-out transition). */
    this._lastPaintedDownloadPending = null
    /** @type {number|null} Last painted pending resolution count (slide-out transition). */
    this._lastPaintedResolutionPending = null
    /** @type {number|null} Last pending download count written to signal atoms. */
    this._lastCommittedPendingDownload = null
    /** @type {number|null} Last pending resolution count written to signal atoms. */
    this._lastCommittedPendingResolution = null
    /** @type {string|null} Active discovery review dedup signature (itemId+mode+tag names). */
    this._activeDiscoveryReviewSignature = null
    this._progressElement = null
    /** @type {boolean} True while Confirm/Skip is finishing work after an immediate hide. */
    this._tagDiscoveryActionInFlight = false
    /**
     * When set, the next discovery refresh runs only if the open panel lists one of these names.
     * `null` means unrestricted; `undefined` means no pending filter from the last schedule.
     * @type {string[]|null|undefined}
     */
    this._tagDiscoveryRefreshOnlyTags = undefined
    /** Per-lane human-interaction dock panels. */
    this._humanInteractionPanels = {resolution: null, download: null}
    /** @type {number|null} */
    this._humanInteractionWatchdogTimer = null
    /** @type {number|null} Debounced follower dock panel restore after gate patches. */
    this._followerPanelRestoreTimer = null
    /** @type {number|null} Debounced follower selection-mark reconcile after queue count patches. */
    this._followerSelectionMarksTimer = null
    /** @type {number|null} Retry timer for follower snapshot handshake. */
    this._followerSnapshotRetryTimer = null
    /** @type {boolean} Follower snapshot request in flight (suppress duplicate retries). */
    this._followerSnapshotInFlight = false
    /** @type {HTMLElement|null} */
    this._tagDiscoveryPanel = null
    /** @type {number} */
    this._similarDiscoveryLoadId = 0
    /** @type {number|null} */
    this._similarDiscoveryRaf = null
    /** @type {string|null} */
    this._similarDiscoveryFingerprint = null
    /** @type {Array<{name: string, type: string|null, count: null}>|null} */
    this._similarDiscoveryCachedTags = null
    /** @type {number|null} */
    this._tagDiscoveryRefreshTimer = null
    /**
     * True while a navigation-interrupted in-progress download awaits a timed-panel decision.
     * Gates the download lane so nothing new starts in parallel.
     * @type {boolean}
     */
    this._downloadInterruptionPending = false
    /**
     * Orphan `downloading` rows with `inProgress` awaiting the interruption panel decision.
     * @type {object[]|null}
     */
    this._downloadInterruptionRows = null
    /** @type {HTMLElement|null} */
    this._downloadInterruptionPanel = null
    this._trackedItemElements = new Map()
    /** @type {Map<string, number>} Bumps per itemId so a later click can cancel an in-flight toggle. */
    this._selectionOpEpoch = new Map()
    /**
     * @type {Set<string>}
     * Item ids whose overlay was cleared by a local deselect/cancel. Blocks stale
     * `_syncFromStorage` / processor paints until the row is actually gone from the queues.
     */
    this._selectionUiHidden = new Set()
    /**
     * @type {Set<string>}
     * Optimistic select in flight (painted, IDB put not settled). Keeps refresh/reconcile
     * from clearing the overlay when `isQueued` is still false.
     */
    this._selectionUiPending = new Set()
    this._resolutionGapMs = config.resolutionInitiationGapMs ?? 2000
    this._downloadGapMs = config.downloadInitiationGapMs ?? 2000
    /** @type {number} In-tab clock — avoids IDB lost-update races wiping the download gap. */
    this._lastDownloadInitiationAt = 0
    /** @type {number} */
    this._lastResolutionInitiationAt = 0
    /** @type {Promise<void>} Serializes DM state mutations that must re-read before write. */
    this._coordinatorStateTail = Promise.resolve()
    /** @type {Map<string, number>} Per-item paint generation — drops stale async _renderItemProgress. */
    this._itemProgressPaintGen = new Map()
    /** @type {Promise<void>} Serializes download initiation pacing (batch + immediate). */
    this._downloadInitiateTail = Promise.resolve()
    /** @type {Promise<void>} Serializes resolution / same-origin site fetch pacing. */
    this._resolutionInitiateTail = Promise.resolve()
    /** @type {number|null} Debounce timer for incoming wake / focus resume. */
    this._processorsWakeReceiveTimer = null
    /** @type {Promise<void>} Serializes `_resumeProcessorsFromExternalWake` work. */
    this._processorsWakeResumeTail = Promise.resolve()
    /** @type {boolean} One-shot guard for sticky coordinator reclaim after same-tab reload. */
    this._coordinatorStickyReclaimAttempted = false
    /** @type {boolean} OR of refreshAllItems across coalesced refresh requests. */
    this._orphanDownloadRequeueAttempted = false
    this._registerConfigFields()
    this._registerDownloadPathFields()
    this._setupDock()
  }

  /**
   * @private
   */
  _registerConfigFields()
  {
    let seeds = {}
    if (this._config.enabled !== true) {
      let enableKey = this._config.enableConfigKey ?? OPTION_ENABLE_DOWNLOAD_MANAGER
      if (!this._cm.getField(enableKey)) {
        seeds[enableKey] = !!this._config.enableDefault
        this._cm.addFlagField(enableKey).
            setTitle('Enable Download Manager').
            setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.ENABLE_DOWNLOAD_MANAGER)
      }
    }

    let selectionDefaultKey = this._config.selectionModeDefaultConfigKey ?? OPTION_DOWNLOAD_SELECTION_MODE_DEFAULT
    if (!this._cm.getField(selectionDefaultKey)) {
      seeds[selectionDefaultKey] = false
      this._cm.addFlagField(selectionDefaultKey).
          setTitle('Start in Selection Mode').
          setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.SELECTION_MODE_DEFAULT)
    }

    if (!this._cm.getField(OPTION_REVIEW_IGNORED_FILENAME_PINS)) {
      seeds[OPTION_REVIEW_IGNORED_FILENAME_PINS] = false
      this._cm.addFlagField(OPTION_REVIEW_IGNORED_FILENAME_PINS).
          setTitle('Review Ignored Download Path Tags').
          setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.REVIEW_IGNORED_FILENAME_PINS)
    }

    if (!this._cm.getField(OPTION_SKIP_EMPTY_FILENAME_PINS)) {
      seeds[OPTION_SKIP_EMPTY_FILENAME_PINS] = false
      this._cm.addFlagField(OPTION_SKIP_EMPTY_FILENAME_PINS).
          setTitle('Skip Media Without Download Path Tags').
          setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.SKIP_EMPTY_FILENAME_PINS)
    }

    if (!this._cm.getField(OPTION_DEFER_TAG_DISCOVERY)) {
      seeds[OPTION_DEFER_TAG_DISCOVERY] = false
      this._cm.addFlagField(OPTION_DEFER_TAG_DISCOVERY).
          setTitle('Defer Tag Discovery Until Resolution Completes').
          setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.DEFER_TAG_DISCOVERY)
    }
    if (Object.keys(seeds).length) {
      this._cm.registerFieldSeeds(seeds)
    }
  }

  /**
   * Registers download-path settings fields when {@link BrazenDownloadManager} config includes `downloadPaths`.
   * Consumer scripts may override titles, defaults, or detailed help per field after `configureDownloadManager()`.
   *
   * @private
   */
  _registerDownloadPathFields()
  {
    let paths = this._config.downloadPaths
    if (!paths) {
      return
    }

    let seeds = {}
    let folderKey = paths.folderConfigKey || BRAZEN_DOWNLOAD_PATH_FIELD_KEYS.folder
    if (!this._cm.getField(folderKey)) {
      if (paths.defaultFolder) {
        seeds[folderKey] = paths.defaultFolder
      }
      this._cm.addTextField(folderKey).
          setTitle('Download Folder').
          setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.DOWNLOAD_FOLDER)
    }

    let filenameKey = paths.filenamePatternConfigKey || BRAZEN_DOWNLOAD_PATH_FIELD_KEYS.filenamePattern
    if (!this._cm.getField(filenameKey)) {
      seeds[filenameKey] = paths.defaultFilenamePattern ?? 'ID'
      this._cm.addTextField(filenameKey).
          setTitle('Filename Pattern').
          setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.FILENAME_PATTERN)
    }

    let subfolderKey = paths.subfolderPatternConfigKey || BRAZEN_DOWNLOAD_PATH_FIELD_KEYS.subfolderPattern
    if (!this._cm.getField(subfolderKey)) {
      this._cm.addTextField(subfolderKey).
          setTitle('Subfolder Pattern').
          setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.SUBFOLDER_PATTERN)
    }

    let substitutionsKey = paths.substitutionsFieldKey || BRAZEN_DOWNLOAD_PATH_FIELD_KEYS.substitutions
    if (!this._cm.getField(substitutionsKey)) {
      seeds[substitutionsKey] = {
        templateId: 'substitution',
        templateConfig: {attributeKind: 'requiresTag'},
        config: {...DEFAULT_RULESET_USER_CONFIG, autoSort: true},
      }
      this._cm.addRulesetField(substitutionsKey).
          setTitle('Filename Tag Substitutions').
          setTemplate('substitution').
          setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.TAG_SUBSTITUTIONS)
    }

    let ignoreKey = paths.tagIgnoreFieldKey || BRAZEN_DOWNLOAD_PATH_FIELD_KEYS.tagIgnore
    if (!this._cm.getField(ignoreKey)) {
      seeds[ignoreKey] = {
        templateId: 'tag-sole-ignore',
        templateConfig: {attributeKind: 'toggle'},
        config: {...DEFAULT_RULESET_USER_CONFIG, autoSort: true},
      }
      this._cm.addRulesetField(ignoreKey).
          setTitle('Filename Tag Ignore List').
          setTemplate('tag-sole-ignore').
          setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.TAG_IGNORE)
    }

    let stripSeriesKey = paths.stripCharacterSeriesConfigKey || BRAZEN_DOWNLOAD_PATH_FIELD_KEYS.stripCharacterSeries
    if (!this._cm.getField(stripSeriesKey)) {
      this._cm.addFlagField(stripSeriesKey).
          setTitle('Strip Series From Character Tags').
          setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.STRIP_CHARACTER_SERIES)
    }
    if (Object.keys(seeds).length) {
      this._cm.registerFieldSeeds(seeds)
    }
  }

  /**
   * @return {Promise<void>}
   */
  async initialize()
  {
    let profiler = globalThis.__brazenReactor
    let bootStop = profiler?.time?.('boot', 'initialize')
    await this._ensureState()
    this._syncDiscoveryTagTypesFromPatterns()
    this._selectionModeActive = this._shouldStartSelectionMode()
    try {
      sessionStorage.removeItem(BrazenDownloadManager.storageKey(this._cm._scriptPrefix, 'dm-selection'))
    } catch (error) {
      // ignore unavailable sessionStorage
    }
    this._setupCrossTabSync()
    if (this._cm.canPersist()) {
      this._hydrateInitiationClocksFromState(await this._getState())
    }
    this._framework.onConfigurationChange((event) => {
      let source = event?.source
      // Queue / DM state / ledger puts fire on every processor step — UI updates are owned
      // by task-complete hooks, not by this broadcast.
      if (source === 'downloadResolutionQueue' || source === 'downloadQueue' ||
          source === 'downloadManagerState' || source === 'ledger') {
        return
      }
      if (source === 'tags') {
        // Tag attribute chrome — consumer refreshTagActionSurfaces owns discovery repaint.
        return
      }
      if (source === 'foreign-ruleset-sync') {
        // Cross-tab ruleset hydrate only — compliance effects already re-validated tiles.
        return
      }
      let dmState = this.readDmState()
      if (dmState?.resolutionBlocked || (Array.isArray(dmState?.discoveryPanelTags) &&
          dmState.discoveryPanelTags.length)) {
        this._requestTagDiscoveryPanelRefresh(null)
      }
      this._refreshDockProgress()
      this._refreshSelectionMarks()
      this._syncDiscoveryTagTypesFromPatterns()
      if (this.isDownloadPageRole('selection')) {
        this._bindSelectionHandlers()
        this.registerSelectionDragGuard()
      }
    })

    if (this.isDownloadPageRole('selection')) {
      this._bindSelectionHandlers()
      this.registerSelectionDragGuard()
    }

    // Signal-backed dock paint before first storage sync so pending counts paint coalesced.
    let uiEffectsStop = profiler?.time?.('boot', 'ensureDmUiEffects')
    this._ensureDmUiEffects()
    uiEffectsStop?.()
    let syncStop = profiler?.time?.('boot', 'syncFromStorage')
    await this._syncFromStorage()
    syncStop?.()
    let reactorStop = profiler?.time?.('boot', 'startReactor')
    await this._startReactor()
    reactorStop?.()
    // HI restore + dock progress remount run after the dock exists (Framework post-UI).
    // Painting progress here targets a detached slot; HI panels also need the dock stack.
    this._startHumanInteractionWatchdog()
    if (this._downloadInterruptionPending) {
      this._showDownloadInterruptionPanel(this._downloadInterruptionRows)
    }
    this._kickProcessorsIfReady()
    bootStop?.()
    profiler?.mark?.('boot', 'initialize-end')
  }

  /**
   * Start the coordinator scheduler pump only after the Framework dock exists so init
   * IDB/reactor work cannot starve click handlers and dock orientation apply.
   * @private
   */
  _kickProcessorsIfReady()
  {
    globalThis.__brazenReactor?.mark?.('boot', 'kickProcessorsIfReady')
    if (!this._shouldRunProcessors()) {
      return
    }
    if (!this._processorsUiReady) {
      this._processorsDeferredWake = true
      return
    }
    if (this._processorsKickHandle != null) {
      return
    }
    let run = () => {
      this._processorsKickHandle = null
      this._processorsDeferredWake = false
      void this._runProcessors()
    }
    if (typeof requestIdleCallback === 'function') {
      this._processorsKickHandle = requestIdleCallback(run, {timeout: 500})
    } else {
      this._processorsKickHandle = setTimeout(run, 0)
    }
  }

  /**
   * After Framework mounts the dock: remount progress onto the live slot, restore HI /
   * discovery panels. Safe to call once UI exists (or again on focus).
   * @return {Promise<void>}
   */
  async afterDockReady()
  {
    this._processorsUiReady = true
    // Live `.bv-dock-progress-panel` now exists — sync queue counts before first paint.
    if (this._cm.canPersist()) {
      await this._syncPendingCountSignalsFromIdb()
    }
    this._refreshDockProgress({refreshAllItems: true})
    await this._restoreVisibleDockPanelsIfNeeded()
    if (this._downloadInterruptionPending) {
      this._showDownloadInterruptionPanel(this._downloadInterruptionRows)
    }
    this._kickProcessorsIfReady()
  }

  /**
   * Re-sync dock queue chrome after Framework `hydrateBootFieldsFromStorage()` — ruleset
   * compile can contend with the first boot count read and leave Start/Pause idle until now.
   * @return {Promise<void>}
   */
  async afterBootHydrate()
  {
    await this._syncFromStorage()
    this._kickProcessorsIfReady()
  }

  /**
   * Restore tag-discovery and HI panels after dock mount or external wake.
   * @return {Promise<void>}
   * @private
   */
  async _restoreVisibleDockPanelsIfNeeded()
  {
    await this._restoreHumanInteractionPanelsIfNeeded()
    await this.restoreTagDiscoveryPanelIfNeeded()
  }

  /**
   * Drop terminal queue rows (and their fat payloads) left after crashes / missed finally.
   * Safe across tabs — only deletes terminal statuses.
   * @return {Promise<void>}
   * @private
   */
  async _pruneAllTerminalQueueRows()
  {
    if (!this._cm.canPersist() || !this.isCoordinator()) {
      return
    }
    try {
      await this._repos().download.pruneAllTerminalRows()
    } catch (error) {
      console.log('Terminal queue prune failed:', error)
    }
  }

  /**
   * @return {boolean}
   * @private
   */
  _shouldStartSelectionMode()
  {
    if (!this.isDownloadManagerEnabled() || !this.isDownloadPageRole('selection')) {
      return false
    }
    let key = this._config.selectionModeDefaultConfigKey ?? OPTION_DOWNLOAD_SELECTION_MODE_DEFAULT
    return !!this._framework._getConfig(key)
  }

  // -------------------------------------------------------------------------
  // Public API
  // -------------------------------------------------------------------------

  /**
   * Adds a post to the resolution queue when it is not already in the pipeline.
   * Dedupe checks both queues: resolution (pending/in-progress resolve) and download
   * (already resolved, waiting or downloading). Items already in the download queue
   * are never re-enqueued for resolution.
   *
   * @param {object} context
   * @return {Promise<boolean>} whether the item was added to the queue
   */
  async enqueueDownload(context)
  {
    let itemId = this._config.getQueueItemId(context)
    if (!itemId) {
      return false
    }
    if (await this.isQueued(itemId)) {
      return false
    }
    let row = {
      itemId,
      downloadType: context.downloadType ?? this._getActivePageConfig()?.defaultDownloadType ?? 'post',
      sourceUrl: context.sourceUrl ?? '',
      status: 'queued',
      originTabId: this._tabId,
      resolveContext: context.resolveContext ?? {},
      pendingTagGroups: null,
      addedAt: Date.now(),
      error: null,
    }
    await this.sendCommand({type: 'enqueue-download', payload: {itemId, row}})
    await this._syncFromStorage()
    this._signalProcessorsWakeAndRunIfCoordinator()
    return true
  }

  /**
   * @param {string} itemId
   * @return {Promise<void>}
   * @private
   */
  async _clearTerminalQueueRows(itemId)
  {
    await this._repos().download.removeTerminal(itemId)
  }

  /**
   * @param {string} itemId
   * @return {Promise<void>}
   */
  async dequeueDownload(itemId)
  {
    let state = this.readDmState()
    let blockedId = state?.resolutionBlockedItemId
    let cancelDiscovery = blockedId != null
        && String(itemId) === String(blockedId)
        && !this._tagDiscoveryActionInFlight
    let itemKey = String(itemId)
    let clearHiContexts = /** @type {BrazenDownloadManagerLaneId[]} */ ([])
    for (let ctx of /** @type {BrazenDownloadManagerLaneId[]} */ (['resolution', 'download'])) {
      let lane = this._hiLane(state, ctx)
      if (lane?.itemId != null && String(lane.itemId) === itemKey) {
        clearHiContexts.push(ctx)
      }
    }

    await this.sendCommand({
      type: 'dequeue-download',
      payload: {itemId, cancelDiscovery, clearHiContexts},
    })

    if (cancelDiscovery) {
      this._hideTagDiscoveryPanel()
      if (String(this._activeResolutionItemId) === String(itemId)
          || String(this._activeDownloadItemId) === String(itemId)) {
        this._activeResolutionItemId = null
        this._activeDownloadItemId = null
      }
      await this._maybeEndDiscoveryLanePhase()
      this._signalProcessorsWakeAndRunIfCoordinator()
    }

    for (let ctx of clearHiContexts) {
      this._hideHumanInteractionPanel(ctx)
    }
    if (clearHiContexts.length) {
      this._signalProcessorsWakeAndRunIfCoordinator()
    }

    await this._syncFromStorage()
    this._refreshSelectionMarks()
  }

  /**
   * Whether a post is anywhere in the active download pipeline (either queue).
   * Resolution queue: queued, resolving, tagReview, etc.
   * Download queue: queued or downloading (already resolved — no re-resolution).
   *
   * @param {string} itemId
   * @return {Promise<boolean>}
   */
  async isQueued(itemId)
  {
    let resolution = await this._repos().downloadResolutionQueue.get(itemId)
    if (resolution && !RESOLUTION_TERMINAL.has(resolution.status)) {
      return true
    }
    let download = await this._repos().downloadQueue.get(itemId)
    return !!(download && !DOWNLOAD_TERMINAL.has(download.status))
  }

  /**
   * @return {Promise<void>}
   */
  async confirmTagDiscoveryMappings()
  {
    this._hideTagDiscoveryPanel()
    await this.sendCommand({type: 'confirm-tag-discovery', payload: {}})
  }

  /**
   * @param {() => (void|Promise<void>)} fn
   * @return {Promise<void>}
   * @private
   */
  async _withTagDiscoveryActionInFlight(fn)
  {
    if (this._tagDiscoveryActionInFlight) {
      return
    }
    this._tagDiscoveryActionInFlight = true
    try {
      await fn()
    } finally {
      this._tagDiscoveryActionInFlight = false
    }
  }

  /**
   * Coordinator write-through body for confirm-tag-discovery Command.
   * @return {Promise<void>}
   * @private
   */
  async _executeConfirmTagDiscoveryCoordinator()
  {
    await this._withTagDiscoveryActionInFlight(async () => {
      let state = await this._getState()
      if (!state.resolutionBlocked) {
        return
      }

      let reviewMode = state.discoveryReviewMode ?? 'unknown'
      if (reviewMode !== 'ignoredPins') {
        await this._confirmDiscoveryPanelTagTypes(state.discoveryPanelTags)
      }

      let itemId = state.resolutionBlockedItemId
      if (!itemId || !(await this._repos().downloadResolutionQueue.get(itemId))?.pendingTagGroups) {
        await this._abortDiscoveryReviewIfNoSubject()
        return
      }

      let item = await this._repos().downloadResolutionQueue.get(itemId)
      if (!item) {
        await this._abortDiscoveryReviewIfNoSubject()
        return
      }

      let outcome = await this._reconcileDiscoveryGateFromPending(item, {wakeProcessors: true})
      if (outcome === 'cleared') {
        await this._abortDiscoveryReviewIfNoSubject()
      }
    })
  }

  /**
   * Clear discovery gate when confirm/skip has no review subject.
   * @return {Promise<void>}
   * @private
   */
  async _abortDiscoveryReviewIfNoSubject()
  {
    await this._commitCoordinatorDmState((next) => {
      this._clearDiscoveryReviewState(next)
    })
    await this._maybeEndDiscoveryLanePhase()
    this._signalProcessorsWakeAndRunIfCoordinator()
  }

  /**
   * Open tag-discovery review panel with gate state.
   * @param {'unknown'|'ignoredPins'} mode
   * @param {object[]} tags
   * @param {object[]} knownTags
   * @param {{stealOwnership?: boolean, itemId?: string|null, wakeProcessors?: boolean}} [options]
   * @return {Promise<void>}
   * @private
   */
  async _beginDiscoveryReview(mode, tags, knownTags, options = {})
  {
    let stealOwnership = options.stealOwnership !== false
    let itemId = options.itemId ?? null
    let known = mode === 'unknown' ? knownTags : []
    let signature = this._discoveryReviewSignature(itemId, mode, tags)
    let state = this.readDmState()
    if (signature === this._activeDiscoveryReviewSignature &&
        state?.resolutionBlocked &&
        state.resolutionBlockedItemId === itemId &&
        state.discoveryReviewMode === mode &&
        this._tagDiscoveryPanel &&
        BrazenViewLayer.isDockSlidePanelVisible(this._tagDiscoveryPanel)) {
      return
    }
    this._activeDiscoveryReviewSignature = signature
    let base = state
    if (base && typeof base === 'object') {
      let optimistic = structuredClone(base)
      if (itemId) {
        optimistic.resolutionBlocked = true
        optimistic.resolutionBlockedItemId = itemId
      }
      optimistic.discoveryReviewMode = mode
      optimistic.discoveryPanelTags = tags
      optimistic.discoveryPanelKnownTags = known
      if (stealOwnership) {
        // Background processor leaves panel unowned so a focused peer can steal on wake.
        optimistic.tagDiscoveryPanelTabId = document.visibilityState === 'visible' ? this._tabId : null
      }
      this._publishDmStateToAtoms(optimistic)
    }
    void this._commitCoordinatorDmState((next) => {
      if (itemId) {
        next.resolutionBlocked = true
        next.resolutionBlockedItemId = itemId
      }
      next.discoveryReviewMode = mode
      next.discoveryPanelTags = tags
      next.discoveryPanelKnownTags = known
      if (stealOwnership) {
        next.tagDiscoveryPanelTabId = document.visibilityState === 'visible' ? this._tabId : null
      }
    }).catch(() => {})
    if (options.wakeProcessors) {
      await this._presentTagDiscoveryReview(tags, known)
    } else {
      await this._showTagDiscoveryPanel(tags, known)
    }
  }

  /**
   * Resolution funnel: fresh payload → defer, open review, finish, or promote tail.
   * @param {object} item
   * @param {object} resolved
   * @param {{registerTags?: boolean, forceImmediate?: boolean, endDiscoveryLaneOnFinish?: boolean, presentPanel?: boolean}} [options]
   * @return {Promise<'deferred'|'unknownReview'|'ignoredPinReview'|'finished'|'noop'>}
   * @private
   */
  async _applyTagDiscoveryOutcome(item, resolved, options = {})
  {
    let registerTags = options.registerTags !== false
    let forceImmediate = !!options.forceImmediate
    let endDiscoveryLaneOnFinish = !!options.endDiscoveryLaneOnFinish
    let presentPanel = options.presentPanel !== false

    let tagGroups = this._tagGroupsFromResolvedPayload(resolved)
    let tagIncidences = resolved.tagIncidences ?? {}
    let deferRegistration = () => {
      if (registerTags) {
        void this._registerResolvedTagGroups(tagGroups).catch(() => {})
      }
    }

    let discoveryActive = this._isTagDiscoveryActive() && this._shouldRunTagDiscovery('mediaPost')
    if (discoveryActive) {
      let lists = await this._partitionDiscoveryTags(tagGroups, tagIncidences)
      if (lists.unknown.length) {
        if (this._shouldDeferTagDiscoveryReview() && !forceImmediate) {
          item.status = 'discoveryQueued'
          item.pendingTagGroups = resolved
          if (!(await this._commitResolutionRow(item))) {
            return 'noop'
          }
          deferRegistration()
          return 'deferred'
        }
        item.status = 'tagReview'
        item.pendingTagGroups = resolved
        if (!(await this._commitResolutionRow(item))) {
          return 'noop'
        }
        await this._beginDiscoveryReview('unknown', lists.unknown, lists.known, {
          itemId: item.itemId,
          wakeProcessors: presentPanel,
        })
        deferRegistration()
        return 'unknownReview'
      }
    }

    if (registerTags) {
      await this._registerResolvedTagGroups(tagGroups)
    }

    let ignoredPin = await this._collectIgnoredFilenamePinTags(tagGroups, tagIncidences)
    let ignoredTags = ignoredPin.tags
    if (ignoredPin.pinJoinEmpty && ignoredTags.length) {
      if (this._shouldDeferTagDiscoveryReview() && !forceImmediate) {
        item.status = 'discoveryQueued'
        item.pendingTagGroups = resolved
        if (!(await this._commitResolutionRow(item))) {
          return 'noop'
        }
        return 'deferred'
      }
      item.status = 'tagReview'
      item.pendingTagGroups = resolved
      if (!(await this._commitResolutionRow(item))) {
        return 'noop'
      }
      await this._beginDiscoveryReview('ignoredPins', ignoredTags, [], {
        itemId: item.itemId,
        wakeProcessors: presentPanel,
      })
      return 'ignoredPinReview'
    }

    if (this._shouldSkipEmptyFilenamePins(tagGroups)) {
      await this._dropResolvedWithoutDownload(item)
      if (endDiscoveryLaneOnFinish) {
        await this._maybeEndDiscoveryLanePhase()
      }
      return 'finished'
    }

    return 'noop'
  }

  /**
   * Stale gate / orphan row / post-confirm re-partition from pending tag groups.
   * @param {object|string|null} itemOrId
   * @param {{refreshIgnoredOnly?: boolean, wakeProcessors?: boolean, stealOwnership?: boolean, mode?: string}} [options]
   * @return {Promise<'unknownReview'|'ignoredPinReview'|'finished'|'cleared'|'unchanged'>}
   * @private
   */
  async _reconcileDiscoveryGateFromPending(itemOrId, options = {})
  {
    // Gate open/reconcile writes dm.state — coordinator-only (followers restore UI from patches).
    if (!this.isCoordinator()) {
      return 'unchanged'
    }
    let itemId = typeof itemOrId === 'object' ? itemOrId?.itemId : itemOrId
    let item = typeof itemOrId === 'object' ? itemOrId : null
    if (!item && itemId) {
      item = await this._repos().downloadResolutionQueue.get(itemId)
    }

    if (!item?.pendingTagGroups) {
      this._hideTagDiscoveryPanel()
      await this._commitCoordinatorDmState((next) => {
        this._clearDiscoveryReviewState(next)
      })
      if (options.wakeProcessors) {
        this._signalProcessorsWakeAndRunIfCoordinator()
      }
      return 'cleared'
    }

    let resolved = item.pendingTagGroups
    let tagGroups = this._tagGroupsFromResolvedPayload(resolved)
    let tagIncidences = resolved.tagIncidences ?? {}
    let state = this.readDmState()
    let mode = options.mode ?? state?.discoveryReviewMode

    if (options.refreshIgnoredOnly || mode === 'ignoredPins') {
      let ignoredPin = await this._collectIgnoredFilenamePinTags(tagGroups, tagIncidences)
      let ignoredTags = ignoredPin.tags
      if (!ignoredPin.pinJoinEmpty || !ignoredTags.length) {
        await this._finishResolutionAfterTagDiscovery(item)
        return 'finished'
      }
      await this._beginDiscoveryReview('ignoredPins', ignoredTags, [], {
        itemId: item.itemId,
        stealOwnership: options.stealOwnership,
        wakeProcessors: options.wakeProcessors,
      })
      if (!options.wakeProcessors) {
        this._renderTagDiscoveryPanel(ignoredTags, [])
      }
      return 'ignoredPinReview'
    }

    if (this._isTagDiscoveryActive()) {
      let lists = await this._partitionDiscoveryTags(tagGroups, tagIncidences)
      if (lists.unknown.length) {
        await this._beginDiscoveryReview('unknown', lists.unknown, lists.known, {
          itemId: item.itemId,
          stealOwnership: options.stealOwnership,
          wakeProcessors: options.wakeProcessors,
        })
        return 'unknownReview'
      }
    }

    let ignoredPin = await this._collectIgnoredFilenamePinTags(tagGroups, tagIncidences)
    if (ignoredPin.pinJoinEmpty && ignoredPin.tags.length) {
      await this._beginDiscoveryReview('ignoredPins', ignoredPin.tags, [], {
        itemId: item.itemId,
        stealOwnership: options.stealOwnership,
        wakeProcessors: options.wakeProcessors,
      })
      return 'ignoredPinReview'
    }

    await this._finishResolutionAfterTagDiscovery(item)
    return 'finished'
  }

  /**
   * Promote or skip-empty after discovery/ignored-pin review when nothing remains to review.
   * @param {object} item Resolution queue row with `pendingTagGroups`
   * @return {Promise<void>}
   * @private
   */
  async _finishResolutionAfterTagDiscovery(item)
  {
    let itemId = item?.itemId ?? null
    try {
      if (!item?.pendingTagGroups) {
        return
      }
      let resolvedPayload = item.pendingTagGroups
      let tagGroups = this._tagGroupsFromResolvedPayload(resolvedPayload)
      if (this._shouldSkipEmptyFilenamePins(tagGroups)) {
        await this._dropResolvedWithoutDownload(item, {clearDiscoveryGate: false})
        return
      }
      if (!(await this._promoteToDownloadQueue(item, resolvedPayload))) {
        return
      }
      await this._repos().download.removeResolution(itemId)
      if (itemId) {
        void this._renderItemProgress(itemId)
      }
      await this._incrementResolutionProgress()
    } catch (error) {
      console.error('[BrazenDownloadManager] finish after tag discovery failed', error)
    } finally {
      await this._commitCoordinatorDmState((next) => {
        this._clearDiscoveryReviewState(next)
      })
      try {
        await this._syncFromStorage()
      } catch (syncError) {
        /* ignore */
      }
      await this._maybeEndDiscoveryLanePhase()
      this._signalProcessorsWakeAndRunIfCoordinator()
    }
  }

  /**
   * Clear discovery panel gate fields on a state snapshot.
   * @param {object} state
   * @private
   */
  _clearDiscoveryReviewState(state)
  {
    state.resolutionBlocked = false
    state.resolutionBlockedItemId = null
    state.discoveryPanelTags = null
    state.discoveryPanelKnownTags = null
    state.discoveryReviewMode = null
    state.tagDiscoveryPanelTabId = null
    this._activeDiscoveryReviewSignature = null
  }

  /**
   * @param {string|null|undefined} itemId
   * @param {'unknown'|'ignoredPins'|string|null|undefined} mode
   * @param {Array<{name?: string}>|null|undefined} tags
   * @return {string}
   * @private
   */
  _discoveryReviewSignature(itemId, mode, tags)
  {
    let names = (tags ?? []).map((tag) => tag?.name).filter(Boolean).sort().join('\0')
    return `${itemId ?? ''}\0${mode ?? ''}\0${names}`
  }

  /**
   * Skip the current resolution item waiting on tag review (do not promote to download;
   * do not set isDiscovered so discovery can fire again later).
   * @return {Promise<void>}
   */
  async skipTagDiscoveryInclusion()
  {
    this._hideTagDiscoveryPanel()
    let itemId = this.readDmState()?.resolutionBlockedItemId
    await this.sendCommand({type: 'skip-tag-discovery', payload: {}})
    if (itemId) {
      let element = this._trackedItemElements.get(String(itemId))
      this._untrackItemElement(itemId)
      if (element) {
        this._setItemProgress(element, null)
      }
    }
    await this._syncFromStorage()
  }

  /**
   * Coordinator post-skip hook (IDB already committed in kernelWriteThrough).
   * @return {Promise<void>}
   * @private
   */
  async _executeSkipTagDiscoveryCoordinator()
  {
    await this._withTagDiscoveryActionInFlight(async () => {
      await this._maybeEndDiscoveryLanePhase()
      this._signalProcessorsWakeAndRunIfCoordinator()
      await this._syncFromStorage()
    })
  }

  /**
   * Wake peers and run processors when this tab is coordinator.
   * @private
   */
  _signalProcessorsWakeAndRunIfCoordinator()
  {
    this._requestPipelineRun()
  }

  /**
   * Open the media page for the item currently under tag review.
   * @return {Promise<void>}
   */
  async openTagDiscoveryMedia()
  {
    let state = await this._getState()
    if (!state.resolutionBlocked || !state.resolutionBlockedItemId) {
      return
    }
    let item = await this._repos().downloadResolutionQueue.get(state.resolutionBlockedItemId)
    let openUrl = item?.sourceUrl
        || item?.pendingTagGroups?.metadata?.sourceUrl
        || null
    if (openUrl) {
      Utilities.openUrlInNewTab(openUrl)
    }
  }

  /**
   * @return {Promise<void>}
   */
  async toggleDownloadManagerPaused()
  {
    let state = await this._getState()
    // Linked / interleaved: any HI owns Start. Independent: only download-lane HI.
    if (this._isStartPauseShowingHumanInteraction(state)) {
      void this._promptHumanInteractionResume('download')
      if (this._isLinkQueues()) {
        for (let ctx of this._hiContexts(state)) {
          if (ctx !== 'download') {
            void this._promptHumanInteractionResume(ctx)
          }
        }
      }
      return
    }
    // Start/Pause only affects the download queue — never resolution / tag discovery.
    // Allow toggle while resolution is still in-batch (download may be empty mid-batch).
    let downloadPending = await this._getPendingDownloadCount()
    let resolutionPending = await this._getPendingResolutionCount()
    if (this._isDownloadBatchIdle(downloadPending, resolutionPending)) {
      return
    }
    let nextPaused = !state.paused
    await this.sendCommand({type: 'toggle-paused', payload: {paused: nextPaused}})
    await this._syncFromStorage()
    if (!nextPaused) {
      this._signalProcessorsWakeAndRunIfCoordinator()
    }
  }

  /**
   * Clears pending/in-progress download-queue rows only. Does not touch the resolution
   * queue, tag discovery, selection mode, or processor leadership.
   * @return {Promise<void>}
   */
  async clearDownloadQueue()
  {
    await this.sendCommand({type: 'clear-download-queue', payload: {}})
    this._hideHumanInteractionPanel('download')
    await this._syncFromStorage()
    this._refreshSelectionMarks()
    if (this._shouldRunProcessors()) {
      void this._runProcessors()
    }
  }

  /**
   * @return {Promise<{resolution: {current: number, total: number}, download: {current: number, total: number}}>}
   */
  async getDownloadManagerProgress()
  {
    let downloadActive = await this._getPendingDownloadCount()
    let resolutionActive = await this._getPendingResolutionCount()
    let state = await this._getState()
    let completedResolution = Number(state.completedResolutionCount) || 0
    let completedDownload = Number(state.completedDownloadCount) || 0
    return {
      resolution: {
        current: completedResolution,
        total: completedResolution + resolutionActive,
      },
      download: {
        current: completedDownload,
        total: completedDownload + downloadActive,
      },
    }
  }

  /**
   * @return {Promise<number>}
   * @private
   */
  async _getPendingDownloadCount()
  {
    return this._repos().downloadQueue.countActive()
  }

  /**
   * @return {number}
   */
  getPendingResolutionCountSync()
  {
    return this._pendingResolutionCountAtom?.value ?? 0
  }

  /**
   * @return {Promise<number>}
   * @private
   */
  async _getPendingResolutionCount()
  {
    return this._repos().downloadResolutionQueue.countActive()
  }

  /**
   * Non-terminal items in both queues (item progress / internal pipeline checks).
   * @return {Promise<number>}
   */
  async getPendingPipelineCount()
  {
    return this._getPendingPipelineCount()
  }

  /**
   * @return {Promise<number>}
   * @private
   */
  async _getPendingPipelineCount()
  {
    let downloadCount = await this._getPendingDownloadCount()
    let resolutionCount = await this._getPendingResolutionCount()
    return resolutionCount + downloadCount
  }

  /**
   * @return {number}
   */
  getPendingDownloadCountSync()
  {
    if (typeof this._pendingDownloadCountAtom?.read === 'function') {
      return this._pendingDownloadCountAtom.read()
    }
    return this._pendingDownloadCountAtom?.value ?? 0
  }

  /**
   * @return {Promise<number>}
   */
  async getDownloadQueueCount()
  {
    let downloadCount = await this._getPendingDownloadCount()
    if (this._isTagDiscoveryActive()) {
      return downloadCount
    }
    let resolutionCount = await this._getPendingResolutionCount()
    return resolutionCount + downloadCount
  }

  /**
   * @return {HTMLElement}
   */
  getOrCreateProgressSlot()
  {
    // Prefer the node currently mounted in the dock — slide-out remounts can leave
    // `_progressElement` pointing at a detached panel while the rail shows a stale copy.
    let live = document.querySelector('.bv-dock .bv-dock-progress-panel')
    if (live) {
      this._progressElement = live
      return this._progressElement
    }
    if (this._progressElement != null && this._isProgressSlotConnected(this._progressElement)) {
      return this._progressElement
    }
    this._progressElement = BrazenViewLayer.createDownloadManagerProgressSlot()
    return this._progressElement
  }

  /**
   * @param {HTMLElement|null|undefined} slot
   * @return {boolean}
   * @private
   */
  _isProgressSlotConnected(slot)
  {
    return !!(slot && slot.isConnected)
  }

  /**
   * Rebuild Start/Pause slide-out so a detached progress panel can remount.
   * @private
   */
  _remountDownloadStartPauseSlot()
  {
    let field = this._cm.getField(DOCK_DOWNLOAD_START_PAUSE)
    if (!field?.dockElement) {
      return
    }
    this._cm._refreshDockRootSlot(field)
  }

  /**
   * @return {Promise<void>}
   */
  async toggleTagDiscoveryMode()
  {
    if (!this._config.tagDiscovery) {
      return
    }
    await this._commitCoordinatorDmState((state) => {
      state.tagDiscoveryEnabled = !state.tagDiscoveryEnabled
    })
    this._framework.refreshDockInterface(undefined, {layout: false})
  }

  /**
   * @return {void}
   */
  toggleSelectionMode()
  {
    if (!this.isDownloadPageRole('selection')) {
      return
    }
    this._selectionModeActive = !this._selectionModeActive
    this._framework.refreshDockInterface(undefined, {layout: false})
    this._bindSelectionHandlers()
    this.registerSelectionDragGuard()
    if (!this._selectionModeActive) {
      this._clearAllItemProgress()
      return
    }
    this._refreshSelectionMarks()
  }

  /**
   * Enqueue every visible search tile not already in the resolution/download pipeline.
   * @return {Promise<void>}
   */
  async selectAllVisibleItems()
  {
    if (!this.isDownloadManagerEnabled() || !this.isDownloadPageRole('selection') ||
        !this._isSelectionModeActive()) {
      return
    }
    let pageConfig = this._getActivePageConfig()
    if (!pageConfig?.itemSelector || !pageConfig?.resolveItem) {
      return
    }
    for (let element of document.querySelectorAll(pageConfig.itemSelector)) {
      let resolved = Utilities.callEventHandler(pageConfig.resolveItem, [element], null)
      if (!resolved?.itemId) {
        continue
      }
      let itemKey = String(resolved.itemId)
      if (this._trackedItemElements.has(itemKey) || this._selectionUiPending.has(itemKey)) {
        continue
      }
      if (await this.isQueued(resolved.itemId)) {
        continue
      }
      await this._enqueueSelectionItem(resolved, element)
    }
    this._refreshSelectionMarks()
  }

  /**
   * @return {Promise<void>}
   */
  async toggleCurrentMediaQueued()
  {
    if (!this.isDownloadPageRole('enqueueMedia')) {
      return
    }
    let itemId = this._config.getQueueItemId({sourceUrl: location.href})
    if (!itemId) {
      return
    }
    let wasQueued = await this.isQueued(itemId)
    // Optimistic chrome so the click always flips icon/active before IDB settles.
    this._setCurrentMediaQueued(!wasQueued)
    this._paintCurrentMediaQueueDockButton()
    if (wasQueued) {
      // Stop an in-flight claim from rewriting the row after remove.
      if (String(this._activeResolutionItemId) === String(itemId)
          || String(this._activeDownloadItemId) === String(itemId)) {
        this._activeResolutionItemId = null
        this._activeDownloadItemId = null
      }
      await this.dequeueDownload(itemId)
    } else {
      await this.enqueueDownload({
        itemId,
        sourceUrl: location.href,
        downloadType: this._getActivePageConfig()?.defaultDownloadType ?? 'post',
        resolveContext: {fromMediaPage: true},
      })
    }
    // Authoritative re-read — dequeue/enqueue sync must not leave a stale active icon.
    this._setCurrentMediaQueued(await this.isQueued(itemId))
    this._paintCurrentMediaQueueDockButton()
  }

  /**
   * Paint Add/Remove queue dock chrome from {@link _currentMediaQueued}.
   * @private
   */
  _paintCurrentMediaQueueDockButton()
  {
    let field = this._cm.getField(DOCK_ADD_TO_QUEUE)
    if (!field?.dockElement) {
      this._framework.refreshDockInterface(undefined, {layout: false})
      return
    }
    this._cm._updateDockFieldButton(field)
  }

  /**
   * Refresh sync cache for the media-page Add/Remove queue dock button.
   * @param {{refreshDock?: boolean}} [options]
   * @return {Promise<void>}
   * @private
   */
  async _refreshCurrentMediaQueueState(options = {})
  {
    if (!this.isDownloadPageRole('enqueueMedia')) {
      this._setCurrentMediaQueued(false)
      if (options.refreshDock !== false) {
        this._paintCurrentMediaQueueDockButton()
      }
      return
    }
    let itemId = this._config.getQueueItemId?.({sourceUrl: location.href})
    if (!itemId) {
      this._setCurrentMediaQueued(false)
    } else {
      this._setCurrentMediaQueued(await this.isQueued(itemId))
    }
    if (options.refreshDock !== false) {
      this._paintCurrentMediaQueueDockButton()
    }
  }

  /**
   * Whether the active DM page config lists `role` in `roles`.
   * Known roles: `selection`, `enqueueMedia`, `tagDiscoveryToggle`,
   * `dashboard` (dedicated control / future-analytics host — does not grant page-oriented
   * queue actions and does not auto-claim processor leadership).
   * @param {string} role
   * @return {boolean}
   */
  isDownloadPageRole(role)
  {
    let pageName = this._getActivePageName()
    if (!pageName) {
      return false
    }
    let pageConfig = this._config.pages?.[pageName]
    return !!(pageConfig?.roles?.includes(role))
  }

  /**
   * @return {boolean}
   */
  isDownloadManagerEnabled()
  {
    if (!this._cm.isDockActive()) {
      return false
    }
    if (this._config.enabled === true) {
      return true
    }
    let enableKey = this._config.enableConfigKey ?? OPTION_ENABLE_DOWNLOAD_MANAGER
    return !!this._framework._getConfig(enableKey)
  }

  /**
   * @return {boolean}
   */
  isDownloadManagerRunning()
  {
    if (!this.isDownloadManagerEnabled()) {
      return false
    }
    let state = this.readDmState()
    if (!state) {
      return false
    }
    if (this._processing || this._resolutionProcessing || this._downloadProcessing) {
      return true
    }
    if (this._anyHumanInteraction(state) || state.resolutionBlocked) {
      return true
    }
    if (!state.resolutionBlocked && this.getPendingResolutionCountSync() > 0) {
      return true
    }
    if (!state.paused && this.getPendingDownloadCountSync() > 0) {
      return true
    }
    return false
  }

  /**
   * True when this tab holds the Web Locks script coordinator role.
   * @return {boolean}
   */
  isCoordinator()
  {
    return this._kernel?.isCoordinator() ?? false
  }

  /**
   * Send a Reactor Command: coordinator dispatches locally; followers publish to the bus.
   * Followers apply optimistic dm.state patches until a matching authoritative seq arrives.
   * @param {object} command
   * @return {Promise<void>}
   */
  async sendCommand(command)
  {
    if (!command || typeof command.type !== 'string') {
      throw new Error('BrazenDownloadManager.sendCommand: command.type required')
    }
    if (!this._ensureReactor() || !this._reactorBus) {
      throw new Error('BrazenDownloadManager.sendCommand: Reactor bus not ready')
    }
    if (this.isCoordinator()) {
      await this._kernel.dispatch(command)
      return
    }
    this._applyFollowerCommandOptimistic(command)
    this._reactorBus.publish({
      kind: 'command',
      tabId: this._reactorBus.tabId,
      command,
    })
  }

  /**
   * @param {object} command
   * @private
   */
  _applyFollowerCommandOptimistic(command)
  {
    let patches = this._optimisticPatchesForCommand(command)
    if (!patches.length) {
      return
    }
    this._followerPendingCommands.push({
      paths: patches.map((patch) => patch.path),
      values: Object.fromEntries(patches.map((patch) => [patch.path, structuredClone(patch.value)])),
      sinceSeq: this._lastAppliedPatchSeq,
    })
    this._setLocalEditsDirty(true)
    let state = this.readDmState()
    let pendingDownload = null
    let pendingResolution = null
    for (let patch of patches) {
      if (patch.path === 'dm.pendingDownloadCount') {
        pendingDownload = patch.value
        continue
      }
      if (patch.path === 'dm.pendingResolutionCount') {
        pendingResolution = patch.value
        continue
      }
      if (!patch.path?.startsWith('dm.state.')) {
        continue
      }
      if (!state) {
        continue
      }
      let key = patch.path.slice('dm.state.'.length)
      if (key && key !== 'snapshot') {
        state[key] = structuredClone(patch.value)
      }
    }
    if (state) {
      this._publishDmStateToAtoms(state)
    }
    if (pendingDownload !== null || pendingResolution !== null) {
      this._commitPendingCountSignals(
          pendingDownload ?? this.getPendingDownloadCountSync(),
          pendingResolution ?? this.getPendingResolutionCountSync(),
      )
    }
    if (!this._documentSuspended && document.visibilityState === 'visible') {
      this._requestDockProgressPaint({refreshAllItems: false})
    }
  }

  /**
   * @param {object} command
   * @return {object[]}
   * @private
   */
  _optimisticPatchesForCommand(command)
  {
    switch (command.type) {
      case 'toggle-paused':
        return [{
          path: 'dm.state.paused',
          value: Boolean(command.payload?.paused),
        }]
      case 'enqueue-download': {
        let resolution = this.getPendingResolutionCountSync() + 1
        return [{path: 'dm.pendingResolutionCount', value: resolution}]
      }
      case 'dequeue-download': {
        let resolution = Math.max(0, this.getPendingResolutionCountSync() - 1)
        let download = Math.max(0, this.getPendingDownloadCountSync() - 1)
        return [
          {path: 'dm.pendingResolutionCount', value: resolution},
          {path: 'dm.pendingDownloadCount', value: download},
        ]
      }
      case 'clear-download-queue':
      case 'confirm-tag-discovery':
      case 'skip-tag-discovery':
        return []
      case 'claim-tag-discovery-panel':
        return [{
          path: 'dm.state.tagDiscoveryPanelTabId',
          value: command.payload?.tabId ?? null,
        }]
      case 'claim-hi-prompt': {
        let state = this.readDmState()
        if (!state) {
          return []
        }
        let ctx = command.payload?.context === 'download' ? 'download' : 'resolution'
        let hi = structuredClone(state.humanInteraction ?? {})
        let lane = hi[ctx]
        if (!lane || typeof lane !== 'object') {
          return []
        }
        lane.promptTabId = command.payload?.tabId ?? null
        return [{path: 'dm.state.humanInteraction', value: hi}]
      }
      default:
        return []
    }
  }

  /**
   * @param {number} seq
   * @param {object[]|null|undefined} patches
   * @private
   */
  _reconcileFollowerPendingCommands(seq, patches)
  {
    if (!this._followerPendingCommands.length || !Number.isFinite(seq)) {
      return
    }
    let patchByPath = new Map(
        (patches ?? []).filter((patch) => patch?.path).map((patch) => [patch.path, patch]),
    )
    let patchedPaths = new Set(patchByPath.keys())
    let hadConflict = false
    this._followerPendingCommands = this._followerPendingCommands.filter((pending) => {
      if (seq <= pending.sinceSeq) {
        return true
      }
      if (!pending.paths.every((path) => patchedPaths.has(path))) {
        return true
      }
      for (let path of pending.paths) {
        let patch = patchByPath.get(path)
        let optimistic = pending.values?.[path]
        if (patch && optimistic !== undefined &&
            JSON.stringify(patch.value) !== JSON.stringify(optimistic)) {
          hadConflict = true
        }
      }
      return false
    })
    if (hadConflict && !this._documentSuspended && document.visibilityState === 'visible') {
      this._requestDockProgressPaint({refreshAllItems: true})
      void this._syncPendingCountSignalsFromIdb()
    }
    if (!this._followerPendingCommands.length) {
      this._setLocalEditsDirty(false)
    }
  }

  /**
   * @param {boolean} dirty
   * @private
   */
  _setLocalEditsDirty(dirty)
  {
    this._localEditsDirtyAtom?.write?.(!!dirty) ??
        this._localEditsDirtyAtom?.set?.(!!dirty)
  }

  /**
   * Reactor-native sync read helper (v2 §1).
   * @return {object}
   */
  readDmState()
  {
    if (typeof this._dmStateAtom?.read === 'function') {
      return this._dmStateAtom.read() ?? {}
    }
    return this._dmStateAtom?.value ?? {}
  }

  /**
   * @return {string}
   * @private
   */
  _coordinatorStickyStorageKey()
  {
    return BrazenDownloadManager.storageKey(this._cm._scriptPrefix, 'coordinator-sticky')
  }

  /**
   * @private
   */
  _markCoordinatorSticky()
  {
    try {
      sessionStorage.setItem(this._coordinatorStickyStorageKey(), '1')
    } catch (_error) {
      // ignore unavailable sessionStorage
    }
  }

  /**
   * @private
   */
  _clearCoordinatorSticky()
  {
    try {
      sessionStorage.removeItem(this._coordinatorStickyStorageKey())
    } catch (_error) {
      // ignore unavailable sessionStorage
    }
  }

  /**
   * @return {boolean}
   * @private
   */
  _wasCoordinatorSticky()
  {
    try {
      return sessionStorage.getItem(this._coordinatorStickyStorageKey()) === '1'
    } catch (_error) {
      return false
    }
  }

  /**
   * Try to become the script coordinator tab (config + downloads).
   * Steal is allowed while work is active — the prior coordinator aborts via AbortSignal.
   * @param {{steal?: boolean, ifAvailable?: boolean}} [options]
   * @return {Promise<boolean>}
   */
  async requestCoordinatorRole(options = {steal: true})
  {
    if (this._documentSuspended || !this._cm.canPersist() || !this.isDownloadManagerEnabled()) {
      return false
    }
    if (this._isQueueVerificationTab()) {
      return false
    }
    if (!this._ensureReactor() || !this._kernel) {
      return false
    }
    if (this.isCoordinator()) {
      return true
    }
    this._commitPendingCountSignals(
        await this._getPendingDownloadCount(),
        await this._getPendingResolutionCount(),
    )
    this._publishDmStateToAtoms(await this._getState())
    let claimed = await this._kernel.requestCoordinatorRole(options)
    if (!claimed) {
      return false
    }
    this._cm.refreshDockButtonStates()
    this._signalProcessorsWakeAndRunIfCoordinator()
    return this.isCoordinator()
  }


  /**
   * Consumer tag normalizer for filename ignore / substitution lookups (same as attribute actions).
   * @return {function(string): string}
   * @private
   */
  _getDownloadTagNormalize()
  {
    let fromResolver = null
    try {
      fromResolver = this._config.downloadPaths?.getPatternResolver?.()?.normalizeTag
    } catch (error) {
      fromResolver = null
    }
    if (typeof fromResolver === 'function') {
      return fromResolver
    }
    let discovery = this._config.tagDiscovery ?? {}
    let fromActions = discovery.actions?.normalize ?? discovery.normalizeTag
    if (typeof fromActions === 'function') {
      return fromActions
    }
    return (value) => String(value ?? '').trim()
  }

  // -------------------------------------------------------------------------
  // Path creation
  // -------------------------------------------------------------------------

  /**
   * @param {string} text
   * @return {string}
   */
  decodeHtmlEntities(text)
  {
    return Utilities.decodeHtmlEntities(text)
  }

  /**
   * @param {string} segment
   * @return {string}
   */
  sanitizePathSegment(segment)
  {
    return this.decodeHtmlEntities(String(segment)).
        replace(/[<>:"/\\|?*]/g, '-').
        replace(/[.\s]+$/, '').
        trim()
  }

  /**
   * @param {string} folder
   * @param {string} name
   * @return {string}
   */
  buildDownloadPath(folder, name)
  {
    let folderPath = String(folder).split('/').
        map((segment) => this.sanitizePathSegment(segment)).
        filter((segment) => segment.length).
        join('/').
        substring(0, 120).
        replace(/[.\s/]+$/, '')

    let fileName = this.sanitizePathSegment(name) || 'media'
    return folderPath ? folderPath + '/' + fileName : fileName
  }

  /**
   * Prefetch tag registry rows needed for filename ignore / substitutions.
   * @param {{}} tagGroups
   * @return {Promise<void>}
   */
  async _ensureDownloadPathTagsLoaded(tagGroups)
  {
    let tagRuntime = this._cm.getTagRuntime()
    if (!tagRuntime) {
      return
    }
    let normalize = this._getDownloadTagNormalize()
    let stripCharacterSeries = false
    try {
      stripCharacterSeries = !!this._config.downloadPaths?.getPatternResolver?.()?.stripCharacterSeries
    } catch (error) {
      stripCharacterSeries = false
    }
    let names = []
    for (let group of Object.values(tagGroups ?? {})) {
      if (Array.isArray(group)) {
        for (let name of group) {
          if (!name) {
            continue
          }
          names.push(name)
          let normalized = normalize(name)
          if (normalized && normalized !== name) {
            names.push(normalized)
          }
          if (stripCharacterSeries && normalized) {
            let stripped = normalized.replace(/_\([^)]*\)$/, '')
            if (stripped && stripped !== normalized) {
              names.push(stripped)
            }
          }
        }
      }
    }
    if (names.length) {
      await tagRuntime.ensureNames(names)
    } else {
      await tagRuntime.warmCache()
    }
    try {
      await tagRuntime.refreshDownloadRulesetMaps?.()
    } catch (error) {
      // Join / ignore review fall back to ruleset reads when maps are unavailable.
    }
  }

  /**
   * @param {{}} data
   * @param {{}} tagGroups
   * @return {string}
   */
  buildDownloadPathFromPatterns(data, tagGroups)
  {
    let paths = this._config.downloadPaths
    let resolver = paths.getPatternResolver()
    let filenamePattern = this._framework._getConfig(paths.filenamePatternConfigKey)
    let name = this._resolveDownloadPattern(filenamePattern, data, tagGroups, resolver)
    if (!name) {
      name = paths.nameFallback?.(data) ?? (data.md5 || data.id || 'media')
    }
    if (paths.appendExtension && data.ext) {
      name += '.' + data.ext
    }

    let root = this._framework._getConfig(paths.folderConfigKey) || paths.defaultFolder || ''
    let subfolderPattern = paths.subfolderPatternConfigKey ?
        this._framework._getConfig(paths.subfolderPatternConfigKey) :
        ''
    let subfolder = subfolderPattern ? this._resolveDownloadPattern(subfolderPattern, data, tagGroups, resolver) : ''
    let folder = subfolder ? root + '/' + subfolder : root
    return this.buildDownloadPath(folder, name)
  }

  /**
   * True when {@link pattern} references a chip via label, {label}, or {token}.
   * @param {string} pattern
   * @param {{token?: string, label?: string}} chip
   * @return {boolean}
   */
  patternIncludesChip(pattern, chip)
  {
    if (!pattern || !chip) {
      return false
    }
    let needles = []
    if (chip.label) {
      needles.push(chip.label, `{${chip.label}}`)
    }
    if (chip.token) {
      needles.push(`{${chip.token}}`)
    }
    for (let needle of needles) {
      if (needle && pattern.includes(needle)) {
        return true
      }
    }
    return false
  }

  /**
   * @param {string[]|{subject: string, replacement: string}[]} lines
   * @param {function(string): string} normalizeToken
   * @return {{subject: string, replacement: string}[]}
   */
  parseDownloadTagSubstitutionLines(lines, normalizeToken)
  {
    let rules = []
    for (let line of lines) {
      if (typeof line === 'object' && line?.subject && line?.replacement) {
        let subject = normalizeToken(line.subject)
        let replacement = normalizeToken(line.replacement)
        if (subject.length && replacement.length) {
          rules.push({subject, replacement})
        }
        continue
      }
      if (typeof line !== 'string') {
        continue
      }
      let match = line.match(/^(.+?)\s*(?:→|->|-)\s+(.+)$/)
      if (!match) {
        continue
      }
      let subject = normalizeToken(match[1])
      let replacement = normalizeToken(match[2])
      if (subject.length && replacement.length) {
        rules.push({subject, replacement})
      }
    }
    return rules
  }

  /**
   * @param {{subject: string, replacement: string}[]} rules
   * @return {Map<string, string>}
   */
  buildDownloadTagSubstitutionMap(rules)
  {
    let map = new Map()
    for (let rule of rules) {
      map.set(rule.subject, rule.replacement)
    }
    return map
  }

  /**
   * @param {string} pattern
   * @param {{}} data
   * @param {{}} tagGroups
   * @param {object} resolver
   * @return {string}
   * @private
   */
  _resolveDownloadPattern(pattern, data, tagGroups, resolver)
  {
    let resolved = pattern
    let chips = [...resolver.chips].sort((a, b) => b.label.length - a.label.length)
    for (let chip of chips) {
      let value
      if (resolver.tagTypes.includes(chip.token)) {
        value = this._resolveDownloadTagTypeTokenForPath(pattern, tagGroups[chip.token] ?? [], chip.token, resolver.ignore, resolver)
      } else {
        value = data[chip.token] ?? ''
      }
      resolved = resolved.replaceAll(chip.label, value)
    }
    return resolved.replace(/\s+/g, ' ').trim()
  }

  /**
   * @param {string} pattern
   * @param {string[]} tags
   * @param {string} type
   * @param {Set<string>} ignore
   * @param {object} resolver
   * @return {string}
   * @private
   */
  _resolveDownloadTagTypeTokenForPath(pattern, tags, type, ignore, resolver)
  {
    let value = this._joinTagsForDownloadPath(tags, type, ignore, resolver)
    if (!value && this._patternEmploysDownloadTagTypeToken(pattern, type, resolver.chips, resolver.unknownTypes)) {
      return resolver.unknownDefault
    }
    return value
  }

  /**
   * @param {string} pattern
   * @param {string} type
   * @param {{token: string, label: string}[]} chips
   * @param {Set<string>} unknownTypes
   * @return {boolean}
   * @private
   */
  _patternEmploysDownloadTagTypeToken(pattern, type, chips, unknownTypes)
  {
    if (!unknownTypes.has(type)) {
      return false
    }
    for (let chip of chips) {
      if (chip.token === type && this.patternIncludesChip(pattern, chip)) {
        return true
      }
    }
    return false
  }

  /**
   * @param {string[]} tags
   * @param {string|null} type
   * @param {Set<string>|null} ignore
   * @param {object} options
   * @return {string}
   * @private
   */
  _joinTagsForDownloadPath(tags, type, ignore, options)
  {
    let normalize = typeof options.normalizeTag === 'function' ?
        options.normalizeTag :
        this._getDownloadTagNormalize()
    tags = (Array.isArray(tags) ? tags : []).
        map((tag) => normalize(tag)).
        filter(Boolean)
    let applyFilenameIgnore = options.applyFilenameIgnore !== false
    if (options.tagRuntime?._warmed) {
      tags = options.tagRuntime.applyDownloadAttributesSync(
          tags, options.stripCharacterSeries, applyFilenameIgnore)
    } else {
      tags = this._applyDownloadTagSubstitutions(tags, type, options.substitutions, options.stripCharacterSeries)
      if (applyFilenameIgnore && ignore?.size) {
        tags = tags.filter((tag) => {
          if (ignore.has(tag) || ignore.has(normalize(tag))) {
            return false
          }
          if (options.stripCharacterSeries && type === 'character') {
            let stripped = tag.replace(/_\([^)]*\)$/, '')
            if (stripped !== tag && (ignore.has(stripped) || ignore.has(normalize(stripped)))) {
              return false
            }
          }
          return true
        })
      }
    }

    let seen = new Set()
    let unique = []
    for (let tag of tags) {
      let formatted = this._formatTagForDownloadPath(tag, type, options.stripCharacterSeries)
      if (!formatted || seen.has(formatted)) {
        continue
      }
      seen.add(formatted)
      unique.push(formatted)
    }
    return unique.
        sort((left, right) => left.localeCompare(right, undefined, {sensitivity: 'base'})).
        join(options.multiTagSeparator)
  }

  /**
   * @param {string[]} tags
   * @param {string|null} type
   * @param {Map<string, string>} substitutions
   * @param {boolean} stripCharacterSeries
   * @return {string[]}
   * @private
   */
  _applyDownloadTagSubstitutions(tags, type, substitutions, stripCharacterSeries)
  {
    if (!substitutions.size) {
      return tags
    }
    return tags.map((tag) => this._resolveDownloadTagSubstitution(tag, type, substitutions, stripCharacterSeries))
  }

  /**
   * @param {string} tag
   * @param {string|null} type
   * @param {Map<string, string>} substitutions
   * @param {boolean} stripCharacterSeries
   * @return {string}
   * @private
   */
  _resolveDownloadTagSubstitution(tag, type, substitutions, stripCharacterSeries)
  {
    if (substitutions.has(tag)) {
      return substitutions.get(tag)
    }
    if (type === 'character' && stripCharacterSeries) {
      let stripped = tag.replace(/_\([^)]*\)$/, '')
      if (stripped !== tag && substitutions.has(stripped)) {
        return substitutions.get(stripped)
      }
    }
    return tag
  }

  /**
   * @param {string} tag
   * @param {string|null} type
   * @param {boolean} stripCharacterSeries
   * @return {string}
   * @private
   */
  _formatTagForDownloadPath(tag, type, stripCharacterSeries)
  {
    let formatted = this.decodeHtmlEntities(tag)
    if (type === 'character' && stripCharacterSeries) {
      formatted = formatted.replace(/_\([^)]*\)$/, '')
    }
    return formatted.replaceAll('_', ' ')
  }

  // -------------------------------------------------------------------------
  // Download execution
  // -------------------------------------------------------------------------

  /**
   * @param {{name: string|null, element: HTMLElement|null, url: string, downloadId?: string|null, restorePictureOnFailure?: boolean}} download
   * @return {Promise<void>}
   * @private
   */
  _wrapDownloadTask(download)
  {
    let path = download.name
    if (!path) {
      alert('Download failed: missing file path.')
      return Promise.resolve()
    }

    return new Promise((resolve) => {
      download.element?.remove()
      GM_download({
        url: download.url,
        name: path,
        // Filenames come from user patterns — never uniquify (no file(1).ext).
        conflictAction: 'overwrite',
        onload: () => {
          this._framework._handleDownloadSucceeded(download)
          resolve()
        },
        onerror: (error) => {
          this._framework._handleDownloadFailed(download, error)
          resolve()
        },
      })
    })
  }

  /**
   * @param {object} item
   * @return {Promise<void>}
   * @private
   */
  async _processDownloadItem(item)
  {
    try {
      // Status is already `downloading` from `_claimDownloadQueueItem`.

      let payload = item.resolvedPayload ?? {}
      await this._ensureDownloadPathTagsLoaded(payload.tagGroups ?? {})
      let path = this.buildDownloadPathFromPatterns(payload.data ?? {}, payload.tagGroups ?? {})
      let downloadUrl = payload.mediaUrl ?? ''
      let downloadId = payload.downloadId ?? item.downloadId ?? item.itemId

      // Claim once per queue item. Retries after lock steal / requeue must not re-claim —
      // the ledger is reserved before GM_download (Tampermonkey often never fires onload).
      if (!item.ledgerClaimed) {
        // IndexedDB membership (bounded positive cache + postId index) — never getAll the ledger.
        if (await this._framework._isDownloadDuplicateAsync(downloadId)) {
          item.status = 'duplicate'
          if (await this._commitDownloadRow(item)) {
            await this._incrementDownloadProgress()
          }
          return
        }
        if (downloadId && this._framework._shouldClaimDownloadDuplicateLedger() &&
            !(await this._framework._claimDownloadDuplicateLedgerSlot(downloadId))) {
          item.status = 'duplicate'
          if (await this._commitDownloadRow(item)) {
            await this._incrementDownloadProgress()
          }
          return
        }
        if (this._framework._shouldClaimDownloadDuplicateLedger() && downloadId) {
          item.ledgerClaimed = true
          if (!(await this._commitDownloadRow(item))) {
            return
          }
        }
      }

      if (!downloadUrl) {
        item.status = 'failed'
        item.error = 'Missing media URL'
        if (await this._commitDownloadRow(item)) {
          await this._incrementDownloadProgress()
        }
        return
      }

      try {
        await this._paceDownloadInitiation()
        if (!(await this._isActiveDownloadClaim(item.itemId))) {
          return
        }
        // Persist before GM_download so a mid-transfer navigation can detect an orphan.
        item.inProgress = true
        item.startedAt = Date.now()
        if (!(await this._commitDownloadRow(item))) {
          return
        }
        await this._wrapDownloadTask({name: path, element: null, url: downloadUrl, downloadId})
        item.status = 'done'
        item.inProgress = false
      } catch (error) {
        item.inProgress = false
        let rateLimit = await this._handleRateLimit('download', error, {sourceUrl: downloadUrl, resolvingUrl: downloadUrl}, item)
        if (rateLimit === 'blocked') {
          item.status = 'queued'
          item.error = null
          await this._commitDownloadRow(item)
          return
        }
        item.status = 'failed'
        item.error = String(error)
      }
      if (await this._commitDownloadRow(item)) {
        await this._incrementDownloadProgress()
      }
    } finally {
      await this._onPipelineTaskComplete(item.itemId)
    }
  }

  // -------------------------------------------------------------------------
  // Resolution pipeline
  // -------------------------------------------------------------------------

  /**
   * @param {string} url
   * @return {Promise<Document>}
   * @private
   */
  async _resolvePage(url)
  {
    let controller = new AbortController()
    let timeoutId = setTimeout(() => controller.abort(), 30000)
    let onPageHide = () => controller.abort()
    let onCoordinatorAbort = () => controller.abort()
    window.addEventListener('pagehide', onPageHide, {once: true})
    this._kernel?.abortSignal?.addEventListener('abort', onCoordinatorAbort, {once: true})
    try {
      if (this._documentSuspended || this._kernel?.abortSignal?.aborted) {
        throw Object.assign(new Error('Page resolution aborted'), {status: 0, doc: null, url})
      }
      let response = await fetch(url, {
        credentials: 'include',
        signal: controller.signal,
        headers: {Accept: 'text/html'},
      })
      if (this._documentSuspended || this._kernel?.abortSignal?.aborted) {
        throw Object.assign(new Error('Page resolution aborted'), {status: 0, doc: null, url})
      }
      let text = await response.text()
      let doc = new DOMParser().parseFromString(text, 'text/html')
      // Keep status on reject so timedReload / humanInteraction detectors can use it;
      // still attach parsed HTML (site 429 / Cloudflare bodies often arrive as 200 or 4xx).
      if (response.status === 429) {
        throw Object.assign(new Error('HTTP 429'), {status: 429, doc, url})
      }
      if (!response.ok) {
        throw Object.assign(new Error('HTTP ' + response.status), {status: response.status, doc, url})
      }
      return doc
    } catch (error) {
      if (error?.doc !== undefined || error?.status !== undefined) {
        throw error
      }
      let message = error?.name === 'AbortError'
          ? 'Page resolution aborted'
          : ('Page resolution failed: ' + url + ' — ' + (error?.message || error))
      throw Object.assign(new Error(message), {status: 0, doc: null, url})
    } finally {
      clearTimeout(timeoutId)
      window.removeEventListener('pagehide', onPageHide)
      this._kernel?.abortSignal?.removeEventListener('abort', onCoordinatorAbort)
    }
  }

  /**
   * @param {object} item
   * @return {Promise<void>}
   * @private
   */
  async _processResolutionItem(item)
  {
    try {
      // Status is already `resolving` from `_claimResolutionWorkItem`.
      let typeHandler = this._config.downloadTypes?.[item.downloadType]
      if (!typeHandler) {
        item.status = 'failed'
        item.error = 'Unknown download type: ' + item.downloadType
        if (await this._commitResolutionRow(item)) {
          await this._incrementResolutionProgress()
        }
        return
      }

      try {
        let ctx = {
          ...item.resolveContext,
          sourceUrl: item.sourceUrl,
          itemId: item.itemId,
          downloadType: item.downloadType,
          resolvingUrl: item.sourceUrl,
        }

        if (ctx.fromMediaPage && typeHandler.resolveFromMedia) {
          let resolved = await Utilities.callEventHandler(typeHandler.resolveFromMedia, [document, ctx], null)
          await this._handleResolvedPayload(item, resolved)
          return
        }

        let searchStep = typeHandler.resolveFromSearch ?
            await Utilities.callEventHandler(typeHandler.resolveFromSearch, [ctx], null) :
            {nextUrl: item.sourceUrl, downloadType: item.downloadType}
        let nextUrl = searchStep?.nextUrl ?? item.sourceUrl
        ctx.resolvingUrl = nextUrl

        let doc
        while (true) {
          // Pace every attempt (including timedReload retries and post–human-interaction
          // resume) so a long Cloudflare pause cannot leave the clock stale relative to
          // the next burst of resolves.
          await this._paceResolutionInitiation()
          if (!(await this._isActiveResolutionClaim(item.itemId))) {
            return
          }
          try {
            doc = await this._resolvePage(nextUrl)
          } catch (error) {
            let rateLimit = await this._handleRateLimit('resolution', error, ctx, item)
            if (rateLimit === 'retry') {
              continue
            }
            if (rateLimit === 'blocked') {
              item.status = 'queued'
              await this._commitResolutionRow(item)
              return
            }
            throw error
          }

          let rateLimit = await this._handleRateLimit('resolution', doc, ctx, item)
          if (rateLimit === 'retry') {
            continue
          }
          if (rateLimit === 'blocked') {
            item.status = 'queued'
            await this._commitResolutionRow(item)
            return
          }
          break
        }

        if (typeHandler.resolveFromMedia) {
          let resolved = await Utilities.callEventHandler(typeHandler.resolveFromMedia, [doc, ctx], null)
          if (resolved?.childUrls?.length) {
            await this._fanOutChildUrls(item, resolved.childUrls, resolved.downloadType ?? 'post')
            item.status = 'done'
            if (await this._removeResolutionRowIfActive(item.itemId)) {
              await this._incrementResolutionProgress()
            }
            return
          }
          await this._handleResolvedPayload(item, resolved)
          return
        }

        item.status = 'failed'
        item.error = 'No resolveFromMedia handler'
        if (await this._commitResolutionRow(item)) {
          await this._incrementResolutionProgress()
        }
      } catch (error) {
        item.status = 'failed'
        item.error = String(error?.message ?? error)
        if (await this._commitResolutionRow(item)) {
          await this._incrementResolutionProgress()
        }
      }
    } finally {
      await this._onPipelineTaskComplete(item.itemId)
    }
  }

  /**
   * @param {object} item
   * @param {object|null} resolved
   * @return {Promise<void>}
   * @private
   */
  async _handleResolvedPayload(item, resolved)
  {
    if (!resolved?.mediaUrl) {
      item.status = 'failed'
      item.error = 'Resolution produced no media URL'
      if (await this._commitResolutionRow(item)) {
        await this._incrementResolutionProgress()
      }
      return
    }

    let outcome = await this._applyTagDiscoveryOutcome(item, resolved)
    if (outcome !== 'noop') {
      return
    }

    if (!(await this._promoteToDownloadQueue(item, resolved))) {
      return
    }
    if (!(await this._removeResolutionRowIfActive(item.itemId))) {
      return
    }
    await this._incrementResolutionProgress()
  }

  /**
   * @param {object} item
   * @param {object} resolved
   * @return {Promise<boolean>}
   * @private
   */
  async _promoteToDownloadQueue(item, resolved)
  {
    let itemId = item?.itemId
    if (!itemId) {
      return false
    }
    let promoted = await this._repos().download.promoteToDownload(item, resolved)
    if (!promoted) {
      return false
    }
    this._signalProcessorsWakeAndRunIfCoordinator()
    return true
  }

  /**
   * @param {object} item
   * @param {string[]} childUrls
   * @param {string} childType
   * @return {Promise<void>}
   * @private
   */
  async _fanOutChildUrls(item, childUrls, childType)
  {
    for (let sourceUrl of childUrls) {
      let childId = this._config.getQueueItemId({sourceUrl})
      if (!childId) {
        continue
      }
      await this.enqueueDownload({
        itemId: childId,
        sourceUrl,
        downloadType: childType,
        resolveContext: {parentItemId: item.itemId},
      })
    }
  }

  // -------------------------------------------------------------------------
  // Rate limits
  // -------------------------------------------------------------------------

  /**
   * @param {'resolution'|'download'} context
   * @param {*} signal
   * @param {object} ctx
   * @param {object} item
   * @return {Promise<'retry'|'blocked'|false>}
   * @private
   */
  async _handleRateLimit(context, signal, ctx, item)
  {
    let handlers = this._config.rateLimitHandlers?.[context]
    if (!handlers) {
      return false
    }

    // Prefer humanInteraction over timedReload. Cloudflare challenges often arrive as
    // HTTP 429 with a CAPTCHA body — a bare status===429 must not auto-retry those.
    let payload = signal?.doc ?? signal

    if (handlers.humanInteraction) {
      let detect = handlers.humanInteraction.detect
      let matched = detect ?
          Utilities.callEventHandler(detect, [payload, ctx], false) :
          signal?.status === 429
      if (matched) {
        let openUrl = handlers.humanInteraction.openUrl?.(ctx) ?? ctx.resolvingUrl ?? ctx.sourceUrl
        await this._beginHumanInteractionRateLimit(context, item, openUrl)
        return 'blocked'
      }
    }

    if (handlers.timedReload) {
      let detect = handlers.timedReload.detect
      if (Utilities.callEventHandler(detect, [payload, ctx], false)) {
        let delayMs = typeof handlers.timedReload.delayMs === 'function' ?
            handlers.timedReload.delayMs(ctx) :
            handlers.timedReload.delayMs
        await Utilities.sleep(delayMs ?? 5000)
        return 'retry'
      }
    }

    return false
  }

  // -------------------------------------------------------------------------
  // Human interaction
  // -------------------------------------------------------------------------

  async _beginHumanInteractionRateLimit(context, item, openUrl)
  {
    let openUrlMarked = openUrl ? this._markHumanInteractionOpenUrl(openUrl, context) : null
    // Sync mirror before the user opens the challenge so Phase-1 mediaCloudflare can detect HI.
    this._writeHumanInteractionBlockMirror(true)
    // Only a visible tab may own the Open/Done chrome. Background processor leaders leave
    // `promptTabId` null so a focused follower can steal immediately on wake.
    let canOwnPrompt = document.visibilityState === 'visible' && !this._documentSuspended
    // Serialize through `_commitCoordinatorDmState` so a concurrent heartbeat/progress put cannot wipe
    // initiation clocks (or clear this block) via a stale raw `_putState` snapshot.
    // Per-lane: writing one lane never clobbers the other.
    let began = false
    await this._commitCoordinatorDmState((state) => {
      this._ensureHumanInteractionMap(state)
      if (state.humanInteraction[context]) {
        // Refresh URL / item; do not re-pin ownership from a hidden initiator.
        let existing = state.humanInteraction[context]
        let promptTabId = canOwnPrompt
            ? this._tabId
            : (existing.promptTabId === this._tabId ? null : (existing.promptTabId ?? null))
        state.humanInteraction[context] = {
          itemId: item?.itemId ?? existing.itemId ?? null,
          promptTabId,
          openUrl: openUrlMarked ?? existing.openUrl ?? null,
          at: existing.at ?? Date.now(),
        }
        began = true
        return
      }
      state.humanInteraction[context] = {
        itemId: item?.itemId ?? null,
        promptTabId: canOwnPrompt ? this._tabId : null,
        openUrl: openUrlMarked,
        at: Date.now(),
      }
      began = true
    })
    if (!began) {
      return
    }
    this._refreshAllItemProgress()
    // Show the dock panel only when this tab is focused — do not auto-open the challenge.
    if (canOwnPrompt) {
      await this._promptHumanInteractionResume(context)
    }
    // Wake other visible tabs so a focused follower can steal the panel if this tab
    // stays backgrounded (same pattern as tag discovery).
    this._signalProcessorsWake()
    await this._syncFromStorage()
  }
  
  /**
   * Marks verification URLs so the opened mediaCloudflare tab stays silent
   * (queue confirmation stays on the processor dock panel).
   * Query `brazen_hi=1` (+ `brazen_hi_ctx`) plus hash — CF often replaceState-strips
   * search params but leaves the hash.
   * @param {string} url
   * @param {BrazenHumanInteractionLaneId} [context]
   * @return {string}
   * @private
   */
  _markHumanInteractionOpenUrl(url, context = null)
  {
    try {
      let parsed = new URL(url, location.href)
      parsed.searchParams.set('brazen_hi', '1')
      if (context === 'resolution' || context === 'download') {
        parsed.searchParams.set('brazen_hi_ctx', context)
      }
      let hashParts = ['brazen_hi=1']
      if (context === 'resolution' || context === 'download') {
        hashParts.push('brazen_hi_ctx=' + context)
      }
      if (!/(^|[&#])brazen_hi=1\b/.test(parsed.hash)) {
        parsed.hash = hashParts.join('&')
      } else if (context && !/(^|[&#])brazen_hi_ctx=/.test(parsed.hash)) {
        parsed.hash = parsed.hash.replace(/^#/, '') + '&brazen_hi_ctx=' + context
      }
      return parsed.href
    } catch {
      return url
    }
  }
  
  /**
   * Open the verification tab while keeping `window.opener` set.
   * Chrome 88+ treats `window.open(url, '_blank')` as `noopener` by default, which
   * made queue CF tabs look "standalone" and revived the old grey confirm dialog.
   * @param {string} url
   * @private
   */
  _openHumanInteractionTab(url)
  {
    Utilities.openUrlInNewTab(url, {rel: 'opener'})
  }
  
  /**
   * @return {string}
   * @private
   */
  _humanInteractionBlockMirrorKey()
  {
    return BrazenDownloadManager.storageKey(this._cm._scriptPrefix, 'dm-human-interaction-blocked')
  }
  
  /**
   * @param {boolean} blocked
   * @private
   */
  _writeHumanInteractionBlockMirror(blocked)
  {
    try {
      let key = this.humanInteractionBlockMirrorKey()
      if (blocked) {
        localStorage.setItem(key, '1')
      } else {
        localStorage.removeItem(key)
      }
    } catch (e) {
      // ignore quota / private-mode localStorage failures
    }
  }
  
  isHumanInteractionBlockedSync()
  {
    return this._anyHumanInteraction(this.readDmState())
  }
  
  async handleMediaCloudflarePage(options = {})
  {
    let scriptPrefix = BrazenDownloadManager.storageKey(this._cm?._scriptPrefix, '')
    if (await BrazenDownloadManager.shouldSilenceMediaCloudflarePrompt(scriptPrefix, this)) {
      this._showQueueChallengeResumePanel(options)
      return
    }
    this._showStandaloneCloudflareReloadPanel(options)
  }
  
  /**
   * @return {Promise<boolean>}
   */
  async shouldSilenceMediaCloudflarePrompt()
  {
    let scriptPrefix = BrazenDownloadManager.storageKey(this._cm?._scriptPrefix, '')
    return BrazenDownloadManager.shouldSilenceMediaCloudflarePrompt(scriptPrefix, this)
  }
  
  _hiLane(state, ctx)
  {
    return BrazenDownloadManager._hiLaneState(state, ctx)
  }
  
  /**
   * @param {object|null|undefined} state
   * @return {boolean}
   * @private
   */
  _anyHumanInteraction(state)
  {
    return BrazenDownloadManager._anyHumanInteractionState(state)
  }
  
  /**
   * @param {object|null|undefined} state
   * @return {BrazenHumanInteractionLaneId[]}
   * @private
   */
  _hiContexts(state)
  {
    return BrazenDownloadManager._hiContextsState(state)
  }
  
  /**
   * Ensure `state.humanInteraction` is a per-lane map (mutates in place).
   * @param {object} state
   * @private
   */
  _ensureHumanInteractionMap(state)
  {
    if (!state.humanInteraction || typeof state.humanInteraction !== 'object') {
      state.humanInteraction = {resolution: null, download: null}
    }
    if (!('resolution' in state.humanInteraction)) {
      state.humanInteraction.resolution = null
    }
    if (!('download' in state.humanInteraction)) {
      state.humanInteraction.download = null
    }
  }
  
  /**
   * Show the human-interaction dock panel for one lane on the focused tab (steals ownership).
   * Includes the queue verification / media challenge tab so Done — resume appears
   * where the CAPTCHA was completed.
   * @param {BrazenHumanInteractionLaneId} context
   * @param {{force?: boolean}} [options] `force` skips the visibility gate (rare; preferred
   *   path only shows on visible tabs so background leaders do not pin the chrome).
   * @return {Promise<void>}
   * @private
   */
  async _promptHumanInteractionResume(context, options = {})
  {
    if (context !== 'resolution' && context !== 'download') {
      return
    }
    let state = await this._getState()
    if (!this._hiLane(state, context)) {
      // Remote clear — drop a stale local panel instead of leaving a dead zombie.
      this._hideHumanInteractionPanel(context)
      return
    }
    if (!options.force && document.visibilityState !== 'visible') {
      return
    }
    // Focused tab always claims / steals — same model as tag discovery (not processor-leader-only).
    if (!(await this._claimHumanInteractionPromptOwnership(context))) {
      return
    }
    state = this.readDmState()
    let lane = this._hiLane(state, context)
    if (!lane || lane.promptTabId !== this._tabId) {
      return
    }
    let handlers = this._config.rateLimitHandlers?.[context]?.humanInteraction ?? {}
    let onChallengeTab = this._isQueueVerificationTab()
    let openLabel = this._resolveHumanInteractionOpenLabel(handlers)
    let defaultTitle = this._humanInteractionDefaultTitle(context)
    // Challenge media tab: always use the Phase-1 challenge pane (Done — resume +
    // Done — resume — close tab). Do not reuse the leader Open/Done chrome.
    if (onChallengeTab) {
      this._showQueueChallengeResumePanel({
        title: handlers.confirmTitle ?? defaultTitle,
        message: handlers.challengeConfirmMessage
            ?? 'Complete the verification on this page, then resume the download queue.',
        confirmLabel: handlers.confirmLabel,
        confirmAndCloseLabel: handlers.confirmAndCloseLabel,
      })
      return
    }
    let panel = this._ensureHumanInteractionPanel(context, {reopenLabel: openLabel})
    BrazenViewLayer.updateHumanInteractionPanelContent(panel, {
      title: handlers.confirmTitle ?? defaultTitle,
      message: handlers.confirmMessage,
      confirmLabel: handlers.confirmLabel,
      reopenLabel: openLabel,
    })
    let reopenButton = panel.querySelector('.bv-human-interaction-reopen')
    if (reopenButton) {
      reopenButton.hidden = false
    }
    this._showHumanInteractionPanelInteractive(context, panel, {forceShow: true})
  }
  
  /**
   * Label for the Open-challenge control. Ignores legacy consumer copy
   * ("Reopen verification tab") so GreasyFork apps without a local override still rename.
   * @param {object} [handlers]
   * @return {string}
   * @private
   */
  _resolveHumanInteractionOpenLabel(handlers = {})
  {
    let label = handlers.openChallengeLabel ?? handlers.reopenLabel
    if (!label || label === 'Reopen verification tab') {
      return 'Open Cloudflare Challenge'
    }
    return label
  }
  
  /**
   * @param {BrazenHumanInteractionLaneId} context
   * @return {string}
   * @private
   */
  _humanInteractionDefaultTitle(context)
  {
    return context === 'download' ? 'Download verification required' : 'Resolution verification required'
  }
  
  /**
   * @param {BrazenHumanInteractionLaneId} context
   * @param {{reopenLabel?: string}} [options]
   * @return {HTMLElement}
   * @private
   */
  _ensureHumanInteractionPanel(context, options = {})
  {
    let existing = this._humanInteractionPanels[context]
    if (existing) {
      // Recreate when left in an inconsistent offscreen/visible state (dead clicks).
      let stuckOffscreen = existing.classList.contains('bv-dock-panel-offscreen')
          && !existing.classList.contains('bv-dock-panel-hidden')
          && existing.style.display === 'flex'
      if (stuckOffscreen) {
        this._framework._hideDockSlidePanel(existing, false)
        existing.remove()
        this._humanInteractionPanels[context] = null
      } else {
        if (options.reopenLabel) {
          let reopenButton = existing.querySelector('.bv-human-interaction-reopen')
          if (reopenButton) {
            reopenButton.textContent = options.reopenLabel
          }
        }
        return existing
      }
    }
    let panelId = context === 'download'
        ? 'bv-human-interaction-panel-download'
        : 'bv-human-interaction-panel-resolution'
    let handlers = this._config.rateLimitHandlers?.[context]?.humanInteraction ?? {}
    let panel = BrazenViewLayer.createHumanInteractionPanel({
      id: panelId,
      onConfirm: () => { void this._confirmHumanInteractionResume(context) },
      onReopen: () => { void this._reopenHumanInteractionTab(context) },
      reopenLabel: options.reopenLabel,
      title: handlers.confirmTitle ?? this._humanInteractionDefaultTitle(context),
    })
    this._humanInteractionPanels[context] = panel
    BrazenViewLayer.appendToDockPanelStack(panel)
    return panel
  }
  
  /**
   * Show an HI panel and clear a stuck `bv-dock-panel-offscreen` (pointer-events: none).
   * @param {BrazenHumanInteractionLaneId} context
   * @param {HTMLElement} panel
   * @private
   */
  _showHumanInteractionPanelInteractive(context, panel, options = {})
  {
    if (!panel) {
      return
    }
    BrazenViewLayer._clearDockSlidePanelHideTimer(panel)
    let forceShow = options.forceShow === true
    if (!forceShow && BrazenViewLayer.isDockSlidePanelVisible(panel) &&
        !panel.classList.contains('bv-dock-panel-offscreen')) {
      return
    }
    this._framework._showDockSlidePanel(panel)
    panel.classList.remove('bv-dock-panel-offscreen')
  }
  
  /**
   * Themed dock pane on a browsed challenge page (not queue-driven). Same chrome as the
   * leader verification panel — never a grey modal overlay.
   * @param {{title?: string, message?: string, confirmLabel?: string}} [options]
   * @private
   */
  _showStandaloneCloudflareReloadPanel(options = {})
  {
    if (document.getElementById('bv-human-interaction-panel')) {
      return
    }
    let panel = BrazenViewLayer.createHumanInteractionPanel({
      id: 'bv-human-interaction-panel',
      showReopen: false,
      title: options.title ?? 'Cloudflare challenge',
      message: options.message ??
          'Complete the CAPTCHA on this page, then confirm to reload and continue.',
      confirmLabel: options.confirmLabel ?? 'Done — reload',
      onConfirm: () => location.reload(),
    })
    BrazenViewLayer.appendToDockPanelStack(panel)
    this._framework._showDockSlidePanel(panel)
  }
  
  /**
   * Phase-1 queue Cloudflare tab: show Done — resume on the challenge media page itself
   * (full DM init is halted on mediaCloudflare).
   * @param {{
   *   title?: string,
   *   message?: string,
   *   confirmLabel?: string,
   *   confirmAndCloseLabel?: string,
   * }} [options]
   * @private
   */
  _showQueueChallengeResumePanel(options = {})
  {
    let context = BrazenDownloadManager._peekHumanInteractionContextFromLocation() ?? 'download'
    let panelId = context === 'resolution'
        ? 'bv-human-interaction-panel-resolution'
        : 'bv-human-interaction-panel-download'
    let handlers = this._config?.rateLimitHandlers?.[context]?.humanInteraction ?? {}
    let title = options.title ?? handlers.confirmTitle ??
        (context === 'resolution' ? 'Resolution verification required' : 'Cloudflare challenge')
    let message = options.message ?? handlers.challengeConfirmMessage ??
        'Complete the Cloudflare check on this page, then resume the download queue.'
    // Label defaults live on View Layer createHumanInteractionPanel — omit when unset.
    let confirmLabel = options.confirmLabel ?? handlers.confirmLabel
    let confirmAndCloseLabel = options.confirmAndCloseLabel ?? handlers.confirmAndCloseLabel
  
    let existing = this._humanInteractionPanels[context]
        ?? document.getElementById(panelId)
        ?? document.getElementById('bv-human-interaction-panel')
    if (existing?.querySelector?.('.bv-human-interaction-confirm-close')) {
      BrazenViewLayer.updateHumanInteractionPanelContent(existing, {
        title,
        message,
        confirmLabel,
        confirmAndCloseLabel,
      })
      existing.querySelector('.bv-human-interaction-reopen')?.remove()
      this._humanInteractionPanels[context] = existing
      this._showHumanInteractionPanelInteractive(context, existing, {forceShow: true})
      return
    }
    // Leader Open/Done chrome (or a stale pane) — replace with challenge controls.
    if (existing) {
      this._framework._hideDockSlidePanel(existing)
      existing.remove()
      if (this._humanInteractionPanels[context] === existing) {
        this._humanInteractionPanels[context] = null
      }
    }
  
    let panel = BrazenViewLayer.createHumanInteractionPanel({
      id: panelId,
      showReopen: false,
      title,
      message,
      confirmLabel,
      confirmAndCloseLabel,
      onConfirm: () => { void this._confirmHumanInteractionFromChallengePage(context, panel) },
      onConfirmAndClose: () => {
        void this._confirmHumanInteractionFromChallengePage(context, panel, {closeTab: true})
      },
    })
    this._humanInteractionPanels[context] = panel
    BrazenViewLayer.appendToDockPanelStack(panel)
    this._showHumanInteractionPanelInteractive(context, panel, {forceShow: true})
  }
  
  _hideHumanInteractionPanel(context = null)
  {
    if (context === 'resolution' || context === 'download') {
      let panel = this._humanInteractionPanels[context]
      if (panel) {
        this._framework._hideDockSlidePanel(panel)
      }
      return
    }
    for (let ctx of /** @type {BrazenHumanInteractionLaneId[]} */ (['resolution', 'download'])) {
      let panel = this._humanInteractionPanels[ctx]
      if (panel) {
        this._framework._hideDockSlidePanel(panel)
      }
    }
  }
  
  /**
   * True when this document is a queue-opened Cloudflare / verification tab
   * (`brazen_hi` marker). Same-origin opener alone is not enough — normal search/media
   * tabs opened from another tab must still show Open Cloudflare Challenge.
   * @return {boolean}
   * @private
   */
  _isQueueVerificationTab()
  {
    return BrazenDownloadManager.hasBrazenHumanInteractionMarker()
  }
  
  /**
   * Hide local HI panels and drop ownership so another focused tab can claim them.
   * Does not clear lane blocks — only Done — resume unlocks the queue.
   * @param {{hideLocal?: boolean}} [options] When false, only clears IDB ownership (minimize / tab blur).
   * @return {Promise<void>}
   * @private
   */
  async _releaseHumanInteractionPanelOwnership(options = {})
  {
    let stillBlocked = false
    await Utilities.releaseDockPanelOwnership({
      hideLocal: options.hideLocal,
      hidePanel: () => this._hideHumanInteractionPanel(),
      documentSuspended: this._documentSuspended,
      commitRelease: async () => {
        // Panel ownership is coordinator-only IDB; visibility handlers can fire during
        // initialize() before the Web Lock is held (even on the sole tab).
        if (!this.isCoordinator()) {
          return
        }
        await this._commitCoordinatorDmState((state) => {
          this._ensureHumanInteractionMap(state)
          for (let ctx of /** @type {BrazenHumanInteractionLaneId[]} */ (['resolution', 'download'])) {
            let lane = state.humanInteraction[ctx]
            if (lane?.promptTabId === this._tabId) {
              lane.promptTabId = null
            }
          }
          stillBlocked = this._anyHumanInteraction(state)
        })
      },
      wakeAfterRelease: () => {
        if (stillBlocked) {
          this._requestPipelineRun()
        }
      },
    })
  }
  
  async _confirmHumanInteractionFromChallengePage(context = null, panel = null, options = {})
  {
    let prefix = BrazenDownloadManager.storageKey(this._cm?._scriptPrefix, '')
    let state = null
    let repos = null
    try {
      if (typeof BrazenStorageRepositories === 'function') {
        repos = new BrazenStorageRepositories(prefix)
        if (repos.storage.available) {
          await repos.storage.open()
          state = await repos.downloadManagerState.get()
        }
      }
    } catch (e) {
      // ignore — fall through to URL / default context
    }
    let ctx = context
        ?? BrazenDownloadManager._peekHumanInteractionContextFromLocation()
        ?? BrazenDownloadManager._resolveChallengeHumanInteractionContext(state)
        ?? 'download'
    let el = panel
        ?? document.getElementById(
            ctx === 'resolution'
                ? 'bv-human-interaction-panel-resolution'
                : 'bv-human-interaction-panel-download',
        )
        ?? document.getElementById('bv-human-interaction-panel')
    if (el) {
      this._framework._hideDockSlidePanel(el)
    }
    try {
      if (repos?.storage?.available && state) {
        state = await repos.download.clearHumanInteractionLaneFromSnapshot(ctx, state)
        if (state) {
          this._publishDmStateToAtoms(state)
          this._writeHumanInteractionBlockMirror(
              BrazenDownloadManager._anyHumanInteractionState(state),
          )
        }
      }
    } catch (e) {
      // ignore IDB failures on challenge tabs
      this._writeHumanInteractionBlockMirror(false)
    }
    this._signalProcessorsWakeAndRunIfCoordinator()
    if (!options.closeTab) {
      return
    }
    try {
      window.close()
    } catch (e) {
      // ignore — browsers often block close for non-script-opened tabs
    }
    // Resume already succeeded; if close was ignored, surface a short note.
    await new Promise((resolve) => setTimeout(resolve, 50))
    if (window.closed || !el) {
      return
    }
    BrazenViewLayer.updateHumanInteractionPanelContent(el, {
      message: 'Queue resumed. Close this tab manually if it stays open.',
    })
    el.querySelector('.bv-dock-list-footer')?.replaceChildren()
    this._framework._showDockSlidePanel(el)
  }
  
  /**
   * @param {BrazenHumanInteractionLaneId} context
   * @return {Promise<void>}
   * @private
   */
  async _confirmHumanInteractionResume(context)
  {
    this._hideHumanInteractionPanel(context)
    await this._clearHumanInteractionBlock(context)
    this._signalProcessorsWakeAndRunIfCoordinator()
  }
  
  /**
   * Re-open the verification URL if the user closed that tab by mistake.
   * Opens synchronously from the sync cache so the click keeps user activation.
   * @param {BrazenHumanInteractionLaneId} context
   * @return {Promise<void>}
   * @private
   */
  async _reopenHumanInteractionTab(context)
  {
    let openUrl = this._hiLane(this.readDmState(), context)?.openUrl
    if (openUrl) {
      this._openHumanInteractionTab(this._markHumanInteractionOpenUrl(openUrl, context))
      return
    }
    // Cache miss — await may lose transient user activation; best-effort only.
    let state = await this._getState()
    openUrl = this._hiLane(state, context)?.openUrl
    if (openUrl) {
      this._openHumanInteractionTab(this._markHumanInteractionOpenUrl(openUrl, context))
      return
    }
    let panel = this._humanInteractionPanels[context]
    if (panel) {
      BrazenViewLayer.updateHumanInteractionPanelContent(panel, {
        message: 'Could not open the challenge tab (missing URL). Try Done — resume, or reload this page.',
      })
    }
  }
  
  async _clearHumanInteractionBlock(context)
  {
    if (context !== 'resolution' && context !== 'download') {
      return
    }
    this._hideHumanInteractionPanel(context)
    this._stampInitiationClock(context, Date.now())
    await this.sendCommand({type: 'clear-hi-lane', payload: {context}})
    this._writeHumanInteractionBlockMirror(
        this._anyHumanInteraction(this.readDmState()),
    )
    this._refreshAllItemProgress()
    await this._syncFromStorage()
    this._stampInitiationClock(context, Date.now())
  }
  
  async _maybeExpireHumanInteractionBlocks(state = null)
  {
    let snapshot = state ?? this.readDmState() ?? await this._getState()
    let clearedAny = false
    for (let ctx of /** @type {BrazenHumanInteractionLaneId[]} */ (['resolution', 'download'])) {
      let lane = this._hiLane(snapshot, ctx)
      if (!lane) {
        continue
      }
      let expireMs = Number(
          this._config.rateLimitHandlers?.[ctx]?.humanInteraction?.softExpireMs,
      )
      if (!(Number.isFinite(expireMs) && expireMs > 0)) {
        expireMs = BrazenDownloadManager.SOFT_EXPIRE_MS
      }
      if (Date.now() - (Number(lane.at) || 0) < expireMs) {
        continue
      }
      await this._clearHumanInteractionBlock(ctx)
      clearedAny = true
      snapshot = this.readDmState() ?? snapshot
    }
    if (clearedAny) {
      this._signalProcessorsWakeAndRunIfCoordinator()
    }
    return clearedAny
  }
  
  /**
   * Re-show / reclaim HI panels on a visible tab; hide local panels when the lane was cleared remotely.
   * @param {object|null|undefined} [state]
   * @return {Promise<void>}
   * @private
   */
  async _restoreHumanInteractionPanelsIfNeeded(state = null)
  {
    if (this._documentSuspended) {
      return
    }
    await this._maybeExpireHumanInteractionBlocks(state)
    let snapshot = this.readDmState() ?? state ?? await this._getState()
    for (let ctx of /** @type {BrazenHumanInteractionLaneId[]} */ (['resolution', 'download'])) {
      let lane = this._hiLane(snapshot, ctx)
      if (!lane) {
        this._hideHumanInteractionPanel(ctx)
        continue
      }
      // Orphan lane (item gone from both queues) — clear so Waiting cannot stick forever.
      if (lane.itemId != null && this._cm.canPersist()) {
        let itemKey = String(lane.itemId)
        let [resolution, download] = await Promise.all([
          this._repos().downloadResolutionQueue.get(itemKey),
          this._repos().downloadQueue.get(itemKey),
        ])
        let resolutionAlive = !!(resolution && !RESOLUTION_TERMINAL.has(resolution.status))
        let downloadAlive = !!(download && !DOWNLOAD_TERMINAL.has(download.status))
        if (!resolutionAlive && !downloadAlive) {
          await this._clearHumanInteractionBlock(ctx)
          this._signalProcessorsWakeAndRunIfCoordinator()
          snapshot = this.readDmState() ?? snapshot
          continue
        }
      }
      if (document.visibilityState !== 'visible') {
        continue
      }
      void this._promptHumanInteractionResume(ctx)
    }
  }
  
  /**
   * Lightweight periodic reclaim so a stale/closed `promptTabId` cannot leave HI blocked forever.
   * @private
   */
  _startHumanInteractionWatchdog()
  {
    if (this._humanInteractionWatchdogTimer) {
      return
    }
    this._humanInteractionWatchdogTimer = setInterval(() => {
      if (this._documentSuspended) {
        return
      }
      void this._maybeExpireHumanInteractionBlocks()
      if (document.visibilityState !== 'visible') {
        return
      }
      let state = this.readDmState()
      if (!this._anyHumanInteraction(state)) {
        // Still hide any leftover local zombie if lanes are clear.
        this._hideHumanInteractionPanel()
      } else {
        void this._restoreHumanInteractionPanelsIfNeeded(state)
      }
      let snapshot = this.readDmState() ?? state
      if (snapshot?.resolutionBlocked) {
        void this.restoreTagDiscoveryPanelIfNeeded()
      }
    }, BrazenDownloadManager.WATCHDOG_MS)
  }
  
  /**
   * @private
   */
  _stopHumanInteractionWatchdog()
  {
    if (!this._humanInteractionWatchdogTimer) {
      return
    }
    clearInterval(this._humanInteractionWatchdogTimer)
    this._humanInteractionWatchdogTimer = null
  }

  /**
   * Show the navigation-interrupted download decision panel (visible leader only).
   * Click marks pending rows done and pauses the queue; timeout requeues and relaunches.
   * @param {object[]|null|undefined} rows
   * @private
   */
  _showDownloadInterruptionPanel(rows)
  {
    if (!rows?.length || document.visibilityState !== 'visible' || this._documentSuspended) {
      return
    }
    this._downloadInterruptionPending = true
    this._downloadInterruptionRows = rows
    this._hideDownloadInterruptionPanel({keepPending: true})
    let count = rows.length
    let panel = BrazenViewLayer.createDownloadInterruptionPanel({
      message: count === 1
          ? 'A download was still in progress when this page loaded. Click below to mark it complete and pause the queue, or wait for the timer to relaunch it.'
          : `${count} downloads were still in progress when this page loaded. Click below to mark them complete and pause the queue, or wait for the timer to relaunch them.`,
      onComplete: () => { void this._completeDownloadInterruption() },
      onTimeout: () => { void this._timeoutDownloadInterruption() },
    })
    this._downloadInterruptionPanel = panel
    BrazenViewLayer.appendToDockPanelStack(panel)
    this._framework._showDockSlidePanel(panel)
  }

  /**
   * @param {{keepPending?: boolean}} [options]
   * @private
   */
  _hideDownloadInterruptionPanel(options = {})
  {
    let panel = this._downloadInterruptionPanel
        ?? document.getElementById('bv-download-interruption-panel')
    if (panel) {
      panel._bvTimedButtonApi?.cancel?.()
      this._framework._hideDockSlidePanel(panel)
      panel.remove()
    }
    this._downloadInterruptionPanel = null
    if (!options.keepPending) {
      this._downloadInterruptionPending = false
      this._downloadInterruptionRows = null
    }
  }

  /**
   * User clicked the timed button: treat interrupted downloads as complete and pause.
   * @return {Promise<void>}
   * @private
   */
  async _completeDownloadInterruption()
  {
    let rows = this._downloadInterruptionRows ?? []
    this._hideDownloadInterruptionPanel()
    if (!this._cm.canPersist()) {
      return
    }
    for (let row of rows) {
      let current = await this._repos().downloadQueue.get(row.itemId)
      if (!current || current.status !== 'downloading') {
        continue
      }
      current.status = 'done'
      current.inProgress = false
      current.error = null
      await this._repos().download.commitDownload(current)
      await this._incrementDownloadProgress()
      await this._clearTerminalQueueRows(current.itemId)
    }
    await this._commitCoordinatorDmState((state) => {
      state.paused = true
    })
    this._refreshDockProgress({refreshAllItems: true})
    await this._syncFromStorage()
  }

  /**
   * Timed button expired: requeue interrupted downloads and relaunch (today's default).
   * @return {Promise<void>}
   * @private
   */
  async _timeoutDownloadInterruption()
  {
    let rows = this._downloadInterruptionRows ?? []
    this._hideDownloadInterruptionPanel()
    if (!this._cm.canPersist()) {
      return
    }
    for (let row of rows) {
      let current = await this._repos().downloadQueue.get(row.itemId)
      if (!current || current.status !== 'downloading') {
        continue
      }
      current.status = 'queued'
      current.inProgress = false
      current.error = null
      await this._repos().download.commitDownload(current)
    }
    this._signalProcessorsWakeAndRunIfCoordinator()
  }

  /**
   * Advance one pipeline's in-tab initiation clock to at least `at`.
   * @param {'resolution'|'download'} kind
   * @param {number} at
   * @private
   */
  _stampInitiationClock(kind, at)
  {
    let ts = at ?? Date.now()
    let localKey = kind === 'download' ? '_lastDownloadInitiationAt' : '_lastResolutionInitiationAt'
    let stateKey = kind === 'download' ? 'lastDownloadInitiationAt' : 'lastResolutionInitiationAt'
    this[localKey] = Math.max(this[localKey] ?? 0, ts)
    let dmState = this.readDmState()
    if (dmState) {
      dmState[stateKey] = Math.max(dmState[stateKey] ?? 0, ts)
      this._publishDmStateToAtoms(dmState)
    }
  }

  // -------------------------------------------------------------------------
  // Tag discovery
  // -------------------------------------------------------------------------

  /**
   * Write extracted typed tag groups into the tag registry.
   * Discovery on → register-on-seen (`media`, lastSeenTypeEntryId only; isDiscovered stays null).
   * Discovery off → confirm as-is (`resolution`, sets typeEntryId and isDiscovered).
   *
   * @param {Record<string, string[]>|null|undefined} tagGroups
   * @return {Promise<void>}
   * @private
   */
  async _registerResolvedTagGroups(tagGroups)
  {
    if (!this._cm.canPersist() || !tagGroups || typeof tagGroups !== 'object') {
      return
    }
    let normalize = this._getDownloadTagNormalize()
    let normalizedGroups = {}
    for (let [typeName, names] of Object.entries(tagGroups)) {
      if (!Array.isArray(names)) {
        continue
      }
      let list = []
      let seen = new Set()
      for (let name of names) {
        if (!name) {
          continue
        }
        let normalized = normalize(name) || name
        if (seen.has(normalized)) {
          continue
        }
        seen.add(normalized)
        list.push(normalized)
      }
      if (list.length) {
        normalizedGroups[typeName] = list
      }
    }
    let hasNames = Object.values(normalizedGroups).some((names) => names.some(Boolean))
    if (!hasNames) {
      return
    }
    let source = this._isTagDiscoveryActive() ? 'media' : 'resolution'
    await this._cm.registerTypedTagGroups(normalizedGroups, source)
  }

  /**
   * @return {boolean}
   */
  isTagDiscoveryEnabled()
  {
    return !!this.readDmState()?.tagDiscoveryEnabled
  }

  /**
   * @return {boolean}
   * @private
   */
  _isTagDiscoveryActive()
  {
    return !!this._config.tagDiscovery && this.isTagDiscoveryEnabled()
  }

  /**
   * @param {string} step
   * @return {boolean}
   * @private
   */
  _shouldRunTagDiscovery(step)
  {
    return this._config.tagDiscovery?.discoverAt?.includes(step) ?? false
  }

  /**
   * After discovery review: mark tags {@link TagEntry.isDiscovered} and promote seen types
   * to {@link TagEntry.typeEntryId} when still unset (attributes may have typed earlier).
   * @param {Array<{name: string, type: string|null}>|null} tags
   * @return {Promise<void>}
   * @private
   */
  async _confirmDiscoveryPanelTagTypes(tags)
  {
    if (!tags?.length) {
      return
    }
    let tagRuntime = this._cm.getTagRuntime()
    if (!tagRuntime) {
      return
    }
    await this._repos().meta.beginTagsRevisionBatch()
    try {
      for (let tag of tags) {
        if (!tag?.name) {
          continue
        }
        let entry = await tagRuntime.getTag(tag.name)
        // Already discovered with a type — nothing left for Confirm.
        if (entry?.isDiscovered === true && entry?.typeEntryId != null) {
          continue
        }
        // Prefer existing type / lastSeen from resolution; fall back to the panel row's type.
        let typeEntryId = null
        let typeName = null
        if (entry?.typeEntryId == null) {
          typeEntryId = entry?.meta?.lastSeenTypeEntryId ?? null
          if (typeEntryId == null && tag.type) {
            typeName = tag.type
            typeEntryId = await this._repos().tags.resolveCanonicalTypeEntryId(tag.type)
          }
        } else {
          typeEntryId = entry.typeEntryId
        }
        // discovery-confirm sets isDiscovered even when typeEntryId was set via attributes.
        await tagRuntime.ensureTag(tag.name, {
          typeEntryId,
          typeName,
          source: 'discovery-confirm',
        })
      }
    } finally {
      await this._repos().meta.endTagsRevisionBatch()
    }
  }

  /**
   * @param {{}} tagGroups
   * @param {Record<string, number|string>|null|undefined} [tagIncidences]
   * @return {Promise<{unknown: Array<{name: string, type: string|null, count: number|null}>, known: Array<{name: string, type: string|null, count: number|null}>}>}
   * @private
   */
  async _partitionDiscoveryTags(tagGroups, tagIncidences = {})
  {
    let empty = {unknown: [], known: []}
    if (!this._config.tagDiscovery) {
      return empty
    }
    let resolver = this._config.downloadPaths.getPatternResolver()
    let patterns = this._getActiveDownloadPatterns()
    let relevantTypes = new Set()
    for (let chip of resolver.chips) {
      if (!resolver.tagTypes.includes(chip.token)) {
        continue
      }
      for (let pattern of patterns) {
        if (this.patternIncludesChip(pattern, chip)) {
          relevantTypes.add(chip.token)
        }
      }
    }

    // Pattern pins always own scan scope. Optional tagDiscovery.tagTypes array is an
    // allowlist intersect only — never a replacement that expands beyond pins.
    let tagTypes = [...relevantTypes]
    let configured = this._config.tagDiscovery.tagTypes
    if (Array.isArray(configured) && configured.length) {
      let allow = new Set(configured)
      tagTypes = tagTypes.filter((type) => allow.has(type))
    }
    if (!tagTypes.length) {
      return empty
    }
    let candidates = []
    let incidences = tagIncidences && typeof tagIncidences === 'object' ? tagIncidences : {}
    for (let type of tagTypes) {
      for (let tag of tagGroups[type] ?? []) {
        if (!tag) {
          continue
        }
        candidates.push({
          name: tag,
          type,
          count: this._resolveTagIncidence(tag, incidences),
        })
      }
    }
    // One TagRuntime ensure for the whole set — avoid per-tag IDB round-trips.
    let tagRuntime = this._cm.getTagRuntime?.()
    if (tagRuntime && candidates.length) {
      try {
        await tagRuntime.ensureNames(candidates.map((entry) => entry.name))
      } catch (error) {
        // Fall through to per-tag lookups.
      }
    }
    let unknown = []
    let known = []
    for (let entry of candidates) {
      if (await this._isTagKnownForDiscovery(entry.name)) {
        known.push(entry)
      } else {
        unknown.push(entry)
      }
    }
    return {unknown, known}
  }

  /**
   * @param {string} tagName
   * @param {Record<string, number|string>} incidences
   * @return {number|null}
   * @private
   */
  _resolveTagIncidence(tagName, incidences)
  {
    if (!incidences || typeof incidences !== 'object') {
      return null
    }
    let raw = incidences[tagName]
    if (raw == null) {
      let lower = String(tagName).toLowerCase()
      for (let [key, value] of Object.entries(incidences)) {
        if (String(key).toLowerCase() === lower) {
          raw = value
          break
        }
      }
    }
    if (raw == null || raw === '') {
      return null
    }
    let count = typeof raw === 'number' ? raw : parseInt(String(raw).replace(/[^\d]/g, ''), 10)
    return Number.isFinite(count) ? count : null
  }

  /**
   * Whether a tag has completed discovery review ({@link TagEntry.isDiscovered}).
   * Type may be set earlier via attribute actions; discovery is gated separately.
   * Script {@link tagDiscovery.isTagKnown} overrides; default uses {@link TagRepository.getByName}.
   *
   * @param {string} tagName
   * @return {Promise<boolean>}
   * @private
   */
  async _isTagKnownForDiscovery(tagName)
  {
    if (this._config.tagDiscovery?.isTagKnown) {
      let result = Utilities.callEventHandler(this._config.tagDiscovery.isTagKnown, [tagName], false)
      return !!await result
    }
    let tagRuntime = this._cm.getTagRuntime?.()
    let cached = tagRuntime?.resolveCachedByName?.(tagName)
    if (cached) {
      return cached.isDiscovered === true
    }
    if (!this._cm.canPersist()) {
      return false
    }
    let tag = await this._repos().tags.getByName(tagName)
    return !!(tag && tag.isDiscovered === true)
  }

  /**
   * @return {string[]}
   * @private
   */
  _getActiveDownloadPatterns()
  {
    let paths = this._config.downloadPaths
    let patterns = []
    if (paths.filenamePatternConfigKey) {
      patterns.push(this._framework._getConfig(paths.filenamePatternConfigKey) ?? '')
    }
    if (paths.subfolderPatternConfigKey) {
      patterns.push(this._framework._getConfig(paths.subfolderPatternConfigKey) ?? '')
    }
    return patterns
  }

  /**
   * @return {boolean}
   * @private
   */
  _isIgnoredPinReviewEnabled()
  {
    return this._isTagDiscoveryBehaviorEnabled(
        OPTION_REVIEW_IGNORED_FILENAME_PINS,
        this._config.tagDiscovery?.ignoredPinReview,
    )
  }

  /**
   * @return {boolean}
   * @private
   */
  _isSkipEmptyFilenamePinsEnabled()
  {
    return this._isTagDiscoveryBehaviorEnabled(
        OPTION_SKIP_EMPTY_FILENAME_PINS,
        this._config.tagDiscovery?.skipEmptyFilenamePins,
    )
  }

  /**
   * @param {string} optionConst
   * @param {{isEnabled?: function}|null|undefined} configHook
   * @return {boolean}
   * @private
   */
  _isTagDiscoveryBehaviorEnabled(optionConst, configHook)
  {
    if (configHook?.isEnabled) {
      return !!Utilities.callEventHandler(configHook.isEnabled, [], false)
    }
    if (!this._cm.getField(optionConst)) {
      return false
    }
    return !!this._framework._getConfig(optionConst)
  }

  /**
   * @return {boolean}
   * @private
   */
  _isDeferredTagDiscoveryEnabled()
  {
    if (!this._cm.getField(OPTION_DEFER_TAG_DISCOVERY)) {
      return false
    }
    return !!this._framework._getConfig(OPTION_DEFER_TAG_DISCOVERY)
  }

  /**
   * Defer unknown-tag and ignored-pin review to the discovery lane (batch only).
   * @return {boolean}
   * @private
   */
  _shouldDeferTagDiscoveryReview()
  {
    return this._isDeferredTagDiscoveryEnabled() && this._isTagDiscoveryActive()
  }

  /**
   * @return {Promise<number>}
   * @private
   */
  async _countDiscoveryQueuedItems()
  {
    return this._repos().downloadResolutionQueue.listByStatus('discoveryQueued').
        then((rows) => rows.length)
  }

  /**
   * Clear discovery-lane phase when no deferred rows remain.
   * @return {Promise<void>}
   * @private
   */
  async _maybeEndDiscoveryLanePhase()
  {
    if (!this._isDeferredTagDiscoveryEnabled()) {
      return
    }
    let pending = await this._countDiscoveryQueuedItems()
    if (pending > 0) {
      return
    }
    await this._commitCoordinatorDmState((state) => {
      state.discoveryLanePhaseActive = false
    })
  }

  /**
   * @param {object|null|undefined} state
   * @return {Promise<object|null>}
   * @private
   */
  async _peekResolutionWork(state)
  {
    if (state?.resolutionBlocked) {
      return null
    }
    let repo = this._repos().downloadResolutionQueue
    if (!this._isDeferredTagDiscoveryEnabled()) {
      return repo.peekNextQueued()
    }
    if (state?.discoveryLanePhaseActive) {
      return repo.peekNextDiscoveryQueued()
    }
    let next = await repo.peekNextQueued()
    if (next) {
      return next
    }
    let deferred = await repo.peekNextDiscoveryQueued()
    if (!deferred) {
      return null
    }
    await this._commitCoordinatorDmState((nextState) => {
      nextState.discoveryLanePhaseActive = true
    })
    return deferred
  }

  /**
   * @param {object} item
   * @return {Promise<void>}
   * @private
   */
  async _processResolutionWorkItem(item)
  {
    if (item.pendingTagGroups && this._isDeferredTagDiscoveryEnabled()) {
      await this._activateDeferredDiscoveryItem(item)
      return
    }
    await this._processResolutionItem(item)
  }

  /**
   * Open tag review for a deferred discovery-lane item (no re-fetch).
   * @param {object} item
   * @return {Promise<void>}
   * @private
   */
  async _activateDeferredDiscoveryItem(item)
  {
    try {
      let pending = item.pendingTagGroups
      if (!pending?.mediaUrl) {
        item.status = 'failed'
        item.error = 'Deferred discovery item has no resolved payload'
        if (await this._commitResolutionRow(item)) {
          await this._incrementResolutionProgress()
        }
        return
      }
      let outcome = await this._applyTagDiscoveryOutcome(item, pending, {
        registerTags: false,
        forceImmediate: true,
        endDiscoveryLaneOnFinish: true,
      })
      if (outcome !== 'noop') {
        return
      }
      if (!(await this._promoteToDownloadQueue(item, pending))) {
        return
      }
      if (!(await this._removeResolutionRowIfActive(item.itemId))) {
        return
      }
      await this._incrementResolutionProgress()
      await this._maybeEndDiscoveryLanePhase()
    } finally {
      await this._onPipelineTaskComplete(item.itemId)
    }
  }

  /**
   * Tag-type tokens present as chips in the active filename/subfolder patterns.
   * @return {string[]}
   * @private
   */
  _getPinnedFilenameTagTypes()
  {
    if (!this._config.downloadPaths?.getPatternResolver) {
      return []
    }
    let resolver = this._config.downloadPaths.getPatternResolver()
    let patterns = this._getActiveDownloadPatterns()
    let pinned = []
    let seen = new Set()
    for (let chip of resolver.chips ?? []) {
      if (!resolver.tagTypes.includes(chip.token) || seen.has(chip.token)) {
        continue
      }
      if (!this._activePatternsIncludeTagType(patterns, chip.token, resolver.chips)) {
        continue
      }
      seen.add(chip.token)
      pinned.push(chip.token)
    }
    return pinned
  }

  /**
   * True when any active filename/subfolder tag-type pin has at least one raw tag on the post.
   * Filename Tag Ignore List is not applied.
   * @param {{}} tagGroups
   * @return {boolean}
   * @private
   */
  _hasRawTagsForAnyFilenamePin(tagGroups)
  {
    let groups = tagGroups && typeof tagGroups === 'object' ? tagGroups : {}
    for (let type of this._getPinnedFilenameTagTypes()) {
      if ((groups[type] ?? []).some(Boolean)) {
        return true
      }
    }
    return false
  }

  /**
   * Queue-only: drop before promote when enabled and no pinned type has raw tags.
   * No-op when patterns contain no tag-type pins.
   * @param {{}} tagGroups
   * @return {boolean}
   * @private
   */
  _shouldSkipEmptyFilenamePins(tagGroups)
  {
    if (!this._isSkipEmptyFilenamePinsEnabled()) {
      return false
    }
    let pinned = this._getPinnedFilenameTagTypes()
    if (!pinned.length) {
      return false
    }
    return !this._hasRawTagsForAnyFilenamePin(tagGroups)
  }

  /**
   * Drop a resolved resolution item without promoting to the download queue.
   * @param {object} item
   * @param {{clearDiscoveryGate?: boolean}} [options]
   * @return {Promise<void>}
   * @private
   */
  async _dropResolvedWithoutDownload(item, options = {})
  {
    let itemId = item?.itemId
    if (!itemId) {
      return
    }
    let removed = await this._removeResolutionRowIfActive(itemId)
    if (!removed) {
      removed = await this._repos().download.removeResolutionIfExists(itemId)
    }
    if (this._selectionUiPending.has(String(itemId))) {
      return
    }
    let element = this._trackedItemElements.get(String(itemId))
    this._untrackItemElement(itemId)
    if (element) {
      this._setItemProgress(element, null)
    }
    if (options.clearDiscoveryGate) {
      await this._commitCoordinatorDmState((next) => {
        this._clearDiscoveryReviewState(next)
      })
      await this._syncFromStorage()
    }
    if (removed) {
      await this._incrementResolutionProgress()
    }
  }

  /**
   * True when any active filename/subfolder pattern includes a chip for {@link type}.
   * @param {string[]} patterns
   * @param {string} type
   * @param {{token: string, label: string}[]} chips
   * @return {boolean}
   * @private
   */
  _activePatternsIncludeTagType(patterns, type, chips)
  {
    for (let chip of chips ?? []) {
      if (chip.token !== type) {
        continue
      }
      for (let pattern of patterns) {
        if (this.patternIncludesChip(pattern, chip)) {
          return true
        }
      }
    }
    return false
  }

  /**
   * Resolve `tagGroups` from a resolution payload (nested or flat).
   * @param {object|null|undefined} resolved
   * @return {{}}
   * @private
   */
  _tagGroupsFromResolvedPayload(resolved)
  {
    if (!resolved || typeof resolved !== 'object') {
      return {}
    }
    if (resolved.tagGroups && typeof resolved.tagGroups === 'object' && !Array.isArray(resolved.tagGroups)) {
      return resolved.tagGroups
    }
    // Defensive: older/flat payloads stored groups at the top level.
    if (Array.isArray(resolved.author) || Array.isArray(resolved.character) ||
        Array.isArray(resolved.copyright) || Array.isArray(resolved.general)) {
      return resolved
    }
    return {}
  }

  /**
   * Whether a post tag is filename-ignored — same source as the Aa ignore button
   * ({@link BrazenConfigurationManager.hasTagSoleAttribute} / shared cache row).
   * When strip-character-series is on, an ignore on the series-stripped base also counts.
   * @param {string} name
   * @param {object} resolver
   * @param {string|null} [type]
   * @return {boolean}
   * @private
   */
  _isFilenameIgnoredTag(name, resolver, type = null)
  {
    if (!name) {
      return false
    }
    let normalize = typeof resolver?.normalizeTag === 'function' ?
        resolver.normalizeTag :
        this._getDownloadTagNormalize()
    let normalized = normalize(name)
    if (!normalized) {
      return false
    }

    let isIgnoredName = (candidate) => {
      if (!candidate) {
        return false
      }
      if (this._cm?.canPersist?.()) {
        return !!this._cm.hasTagSoleAttribute('filename-tag-ignore-list', candidate)
      }
      let ignore = resolver?.ignore
      return !!(ignore?.size && (ignore.has(candidate) || ignore.has(normalize(candidate))))
    }

    if (isIgnoredName(normalized) || (normalized !== name && isIgnoredName(name))) {
      return true
    }
    if (resolver?.stripCharacterSeries && (type === 'character' || type == null)) {
      let stripped = normalized.replace(/_\([^)]*\)$/, '')
      if (stripped && stripped !== normalized && isIgnoredName(stripped)) {
        return true
      }
    }
    return false
  }

  /**
   * Filename-ignored tags for active filename/subfolder tag-type pins that are blocking.
   * A pin type is blocking when it has raw tags, the download-path join is empty with ignore
   * applied, and a no-ignore join is non-empty (ignore emptied the slot; substitution-only
   * empties do not block). Lists filename-ignored tags only for those blocking types.
   * `pinJoinEmpty` is true only when `tags.length > 0`.
   *
   * @param {{}} tagGroups
   * @param {Record<string, number|string>|null|undefined} [tagIncidences]
   * @return {Promise<{tags: Array<{name: string, type: string|null, count: number|null}>, pinJoinEmpty: boolean}>}
   * @private
   */
  async _collectIgnoredFilenamePinTags(tagGroups, tagIncidences = {})
  {
    let empty = {tags: [], pinJoinEmpty: false}
    if (!this._isIgnoredPinReviewEnabled() || !this._config.downloadPaths?.getPatternResolver) {
      return empty
    }
    let types = this._getPinnedFilenameTagTypes()
    if (!types.length) {
      return empty
    }
    await this._ensureDownloadPathTagsLoaded(tagGroups)
    let resolver = this._config.downloadPaths.getPatternResolver()
    let normalize = typeof resolver?.normalizeTag === 'function' ?
        resolver.normalizeTag :
        this._getDownloadTagNormalize()
    let incidences = tagIncidences && typeof tagIncidences === 'object' ? tagIncidences : {}
    /** @type {string[]} */
    let blockingTypes = []
    for (let type of types) {
      let raw = (tagGroups[type] ?? []).filter(Boolean)
      if (!raw.length) {
        continue
      }
      // Product truth: slot empty after ignore/substitution — same join as the download path.
      let joined = this._joinTagsForDownloadPath(raw, type, resolver.ignore, resolver)
      if (joined) {
        continue
      }
      // Substitution-only empties do not block; ignore-caused empties do.
      let joinedNoIgnore = this._joinTagsForDownloadPath(raw, type, resolver.ignore, {
        ...resolver,
        applyFilenameIgnore: false,
      })
      if (!joinedNoIgnore) {
        continue
      }
      blockingTypes.push(type)
    }
    if (!blockingTypes.length) {
      return empty
    }
    let collected = []
    let seen = new Set()
    for (let type of blockingTypes) {
      let raw = (tagGroups[type] ?? []).filter(Boolean)
      for (let name of raw) {
        if (!this._isFilenameIgnoredTag(name, resolver, type)) {
          continue
        }
        let normalized = normalize(name) || name
        if (seen.has(normalized)) {
          continue
        }
        seen.add(normalized)
        collected.push({
          name: normalized,
          type,
          count: this._resolveTagIncidence(normalized, incidences) ??
              this._resolveTagIncidence(name, incidences),
        })
      }
    }
    if (!collected.length) {
      return empty
    }
    return {tags: collected, pinJoinEmpty: true}
  }

  /**
   * Open tag discovery on this tab if visible, and wake other tabs so a focused
   * non-leader can claim the panel when the processor leader is in the background.
   * @param {Array} tags
   * @param {Array|null|undefined} [knownTags]
   * @return {Promise<void>}
   * @private
   */

  // -------------------------------------------------------------------------
  // Tag discovery panel
  // -------------------------------------------------------------------------

  /**
   * @return {Array<{name: string, type: string|null, count: null}>|null}
   */
  _getSimilarDiscoveryCachedTags()
  {
    return this._similarDiscoveryCachedTags
  }

  /**
   * Claim tag-discovery panel ownership on the visible tab (coordinator writes IDB;
   * followers publish claim-tag-discovery-panel Command).
   * @return {Promise<boolean>}
   * @private
   */
  async _claimTagDiscoveryPanelOwnership()
  {
    if (this._documentSuspended || document.visibilityState !== 'visible') {
      return false
    }
    if (!this._ensureReactor() || !this._kernel) {
      return false
    }
    if (this.isCoordinator()) {
      let current = this.readDmState()
      if (current?.tagDiscoveryPanelTabId === this._tabId) {
        return true
      }
      if (current && typeof current === 'object') {
        let optimistic = structuredClone(current)
        optimistic.tagDiscoveryPanelTabId = this._tabId
        this._publishDmStateToAtoms(optimistic)
      }
      void this._commitCoordinatorDmState((state) => {
        state.tagDiscoveryPanelTabId = this._tabId
      }).catch(() => {})
      return true
    }
    await this.sendCommand({
      type: 'claim-tag-discovery-panel',
      payload: {tabId: this._tabId},
    })
    return true
  }

  /**
   * Claim human-interaction prompt ownership on the visible tab.
   * @param {BrazenHumanInteractionLaneId} context
   * @return {Promise<boolean>}
   * @private
   */
  async _claimHumanInteractionPromptOwnership(context)
  {
    if (context !== 'resolution' && context !== 'download') {
      return false
    }
    if (this._documentSuspended || document.visibilityState !== 'visible') {
      return false
    }
    if (!this._ensureReactor() || !this._kernel) {
      return false
    }
    if (this.isCoordinator()) {
      await this._commitCoordinatorDmState((next) => {
        this._ensureHumanInteractionMap(next)
        let lane = next.humanInteraction[context]
        if (!lane) {
          return
        }
        lane.promptTabId = this._tabId
      })
      return true
    }
    await this.sendCommand({
      type: 'claim-hi-prompt',
      payload: {context, tabId: this._tabId},
    })
    return true
  }

  /**
   * Show the tag-discovery panel on the focused tab. Visible tabs may steal
   * `tagDiscoveryPanelTabId` so opening another search tab cannot leave review
   * locked on a background owner with no UI.
   * @param {Array} tags
   * @param {Array|null|undefined} [knownTags]
   * @return {Promise<void>}
   */
  async _showTagDiscoveryPanel(tags, knownTags = null)
  {
    if (document.visibilityState !== 'visible') {
      return
    }
    let state = this.readDmState()
    if (!state?.resolutionBlocked) {
      this._hideTagDiscoveryPanel()
      return
    }
    await this._claimTagDiscoveryPanelOwnership()
    if (knownTags != null && this.isCoordinator()) {
      let currentKnown = state?.discoveryPanelKnownTags
      let knownJson = JSON.stringify(knownTags ?? [])
      let currentJson = JSON.stringify(currentKnown ?? [])
      if (knownJson !== currentJson) {
        if (state && typeof state === 'object') {
          let optimistic = structuredClone(state)
          optimistic.discoveryPanelKnownTags = knownTags
          this._publishDmStateToAtoms(optimistic)
        }
        void this._commitCoordinatorDmState((next) => {
          next.discoveryPanelKnownTags = knownTags
        }).catch(() => {})
      }
    }
    state = this.readDmState()
    let known = state?.discoveryPanelKnownTags ?? knownTags ?? []
    let tagRuntime = this._cm.getTagRuntime()
    if (tagRuntime) {
      let names = []
      for (let tag of [...(tags ?? []), ...known]) {
        if (tag?.name) {
          names.push(tag.name)
        }
      }
      await tagRuntime.ensureNames(names)
    }
    this._renderTagDiscoveryPanel(tags, known, {forceShow: true})
  }

  /**
   * @private
   */
  _hideTagDiscoveryPanel()
  {
    if (this._tagDiscoveryRefreshTimer != null) {
      clearTimeout(this._tagDiscoveryRefreshTimer)
      this._tagDiscoveryRefreshTimer = null
    }
    this._cancelSimilarDiscoveryLoad()
    this._similarDiscoveryCachedTags = null
    this._similarDiscoveryFingerprint = null
    if (!this._tagDiscoveryPanel) {
      return
    }
    this._framework._hideDockSlidePanel(this._tagDiscoveryPanel)
  }

  /**
   * @param {Array} tags
   * @param {Array|null|undefined} [knownTags]
   * @param {{forceShow?: boolean}} [options]
   */
  _renderTagDiscoveryPanel(tags, knownTags = null, options = {})
  {
    if (!this._config.tagDiscovery) {
      return
    }
    if (!this.readDmState()?.resolutionBlocked) {
      this._hideTagDiscoveryPanel()
      return
    }
    if (!this._tagDiscoveryPanel) {
      this._tagDiscoveryPanel = BrazenViewLayer.createTagDiscoveryPanel({
        onConfirm: () => { void void this.confirmTagDiscoveryMappings() },
        onSkip: () => { void void this.skipTagDiscoveryInclusion() },
        onOpenMedia: () => { void void this.openTagDiscoveryMedia() },
      })
      BrazenViewLayer.appendToDockPanelStack(this._tagDiscoveryPanel)
    }
    let known = knownTags ?? this.readDmState()?.discoveryPanelKnownTags ?? []
    let reviewMode = this.readDmState()?.discoveryReviewMode ?? 'unknown'
    let panelOptions = {
      ...(this._config.tagDiscovery?.panel ?? {}),
      knownTags: known,
      resolveTagNameHref: (tag) => this._resolveTagDiscoveryTagHref(tag),
      onTagNameClick: (tag) => this._openTagDiscoverySearch(tag),
      resetSimilar: true,
      intro: reviewMode === 'ignoredPins'
          ? 'Filename tag slots are empty after ignore. Review ignored tags or similar matches, then confirm to continue.'
          : 'New tags were found. Review actions, then confirm to continue.',
      unknownSectionLabel: reviewMode === 'ignoredPins' ? 'Ignored tags' : undefined,
    }
    if (reviewMode === 'ignoredPins') {
      panelOptions.similarOpen = true
      panelOptions.similarShowCount = false
    }
    let fingerprint = this._discoverySimilarFingerprint(tags, known)
    let sameFingerprint = fingerprint === this._similarDiscoveryFingerprint
    let reuseSimilar = sameFingerprint && Array.isArray(this._similarDiscoveryCachedTags)
    // New tag set → soft-reset Similar (keep open + count) instead of collapsing/flashing.
    panelOptions.resetSimilar = !sameFingerprint
    panelOptions.preserveSimilarOpen = true
    panelOptions.clearSimilarCount = false
    BrazenViewLayer.renderTagDiscoveryPanelContent(
        this._tagDiscoveryPanel,
        tags,
        (row, tag, actionsElement) => {
          this._appendTagDiscoveryFrameworkActions(row, tag, actionsElement)
        },
        panelOptions,
    )
    let forceShow = options.forceShow === true
    if (forceShow || !BrazenViewLayer.isDockSlidePanelVisible(this._tagDiscoveryPanel)) {
      // Steal/restore after hideLocal blur — local DOM may look open without IDB ownership.
      this._framework._showDockSlidePanel(this._tagDiscoveryPanel)
      this._tagDiscoveryPanel.classList.remove('bv-dock-panel-offscreen')
      BrazenViewLayer.ensureDockSlidePanelInteractive(this._tagDiscoveryPanel)
    } else {
      // Content-only refresh — keep the open panel still; only re-stack positions.
      this._framework._syncDockPanelPosition()
    }
    if (reuseSimilar) {
      // Attribute-only refresh: repaint cached similar rows (action chrome) without IDB scan.
      let excluded = this._buildSimilarDiscoveryExcludedNames(tags, known)
      let cached = (this._similarDiscoveryCachedTags ?? []).filter((tag) => !excluded.has(tag.name))
      this._similarDiscoveryCachedTags = cached
      void this._paintSimilarDiscoveryTags(cached, panelOptions)
      return
    }
    if (sameFingerprint && this._similarDiscoveryRaf != null) {
      // Prefetch already scheduled for this tag set — do not restart.
      return
    }
    // Same fingerprint with no cache (cancelled mid-load) or a new fingerprint → (re)load.
    this._scheduleSimilarDiscoveryLoad(tags, known, fingerprint)
  }

  /**
   * @param {string[]|null|undefined} [onlyTagsOverride]
   */
  _refreshTagDiscoveryPanelUi(onlyTagsOverride = undefined)
  {
    if (this._tagDiscoveryActionInFlight) {
      return
    }
    let onlyTags = onlyTagsOverride
    if (onlyTags === undefined) {
      onlyTags = this._tagDiscoveryRefreshOnlyTags
      this._tagDiscoveryRefreshOnlyTags = undefined
    }
    let state = this.readDmState()
    if (!state?.resolutionBlocked) {
      this._hideTagDiscoveryPanel()
      return
    }
    if (state.tagDiscoveryPanelTabId !== this._tabId) {
      return
    }
    if (!this._discoveryPanelHasOpenTags(state)) {
      return
    }
    if (Array.isArray(onlyTags) && onlyTags.length &&
        !this._discoveryOpenTagNamesContainsAny(onlyTags, state)) {
      return
    }
    // Keep the open review list on attribute toggles (including ignored-pin un-ignore).
    // Re-collect only on Confirm / restore — do not drop rows the moment Aa is cleared.
    this._refreshTagDiscoveryActionChrome(onlyTags)
  }

  /**
   * @return {object}
   * @private
   */
  _getTagDiscoveryActionsConfig()
  {
    let discovery = this._config.tagDiscovery ?? {}
    let actions = discovery.actions ?? {}
    return {
      className: actions.className ?? discovery.actionButtonClass,
      iconClass: actions.iconClass ?? discovery.actionIconClass,
      ignoreClassName: actions.ignoreClassName,
      normalize: actions.normalize ?? discovery.normalizeTag,
      bookmark: actions.bookmark,
      ignore: actions.ignore,
      substitute: actions.substitute ?? {fieldKey: discovery.substitutionFieldKey ?? 'filename-tag-substitutions'},
      blacklist: actions.blacklist,
      explore: actions.explore,
      getRowState: actions.getRowState ?? discovery.getRowState,
      extraMutedClasses: actions.extraMutedClasses ?? discovery.extraMutedClasses,
      extraHiddenClasses: actions.extraHiddenClasses ?? discovery.extraHiddenClasses,
    }
  }

  /**
   * Row-scoped tag action chrome refresh — does not rebuild discovery panel groups.
   * @param {string[]|null|undefined} [onlyTags]
   * @private
   */
  _refreshTagDiscoveryActionChrome(onlyTags = null)
  {
    if (!this._tagDiscoveryPanel) {
      return
    }
    let state = this.readDmState()
    if (!state?.resolutionBlocked || state.tagDiscoveryPanelTabId !== this._tabId) {
      return
    }
    let config = this._getTagDiscoveryActionsConfig()
    let normalize = config.normalize
    let tagByName = new Map()
    let addTag = (tag) => {
      if (!tag?.name) {
        return
      }
      tagByName.set(tag.name, tag)
      if (typeof normalize === 'function') {
        let normalized = normalize(tag.name)
        if (normalized) {
          tagByName.set(normalized, tag)
        }
      }
    }
    for (let tag of state.discoveryPanelTags ?? []) {
      addTag(tag)
    }
    for (let tag of state.discoveryPanelKnownTags ?? []) {
      addTag(tag)
    }
    for (let tag of this._similarDiscoveryCachedTags ?? []) {
      addTag(tag)
    }
    let scopedSet = null
    if (Array.isArray(onlyTags) && onlyTags.length) {
      scopedSet = new Set()
      for (let name of onlyTags) {
        if (!name) {
          continue
        }
        scopedSet.add(name)
        if (typeof normalize === 'function') {
          let normalized = normalize(name)
          if (normalized) {
            scopedSet.add(normalized)
          }
        }
      }
    }
    for (let row of this._tagDiscoveryPanel.querySelectorAll('.bv-dock-list-row')) {
      let nameEl = row.querySelector('.bv-dock-list-name[data-tag-name]')
      let rawName = nameEl?.dataset?.tagName ?? ''
      if (!rawName) {
        continue
      }
      if (scopedSet) {
        let normalized = typeof normalize === 'function' ? normalize(rawName) : rawName
        if (!scopedSet.has(rawName) && !scopedSet.has(normalized)) {
          continue
        }
      }
      let tag = tagByName.get(rawName)
      if (!tag && typeof normalize === 'function') {
        tag = tagByName.get(normalize(rawName))
      }
      if (!tag) {
        let typeAttr = nameEl?.dataset?.tagType
        tag = {
          name: rawName,
          type: typeAttr && typeAttr !== '' ? typeAttr : null,
        }
      }
      let actionsElement = row.querySelector('.bv-tag-actions')
      if (!actionsElement) {
        continue
      }
      this._framework.repaintTagAttributeActionRow(actionsElement, row, tag, config)
    }
  }

  /**
   * Build the search-list URL for a discovery-panel tag name (native `<a href>` when possible).
   * @param {{name?: string, type?: string|null}} tag
   * @return {string}
   * @private
   */
  _resolveTagDiscoveryTagHref(tag)
  {
    let discovery = this._config.tagDiscovery ?? {}
    let actions = discovery.actions ?? {}
    let buildUrl = actions.bookmark?.buildUrl
    if (!buildUrl || !tag?.name) {
      return ''
    }
    let normalize = actions.normalize ?? discovery.normalizeTag ?? ((name) => name)
    return String(buildUrl(normalize(tag.name)) ?? '').trim()
  }

  /**
   * Open the consumer search-list URL for a discovery-panel tag name.
   * @param {{name: string, type?: string|null}} tag
   * @private
   */
  _openTagDiscoverySearch(tag)
  {
    let url = this._resolveTagDiscoveryTagHref(tag)
    if (url) {
      Utilities.openUrlInNewTab(url)
    }
  }

  /**
   * @param {Array} tags
   * @param {Array} knownTags
   * @return {string}
   * @private
   */
  _discoverySimilarFingerprint(tags, knownTags)
  {
    let newKey = (Array.isArray(tags) ? tags : [])
        .map((tag) => `${tag?.type ?? ''}\0${tag?.name ?? ''}`)
        .sort()
        .join('\n')
    let knownKey = (Array.isArray(knownTags) ? knownTags : [])
        .map((tag) => tag?.name ?? '')
        .sort()
        .join('\n')
    return newKey + '\n---\n' + knownKey
  }

  /**
   * Cancel in-flight similar prefetch. Keeps a completed cache so hide/restore
   * (e.g. brief visibility loss) can reuse rows without a Loading flash.
   * @private
   */
  _cancelSimilarDiscoveryLoad()
  {
    this._similarDiscoveryLoadId++
    if (this._similarDiscoveryRaf != null) {
      cancelAnimationFrame(this._similarDiscoveryRaf)
      this._similarDiscoveryRaf = null
    }
    if (!Array.isArray(this._similarDiscoveryCachedTags)) {
      // Incomplete load — allow the next render to schedule a fresh prefetch.
      this._similarDiscoveryFingerprint = null
    }
  }

  /**
   * Raw + consumer-normalized tag names currently shown on the open discovery panel.
   * @param {object|null|undefined} [state]
   * @return {Set<string>}
   */
  _buildDiscoveryOpenTagNames(state = null)
  {
    state = state ?? this.readDmState()
    let excluded = this._buildSimilarDiscoveryExcludedNames(
        state?.discoveryPanelTags ?? [],
        state?.discoveryPanelKnownTags ?? [],
    )
    for (let tag of this._similarDiscoveryCachedTags ?? []) {
      let name = tag?.name
      if (name) {
        excluded.add(name)
        let discovery = this._config.tagDiscovery ?? {}
        let normalize = discovery.actions?.normalize ?? discovery.normalizeTag ?? null
        if (typeof normalize === 'function') {
          let normalized = normalize(name)
          if (normalized) {
            excluded.add(normalized)
          }
        }
      }
    }
    return excluded
  }

  /**
   * @param {string|string[]} names Notify/detail tag names (often normalized).
   * @param {object|null|undefined} [state]
   * @return {boolean}
   */
  _discoveryOpenTagNamesContainsAny(names, state = null)
  {
    let openNames = this._buildDiscoveryOpenTagNames(state)
    let list = Array.isArray(names) ? names : (names != null && names !== '' ? [names] : [])
    let discovery = this._config.tagDiscovery ?? {}
    let normalize = discovery.actions?.normalize ?? discovery.normalizeTag ?? null
    return list.some((name) => {
      if (!name) {
        return false
      }
      if (openNames.has(name)) {
        return true
      }
      if (typeof normalize === 'function') {
        let normalized = normalize(name)
        if (normalized && openNames.has(normalized)) {
          return true
        }
      }
      return false
    })
  }

  /**
   * Names already on the discovery panel (New + Known) — never suggest these as Similar.
   * Includes raw and consumer-normalized forms when a normalize hook exists.
   *
   * @param {Array} tags
   * @param {Array} knownTags
   * @return {Set<string>}
   * @private
   */
  _buildSimilarDiscoveryExcludedNames(tags, knownTags)
  {
    let discovery = this._config.tagDiscovery ?? {}
    let normalize = discovery.actions?.normalize ?? discovery.normalizeTag ?? null
    let excluded = new Set()
    let add = (name) => {
      if (!name) {
        return
      }
      excluded.add(name)
      if (typeof normalize === 'function') {
        let normalized = normalize(name)
        if (normalized) {
          excluded.add(normalized)
        }
      }
    }
    for (let tag of Array.isArray(tags) ? tags : []) {
      add(tag?.name)
    }
    for (let tag of Array.isArray(knownTags) ? knownTags : []) {
      add(tag?.name)
    }
    return excluded
  }

  /**
   * Ensure TagRuntime rows for similar names, then paint Similar section actions.
   * @param {Array<{name: string, type: string|null, count?: number|null}>} similarTags
   * @param {object} panelOptions
   * @return {Promise<void>}
   * @private
   */
  async _paintSimilarDiscoveryTags(similarTags, panelOptions)
  {
    if (!this._tagDiscoveryPanel) {
      return
    }
    let tags = Array.isArray(similarTags) ? similarTags.filter((tag) => tag?.name) : []
    let tagRuntime = this._cm.getTagRuntime()
    if (tagRuntime && tags.length) {
      await tagRuntime.ensureNames(tags.map((tag) => tag.name))
    }
    if (!this._tagDiscoveryPanel) {
      return
    }
    BrazenViewLayer.renderTagDiscoverySimilarContent(
        this._tagDiscoveryPanel,
        tags,
        (row, tag, actionsElement) => {
          this._appendTagDiscoveryFrameworkActions(row, tag, actionsElement)
        },
        panelOptions,
    )
  }

  /**
   * After paint: prefetch similar registry tags for the current new-tag set.
   * @param {Array} tags
   * @param {Array} knownTags
   * @param {string} fingerprint
   * @private
   */
  _scheduleSimilarDiscoveryLoad(tags, knownTags, fingerprint)
  {
    if (this._similarDiscoveryRaf != null) {
      cancelAnimationFrame(this._similarDiscoveryRaf)
      this._similarDiscoveryRaf = null
    }
    let loadId = ++this._similarDiscoveryLoadId
    this._similarDiscoveryFingerprint = fingerprint
    this._similarDiscoveryCachedTags = null
    this._similarDiscoveryRaf = requestAnimationFrame(() => {
      this._similarDiscoveryRaf = null
      void this._loadSimilarDiscoveryTags(loadId, tags, knownTags, fingerprint)
    })
  }

  /**
   * Strip bracket segments and split snake_case tokens for similarity scoring.
   * @param {string} name
   * @return {string[]}
   * @private
   */
  _normalizeTagTokensForSimilarity(name)
  {
    let cleaned = String(name ?? '').replace(/\([^)]*\)|\[[^\]]*\]/g, '')
    cleaned = cleaned.replace(/_+/g, '_').replace(/^_|_$/g, '')
    if (!cleaned) {
      return []
    }
    return cleaned.split('_').filter(Boolean)
  }

  /**
   * Coverage of the longest contiguous shared token run over the shorter joined length.
   * @param {string[]} tokensA
   * @param {string[]} tokensB
   * @return {number}
   * @private
   */
  _scoreTagTokensSimilarity(tokensA, tokensB)
  {
    if (!tokensA?.length || !tokensB?.length) {
      return 0
    }
    let bestLen = 0
    let bestJoined = 0
    for (let i = 0; i < tokensA.length; i++) {
      for (let j = 0; j < tokensB.length; j++) {
        let k = 0
        while (i + k < tokensA.length && j + k < tokensB.length && tokensA[i + k] === tokensB[j + k]) {
          k++
        }
        if (k > bestLen) {
          bestLen = k
          bestJoined = tokensA.slice(i, i + k).join('_').length
        }
      }
    }
    if (!bestLen) {
      return 0
    }
    let shorter = Math.min(tokensA.join('_').length, tokensB.join('_').length)
    return shorter > 0 ? bestJoined / shorter : 0
  }

  /**
   * Keep at most {@link SIMILAR_DISCOVERY_LIMIT} unique candidates (by name), ranked by score.
   * @param {Array<{name: string, type: string|null, count: null, score: number}>} top
   * @param {{name: string, type: string|null, count: null, score: number}} candidate
   * @private
   */
  _considerSimilarDiscoveryCandidate(top, candidate)
  {
    let existingIdx = top.findIndex((row) => row.name === candidate.name)
    if (existingIdx >= 0) {
      if (candidate.score > top[existingIdx].score) {
        top[existingIdx] = candidate
        top.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name))
      }
      return
    }
    if (top.length < SIMILAR_DISCOVERY_LIMIT) {
      top.push(candidate)
      top.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name))
      return
    }
    let worst = top[top.length - 1]
    if (candidate.score < worst.score ||
        (candidate.score === worst.score && candidate.name >= worst.name)) {
      return
    }
    top[top.length - 1] = candidate
    top.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name))
  }

  /**
   * @param {number} loadId
   * @param {Array} tags
   * @param {Array} knownTags
   * @param {string} fingerprint
   * @return {Promise<void>}
   * @private
   */
  async _loadSimilarDiscoveryTags(loadId, tags, knownTags, fingerprint)
  {
    if (!this._tagDiscoveryPanel || loadId !== this._similarDiscoveryLoadId) {
      return
    }
    let newTags = Array.isArray(tags) ? tags.filter((tag) => tag?.name) : []
    let reviewMode = this.readDmState()?.discoveryReviewMode ?? 'unknown'
    let panelOptions = {
      ...(this._config.tagDiscovery?.panel ?? {}),
      onTagNameClick: (tag) => this._openTagDiscoverySearch(tag),
    }
    if (reviewMode === 'ignoredPins') {
      panelOptions.similarOpen = true
      panelOptions.similarShowCount = false
    }
    let finish = async (similar) => {
      if (loadId !== this._similarDiscoveryLoadId) {
        return
      }
      let excluded = this._buildSimilarDiscoveryExcludedNames(tags, knownTags)
      let filtered = (Array.isArray(similar) ? similar : []).filter((tag) => tag?.name && !excluded.has(tag.name))
      this._similarDiscoveryCachedTags = filtered
      this._similarDiscoveryFingerprint = fingerprint
      await this._paintSimilarDiscoveryTags(filtered, panelOptions)
      if (loadId !== this._similarDiscoveryLoadId) {
        return
      }
    }
    if (!newTags.length || !this._cm.canPersist()) {
      await finish([])
      return
    }

    // Exclude every name already listed under New tags or Known tags.
    let excluded = this._buildSimilarDiscoveryExcludedNames(newTags, knownTags)

    /** @type {Array<{name: string, type: string|null, count: null, score: number}>} */
    let top = []
    /** @type {Map<string, Array<{name: string, tokens: string[]}>>} */
    let tagsByType = new Map()
    for (let tag of newTags) {
      let type = tag.type ?? 'unknown'
      if (!tagsByType.has(type)) {
        tagsByType.set(type, [])
      }
      tagsByType.get(type).push({
        name: tag.name,
        tokens: this._normalizeTagTokensForSimilarity(tag.name),
      })
    }

    let tagRepo = this._repos()?.tags
    if (!tagRepo) {
      await finish([])
      return
    }

    for (let [type, typeNewTags] of tagsByType) {
      if (loadId !== this._similarDiscoveryLoadId) {
        return
      }
      let typeEntryId = type && type !== 'unknown'
          ? await tagRepo.resolveCanonicalTypeEntryId(type)
          : null
      if (typeEntryId == null) {
        continue
      }
      let discovery = this._config.tagDiscovery ?? {}
      let normalize = discovery.actions?.normalize ?? discovery.normalizeTag
      let cursor = null
      while (true) {
        if (loadId !== this._similarDiscoveryLoadId) {
          return
        }
        let page = await tagRepo.listTags({
          typeEntryId,
          limit: SIMILAR_DISCOVERY_PAGE_SIZE,
          cursor,
        })
        let entries = page.entries ?? []
        let nextCursor = page.nextCursor
        for (let entry of entries) {
          let candidateName = entry?.name
          if (!candidateName || excluded.has(candidateName)) {
            continue
          }
          if (typeof normalize === 'function') {
            let normalizedCandidate = normalize(candidateName)
            if (normalizedCandidate && excluded.has(normalizedCandidate)) {
              continue
            }
          }
          let candidateTokens = this._normalizeTagTokensForSimilarity(candidateName)
          let bestScore = 0
          for (let newTag of typeNewTags) {
            let score = this._scoreTagTokensSimilarity(newTag.tokens, candidateTokens)
            if (score > bestScore) {
              bestScore = score
            }
          }
          if (bestScore <= 0.5) {
            continue
          }
          this._considerSimilarDiscoveryCandidate(top, {
            name: candidateName,
            type,
            count: null,
            score: bestScore,
          })
        }
        if (!nextCursor) {
          break
        }
        cursor = nextCursor
        await Utilities.sleep(0)
      }
    }

    await finish(top.map(({name, type, count}) => ({name, type, count})))
  }

  /**
   * Framework owns tag-attribute actions; discovery panel maps them internally.
   * @param {HTMLElement} row
   * @param {{name: string, type: string|null}} tag
   * @param {HTMLElement} actionsElement
   * @private
   */
  _appendTagDiscoveryFrameworkActions(row, tag, actionsElement)
  {
    this._framework.repaintTagAttributeActionRow(
        actionsElement, row, tag, this._getTagDiscoveryActionsConfig())
  }

  async _presentTagDiscoveryReview(tags, knownTags = null)
  {
    await this._showTagDiscoveryPanel(tags, knownTags)
    this._signalProcessorsWake()
  }

  async _releaseTagDiscoveryPanelOwnership(options = {})
  {
    await Utilities.releaseDockPanelOwnership({
      hideLocal: options.hideLocal,
      hidePanel: () => this._hideTagDiscoveryPanel(),
      documentSuspended: this._documentSuspended,
      commitRelease: async () => {
        // Panel ownership is coordinator-only IDB; visibility handlers can fire during
        // initialize() before the Web Lock is held (even on the sole tab).
        if (!this.isCoordinator()) {
          return
        }
        await this._commitCoordinatorDmState((state) => {
          if (state.tagDiscoveryPanelTabId === this._tabId) {
            state.tagDiscoveryPanelTabId = null
          }
        })
      },
    })
  }

  /**
   * Re-collect ignored-pin rows for restore / Confirm (not attribute toggles).
   * Attribute toggles keep the open list and only refresh action chrome.
   * @return {Promise<void>}
   * @private
   */
  async _refreshIgnoredPinDiscoveryPanel()
  {
    if (!this.isCoordinator()) {
      return
    }
    let state = this.readDmState()
    if (!state?.resolutionBlocked || state.discoveryReviewMode !== 'ignoredPins' ||
        state.tagDiscoveryPanelTabId !== this._tabId) {
      return
    }
    let refreshGen = (this._ignoredPinRefreshGen = (this._ignoredPinRefreshGen ?? 0) + 1)

    let itemId = state.resolutionBlockedItemId
    if (!itemId) {
      await this._reconcileDiscoveryGateFromPending(null, {refreshIgnoredOnly: true, wakeProcessors: true})
      return
    }
    let queueItem = await this._repos().downloadResolutionQueue.get(itemId)
    if (refreshGen !== this._ignoredPinRefreshGen) {
      return
    }
    if (!queueItem?.pendingTagGroups) {
      await this._reconcileDiscoveryGateFromPending(itemId, {refreshIgnoredOnly: true, wakeProcessors: true})
      return
    }
    await this._reconcileDiscoveryGateFromPending(queueItem, {refreshIgnoredOnly: true})
    if (refreshGen !== this._ignoredPinRefreshGen) {
      return
    }
  }

  /**
   * Re-render tag discovery panel rows (e.g. after bookmark/rule toggles).
   * @param {string[]|null} [onlyTagNames] When set, no-op unless the open panel lists one.
   */
  refreshTagDiscoveryPanel(onlyTagNames = null)
  {
    this._requestTagDiscoveryPanelRefresh(onlyTagNames)
  }

  /**
   * @param {string|string[]} tags
   * @return {boolean}
   */
  openTagDiscoveryContainsAnyTag(tags)
  {
    let names = Array.isArray(tags) ? tags : (tags != null && tags !== '' ? [tags] : [])
    if (!names.length) {
      return false
    }
    let state = this.readDmState()
    if (!state?.resolutionBlocked || state.tagDiscoveryPanelTabId !== this._tabId ||
        !this._discoveryPanelHasOpenTags(state)) {
      return false
    }
    return this._discoveryOpenTagNamesContainsAny(names, state)
  }

  /**
   * @param {object|null|undefined} state
   * @return {boolean}
   * @private
   */
  _discoveryPanelHasOpenTags(state)
  {
    if (!state) {
      return false
    }
    if ((state.discoveryPanelTags?.length ?? 0) > 0) {
      return true
    }
    if ((state.discoveryPanelKnownTags?.length ?? 0) > 0) {
      return true
    }
    return (this._similarDiscoveryCachedTags?.length ?? 0) > 0
  }

  /**
   * Re-open the tag discovery panel after reload / tab focus when review is still pending.
   * Safe to call once the dock exists (or on later focus). Visible tabs steal ownership.
   * @return {Promise<void>}
   */
  async restoreTagDiscoveryPanelIfNeeded()
  {
    if (!this._config.tagDiscovery) {
      return
    }
    if (this._tagDiscoveryActionInFlight) {
      return
    }
    if (document.visibilityState !== 'visible') {
      return
    }
    let state = await this._getState()
    if (!state.resolutionBlocked) {
      this._hideTagDiscoveryPanel()
      await this._recoverOrphanTagDiscoveryReviewIfNeeded()
      return
    }
    // Followers: read-only restore from coordinator patches (claim ownership + show cached rows).
    if (!this.isCoordinator()) {
      if (state.discoveryPanelTags?.length) {
        await this._showTagDiscoveryPanel(state.discoveryPanelTags, state.discoveryPanelKnownTags ?? [])
      }
      return
    }
    // Re-collect ignored-pin rows so un-ignore / strip-base ignores match current registry.
    if (state.discoveryReviewMode === 'ignoredPins') {
      await this._claimTagDiscoveryPanelOwnership()
      await this._refreshIgnoredPinDiscoveryPanel()
      let after = this.readDmState()
      if (!after?.resolutionBlocked) {
        this._hideTagDiscoveryPanel()
        return
      }
      if (after.discoveryPanelTags?.length) {
        await this._showTagDiscoveryPanel(after.discoveryPanelTags, [])
        return
      }
      this._hideTagDiscoveryPanel()
      let stuckId = after.resolutionBlockedItemId
      let stuckItem = stuckId ? await this._repos().downloadResolutionQueue.get(stuckId) : null
      if (stuckItem?.pendingTagGroups) {
        await this._finishResolutionAfterTagDiscovery(stuckItem)
      } else if (this.isCoordinator()) {
        await this._commitCoordinatorDmState((next) => {
          this._clearDiscoveryReviewState(next)
        })
        this._signalProcessorsWakeAndRunIfCoordinator()
      }
      return
    }
    let queueItem = null
    if (state.resolutionBlockedItemId) {
      queueItem = await this._repos().downloadResolutionQueue.get(state.resolutionBlockedItemId)
    }
    if (queueItem?.pendingTagGroups) {
      let outcome = await this._reconcileDiscoveryGateFromPending(queueItem, {stealOwnership: true})
      if (outcome === 'unknownReview' || outcome === 'ignoredPinReview') {
        return
      }
      if (outcome === 'finished' || outcome === 'cleared') {
        this._hideTagDiscoveryPanel()
        return
      }
    }
    if (!(queueItem?.pendingTagGroups)) {
      this._hideTagDiscoveryPanel()
      if (this.isCoordinator()) {
        await this._commitDmStateAndWake((next) => {
          this._clearDiscoveryReviewState(next)
        })
      }
      return
    }
    // No tag groups available to re-scope — last resort show cached rows.
    if (state.discoveryPanelTags?.length) {
      await this._showTagDiscoveryPanel(state.discoveryPanelTags, state.discoveryPanelKnownTags ?? [])
    }
  }

  /**
   * Recover orphan `tagReview` rows when IDB gate is clear (reload / cross-tab Confirm).
   * @return {Promise<void>}
   * @private
   */
  async _recoverOrphanTagDiscoveryReviewIfNeeded()
  {
    if (!this.isCoordinator()) {
      return
    }
    let resolutionRows = await this._repos().downloadResolutionQueue.listByStatus('tagReview')
    let stuck = resolutionRows.find((row) => row.pendingTagGroups)
    if (!stuck) {
      return
    }
    let outcome = await this._reconcileDiscoveryGateFromPending(stuck, {wakeProcessors: true})
    if (outcome !== 'ignoredPinReview') {
      return
    }
    let latest = this.readDmState() ?? await this._getState()
    if (this._isDeferredTagDiscoveryEnabled() && !latest?.resolutionBlocked) {
      let [deferredRows, queuedRows] = await Promise.all([
        this._repos().downloadResolutionQueue.listByStatus('discoveryQueued'),
        this._repos().downloadResolutionQueue.listByStatus('queued'),
      ])
      if (deferredRows.length && !queuedRows.length) {
        await this._commitDmStateAndWake((next) => {
          next.discoveryLanePhaseActive = true
        })
      }
    }
  }

  // -------------------------------------------------------------------------
  // Processors & sync
  // -------------------------------------------------------------------------

  /**
   * Notify other tabs that queue work may be available (Reactor Command bus).
   * @private
   */
  _signalProcessorsWake()
  {
    this._requestPipelineRun()
  }

  /**
   * @param {object|null|undefined} state
   * @private
   */
  _publishDmStateToAtoms(state)
  {
    let nextJson
    try {
      nextJson = JSON.stringify(state ?? null)
    } catch (_e) {
      nextJson = null
    }
    if (nextJson === this._lastPublishedStateJson) {
      return
    }
    this._lastPublishedStateJson = nextJson
    if (this._dmStateAtom?.write) {
      this._dmStateAtom.write(state ?? null)
    } else {
      this._dmStateAtom?.set?.(state ?? null)
    }
    this._requestDockProgressPaint({refreshAllItems: false})
  }

  /**
   * @param {boolean} value
   * @private
   */
  _setCurrentMediaQueued(value)
  {
    this._currentMediaQueued = !!value
    this._currentMediaQueuedAtom?.write?.(this._currentMediaQueued) ??
        this._currentMediaQueuedAtom?.set?.(this._currentMediaQueued)
  }

  /**
   * @param {number} download
   * @param {number} resolution
   * @private
   */
  _commitPendingCountSignals(download, resolution)
  {
    if (download === this._lastCommittedPendingDownload &&
        resolution === this._lastCommittedPendingResolution) {
      return
    }
    this._lastCommittedPendingDownload = download
    this._lastCommittedPendingResolution = resolution
    this._pendingDownloadCountAtom?.write?.(download) ??
        this._pendingDownloadCountAtom?.set?.(download)
    this._pendingResolutionCountAtom?.write?.(resolution) ??
        this._pendingResolutionCountAtom?.set?.(resolution)
    this._requestDockProgressPaint({refreshAllItems: false})
  }

  /**
   * Wire DM dock progress + tag-discovery UI as signal effects (Reactor Phase 2).
   * @return {boolean}
   * @private
   */
  _ensureDmUiEffects()
  {
    if (this._dmUiEffectsReady) {
      return true
    }
    let signals = globalThis.BrazenSignals
    if (!signals?.atom || !signals?.effect) {
      return false
    }

    this._dmStateAtom = signals.atom('dm.state.snapshot', this.readDmState() ?? null)
    void this._getState().then((state) => {
      this._dmStateAtom?.write?.(state ?? null)
    })
    this._pendingDownloadCountAtom = signals.atom('dm.pendingDownloadCount', 0)
    this._pendingResolutionCountAtom = signals.atom('dm.pendingResolutionCount', 0)
    this._currentMediaQueuedAtom = signals.atom('dm.currentMediaQueued', !!this._currentMediaQueued)
    this._dockProgressEpochAtom = signals.atom('dm.dockProgressEpoch', 0)
    this._tagDiscoveryRefreshAtom = signals.atom('dm.tagDiscoveryRefresh', {epoch: 0, onlyTags: null})
    this._localEditsDirtyAtom = signals.atom('reactor.localEditsDirty', false)

    this._dmUiEffectDisposers.push(signals.effect(() => {
      void this._dockProgressEpochAtom.read()
    }, () => {
      this._scheduleCoalescedDockProgressPaint()
      return () => {
        this._dockProgressPaintCancel?.()
        this._dockProgressPaintCancel = null
        this._dockProgressPaintRaf = null
        this._dockProgressEpochBumpCancel?.()
        this._dockProgressEpochBumpCancel = null
        this._dockProgressEpochBumpRaf = null
        this._tagDiscoveryRefreshBumpCancel?.()
        this._tagDiscoveryRefreshBumpCancel = null
        this._tagDiscoveryRefreshBumpRaf = null
      }
    }, 'dmDockProgress'))

    this._dmUiEffectDisposers.push(signals.effect(() => {
      let req = this._tagDiscoveryRefreshAtom.read()
      void req.epoch
    }, () => {
      globalThis.__brazenReactor?.mark?.('effect', 'dmTagDiscovery', {key: 'tagDiscovery'})
      if (this._tagDiscoveryActionInFlight) {
        return
      }
      let req = this._tagDiscoveryRefreshAtom.read()
      let timer = setTimeout(() => {
        let stop = globalThis.__brazenReactor?.time?.('effect', 'dmTagDiscoveryPaint', {key: 'tagDiscovery'})
        this._refreshTagDiscoveryPanelUi(req.onlyTags)
        stop?.()
      }, 0)
      return () => clearTimeout(timer)
    }, 'dmTagDiscovery'))

    this._dmUiEffectsReady = true
    return true
  }

  /**
   * Coalesce dock progress paints to at most one per ~16ms (follower patch bursts).
   * Uses timers (not rAF) so paints still run on backgrounded tabs and between lane awaits.
   * @private
   */
  _scheduleCoalescedDockProgressPaint()
  {
    if (this._dockProgressPaintCancel) {
      return
    }
    let run = () => {
      this._dockProgressPaintCancel = null
      this._dockProgressPaintRaf = null
      globalThis.__brazenReactor?.mark?.('effect', 'dmDockProgress', {key: 'dockProgress'})
      let refreshAllItems = this._dockProgressRefreshAllItems
      this._dockProgressRefreshAllItems = false
      let stop = globalThis.__brazenReactor?.time?.('effect', 'dmDockPaint', {key: 'dockProgress'})
      void this._paintDockProgress({refreshAllItems}).finally(() => {
        stop?.()
      })
    }
    this._dockProgressPaintRaf = setTimeout(run, 16)
    this._dockProgressPaintCancel = () => clearTimeout(this._dockProgressPaintRaf)
  }

  /**
   * Coalesce dock progress epoch bumps to at most one per animation frame.
   * @private
   */
  _scheduleCoalescedDockProgressEpochBump()
  {
    if (this._dockProgressEpochBumpCancel) {
      return
    }
    let run = () => {
      this._dockProgressEpochBumpCancel = null
      this._dockProgressEpochBumpRaf = null
      if (this._dockProgressEpochAtom?.write) {
        this._dockProgressEpochAtom.write(this._dockProgressEpochAtom.read() + 1)
        return
      }
      if (this._dockProgressEpochAtom?.set) {
        this._dockProgressEpochAtom.set(this._dockProgressEpochAtom.value + 1)
        return
      }
      this._scheduleCoalescedDockProgressPaint()
    }
    if (typeof requestAnimationFrame === 'function') {
      this._dockProgressEpochBumpRaf = requestAnimationFrame(run)
      this._dockProgressEpochBumpCancel = () => cancelAnimationFrame(this._dockProgressEpochBumpRaf)
    } else {
      this._dockProgressEpochBumpRaf = setTimeout(run, 16)
      this._dockProgressEpochBumpCancel = () => clearTimeout(this._dockProgressEpochBumpRaf)
    }
  }

  /**
   * @param {{refreshAllItems?: boolean}} [options]
   * @private
   */
  _requestDockProgressPaint(options = {})
  {
    if (options.refreshAllItems === true) {
      this._dockProgressRefreshAllItems = true
    }
    this._scheduleCoalescedDockProgressPaint()
  }

  /**
   * @param {string[]|null} [onlyTagNames]
   * @private
   */
  _requestTagDiscoveryPanelRefresh(onlyTagNames = null)
  {
    if (this._tagDiscoveryActionInFlight) {
      return
    }
    if (!this._tagDiscoveryRefreshAtom) {
      return
    }
    if (onlyTagNames == null) {
      this._tagDiscoveryRefreshPendingOnlyTags = null
    } else if (Array.isArray(onlyTagNames) && onlyTagNames.length) {
      let pending = this._tagDiscoveryRefreshPendingOnlyTags
      if (pending === null) {
        // Already unrestricted this frame.
      } else {
        let atomPrev = this._tagDiscoveryRefreshAtom.read?.() ?? this._tagDiscoveryRefreshAtom.value
        if (pending === undefined) {
          if (atomPrev?.onlyTags == null && (atomPrev?.epoch ?? 0) > 0) {
            this._tagDiscoveryRefreshPendingOnlyTags = null
          } else if (atomPrev?.onlyTags == null && (atomPrev?.epoch ?? 0) === 0) {
            this._tagDiscoveryRefreshPendingOnlyTags = onlyTagNames.slice()
          } else if (Array.isArray(atomPrev?.onlyTags)) {
            this._tagDiscoveryRefreshPendingOnlyTags = atomPrev.onlyTags.slice()
          } else {
            this._tagDiscoveryRefreshPendingOnlyTags = onlyTagNames.slice()
          }
        }
        pending = this._tagDiscoveryRefreshPendingOnlyTags
        if (Array.isArray(pending)) {
          let seen = new Set(pending)
          for (let name of onlyTagNames) {
            if (name && !seen.has(name)) {
              seen.add(name)
              pending.push(name)
            }
          }
        }
      }
    }
    this._scheduleCoalescedTagDiscoveryRefreshBump()
  }

  /**
   * Coalesce tag-discovery panel refresh atom bumps to at most one per ~16ms tick.
   * Uses setTimeout (not rAF) so refreshes still fire on a backgrounded coordinator tab.
   * @private
   */
  _scheduleCoalescedTagDiscoveryRefreshBump()
  {
    if (this._tagDiscoveryRefreshBumpCancel) {
      return
    }
    let run = () => {
      this._tagDiscoveryRefreshBumpCancel = null
      this._tagDiscoveryRefreshBumpRaf = null
      if (!this._tagDiscoveryRefreshAtom) {
        this._tagDiscoveryRefreshPendingOnlyTags = undefined
        return
      }
      let prev = this._tagDiscoveryRefreshAtom.read?.() ?? this._tagDiscoveryRefreshAtom.value
      let onlyTags = this._tagDiscoveryRefreshPendingOnlyTags
      this._tagDiscoveryRefreshPendingOnlyTags = undefined
      if (onlyTags === undefined) {
        return
      }
      if (this._tagDiscoveryRefreshAtom?.write) {
        this._tagDiscoveryRefreshAtom.write({epoch: (prev?.epoch ?? 0) + 1, onlyTags})
      } else {
        this._tagDiscoveryRefreshAtom.set({epoch: (prev?.epoch ?? 0) + 1, onlyTags})
      }
    }
    this._tagDiscoveryRefreshBumpRaf = setTimeout(run, 16)
    this._tagDiscoveryRefreshBumpCancel = () => clearTimeout(this._tagDiscoveryRefreshBumpRaf)
  }

  /**
   * @param {object|null|undefined} state
   * @return {string}
   * @private
   */
  _followerGateSignature(state)
  {
    let parts = []
    for (let key of DM_STATE_GATE_PATCH_FIELDS) {
      parts.push(JSON.stringify(state?.[key] ?? null))
    }
    return parts.join('|')
  }

  /**
   * Follower: repaint dock chrome, selection marks, and gate-driven panels after patch apply.
   * @param {{changed?: boolean, snapshotPatchInBatch?: boolean, queueCountTouched?: boolean, progressFieldTouched?: boolean, gateKeysChanged?: Set<string>, state?: object}} options
   * @private
   */
  _reconcileFollowerVisibleUiAfterPatches(options = {})
  {
    if (this._documentSuspended || document.visibilityState !== 'visible') {
      return
    }
    let changed = options.changed === true
    let snapshotPatchInBatch = options.snapshotPatchInBatch === true
    let queueCountTouched = options.queueCountTouched === true
    let progressFieldTouched = options.progressFieldTouched === true
    let gateKeysChanged = options.gateKeysChanged
    if (changed || snapshotPatchInBatch) {
      this._requestDockProgressPaint({refreshAllItems: false})
    } else if (progressFieldTouched) {
      this._requestDockProgressPaint({refreshAllItems: false})
    }
    if (queueCountTouched || progressFieldTouched) {
      this._scheduleFollowerSelectionMarksRefresh()
    } else if (changed || snapshotPatchInBatch) {
      this._refreshSelectionMarks()
    }
    let state = this.readDmState() ?? options.state
    if (!state) {
      return
    }
    let sig = this._followerGateSignature(state)
    if (sig === this._lastFollowerGateSignature) {
      return
    }
    this._lastFollowerGateSignature = sig
    let onlyOwnPanelClaim = gateKeysChanged?.size === 1 &&
        gateKeysChanged.has('tagDiscoveryPanelTabId') &&
        state.tagDiscoveryPanelTabId === this._tabId
    if (!onlyOwnPanelClaim) {
      this._scheduleFollowerVisibleDockPanelRestore()
    }
  }

  /**
   * Coordinator: publish dm.state.* patches via kernel (no direct bus publish).
   * @param {object|null|undefined} state
   * @private
   */
  _publishDmStateProgressViaKernel(state)
  {
    if (!this._reactorReady || !this._kernel?.isCoordinator() || !state) {
      return
    }
    let patches = DM_STATE_PATCH_FIELDS
        .filter((field) => field in state)
        .map((field) => ({
          path: `dm.state.${field}`,
          value: structuredClone(state[field]),
        }))
    if (!patches.length) {
      return
    }
    void this._kernel.commitStatePatches(
        {type: 'dm-state-progress', payload: {}},
        patches,
    )
  }

  /**
   * Follower: config revision patches — schedule local config-ui after atom apply.
   * @param {object[]|null|undefined} patches
   * @private
   */
  _maybeScheduleConfigUiFromPatches(patches)
  {
    if (!patches?.length || this._kernel?.isCoordinator()) {
      return
    }
    let configTouched = patches.some((patch) => {
      let path = patch?.path
      return typeof path === 'string' && path.startsWith('config.')
    })
    if (!configTouched) {
      return
    }
    globalThis.__brazenReactor?.mark?.('scheduler', 'config-ui-from-patches')
    this.scheduleConfigUiReaction({source: 'all', local: false, detail: null})
  }

  /**
   * Follower: apply coordinator dm.state patches to cached state and refresh dock chrome.
   * @param {object[]|null|undefined} patches
   * @param {number} [seq]
   * @private
   */
  _reconcileBusPatches(patches, seq = 0)
  {
    if (!patches?.length || this._kernel?.isCoordinator()) {
      return
    }
    let state = this.readDmState()
    if (!state) {
      return
    }
    if (!this._dmPatchVersions) {
      this._dmPatchVersions = new Map()
    }
    let changed = false
    let queueCountTouched = false
    let progressFieldTouched = false
    let snapshotPatchInBatch = false
    /** @type {Set<string>} */
    let gateKeysChanged = new Set()
    for (let patch of patches) {
      let path = patch?.path
      if (path === 'dm.state.snapshot') {
        snapshotPatchInBatch = true
      }
      if (path === 'dm.pendingResolutionCount' || path === 'dm.pendingDownloadCount') {
        queueCountTouched = true
      }
      if (!path?.startsWith('dm.state.')) {
        continue
      }
      if (path === 'dm.state.snapshot') {
        continue
      }
      let key = path.slice('dm.state.'.length)
      if (!key) {
        continue
      }
      if (DM_STATE_PATCH_FIELDS.includes(key) ||
          key === 'lastResolutionInitiationAt' ||
          key === 'lastDownloadInitiationAt') {
        progressFieldTouched = true
      }
      let patchVersion = typeof patch.version === 'number' ? patch.version : 0
      let lastVersion = this._dmPatchVersions.get(key) ?? 0
      if (patchVersion > 0 && patchVersion <= lastVersion) {
        continue
      }
      if (patchVersion > lastVersion) {
        this._dmPatchVersions.set(key, patchVersion)
      }
      state[key] = patch.value
      changed = true
      if (DM_STATE_GATE_PATCH_FIELDS.includes(key)) {
        gateKeysChanged.add(key)
      }
    }
    if (!changed) {
      if (snapshotPatchInBatch || queueCountTouched || progressFieldTouched) {
        if (Number.isFinite(seq) && seq > this._lastAppliedPatchSeq) {
          this._lastAppliedPatchSeq = seq
        }
        this._reconcileFollowerPendingCommands(seq, patches)
        this._reconcileFollowerVisibleUiAfterPatches({
          changed: false,
          snapshotPatchInBatch,
          queueCountTouched,
          progressFieldTouched,
          gateKeysChanged,
          state,
        })
      }
      return
    }
    if (Number.isFinite(seq) && seq > this._lastAppliedPatchSeq) {
      this._lastAppliedPatchSeq = seq
    }
    this._reconcileFollowerPendingCommands(seq, patches)
    if (!snapshotPatchInBatch) {
      this._publishDmStateToAtoms(state)
    }
    this._reconcileFollowerVisibleUiAfterPatches({
      changed: true,
      snapshotPatchInBatch,
      queueCountTouched,
      progressFieldTouched,
      gateKeysChanged,
      state,
    })
  }

  /**
   * Follower: coalesce gate-patch panel restore (HI + tag discovery) on the visible tab.
   * @private
   */
  _scheduleFollowerVisibleDockPanelRestore()
  {
    if (this._documentSuspended || document.visibilityState !== 'visible') {
      return
    }
    if (this._followerPanelRestoreTimer) {
      clearTimeout(this._followerPanelRestoreTimer)
    }
    this._followerPanelRestoreTimer = setTimeout(() => {
      this._followerPanelRestoreTimer = null
      void this._restoreVisibleDockPanelsIfNeeded()
    }, PROCESSORS_WAKE_RECEIVE_DEBOUNCE_MS)
  }

  /**
   * Follower: coalesce selection-mark reconcile when queue membership count patches arrive.
   * @private
   */
  _scheduleFollowerSelectionMarksRefresh()
  {
    if (this._documentSuspended || document.visibilityState !== 'visible') {
      return
    }
    if (!this.isDownloadPageRole('selection')) {
      return
    }
    if (this._followerSelectionMarksTimer) {
      clearTimeout(this._followerSelectionMarksTimer)
    }
    this._followerSelectionMarksTimer = setTimeout(() => {
      this._followerSelectionMarksTimer = null
      this._refreshSelectionMarks()
    }, PROCESSORS_WAKE_RECEIVE_DEBOUNCE_MS)
  }

  /**
   * Refresh pending queue count signal mirrors from IDB (coalesced dock paint input).
   * @return {Promise<void>}
   * @private
   */
  async _syncPendingCountSignalsFromIdb()
  {
    let download = await this._getPendingDownloadCount()
    let resolution = await this._getPendingResolutionCount()
    this._commitPendingCountSignals(download, resolution)
  }

  /**
   * Notify followers that dock progress counters changed (Reactor patch stream).
   * @private
   */
  _signalProgressUiWake()
  {
    let state = this.readDmState()
    if (this._reactorReady && this._kernel?.isCoordinator()) {
      this._publishDmStateProgressViaKernel(state)
    }
  }

  /**
   * Schedule reclaim / resume after focus, bfcache restore, or cross-tab wake.
   * Debounced so stacked wake events do not run concurrent full syncs.
   * @param {{fullUi?: boolean, requeueInterrupted?: boolean}} [options]
   * @private
   */
  _scheduleResumeFromExternalWake(options = {})
  {
    let fullUi = options.fullUi !== false
    let requeueInterrupted = options.requeueInterrupted === true
    let runResume = () => {
      this._processorsWakeResumeTail = this._processorsWakeResumeTail
          .catch(() => {})
          .then(() => this._resumeProcessorsFromExternalWake({fullUi, requeueInterrupted}))
    }
    if (this._processorsWakeReceiveTimer) {
      clearTimeout(this._processorsWakeReceiveTimer)
    }
    this._processorsWakeReceiveTimer = setTimeout(() => {
      this._processorsWakeReceiveTimer = null
      runResume()
    }, PROCESSORS_WAKE_RECEIVE_DEBOUNCE_MS)
  }

  /**
   * Resume coordinator pump after focus / bfcache restore / cross-tab wake.
   * Hidden tabs skip full UI hydrate — only cheap processor resume.
   * @param {{fullUi?: boolean, requeueInterrupted?: boolean}} [options]
   * @return {Promise<void>}
   * @private
   */
  async _resumeProcessorsFromExternalWake(options = {})
  {
    if (this._documentSuspended) {
      return
    }
    let fullUi = options.fullUi !== false && document.visibilityState === 'visible'
    let requeueInterrupted = options.requeueInterrupted === true
    if (fullUi) {
      await this._syncFromStorage()
    } else if (this._cm.canPersist()) {
      this._publishDmStateToAtoms(await this._repos().downloadManagerState.get())
      if (!this.readDmState()?.resolutionBlocked) {
        this._hideTagDiscoveryPanel()
      }
    }
    if (requeueInterrupted && this._cm.canPersist() && this._kernel?.isCoordinator()) {
      await this._requeueInterruptedPipelineWork({deferInProgress: fullUi})
    }
    await this._startReactor()
    if (fullUi) {
      await this._restoreVisibleDockPanelsIfNeeded()
      if (!this._kernel?.isCoordinator()) {
        void this._requestFollowerSnapshot()
        this._refreshDockProgress({refreshAllItems: false})
        this._refreshSelectionMarks()
      }
      if (this._downloadInterruptionPending) {
        this._showDownloadInterruptionPanel(this._downloadInterruptionRows)
      }
    }
    this._signalProcessorsWakeAndRunIfCoordinator()
  }

  /**
   * Stop local processor ownership so unload / bfcache cannot leave orphan `resolving`
   * rows shielded by stale `_active*ItemId`, and so late awaits cannot recommit.
   * @private
   */
  _interruptDocumentProcessors()
  {
    this._activeResolutionItemId = null
    this._activeDownloadItemId = null
    this._processing = false
    this._dispatcherWakeRequested = false
    this._resolutionProcessing = false
    this._downloadProcessing = false
    this._resolutionWakeRequested = false
    this._downloadWakeRequested = false
  }

  /**
   * Sync unload path: drop processor RAM ownership, close IDB so bfcache cannot pin
   * the database, then release Web Locks coordinator + panel ownership.
   * @private
   */
  _suspendDocumentForUnload()
  {
    if (this._documentSuspended) {
      return
    }
    this._documentSuspended = true
    this._interruptDocumentProcessors()
    if (this._followerPanelRestoreTimer) {
      clearTimeout(this._followerPanelRestoreTimer)
      this._followerPanelRestoreTimer = null
    }
    if (this._followerSelectionMarksTimer) {
      clearTimeout(this._followerSelectionMarksTimer)
      this._followerSelectionMarksTimer = null
    }
    this._cancelFollowerSnapshotRetry()
    this._stopHumanInteractionWatchdog()
    for (let dispose of this._dmUiEffectDisposers) {
      dispose?.dispose?.()
    }
    this._dmUiEffectDisposers = []
    this._dmUiEffectsReady = false
    try {
      this._cm.repos?.storage?.close?.()
    } catch (e) {
      // ignore
    }
    void this._kernel?.stop('pagehide')
    void this._releaseTagDiscoveryPanelOwnership().catch(() => {})
    void this._releaseHumanInteractionPanelOwnership().catch(() => {})
  }

  /**
   * bfcache restore: reopen storage, invalidate any thawed processor continuations, then
   * resume with forced orphan requeue.
   * @private
   */
  _resumeDocumentAfterBfcache()
  {
    this._documentSuspended = false
    this._interruptDocumentProcessors()
    this._startHumanInteractionWatchdog()
    void (async () => {
      try {
        await this._cm.repos?.storage?.open?.()
      } catch (e) {
        // ignore — first IDB helper will retry open()
      }
      this._scheduleResumeFromExternalWake({fullUi: true, requeueInterrupted: true})
    })()
  }

  /**
   * Requeue orphan `resolving` / `downloading` rows after unload / coordinator steal.
   * When `deferInProgress` is true on a visible page, `downloading` rows with `inProgress`
   * are held for the interruption timed panel instead of being flipped to `queued`.
   * @param {{deferInProgress?: boolean}} [options]
   * @return {Promise<void>}
   * @private
   */
  async _requeueInterruptedPipelineWork(options = {})
  {
    let deferInProgress = options.deferInProgress === true &&
        document.visibilityState === 'visible' &&
        !this._documentSuspended

    let resolutionRows = await this._repos().downloadResolutionQueue.listByStatus('resolving')
    for (let row of resolutionRows) {
      // Never requeue an item this tab is actively processing.
      if (String(this._activeResolutionItemId) === String(row.itemId)) {
        continue
      }
      row.status = 'queued'
      row.error = null
      await this._repos().download.commitResolution(row)
    }
    let downloadRows = await this._repos().downloadQueue.listByStatus('downloading')
    /** @type {object[]} */
    let pending = []
    for (let row of downloadRows) {
      if (String(this._activeDownloadItemId) === String(row.itemId)) {
        continue
      }
      if (deferInProgress && row.inProgress) {
        pending.push(row)
        continue
      }
      row.status = 'queued'
      row.error = null
      row.inProgress = false
      // Keep ledgerClaimed — the slot was already reserved before GM_download.
      await this._repos().download.commitDownload(row)
    }
    if (pending.length) {
      this._downloadInterruptionPending = true
      this._downloadInterruptionRows = pending
    }
  }

  /**
   * @return {boolean}
   * @private
   */
  _isLinkQueues()
  {
    return this._schedulerLinkQueues === true
  }

  /**
   * Wire Reactor Core (kernel / bus / scheduler / resolve+download job types). Idempotent.
   * @return {boolean}
   * @private
   */
  _ensureReactor()
  {
    if (this._reactorReady) {
      return true
    }
    let Kernel = globalThis.BrazenKernel
    let Scheduler = globalThis.BrazenScheduler
    let Registry = globalThis.BrazenJobRegistry
    let EventBus = globalThis.BrazenEventBus
    let resolveType = globalThis.JOB_TYPE_RESOLVE
    let downloadType = globalThis.JOB_TYPE_DOWNLOAD
    if (!Kernel || !Scheduler || !Registry || !EventBus) {
      throw new Error('BrazenReactor required')
    }

    this._jobRegistry = new Registry()
    this._registerReactorJobTypes()

    this._cm._ensureConfigReactor()
    this._reactorBus = this._cm.getConfigBus() ?? EventBus.create(this._cm._scriptPrefix)
    this._kernel = new Kernel({
      scriptPrefix: this._cm._scriptPrefix,
      signals: globalThis.BrazenSignals ?? null,
      bus: this._reactorBus,
      repos: this._repos(),
      scheduler: /** @type {object} */ ({}),
      onCoordinatorAcquired: () => {
        void this._onReactorCoordinatorAcquired()
      },
      onCoordinatorLost: (reason) => {
        this._onReactorCoordinatorLost(reason)
      },
      onCommandApplied: (command) => {
        if (command?.type === 'custom' &&
            command.payload?.name === REACTOR_PIPELINE_PUMP &&
            this._kernel?.isCoordinator() &&
            !this._documentSuspended) {
          void this._runProcessors()
        }
      },
    })
    this._repos().bindCoordinatorGuard(() => this._kernel?.isCoordinator() ?? false)

    /** @type {BrazenDownloadManager} */
    let dm = this
    let schedulerKernel = {
      get abortSignal() {
        return dm._kernel.abortSignal
      },
      isCoordinator: () => dm._kernel?.isCoordinator() ?? false,
      readState: () => dm.readDmState() ?? {},
      read: (path) => {
        let state = dm.readDmState()
        if (state && typeof state === 'object' && path in state) {
          return state[path]
        }
        return undefined
      },
      repos: dm._repos(),
    }
    this._scheduler = new Scheduler({
      registry: this._jobRegistry,
      kernel: schedulerKernel,
      linkQueues: this._schedulerLinkQueues,
    })
    this._kernel._scheduler = this._scheduler

    if (this._reactorBusUnsubscribe) {
      this._reactorBusUnsubscribe()
    }
    this._reactorBusUnsubscribe = this._reactorBus.subscribe((message) => {
      if (message?.kind === 'patch') {
        let profiler = globalThis.__brazenReactor
        let paths = message.patches?.map((patch) => patch?.path).filter(Boolean) ?? []
        let apply = () => {
          let signals = globalThis.BrazenSignals
          if (signals?.applyPatches) {
            signals.applyPatches(message.patches)
          }
          this._reconcileBusPatches(message.patches, message.seq)
          this._maybeScheduleConfigUiFromPatches(message.patches)
        }
        if (profiler?.withCause) {
          profiler.withCause({
            kind: 'patch',
            label: String(message.seq ?? 'patch'),
            detail: {seq: message.seq, paths: paths.slice(0, 32)},
          }, apply)
        } else {
          apply()
        }
      }
    })

    this._reactorReady = true
    this._cm.setCommandSender((command) => this.sendCommand(command))
    this._cm.setConfigUiReactionDelegate((event) => this.scheduleConfigUiReaction(event))
    this._cm.setRuntimePipelineChangeDelegate((source) => this._requestRuntimeUiRefresh(source))
    if (this._kernel?.isCoordinator()) {
      this._reactorBus.markSnapshotReady?.()
    } else {
      void this._requestFollowerSnapshot()
    }
    return true
  }

  /**
   * @return {Promise<void>}
   * @private
   */
  async _startReactor()
  {
    if (!this._ensureReactor() || !this._kernel) {
      return
    }
    await this._kernel.start()
    if (!this._coordinatorStickyReclaimAttempted &&
        this._wasCoordinatorSticky() &&
        !this.isCoordinator()) {
      this._coordinatorStickyReclaimAttempted = true
      void this.requestCoordinatorRole({steal: true})
    }
  }

  /**
   * @private
   */
  _onReactorCoordinatorLost(_reason)
  {
    this._activeResolutionItemId = null
    this._activeDownloadItemId = null
    this._processing = false
    this._dispatcherWakeRequested = false
    this._resolutionProcessing = false
    this._downloadProcessing = false
    this._resolutionWakeRequested = false
    this._downloadWakeRequested = false
    if (!this._documentSuspended) {
      this._clearCoordinatorSticky()
    }
    this._cm.refreshDockButtonStates()
    void this._requestFollowerSnapshot()
  }

  /**
   * @return {Promise<void>}
   * @private
   */
  async _onReactorCoordinatorAcquired()
  {
    if (this._documentSuspended || !this.isDownloadManagerEnabled()) {
      return
    }
    this._markCoordinatorSticky()
    this._cm.refreshDockButtonStates()
    this._cancelFollowerSnapshotRetry()
    this._reactorBus?.markSnapshotReady?.()
    await this._ensureState()
    await this._framework._reloadDownloadDuplicateLedgerFromStorage()
    await this._pruneAllTerminalQueueRows()
    await this._requeueInterruptedPipelineWork({
      deferInProgress: document.visibilityState === 'visible',
    })
    await this._resetProgressCountersIfIdle()
    if (this._processorsUiReady) {
      await this._restoreVisibleDockPanelsIfNeeded()
    }
    await this._syncPendingCountSignalsFromIdb()
    this._refreshDockProgress({refreshAllItems: false})
    this._kickProcessorsIfReady()
  }

  /**
   * Debounced cross-tab pipeline wake via Reactor Command bus (coordinator runs pump).
   * @private
   */
  _requestPipelineRun()
  {
    if (this._ensureReactor() && this._kernel?.isCoordinator()) {
      this._kickProcessorsIfReady()
      return
    }
    if (this._reactorPumpSignalTimer) {
      clearTimeout(this._reactorPumpSignalTimer)
    }
    this._reactorPumpSignalTimer = setTimeout(() => {
      this._reactorPumpSignalTimer = null
      void this.sendCommand({
        type: 'custom',
        payload: {name: REACTOR_PIPELINE_PUMP, data: {}},
      })
    }, PROCESSORS_WAKE_SIGNAL_DEBOUNCE_MS)
  }

  /**
   * Lane descriptors wrapping peek/claim/process/block for the dispatcher.
   * @return {{resolution: object, download: object}}
   * @private
   */
  _ensureLanes()
  {
    if (this._lanes) {
      return this._lanes
    }
    this._lanes = {
      resolution: {
        id: /** @type {BrazenDownloadManagerLaneId} */ ('resolution'),
        peek: async () => {
          let state = await this._getState()
          return this._peekResolutionWork(state)
        },
        claim: (itemId) => this._claimResolutionWorkItem(itemId),
        process: (item) => this._processResolutionWorkItem(item),
        ownBlocked: (state) => this._isResolutionPipelineBlocked(state),
        getActiveId: () => this._activeResolutionItemId,
        setActiveId: (itemId) => { this._activeResolutionItemId = itemId },
        processingKey: '_resolutionProcessing',
        wakeKey: '_resolutionWakeRequested',
      },
      download: {
        id: /** @type {BrazenDownloadManagerLaneId} */ ('download'),
        peek: () => this._repos().downloadQueue.peekNextQueued(),
        claim: (itemId) => this._claimDownloadQueueItem(itemId),
        process: (item) => this._processDownloadItem(item),
        ownBlocked: (state) => this._isDownloadPipelineBlocked(state),
        getActiveId: () => this._activeDownloadItemId,
        setActiveId: (itemId) => { this._activeDownloadItemId = itemId },
        processingKey: '_downloadProcessing',
        wakeKey: '_downloadWakeRequested',
      },
    }
    return this._lanes
  }

  /**
   * Scheduling policy from `linkQueues`.
   * IndependentPolicy (default / r34xxx): each unblocked lane advances in parallel.
   * InterleavedPolicy (linked): single serial selection, shared HI pause, opportunistic
   * download during discovery, retractable in-flight jobs.
   * @return {{name: string, isLaneBlocked: function(object, object): boolean, run: function(): void}}
   * @private
   */
  _getSchedulingPolicy()
  {
    if (this._isLinkQueues()) {
      return {
        name: 'interleaved',
        isLaneBlocked: (lane, state) => {
          if (this._anyHumanInteraction(state)) {
            return true
          }
          if (lane.id === 'download' && state.paused) {
            return true
          }
          if (lane.id === 'resolution' && state.resolutionBlocked) {
            return true
          }
          return false
        },
        run: () => { void this._runInterleavedDispatcher() },
      }
    }
    /** @type {{name: string, isLaneBlocked: function(object, object): boolean, run: function(): void}} */
    let independent = {
      name: 'independent',
      isLaneBlocked: (lane, state) => lane.ownBlocked(state),
      run: () => {
        let lanes = this._ensureLanes()
        void this._runLaneProcessor(lanes.resolution, independent)
        void this._runLaneProcessor(lanes.download, independent)
      },
    }
    return independent
  }

  /**
   * @param {{name: string, isLaneBlocked: function(object, object): boolean, run: function(): void}} policy
   * @private
   */
  _runDispatcher(policy)
  {
    policy.run()
  }

  /**
   * IndependentPolicy lane loop (one concurrent loop per unblocked lane).
   * @param {object} lane
   * @param {{isLaneBlocked: function(object, object): boolean}} policy
   * @return {Promise<void>}
   * @private
   */
  async _runLaneProcessor(lane, policy)
  {
    let profiler = globalThis.__brazenReactor
    if (this[lane.processingKey]) {
      profiler?.noteLane?.(lane.id, 'reentry')
      this[lane.wakeKey] = true
      return
    }
    this[lane.processingKey] = true
    let loopStop = profiler?.time?.('lane', 'processor', {key: lane.id})
    let itemsProcessed = 0
    try {
      do {
        this[lane.wakeKey] = false
        if (this._documentSuspended || this._kernel?.abortSignal?.aborted) {
          this[lane.wakeKey] = false
          break
        }
        if (!this._shouldRunProcessors()) {
          break
        }
        while (this._shouldRunProcessors() && !this._kernel?.abortSignal?.aborted) {
          if (this._documentSuspended || this._kernel?.abortSignal?.aborted) {
            break
          }
          let state = await this._getState()
          await this._maybeExpireHumanInteractionBlocks(state)
          state = this.readDmState() ?? state
          if (policy.isLaneBlocked(lane, state)) {
            break
          }

          let next = await lane.peek()
          if (!next) {
            break
          }
          let claimed = await lane.claim(next.itemId)
          if (!claimed) {
            break
          }
          if (this._documentSuspended || this._kernel?.abortSignal?.aborted) {
            break
          }
          lane.setActiveId(claimed.itemId)
          try {
            void this._renderItemProgress(claimed.itemId)
            let itemStop = profiler?.time?.('lane', 'process-item', {key: lane.id})
            profiler?.enterProductiveBusy?.()
            try {
              await lane.process(claimed)
            } finally {
              profiler?.exitProductiveBusy?.()
            }
            itemStop?.()
            itemsProcessed += 1
            if (!this._documentSuspended && !this._kernel?.abortSignal?.aborted) {
              await this._reloadCachedStateQuiet()
            }
          } finally {
            if (!this._documentSuspended) {
              lane.setActiveId(null)
            }
          }
        }
      } while (this[lane.wakeKey] && !this._documentSuspended && !this._kernel?.abortSignal?.aborted)
    } finally {
      loopStop?.()
      profiler?.mark?.('lane', 'processor-end', {key: lane.id, data: {itemsProcessed}})
      if (!this._documentSuspended) {
        this[lane.processingKey] = false
      }
      if (!this._documentSuspended && !this._kernel?.abortSignal?.aborted) {
        if (lane.id === 'resolution' && await this._tryRecoverStrandedDownloads()) {
          this._downloadWakeRequested = true
          let lanes = this._ensureLanes()
          void this._runLaneProcessor(lanes.download, policy)
        } else if (lane.id === 'download' && await this._tryRecoverStrandedDownloads()) {
          this[lane.wakeKey] = true
        }
        await this._pauseDownloadQueueIfIdle()
        await this._resetProgressCountersIfIdle()
        this._refreshDockProgress({refreshAllItems: false})
        if (this[lane.wakeKey]) {
          this[lane.wakeKey] = false
          if (profiler?.noteLane?.(lane.id, 'rearm', {itemsProcessed}) !== false) {
            void this._runLaneProcessor(lane, policy)
          }
        }
      }
    }
  }

  /**
   * InterleavedPolicy: single serial selection across lanes under one shared regime.
   * Prefers resolution; when resolution is discovery-gated, opportunistically downloads.
   * @return {Promise<void>}
   * @private
   */
  async _runInterleavedDispatcher()
  {
    let profiler = globalThis.__brazenReactor
    if (this._processing) {
      profiler?.noteLane?.('interleaved', 'reentry')
      this._dispatcherWakeRequested = true
      return
    }
    this._processing = true
    let loopStop = profiler?.time?.('lane', 'interleaved', {key: 'interleaved'})
    let itemsProcessed = 0
    let policy = this._getSchedulingPolicy()
    let lanes = this._ensureLanes()
    try {
      do {
        this._dispatcherWakeRequested = false
        if (this._documentSuspended || this._kernel?.abortSignal?.aborted) {
          this._dispatcherWakeRequested = false
          break
        }
        if (!this._shouldRunProcessors()) {
          break
        }
        while (this._shouldRunProcessors() && !this._kernel?.abortSignal?.aborted) {
          if (this._documentSuspended || this._kernel?.abortSignal?.aborted) {
            break
          }
          let state = await this._getState()
          await this._maybeExpireHumanInteractionBlocks(state)
          state = this.readDmState() ?? state
          if (this._anyHumanInteraction(state)) {
            break
          }

          let selected = null
          if (!policy.isLaneBlocked(lanes.resolution, state)) {
            let nextResolution = await lanes.resolution.peek()
            if (nextResolution) {
              selected = {lane: lanes.resolution, next: nextResolution}
            }
          }
          if (!selected && !policy.isLaneBlocked(lanes.download, state)) {
            let nextDownload = await lanes.download.peek()
            if (nextDownload) {
              selected = {lane: lanes.download, next: nextDownload}
            }
          }
          if (!selected) {
            break
          }

          let claimed = await selected.lane.claim(selected.next.itemId)
          if (!claimed) {
            break
          }
          if (this._documentSuspended || this._kernel?.abortSignal?.aborted) {
            break
          }
          selected.lane.setActiveId(claimed.itemId)
          try {
            void this._renderItemProgress(claimed.itemId)
            let itemStop = profiler?.time?.('lane', 'process-item', {key: selected.lane.id})
            profiler?.enterProductiveBusy?.()
            try {
              await selected.lane.process(claimed)
            } finally {
              profiler?.exitProductiveBusy?.()
            }
            itemStop?.()
            itemsProcessed += 1
            if (!this._documentSuspended && !this._kernel?.abortSignal?.aborted) {
              await this._reloadCachedStateQuiet()
            }
          } finally {
            if (!this._documentSuspended) {
              await this._retractLaneJobIfGated(selected.lane)
              selected.lane.setActiveId(null)
            }
          }
        }
      } while (this._dispatcherWakeRequested && !this._documentSuspended && !this._kernel?.abortSignal?.aborted)
    } finally {
      loopStop?.()
      profiler?.mark?.('lane', 'interleaved-end', {key: 'interleaved', data: {itemsProcessed}})
      if (!this._documentSuspended) {
        this._processing = false
      }
      if (!this._documentSuspended && !this._kernel?.abortSignal?.aborted) {
        if (await this._tryRecoverStrandedDownloads()) {
          this._dispatcherWakeRequested = true
        }
        await this._pauseDownloadQueueIfIdle()
        await this._resetProgressCountersIfIdle()
        this._refreshDockProgress({refreshAllItems: false})
        if (this._dispatcherWakeRequested) {
          this._dispatcherWakeRequested = false
          if (profiler?.noteLane?.('interleaved', 'rearm', {itemsProcessed}) !== false) {
            void this._runInterleavedDispatcher()
          }
        }
      }
    }
  }

  /**
   * Cooperative retract of an in-flight interleaved job when pause / any-HI gates the regime.
   * @param {object} lane
   * @return {Promise<void>}
   * @private
   */
  async _retractLaneJobIfGated(lane)
  {
    let itemId = lane.getActiveId()
    if (!itemId) {
      return
    }
    let state = this.readDmState() ?? await this._getState()
    let gated = this._anyHumanInteraction(state)
        || (lane.id === 'download' && !!state?.paused)
    if (!gated) {
      return
    }
    let repo = lane.id === 'resolution'
        ? this._repos().downloadResolutionQueue
        : this._repos().downloadQueue
    let expected = lane.id === 'resolution' ? 'resolving' : 'downloading'
    let row = await repo.get(itemId)
    if (!row || row.status !== expected) {
      return
    }
    lane.setActiveId(null)
    row.status = 'queued'
    row.error = null
    if (lane.id === 'resolution') {
      await this._repos().download.commitResolution(row)
    } else {
      row.inProgress = false
      await this._repos().download.commitDownload(row)
    }
  }

  /**
   * Wire Reactor job types (config-sync, tag-discovery). Pipeline work uses DM lane dispatcher.
   * @private
   */
  _registerReactorJobTypes()
  {
    let registry = this._jobRegistry
    this._registerReactorConfigUiJobType(registry)
    this._registerReactorTagDiscoveryJobType(registry, 'confirm-tag-discovery', this._executeConfirmTagDiscoveryCoordinator)
    this._registerReactorTagDiscoveryJobType(registry, 'skip-tag-discovery', this._executeSkipTagDiscoveryCoordinator)
  }

  /**
   * Schedule the per-tab config->UI reaction (local-scope lane).
   * @param {{manager?: object, source?: string, local?: boolean, detail?: {tags?: string[], fieldKeys?: string[]}|null}} event
   */
  scheduleConfigUiReaction(event)
  {
    if (!event || !this._ensureReactor() || !this._kernel) {
      return
    }
    if (this._tagDiscoveryActionInFlight) {
      setTimeout(() => this.scheduleConfigUiReaction(event), 32)
      return
    }
    globalThis.__brazenReactor?.mark?.('scheduler', 'config-ui-schedule', {
      key: event.source ?? 'all',
      data: {
        local: event.local !== false,
        activeCause: globalThis.__brazenReactor?.activeCauseLabel?.(),
      },
    })
    let safeEvent = this._framework._busSafeConfigurationChangeEvent({
      manager: event.manager ?? this._cm,
      source: event.source,
      local: event.local,
      detail: event.detail ?? null,
    })
    this._kernel.scheduleLocal('config-ui', {event: safeEvent})
  }

  /**
   * @param {object} registry
   * @private
   */
  _registerReactorConfigUiJobType(registry)
  {
    registry.register('config-ui', {
      scope: globalThis.JOB_SCOPE_LOCAL ?? 'local',
      coalesceKey: () => 'config-ui',
      merge: (pending, incoming) => {
        let pe = pending.payload?.event ?? {}
        let ie = incoming.payload?.event ?? {}
        let incomingSource = ie.source ?? pe.source ?? 'all'
        if (globalThis.__brazenRuntimePipelineConfigSources.has(incomingSource)) {
          return pending
        }
        let source = incomingSource
        if (pe.source !== source && source !== 'all') {
          source = 'all'
        } else if (pe.source === 'all') {
          source = 'all'
        }
        return {
          ...incoming,
          payload: {
            event: {
              source,
              local: ie.local !== false && pe.local !== false,
              detail: this._cm.mergeChangeDetails?.(pe.detail, ie.detail) ?? ie.detail ?? pe.detail,
            },
          },
        }
      },
      concurrency: 1,
      run: async (_ctx, payload) => {
        let stop = globalThis.__brazenReactor?.time?.('scheduler', 'config-ui-run', {key: 'config-ui'})
        try {
          if (payload?.event) {
            await this._framework._handleConfigurationChange({
              manager: this._cm,
              ...payload.event,
            })
          }
        } finally {
          stop?.()
        }
      },
    })
  }

  /**
   * @param {object} registry
   * @param {function(): Promise<void>} runFn
   * @private
   */
  _registerReactorTagDiscoveryJobType(registry, name, runFn)
  {
    registry.register(name, {
      scope: globalThis.JOB_SCOPE_COORDINATOR ?? 'coordinator',
      coalesceKey: () => 'tag-discovery',
      concurrency: 1,
      run: async () => {
        await runFn.call(this)
      },
    })
  }

  /**
   * Kick entry for init, enqueue, Start, tag confirm/skip, HI resume, and visibility.
   * Coordinator runs DM lane dispatcher; followers publish pipeline-pump Commands.
   * @return {Promise<void>}
   * @private
   */
  async _runProcessors()
  {
    globalThis.__brazenReactor?.mark?.('lane', 'runProcessors')
    if (!this._shouldRunProcessors()) {
      return
    }
    if (!this._ensureReactor()) {
      return
    }
    if (!this._kernel.isCoordinator()) {
      this._dispatcherWakeRequested = false
      this._resolutionWakeRequested = false
      this._downloadWakeRequested = false
      return
    }
    this._runDispatcher(this._getSchedulingPolicy())
  }

  /**
   * @param {object} options
   * @param {object} options.repo
   * @param {string} options.itemId
   * @param {string[]} options.fromStatuses
   * @param {string} options.toStatus
   * @param {function(object): Promise<void>} options.commitFn
   * @return {Promise<object|null>}
   * @private
   */
  async _claimQueueItem({repo, itemId, fromStatuses, toStatus, commitFn})
  {
    let row = await repo.get(itemId)
    if (!row || !fromStatuses.includes(row.status)) {
      return null
    }
    row.status = toStatus
    row.error = null
    await commitFn(row)
    return row
  }

  /**
   * @param {object} options
   * @param {string} options.itemId
   * @param {string} options.activeField
   * @param {object} options.repo
   * @param {string} options.activeStatus
   * @return {Promise<boolean>}
   * @private
   */
  async _isActiveClaim({itemId, activeField, repo, activeStatus})
  {
    if (String(this[activeField]) !== String(itemId)) {
      return false
    }
    let row = await repo.get(itemId)
    return !!(row && row.status === activeStatus)
  }

  /**
   * @param {object} options
   * @param {object} options.item
   * @param {string} options.activeField
   * @param {object} options.repo
   * @param {string} options.expectedStatus
   * @param {function(object): Promise<void>} options.commitFn
   * @return {Promise<boolean>}
   * @private
   */
  async _commitOwnedRow({item, activeField, repo, expectedStatus, commitFn})
  {
    if (String(this[activeField]) !== String(item.itemId)) {
      return false
    }
    let existing = await repo.get(item.itemId)
    if (!existing || existing.status !== expectedStatus) {
      return false
    }
    await commitFn(item)
    return true
  }

  /**
   * Flip a download row from `queued` → `downloading`. Returns null if another worker already claimed it.
   * @param {string} itemId
   * @return {Promise<object|null>}
   * @private
   */
  async _claimDownloadQueueItem(itemId)
  {
    return this._claimQueueItem({
      repo: this._repos().downloadQueue,
      itemId,
      fromStatuses: ['queued'],
      toStatus: 'downloading',
      commitFn: (row) => this._repos().download.commitDownload(row),
    })
  }

  /**
   * @param {string} itemId
   * @return {Promise<object|null>}
   * @private
   */
  async _claimResolutionWorkItem(itemId)
  {
    return this._claimQueueItem({
      repo: this._repos().downloadResolutionQueue,
      itemId,
      fromStatuses: ['queued', 'discoveryQueued'],
      toStatus: 'resolving',
      commitFn: (row) => this._repos().download.commitResolution(row),
    })
  }

  /**
   * True while this tab still owns the in-flight resolution claim for `itemId`.
   * @param {string} itemId
   * @return {Promise<boolean>}
   * @private
   */
  async _isActiveResolutionClaim(itemId)
  {
    return this._isActiveClaim({
      itemId,
      activeField: '_activeResolutionItemId',
      repo: this._repos().downloadResolutionQueue,
      activeStatus: 'resolving',
    })
  }

  /**
   * True while this tab still owns the in-flight download claim for `itemId`.
   * @param {string} itemId
   * @return {Promise<boolean>}
   * @private
   */
  async _isActiveDownloadClaim(itemId)
  {
    return this._isActiveClaim({
      itemId,
      activeField: '_activeDownloadItemId',
      repo: this._repos().downloadQueue,
      activeStatus: 'downloading',
    })
  }

  /**
   * Commit a resolution row only if this tab still owns the `resolving` claim (or is
   * intentionally writing `queued` / `tagReview` / terminal from that claim).
   * @param {object} item
   * @return {Promise<boolean>}
   * @private
   */
  async _commitResolutionRow(item)
  {
    return this._commitOwnedRow({
      item,
      activeField: '_activeResolutionItemId',
      repo: this._repos().downloadResolutionQueue,
      expectedStatus: 'resolving',
      commitFn: (row) => this._repos().download.commitResolution(row),
    })
  }

  /**
   * Commit a download row only if this tab still owns the `downloading` claim.
   * @param {object} item
   * @return {Promise<boolean>}
   * @private
   */
  async _commitDownloadRow(item)
  {
    return this._commitOwnedRow({
      item,
      activeField: '_activeDownloadItemId',
      repo: this._repos().downloadQueue,
      expectedStatus: 'downloading',
      commitFn: (row) => this._repos().download.commitDownload(row),
    })
  }

  /**
   * Remove a resolution row after successful promote when this tab still owns the claim.
   * @param {string} itemId
   * @return {Promise<boolean>}
   * @private
   */
  async _removeResolutionRowIfActive(itemId)
  {
    return this._repos().download.removeResolutionIfClaimed(
        itemId,
        this._activeResolutionItemId,
        ['resolving', 'tagReview'],
    )
  }

  /**
   * Resolution pipeline gate (scheduler `reassess` / enqueue peek).
   * @param {object|null|undefined} state
   * @return {boolean}
   * @private
   */
  _isResolutionPipelineBlocked(state)
  {
    if (!state) {
      return true
    }
    // Interleaved (`linkQueues`): reassess skips all pipeline jobs when any lane has HI.
    if (this._isLinkQueues() && this._anyHumanInteraction(state)) {
      return true
    }
    if (this._hiLane(state, 'resolution')) {
      return true
    }
    return !!state.resolutionBlocked
  }

  /**
   * Download pipeline gate (scheduler `reassess` / enqueue peek).
   * @param {object|null|undefined} state
   * @return {boolean}
   * @private
   */
  _isDownloadPipelineBlocked(state)
  {
    if (!state) {
      return true
    }
    if (state.paused) {
      return true
    }
    if (this._downloadInterruptionPending) {
      return true
    }
    if (this._isLinkQueues() && this._anyHumanInteraction(state)) {
      return true
    }
    return !!this._hiLane(state, 'download')
  }

  /**
   * Whether Start/Pause should surface human-interaction resume instead of toggle.
   * Independent: download-lane HI. Interleaved: any lane HI.
   * @param {object|null|undefined} state
   * @return {boolean}
   * @private
   */
  _isStartPauseShowingHumanInteraction(state)
  {
    if (this._isLinkQueues()) {
      return this._anyHumanInteraction(state)
    }
    return !!this._hiLane(state, 'download')
  }

  /**
   * Whether this tab may run the coordinator scheduler pump.
   * @return {boolean}
   * @private
   */
  _shouldRunProcessors()
  {
    if (this._documentSuspended || !this._cm.canPersist()) {
      return false
    }
    if (!this.isDownloadManagerEnabled()) {
      return false
    }
    if (this._isQueueVerificationTab()) {
      return false
    }
    if (!this._ensureReactor() || !this._kernel) {
      return false
    }
    return this._kernel.isCoordinator()
  }

  /**
   * @param {number} lastAt
   * @param {number} gapMs
   * @return {Promise<number>}
   * @private
   */
  async _awaitInitiationGap(lastAt, gapMs)
  {
    let remaining = gapMs - (Date.now() - (lastAt ?? 0))
    if (remaining > 0) {
      await Utilities.sleep(remaining)
    }
    return Date.now()
  }

  /**
   * Seed in-tab initiation clocks from shared state (leadership claim / renew).
   * @param {object|null|undefined} state
   * @private
   */
  _hydrateInitiationClocksFromState(state)
  {
    this._lastDownloadInitiationAt = Math.max(
        this._lastDownloadInitiationAt ?? 0,
        state?.lastDownloadInitiationAt ?? 0,
    )
    this._lastResolutionInitiationAt = Math.max(
        this._lastResolutionInitiationAt ?? 0,
        state?.lastResolutionInitiationAt ?? 0,
    )
  }

  /**
   * Coordinator-only IDB state mutation (single-writer). Followers send Commands instead.
   * @param {function(object): (void|Promise<void>)} mutator
   * @return {Promise<object>}
   * @private
   */
  _commitCoordinatorDmState(mutator)
  {
    if (this._documentSuspended || !this._cm.canPersist()) {
      return Promise.resolve(this.readDmState())
    }
    if (!this.isCoordinator()) {
      return Promise.reject(new Error('BrazenDownloadManager: follower cannot write downloadManagerState'))
    }
    let run = this._coordinatorStateTail.then(async () => {
      if (this._documentSuspended) {
        return this.readDmState()
      }
      let before = await this._repos().downloadManagerState.get()
      let state = await this._repos().download.mutateState(async (draft) => {
        await mutator(draft)
      })
      this._publishDmStateToAtoms(state)
      if (this.isCoordinator()) {
        this._publishStateDiffViaKernel(before, state)
      }
      return state
    })
    this._coordinatorStateTail = run.then(() => undefined, () => undefined)
    return run
  }

  /**
   * Coordinator state commit then optional pipeline wake (common post-mutation pattern).
   * @param {function(object): (void|Promise<void>)} mutator
   * @param {{wake?: boolean}} [options] `wake: false` skips processor wake
   * @return {Promise<object>}
   * @private
   */
  async _commitDmStateAndWake(mutator, options = {})
  {
    let state = await this._commitCoordinatorDmState(mutator)
    if (options.wake !== false) {
      this._signalProcessorsWakeAndRunIfCoordinator()
    }
    return state
  }

  /**
   * @param {object} before
   * @param {object} after
   * @private
   */
  _publishStateDiffViaKernel(before, after)
  {
    if (!this._kernel?.isCoordinator()) {
      return
    }
    let patches = []
    for (let key of Object.keys(after)) {
      if (key === 'id') {
        continue
      }
      let prev = before?.[key]
      let next = after[key]
      if (JSON.stringify(prev) !== JSON.stringify(next)) {
        patches.push({path: `dm.state.${key}`, value: structuredClone(next)})
      }
    }
    if (!patches.length) {
      return
    }
    void this._kernel.commitStatePatches({type: 'dm-state-mutation', payload: {}}, patches)
  }

  /**
   * Pace the next GM_download / resolution fetch initiation.
   * Uses an in-tab clock so concurrent heartbeat/progress puts cannot wipe lastAt in IDB.
   * @param {'download'|'resolution'} kind
   * @return {Promise<void>}
   * @private
   */
  async _paceInitiation(kind)
  {
    let gapMs = kind === 'resolution' ? this._resolutionGapMs : this._downloadGapMs
    let localKey = kind === 'resolution' ? '_lastResolutionInitiationAt' : '_lastDownloadInitiationAt'
    let stateKey = kind === 'resolution' ? 'lastResolutionInitiationAt' : 'lastDownloadInitiationAt'
    let lastAt = Math.max(this[localKey] ?? 0, this.readDmState()?.[stateKey] ?? 0)
    let nextAt = await this._awaitInitiationGap(lastAt, gapMs)
    this[localKey] = nextAt
    // Keep the atom mirror in lockstep with the in-tab clock so a later
    // `_reloadCachedStateQuiet` of a briefly stale IDB row cannot undercut pacing.
    let syncState = this.readDmState()
    if (syncState) {
      syncState[stateKey] = Math.max(syncState[stateKey] ?? 0, nextAt)
      this._publishDmStateToAtoms(syncState)
    }
    if (!this._cm.canPersist()) {
      return
    }
    await this._commitCoordinatorDmState((state) => {
      state[stateKey] = Math.max(state[stateKey] ?? 0, nextAt)
    })
  }

  /**
   * @return {Promise<void>}
   * @private
   */
  async _paceDownloadInitiation()
  {
    // Serialize download initiations so concurrent unlinked work still honors the gap.
    let run = this._downloadInitiateTail.then(() => this._paceInitiation('download'))
    this._downloadInitiateTail = run.then(() => undefined, () => undefined)
    await run
  }

  /**
   * Pace the next resolution / same-origin HTML fetch.
   * Serialized so concurrent callers (e.g. tag-type lookups during resolve) cannot stampede.
   * @return {Promise<void>}
   */
  async paceResolutionInitiation()
  {
    return this._paceResolutionInitiation()
  }

  /**
   * @return {Promise<void>}
   * @private
   */
  async _paceResolutionInitiation()
  {
    // Serialize resolve-page + site tag-list fetches so parallel workers honor the gap.
    let run = this._resolutionInitiateTail.then(() => this._paceInitiation('resolution'))
    this._resolutionInitiateTail = run.then(() => undefined, () => undefined)
    await run
  }

  /**
   * When pending downloads exist but none are `queued` (orphan `downloading` rows),
   * requeue via the steal/bfcache helper and allow one wake. Latch prevents tight-loop.
   * @return {Promise<boolean>} true when a wake should re-enter the download processor
   * @private
   */
  async _tryRecoverStrandedDownloads()
  {
    let pending = await this._getPendingDownloadCount()
    if (pending <= 0) {
      this._orphanDownloadRequeueAttempted = false
      return false
    }
    if (await this._repos().downloadQueue.peekNextQueued()) {
      this._orphanDownloadRequeueAttempted = false
      return false
    }
    // Interruption panel owns these orphans — do not requeue under it.
    if (this._downloadInterruptionPending) {
      return false
    }
    if (this._orphanDownloadRequeueAttempted) {
      return false
    }
    this._orphanDownloadRequeueAttempted = true
    let defer = document.visibilityState === 'visible' && !this._documentSuspended
    await this._requeueInterruptedPipelineWork({deferInProgress: defer})
    if (this._downloadInterruptionPending) {
      this._showDownloadInterruptionPanel(this._downloadInterruptionRows)
      return false
    }
    return !!(await this._repos().downloadQueue.peekNextQueued())
  }

  /**
   * Download batch is idle only when both queues are empty (and resolution is not mid-tick).
   * Once started, the download side stays in-batch until resolution empties too.
   * @param {number|null} [downloadPending] Omit to use cached sync counts.
   * @param {number|null} [resolutionPending]
   * @return {boolean}
   * @private
   */
  _isDownloadBatchIdle(downloadPending = null, resolutionPending = null)
  {
    if (this._processing || this._dispatcherWakeRequested ||
        this._resolutionProcessing || this._resolutionWakeRequested ||
        this._downloadProcessing || this._downloadWakeRequested) {
      return false
    }
    let download = downloadPending == null ? this.getPendingDownloadCountSync() : downloadPending
    let resolution = resolutionPending == null ? this.getPendingResolutionCountSync() : resolutionPending
    return download <= 0 && resolution <= 0
  }

  /**
   * Zero each dock progress counter when that pipeline's batch is fully idle.
   * Download completed count clears only when both queues are empty.
   * @return {Promise<void>}
   * @private
   */
  async _resetProgressCountersIfIdle()
  {
    // Coordinator writes only — `initialize()` / `_syncFromStorage()` can run before Reactor
    // acquires the Web Lock, so `isCoordinator()` is false even on the sole tab.
    if (!this.isCoordinator()) {
      return
    }
    let downloadPending = await this._getPendingDownloadCount()
    let resolutionPending = await this._getPendingResolutionCount()
    let cleared = false
    await this._commitCoordinatorDmState((state) => {
      if (resolutionPending === 0 && (Number(state.completedResolutionCount) || 0) !== 0) {
        state.completedResolutionCount = 0
        cleared = true
      }
      if (this._isDownloadBatchIdle(downloadPending, resolutionPending) &&
          (Number(state.completedDownloadCount) || 0) !== 0) {
        state.completedDownloadCount = 0
        cleared = true
      }
    })
    if (cleared) {
      this._signalProgressUiWake()
    }
  }

  /**
   * Return to idle (Start required) only when both download and resolution queues are empty
   * so a later batch does not auto-run. Does not clear a user Pause.
   * @return {Promise<void>}
   * @private
   */
  async _pauseDownloadQueueIfIdle()
  {
    let downloadPending = await this._getPendingDownloadCount()
    let resolutionPending = await this._getPendingResolutionCount()
    if (!this._isDownloadBatchIdle(downloadPending, resolutionPending)) {
      return
    }
    await this._commitCoordinatorDmState((state) => {
      if (state.paused) {
        return
      }
      state.paused = true
    })
  }

  /**
   * @return {Promise<void>}
   * @private
   */
  async _incrementResolutionProgress()
  {
    // Serialize with heartbeats — a raw put of a stale snapshot can wipe the count.
    await this._commitCoordinatorDmState((state) => {
      state.completedResolutionCount = (Number(state.completedResolutionCount) || 0) + 1
    })
    this._signalProgressUiWake()
    this._refreshDockProgress({refreshAllItems: false})
  }

  /**
   * @return {Promise<void>}
   * @private
   */
  async _incrementDownloadProgress()
  {
    await this._commitCoordinatorDmState((state) => {
      state.completedDownloadCount = (Number(state.completedDownloadCount) || 0) + 1
    })
    await this._pauseDownloadQueueIfIdle()
    this._signalProgressUiWake()
    this._refreshDockProgress({refreshAllItems: false})
  }

  /**
   * Reload cached DM state without UI rebuilds (used between processor tasks).
   * @return {Promise<void>}
   * @private
   */
  async _reloadCachedStateQuiet()
  {
    if (this._documentSuspended || !this._cm.canPersist()) {
      return
    }
    let run = this._coordinatorStateTail.then(async () => {
      if (this._documentSuspended || !this._cm.canPersist()) {
        return
      }
      this._publishDmStateToAtoms(await this._repos().downloadManagerState.get())
    })
    this._coordinatorStateTail = run.then(() => undefined, () => undefined)
    return run
  }

  /**
   * Shared post-task UI reset for resolution and download pipeline jobs.
   * @param {string} itemId
   * @return {Promise<void>}
   * @private
   */
  async _onPipelineTaskComplete(itemId)
  {
    await this._reloadCachedStateQuiet()
    await this._renderItemProgress(itemId)
    await this._clearTerminalQueueRows(itemId)
    if (!(await this.isQueued(itemId))) {
      this._forgetTrackedItem(itemId)
    }
    this._refreshDockProgress({refreshAllItems: false})
    this._refreshSelectionMarks()
  }

  /**
   * @return {Promise<void>}
   * @private
   */
  async _ensureState()
  {
    if (!this._cm.canPersist()) {
      return
    }
    let state = this.isCoordinator()
        ? await this._repos().downloadManagerState.persistNormalizedIfStale()
        : await this._repos().downloadManagerState.get()
    if (!state?.id) {
      if (this.isCoordinator()) {
        await this._repos().download.resetState()
      }
      return
    }
    this._publishDmStateToAtoms(state)
    this._writeHumanInteractionBlockMirror(this._anyHumanInteraction(state))
  }

  /**
   * @return {Promise<object>}
   * @private
   */
  async _getState()
  {
    let state = await this._repos().downloadManagerState.get()
    this._publishDmStateToAtoms(state)
    return state
  }

  /**
   * @return {Promise<void>}
   * @private
   */
  async _syncFromStorage()
  {
    if (!this._cm.canPersist()) {
      return
    }
    this._publishDmStateToAtoms(await this._repos().downloadManagerState.get())
    await this._syncPendingCountSignalsFromIdb()
    if (!this.readDmState()?.resolutionBlocked) {
      this._hideTagDiscoveryPanel()
    }
    await this._resetProgressCountersIfIdle()
    await this._refreshCurrentMediaQueueState({refreshDock: false})
    this._framework.refreshDockInterface(undefined, {layout: false})
    // Selection marks paint queued tiles; skip `_refreshAllItemProgress` on sync.
    this._refreshDockProgress({refreshAllItems: false})
    this._refreshSelectionMarks()
  }

  /**
   * @private
   */
  _setupCrossTabSync()
  {
    if (this._crossTabVisibilityHandler) {
      document.removeEventListener('visibilitychange', this._crossTabVisibilityHandler)
    }
    this._crossTabVisibilityHandler = () => {
      if (document.visibilityState === 'visible') {
        this._framework._scheduleVisibilityWake?.()
        return
      }
      // Drop panel ownership while hidden so the focused search tab can claim it.
      // Keep local DOM visible on minimize — only unload paths hide panels.
      void this._releaseTagDiscoveryPanelOwnership({hideLocal: false})
      void this._releaseHumanInteractionPanelOwnership({hideLocal: false})
    }
    document.addEventListener('visibilitychange', this._crossTabVisibilityHandler)
    // bfcache back/forward restores the page without a reliable visibility flip — reclaim here.
    if (this._crossTabPageshowHandler) {
      window.removeEventListener('pageshow', this._crossTabPageshowHandler)
    }
    this._crossTabPageshowHandler = (event) => {
      let persisted = !!event.persisted
      if (persisted) {
        // Hard interrupt + reopen IDB + requeue orphans (soft resume is not enough).
        this._resumeDocumentAfterBfcache()
        return
      }
      if (document.visibilityState === 'visible') {
        this._framework._scheduleVisibilityWake?.()
      }
    }
    window.addEventListener('pageshow', this._crossTabPageshowHandler)
    if (this._crossTabPagehideHandler) {
      window.removeEventListener('pagehide', this._crossTabPagehideHandler)
      window.removeEventListener('beforeunload', this._crossTabPagehideHandler)
    }
    this._crossTabPagehideHandler = (event) => {
      // Sync interrupt: stop mirror rewrites, clear active ids, close IDB for bfcache.
      this._suspendDocumentForUnload()
      // Keep overlays on bfcache restore so marks do not blank-flash on back/forward.
      if (!event?.persisted) {
        this._clearAllItemProgress()
      }
    }
    window.addEventListener('pagehide', this._crossTabPagehideHandler)
    window.addEventListener('beforeunload', this._crossTabPagehideHandler)
  }

  // -------------------------------------------------------------------------
  // Search item progress
  // -------------------------------------------------------------------------

  /**
   * @return {{stepCount: number, indices: {queued: number, resolving: number, tagReview: number, downloadQueued: number, downloading: number}}}
   * @private
   */
  _getPipelineStepLayout()
  {
    if (this._isTagDiscoveryActive()) {
      return {
        stepCount: 5,
        indices: {queued: 0, resolving: 1, tagReview: 2, downloadQueued: 3, downloading: 4},
      }
    }
    return {
      stepCount: 4,
      indices: {queued: 0, resolving: 1, tagReview: -1, downloadQueued: 2, downloading: 3},
    }
  }

  /**
   * @param {string|number} itemId
   * @return {HTMLElement|null}
   * @private
   */
  _findSelectionItemElement(itemId)
  {
    let pageConfig = this._getActivePageConfig()
    if (!pageConfig?.itemSelector || !pageConfig.resolveItem) {
      return null
    }
    let itemKey = String(itemId)
    for (let element of document.querySelectorAll(pageConfig.itemSelector)) {
      let resolved = Utilities.callEventHandler(pageConfig.resolveItem, [element], null)
      if (resolved?.itemId != null && String(resolved.itemId) === itemKey) {
        return element
      }
    }
    return null
  }

  /**
   * @param {string|number} itemId
   * @param {HTMLElement|null|undefined} element
   * @private
   */
  _trackItemElement(itemId, element)
  {
    if (itemId == null || !element) {
      return
    }
    this._trackedItemElements.set(String(itemId), element)
  }

  /**
   * @param {string|number} itemId
   * @private
   */
  _untrackItemElement(itemId)
  {
    if (itemId == null) {
      return
    }
    this._trackedItemElements.delete(String(itemId))
  }

  /**
   * Drops DOM + epoch retention for a tile that is no longer in the active pipeline.
   * @param {string|number} itemId
   * @private
   */
  _forgetTrackedItem(itemId)
  {
    if (itemId == null) {
      return
    }
    let itemKey = String(itemId)
    if (this._selectionUiPending.has(itemKey)) {
      return
    }
    let element = this._trackedItemElements.get(itemKey)
    this._untrackItemElement(itemId)
    this._selectionOpEpoch.delete(itemKey)
    this._itemProgressPaintGen.delete(itemKey)
    if (element) {
      this._setItemProgress(element, null)
    }
  }

  /**
   * @private
   */
  _refreshAllItemProgress()
  {
    for (let itemId of this._trackedItemElements.keys()) {
      void this._renderItemProgress(itemId)
    }
  }

  /**
   * @private
   */
  _clearAllItemProgress()
  {
    for (let element of this._trackedItemElements.values()) {
      this._setItemProgress(element, null)
    }
    this._trackedItemElements.clear()
    this._selectionUiHidden.clear()
    this._selectionUiPending.clear()
    this._selectionOpEpoch.clear()
  }

  /**
   * @param {string|number} itemId
   * @return {Promise<void>}
   * @private
   */
  async _renderItemProgress(itemId)
  {
    let itemKey = String(itemId)
    if (this._selectionUiHidden.has(itemKey)) {
      // Local deselect won the UI; ignore late coordinator reconcile/processor paints.
      return
    }
    let paintGen = (this._itemProgressPaintGen.get(itemKey) ?? 0) + 1
    this._itemProgressPaintGen.set(itemKey, paintGen)
    let element = this._trackedItemElements.get(itemKey)
    if (!element) {
      element = this._findSelectionItemElement(itemId)
      if (element) {
        this._trackItemElement(itemId, element)
      }
    }
    if (!element) {
      return
    }
    let progress = await this._resolveItemPipelineView(itemId)
    if (this._itemProgressPaintGen.get(itemKey) !== paintGen) {
      return
    }
    if (this._selectionUiHidden.has(itemKey)) {
      return
    }
    // Optimistic select painted before IDB put — don't wipe it with a null reconcile.
    if (!progress && this._selectionUiPending.has(itemKey)) {
      return
    }
    // Concurrent _renderItemProgress (enqueue + _refreshSelectionMarks) can interleave:
    // one pass confirms the row and deletes pending while another still reads null — never
    // null-clear a tracked optimistic Queued overlay from reconcile; explicit deselect/forget owns teardown.
    if (!progress &&
        this._trackedItemElements.has(itemKey) &&
        this._readItemProgressLabelFromElement(element) === 'Queued') {
      return
    }
    if (progress?.label === 'Waiting' && this._readItemProgressLabelFromElement(element) === 'Queued') {
      let state = await this._getState()
      if (this._itemProgressPaintGen.get(itemKey) !== paintGen) {
        return
      }
      if (!this._isTagReviewBlockedOnOtherItem(state, itemKey)) {
        let layout = this._getPipelineStepLayout()
        progress = {
          label: 'Queued',
          stepIndex: layout.indices.queued,
          stepCount: layout.stepCount,
        }
      }
    }
    this._setItemProgress(element, progress)
    if (progress) {
      this._selectionUiPending.delete(itemKey)
    }
  }

  /**
   * @param {HTMLElement|null|undefined} element
   * @return {string|null}
   * @private
   */
  _readItemProgressLabelFromElement(element)
  {
    let raw = element?.dataset?.bvDmProgress
    if (!raw) {
      return null
    }
    let label = raw.split('|')[0]
    return label || null
  }

  /**
   * True when tag-discovery review blocks the resolution queue on a different item.
   * @param {object|null|undefined} state
   * @param {string|number} itemId
   * @return {boolean}
   * @private
   */
  _isTagReviewBlockedOnOtherItem(state, itemId)
  {
    if (!state?.resolutionBlocked || state.resolutionBlockedItemId == null) {
      return false
    }
    return String(state.resolutionBlockedItemId) !== String(itemId)
  }

  /**
   * @param {string|number} itemId
   * @return {Promise<{label: string, stepIndex: number, stepCount: number, failed?: boolean, complete?: boolean}|null>}
   * @private
   */
  async _resolveItemPipelineView(itemId)
  {
    let layout = this._getPipelineStepLayout()
    let state = await this._getState()
    let itemKey = String(itemId)
    let [resolution, download] = await Promise.all([
      this._repos().downloadResolutionQueue.get(itemKey),
      this._repos().downloadQueue.get(itemKey),
    ])
    let blocked = !!state?.resolutionBlocked
    let blockedItemId = state?.resolutionBlockedItemId != null ? String(state.resolutionBlockedItemId) : null
    let hiResolution = this._hiLane(state, 'resolution')
    let hiDownload = this._hiLane(state, 'download')

    for (let hiEntry of [
      {ctx: /** @type {BrazenDownloadManagerLaneId} */ ('download'), lane: hiDownload},
      {ctx: /** @type {BrazenDownloadManagerLaneId} */ ('resolution'), lane: hiResolution},
    ]) {
      if (!hiEntry.lane) {
        continue
      }
      let humanItemId = hiEntry.lane.itemId != null ? String(hiEntry.lane.itemId) : null
      if (humanItemId === itemKey) {
        return {
          label: 'Verify',
          stepIndex: hiEntry.ctx === 'download' ?
              layout.indices.downloading :
              layout.indices.resolving,
          stepCount: layout.stepCount,
        }
      }
      if (humanItemId) {
        let showWaiting = this._isLinkQueues()
        if (!showWaiting && hiEntry.ctx === 'download') {
          showWaiting = !!(download && !DOWNLOAD_TERMINAL.has(download.status))
        }
        if (!showWaiting && hiEntry.ctx === 'resolution') {
          showWaiting = !!(resolution && !RESOLUTION_TERMINAL.has(resolution.status) && !download)
        }
        if (showWaiting) {
          return {
            label: 'Waiting',
            stepIndex: layout.indices.queued,
            stepCount: layout.stepCount,
          }
        }
      }
    }

    if (download) {
      if (download.status === 'downloading') {
        return {
          label: 'Downloading',
          stepIndex: layout.indices.downloading,
          stepCount: layout.stepCount,
        }
      }
      if (download.status === 'queued') {
        return {
          // paused gates starting downloads; queued items stay Ready until Start (not "Paused")
          label: 'Ready',
          stepIndex: layout.indices.downloadQueued,
          stepCount: layout.stepCount,
        }
      }
      if (download.status === 'done') {
        return {
          label: 'Done',
          stepIndex: layout.indices.downloading,
          stepCount: layout.stepCount,
          complete: true,
        }
      }
      if (download.status === 'duplicate') {
        return {
          label: 'Duplicate',
          stepIndex: layout.indices.downloading,
          stepCount: layout.stepCount,
          complete: true,
        }
      }
      if (download.status === 'skipped') {
        return {
          label: 'Skipped',
          stepIndex: layout.indices.downloading,
          stepCount: layout.stepCount,
          complete: true,
        }
      }
      if (download.status === 'failed') {
        return {
          label: 'Failed',
          stepIndex: layout.indices.downloading,
          stepCount: layout.stepCount,
          failed: true,
        }
      }
    }

    if (resolution) {
      if (resolution.status === 'failed') {
        return {
          label: 'Failed',
          stepIndex: layout.indices.resolving,
          stepCount: layout.stepCount,
          failed: true,
        }
      }
      if (resolution.status === 'discoveryQueued') {
        return {
          label: 'Discovery',
          stepIndex: layout.indices.tagReview >= 0 ? layout.indices.tagReview : layout.indices.resolving,
          stepCount: layout.stepCount,
        }
      }
      if (resolution.status === 'tagReview') {
        if (blocked && blockedItemId === itemKey) {
          return {
            label: 'Tags',
            stepIndex: layout.indices.tagReview >= 0 ? layout.indices.tagReview : layout.indices.downloadQueued,
            stepCount: layout.stepCount,
          }
        }
        if (this._isTagReviewBlockedOnOtherItem(state, itemKey)) {
          return {
            label: 'Waiting',
            stepIndex: layout.indices.queued,
            stepCount: layout.stepCount,
          }
        }
        return {
          label: 'Queued',
          stepIndex: layout.indices.queued,
          stepCount: layout.stepCount,
        }
      }
      if (resolution.status === 'resolving') {
        return {
          label: 'Resolving',
          stepIndex: layout.indices.resolving,
          stepCount: layout.stepCount,
        }
      }
      if (resolution.status === 'queued') {
        if (this._isTagReviewBlockedOnOtherItem(state, itemKey)) {
          return {
            label: 'Waiting',
            stepIndex: layout.indices.queued,
            stepCount: layout.stepCount,
          }
        }
        return {
          label: 'Queued',
          stepIndex: layout.indices.queued,
          stepCount: layout.stepCount,
        }
      }
    }

    return null
  }

  /**
   * @param {HTMLElement|null|undefined} element
   * @param {{label: string, stepIndex: number, stepCount: number, failed?: boolean, complete?: boolean}|null} progress
   * @private
   */
  _setItemProgress(element, progress)
  {
    let pageConfig = this._getActivePageConfig()
    if (pageConfig?.setItemProgress) {
      Utilities.callEventHandler(pageConfig.setItemProgress, [element, progress], null)
      return
    }
    if (progress) {
      BrazenViewLayer.updateDownloadManagerItemProgress(element, progress)
      return
    }
    BrazenViewLayer.clearDownloadManagerItemProgress(element)
  }

  // -------------------------------------------------------------------------
  // Selection mode
  // -------------------------------------------------------------------------

  /**
   * Push filename/subfolder pattern pins into live `tagDiscovery.tagTypes` so discovery
   * scan scope tracks the user's active download-path patterns without app handlers.
   * @private
   */
  _syncDiscoveryTagTypesFromPatterns()
  {
    if (!this._config.tagDiscovery) {
      return
    }
    this._config.tagDiscovery.tagTypes = this._getPinnedFilenameTagTypes()
  }

  /**
   * While download selection mode is on, block native drag on search thumbs
   * so click-to-queue is not stolen by image/link dragging. Applies body class
   * `bv-dm-selection` for consumer CSS (pointer-events on thumb tiles).
   * @return {void}
   */
  registerSelectionDragGuard()
  {
    let pageConfig = this._getActivePageConfig()
    let active = !!(this.isDownloadManagerEnabled() && this._isSelectionModeActive() &&
        this.isDownloadPageRole('selection'))
    document.body.classList.toggle('bv-dm-selection', active)
    if (this._selectionDragStartHandler) {
      document.removeEventListener('dragstart', this._selectionDragStartHandler, true)
      this._selectionDragStartHandler = null
    }
    if (!active || !pageConfig?.itemSelector) {
      return
    }
    let itemSelector = pageConfig.itemSelector
    this._selectionDragStartHandler = (event) => {
      if (event.target instanceof Element && event.target.closest(itemSelector)) {
        event.preventDefault()
      }
    }
    document.addEventListener('dragstart', this._selectionDragStartHandler, true)
  }

  /**
   * @return {boolean}
   * @private
   */
  _isSelectionModeActive()
  {
    return !!this._selectionModeActive
  }

  /**
   * @private
   */
  _bindSelectionHandlers()
  {
    if (this._selectionClickHandler) {
      document.removeEventListener('click', this._selectionClickHandler, true)
      this._selectionClickHandler = null
    }

    let pageConfig = this._getActivePageConfig()
    if (!pageConfig?.resolveItem || !pageConfig.itemSelector) {
      return
    }

    if (!this._isSelectionModeActive() || !this.isDownloadManagerEnabled()) {
      return
    }

    let itemSelector = pageConfig.itemSelector
    this._selectionClickHandler = (event) => {
      if (event.__bvDmSelectionHandled) {
        return
      }
      let target = event.target.closest(itemSelector)
      if (!target) {
        return
      }
      event.__bvDmSelectionHandled = true
      event.preventDefault()
      event.stopPropagation()
      let resolved = Utilities.callEventHandler(pageConfig.resolveItem, [target], null)
      if (!resolved?.itemId) {
        return
      }
      void this._toggleSelectionItem(resolved, target)
    }
    // Capture so one physical click cannot paint (bubble handler) then deselect (duplicate handler).
    document.addEventListener('click', this._selectionClickHandler, true)
  }

  /**
   * @param {object} resolved
   * @param {HTMLElement} element
   * @return {Promise<void>}
   * @private
   */
  async _toggleSelectionItem(resolved, element)
  {
    let itemKey = String(resolved.itemId)
    let epoch = (this._selectionOpEpoch.get(itemKey) || 0) + 1
    this._selectionOpEpoch.set(itemKey, epoch)

    // Sync local heuristic only — never await IDB before painting click feedback.
    let hasOverlay = element.dataset.bvDmProgress != null && element.dataset.bvDmProgress !== ''
    let locallySelected = this._trackedItemElements.has(itemKey) ||
        this._selectionUiPending.has(itemKey) ||
        hasOverlay

    if (locallySelected) {
      // Clear overlay immediately; hide until activeIds reconcile confirms dequeue (blocks late promote/sync repaints).
      this._selectionUiPending.delete(itemKey)
      this._selectionUiHidden.add(itemKey)
      this._untrackItemElement(resolved.itemId)
      this._setItemProgress(element, null)
      await this.dequeueDownload(resolved.itemId)
      return
    }

    // Queued rows with no painted overlay are synced via _enqueueSelectionItem (!added path),
    // not treated as deselect — otherwise a silent IDB row toggles off with no visible UI.

    if (this._selectionOpEpoch.get(itemKey) !== epoch) {
      return
    }

    await this._enqueueSelectionItem(resolved, element, epoch)
  }

  /**
   * Optimistic select + enqueue for one search tile (shared by click and Select All).
   * @param {object} resolved
   * @param {HTMLElement} element
   * @param {number|null|undefined} opEpoch when set (click toggle), reuse without re-bumping epoch
   * @return {Promise<void>}
   * @private
   */
  async _enqueueSelectionItem(resolved, element, opEpoch = null)
  {
    let itemKey = String(resolved.itemId)
    let epoch = opEpoch
    if (epoch == null) {
      epoch = (this._selectionOpEpoch.get(itemKey) || 0) + 1
      this._selectionOpEpoch.set(itemKey, epoch)
    }

    // Optimistic paint before any IDB (isQueued / enqueue) so click→blur is not gated on storage.
    this._selectionUiHidden.delete(itemKey)
    this._selectionUiPending.add(itemKey)
    let layout = this._getPipelineStepLayout()
    this._trackItemElement(resolved.itemId, element)
    this._setItemProgress(element, {
      label: 'Queued',
      stepIndex: layout.indices.queued,
      stepCount: layout.stepCount,
    })

    try {
      if (this._selectionOpEpoch.get(itemKey) !== epoch) {
        return
      }

      let added = await this.enqueueDownload(resolved)
      if (this._selectionOpEpoch.get(itemKey) !== epoch) {
        // A later click cancelled this select — keep UI hidden and drop the row if written.
        this._selectionUiPending.delete(itemKey)
        this._selectionUiHidden.add(itemKey)
        this._untrackItemElement(resolved.itemId)
        this._setItemProgress(element, null)
        if (added) {
          await this.dequeueDownload(resolved.itemId)
        }
        return
      }
      if (!added) {
        // Another tab may have enqueued first — keep overlay if still in the pipeline.
        if (await this.isQueued(resolved.itemId)) {
          await this._renderItemProgress(resolved.itemId)
          return
        }
        // Follower/command in flight — local IDB row not visible yet; keep optimistic paint.
        if (this._selectionUiPending.has(itemKey)) {
          return
        }
        this._selectionUiPending.delete(itemKey)
        this._untrackItemElement(resolved.itemId)
        this._setItemProgress(element, null)
        return
      }
      // Keep _selectionUiPending until _renderItemProgress confirms the IDB row (Reactor follower lag).
      await this._renderItemProgress(resolved.itemId)
    } catch (e) {
      // IDB wedged / closed mid-click — drop optimistic paint so UI matches persistence.
      if (this._selectionOpEpoch.get(itemKey) !== epoch) {
        return
      }
      this._selectionUiPending.delete(itemKey)
      this._untrackItemElement(resolved.itemId)
      this._setItemProgress(element, null)
      console.warn('[BrazenDM] selection enqueue failed:', e)
    }
  }

  /**
   * @private
   */
  _refreshSelectionMarks()
  {
    let pageConfig = this._getActivePageConfig()
    if (!pageConfig?.itemSelector || !this.isDownloadPageRole('selection')) {
      return
    }
    void (async () => {
      let selectionActive = this._isSelectionModeActive()
      let [resolutionIds, downloadIds] = await Promise.all([
        this._repos().downloadResolutionQueue.listActiveItemIds(),
        this._repos().downloadQueue.listActiveItemIds(),
      ])
      let activeIds = new Set([...resolutionIds, ...downloadIds].map(String))
      /** @type {Promise<void>[]} */
      let progressPaints = []
      for (let element of document.querySelectorAll(pageConfig.itemSelector)) {
        let resolved = Utilities.callEventHandler(pageConfig.resolveItem, [element], null)
        if (!resolved?.itemId) {
          continue
        }
        let itemKey = String(resolved.itemId)
        let queued = activeIds.has(itemKey)
        if (this._selectionUiHidden.has(itemKey)) {
          // Sticky until re-select (_enqueueSelectionItem) or selection-mode exit (_clearAllItemProgress).
          continue
        }
        let selected = selectionActive && queued
        let hasOverlay = element.dataset.bvDmProgress != null && element.dataset.bvDmProgress !== ''
        // Keep DOM↔item mapping warm so processor claim/complete refreshes find the tile.
        if (selected) {
          this._trackItemElement(resolved.itemId, element)
          progressPaints.push(this._renderItemProgress(resolved.itemId))
          continue
        }
        if (this._selectionUiPending.has(itemKey)) {
          // Optimistic select — IDB row not visible yet; leave the painted overlay alone.
          continue
        }
        if (this._trackedItemElements.has(itemKey) || hasOverlay) {
          if (selectionActive) {
            // While selection mode is on, reconcile paints only — never tear down overlays from a
            // stale activeIds snapshot taken before enqueue write-through completes.
            progressPaints.push(this._renderItemProgress(resolved.itemId))
            continue
          }
          progressPaints.push((async () => {
            if (this._selectionUiPending.has(itemKey)) {
              return
            }
            await this._renderItemProgress(resolved.itemId)
            if (this._selectionUiPending.has(itemKey)) {
              return
            }
            if (!queued) {
              if (await this.isQueued(resolved.itemId)) {
                await this._renderItemProgress(resolved.itemId)
                return
              }
              let progress = await this._resolveItemPipelineView(resolved.itemId)
              if (!progress) {
                this._untrackItemElement(resolved.itemId)
                this._setItemProgress(element, null)
              }
            }
          })())
        }
      }
      if (progressPaints.length) {
        await Promise.all(progressPaints)
      }
    })()
  }

  // -------------------------------------------------------------------------
  // Dock UI
  // -------------------------------------------------------------------------

  /**
   * @private
   */
  _setupDock()
  {
    if (this._config.tagDiscovery) {
      this._cm.addActionField(DOCK_TAG_DISCOVERY_MODE).
          setTitle('Tag Discovery Mode').
          setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.DOCK_TAG_DISCOVERY_MODE).
          setAction(() => { void this.toggleTagDiscoveryMode() }).
          setDockButton({
            icon: 'discovery',
            getState: () => this.readDmState()?.tagDiscoveryEnabled ? 'bv-dock-btn-active' : '',
            tooltip: () => this.readDmState()?.tagDiscoveryEnabled ?
                'Tag discovery: on — unknown tags pause downloads until you confirm them' :
                'Tag discovery: off — click to pause on unknown tags and review mappings',
            include: function() {
              return this.isDownloadManagerEnabled() && this.isDownloadPageRole('tagDiscoveryToggle')
            },
          })
    }

    this._cm.addActionField(DOCK_SELECTION_MODE).
        setTitle('Selection Mode').
        setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.DOCK_SELECTION_MODE).
        setAction(() => this.toggleSelectionMode()).
        setDockButton({
          icon: 'select',
          getState: () => this._isSelectionModeActive() ? 'bv-dock-btn-active' : '',
          tooltip: () => this._isSelectionModeActive() ?
              'Selection mode: on — click items to queue; hover for Select All' :
              'Selection mode: off — click to select items for download queue',
          slideOutWhen: () => this._isSelectionModeActive(),
          include: function() {
            return this.isDownloadManagerEnabled() && this.isDownloadPageRole('selection')
          },
        }).
        setDockSlideOut([DOCK_SELECTION_SELECT_ALL])

    this._cm.addActionField(DOCK_SELECTION_SELECT_ALL).
        setTitle('Select All').
        setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.DOCK_SELECTION_SELECT_ALL).
        setAction(() => { void this.selectAllVisibleItems() }).
        setDockButton({
          icon: 'select-all',
          tooltip: 'Select all visible posts for the download queue',
        })

    this._cm.addActionField(DOCK_ADD_TO_QUEUE).
        setTitle('Add to Download Queue').
        setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.DOCK_ADD_TO_QUEUE).
        setAction(() => { void this.toggleCurrentMediaQueued() }).
        setDockButton({
          icon: () => this._currentMediaQueued ? 'queue-remove' : 'queue-add',
          getState: () => this._currentMediaQueued ? 'bv-dock-btn-active' : '',
          tooltip: () => this._currentMediaQueued
              ? 'In download queue — click to remove this post'
              : 'Add this post to the download queue',
          include: function() {
            return this.isDownloadManagerEnabled() && this.isDownloadPageRole('enqueueMedia')
          },
        })

    this._cm.addActionField(DOCK_CLEAR_DOWNLOAD_QUEUE).
        setTitle('Clear Download Queue').
        setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.DOCK_CLEAR_DOWNLOAD_QUEUE).
        setAction(() => {
          if (confirm('Clear pending downloads? Resolution work and selection mode are not cleared.')) {
            void this.clearDownloadQueue()
          }
        }).
        setDockButton({
          icon: 'clear',
          tooltip: () => this.getPendingDownloadCountSync() <= 0
              ? 'Download queue empty'
              : 'Clear download queue',
          isDisabled: () => this.getPendingDownloadCountSync() <= 0,
          include: function() {
            return this.isDownloadManagerEnabled()
          },
        })

    this._cm.addActionField(DOCK_DOWNLOAD_START_PAUSE).
        setTitle('Download Queue Start/Pause').
        setHelp(DOWNLOAD_MANAGER_FIELD_DETAILED_HELP.DOCK_DOWNLOAD_START_PAUSE).
        setAction(() => { void this.toggleDownloadManagerPaused() }).
        setDockButton({
          icon: () => {
            let state = this.readDmState()
            if (this._isStartPauseShowingHumanInteraction(state)) {
              return 'play'
            }
            // Idle Play only when both queues empty — not when download drained mid-batch.
            if (this._isDownloadBatchIdle()) {
              return 'play'
            }
            return state?.paused ? 'play' : 'pause'
          },
          getState: () => {
            let state = this.readDmState()
            if (this._isStartPauseShowingHumanInteraction(state)) {
              return ''
            }
            if (this._isDownloadBatchIdle()) {
              return ''
            }
            return state?.paused ? '' : 'bv-dock-btn-active'
          },
          isDisabled: () => {
            let state = this.readDmState()
            if (this._isStartPauseShowingHumanInteraction(state)) {
              return false
            }
            return this._isDownloadBatchIdle()
          },
          tooltip: () => {
            let state = this.readDmState()
            if (this._isStartPauseShowingHumanInteraction(state)) {
              return 'Verification required — click to confirm and resume'
            }
            if (this._isDownloadBatchIdle()) {
              return 'Queues idle — click Start when the next batch has Ready downloads'
            }
            if (this.getPendingDownloadCountSync() <= 0 && this.getPendingResolutionCountSync() > 0) {
              if (state?.paused) {
                return 'Batch still resolving — downloads paused; click to start when Ready'
              }
              return 'Batch still resolving — downloads run as items become Ready; click to pause'
            }
            if (state?.paused) {
              return 'Download queue paused — click to start downloads'
            }
            return 'Download queue running — click to pause downloads'
          },
          slideOutWhen: () => this._isQueueDockSlideOutOpen(),
          slideOutPinnedWhen: () => this._isQueueDockSlideOutPinned(),
          include: function() {
            return this.isDownloadManagerEnabled()
          },
        }).
        setDockSlideOut(this._getQueueDockSlideOutChildKeys()).
        setDockSlideOutNodes(() => this._getQueueDockSlideOutNodes())

    // Skip Duplicate ships its dock template from Framework; DM only attaches Hide as slide-out.
    let skipDuplicateKey = this._framework._downloadDuplicateLedgerConfig?.enableConfigKey ??
        (typeof OPTION_ENABLE_DOWNLOAD_DUPLICATE_LEDGER !== 'undefined' ? OPTION_ENABLE_DOWNLOAD_DUPLICATE_LEDGER : null)
    let skipDuplicateField = skipDuplicateKey ? this._cm.getField(skipDuplicateKey) : null
    if (skipDuplicateField) {
      let hideDownloadedKey = typeof OPTION_HIDE_DOWNLOADED_MEDIA !== 'undefined' ?
          OPTION_HIDE_DOWNLOADED_MEDIA :
          'hide-downloaded-media'
      let hideField = this._cm.getField(hideDownloadedKey)
      if (hideField && !hideField.dock) {
        hideField.applyDockTemplate('hideDownloaded')
      }
      skipDuplicateField.setDockSlideOut([hideDownloadedKey])
    }
  }

  /**
   * @return {boolean}
   * @private
   */
  _isQueueDockSlideOutOpen()
  {
    let slideOut = this._config.queueDockSlideOut
    if (slideOut?.slideOutWhen) {
      return !!Utilities.callEventHandler(slideOut.slideOutWhen, [this], null)
    }
    return this.getPendingResolutionCountSync() > 0 || this.getPendingDownloadCountSync() > 0
  }

  /**
   * @return {boolean}
   * @private
   */
  _isQueueDockSlideOutPinned()
  {
    let slideOut = this._config.queueDockSlideOut
    if (slideOut?.slideOutPinnedWhen) {
      return !!Utilities.callEventHandler(slideOut.slideOutPinnedWhen, [this], null)
    }
    return this._isQueueDockSlideOutOpen()
  }

  /**
   * @return {HTMLElement[]}
   * @private
   */
  _getQueueDockSlideOutNodes()
  {
    let slideOut = this._config.queueDockSlideOut
    if (slideOut?.getSlideOutNodes) {
      let nodes = Utilities.callEventHandler(slideOut.getSlideOutNodes, [this, this._cm], null)
      return Array.isArray(nodes) ? nodes.filter((node) => node != null) : []
    }
    return [this.getOrCreateProgressSlot()]
  }

  /**
   * @return {string[]}
   * @private
   */
  _getQueueDockSlideOutChildKeys()
  {
    let slideOut = this._config.queueDockSlideOut
    if (Array.isArray(slideOut?.childFields)) {
      return slideOut.childFields
    }
    return [DOCK_CLEAR_DOWNLOAD_QUEUE]
  }

  /**
   * Coalesced dock progress paint via signal effect (`dockProgressPaint`).
   * @param {{refreshAllItems?: boolean}} [options]
   * @private
   */
  _refreshDockProgress(options = {})
  {
    this._requestDockProgressPaint(options)
  }

  /**
   * Read-only DM UI refresh after runtime pipeline store writes (replaces incidental config-ui:all).
   * @param {string} [_source]
   * @private
   */
  _requestRuntimeUiRefresh(_source)
  {
    if (this._runtimeUiRefreshHandle != null) {
      clearTimeout(this._runtimeUiRefreshHandle)
    }
    this._runtimeUiRefreshHandle = setTimeout(() => {
      this._runtimeUiRefreshHandle = null
      this._runRuntimeUiRefresh()
    }, RUNTIME_UI_REFRESH_DEBOUNCE_MS)
  }

  /**
   * Former config-ui:all storm body — paint/atom only, no IDB store writes.
   * @private
   */
  _runRuntimeUiRefresh()
  {
    if (this._documentSuspended || !this._processorsUiReady) {
      return
    }
    globalThis.__brazenReactor?.mark?.('effect', 'runtimeUiRefresh', {
      key: 'pipeline',
    })
    this._requestTagDiscoveryPanelRefresh(null)
    this._refreshDockProgress({refreshAllItems: false})
    this._refreshSelectionMarks()
    this._syncDiscoveryTagTypesFromPatterns()
  }

  /**
   * @private
   */
  _cancelFollowerSnapshotRetry()
  {
    if (this._followerSnapshotRetryTimer != null) {
      clearTimeout(this._followerSnapshotRetryTimer)
      this._followerSnapshotRetryTimer = null
    }
  }

  /**
   * @param {Record<string, *>|null|undefined} snapshot
   * @param {number} version
   * @return {object[]}
   * @private
   */
  _snapshotRecordToPatches(snapshot, version)
  {
    if (!snapshot || typeof snapshot !== 'object') {
      return []
    }
    let seq = Number(version) || 0
    let patches = []
    for (let [path, value] of Object.entries(snapshot)) {
      if (!path) {
        continue
      }
      patches.push({path, value: structuredClone(value), version: seq})
    }
    return patches
  }

  /**
   * Hydrate follower atoms + cached dm.state from a coordinator snapshot response.
   * @param {{snapshot?: Record<string, *>, snapshotSeq?: number, catchUpPatches?: object[]}} response
   * @private
   */
  _applyFollowerSnapshotResponse(response)
  {
    if (!response || this._kernel?.isCoordinator()) {
      return
    }
    let seq = Number(response.snapshotSeq) || 0
    let patches = this._snapshotRecordToPatches(response.snapshot, seq)
    if (response.catchUpPatches?.length) {
      for (let patch of response.catchUpPatches) {
        if (!patch?.path) {
          continue
        }
        patches.push({
          path: patch.path,
          value: structuredClone(patch.value),
          version: typeof patch.version === 'number' ? patch.version : seq,
        })
      }
    }
    if (!patches.length) {
      return
    }
    globalThis.BrazenSignals?.applyPatches?.(patches)
    this._reconcileBusPatches(patches, seq)
  }

  /**
   * @param {number} [delayMs]
   * @private
   */
  _scheduleFollowerSnapshotRetry(delayMs = FOLLOWER_SNAPSHOT_RETRY_MS)
  {
    this._cancelFollowerSnapshotRetry()
    if (this._documentSuspended || this._kernel?.isCoordinator()) {
      return
    }
    this._followerSnapshotRetryTimer = setTimeout(() => {
      this._followerSnapshotRetryTimer = null
      void this._requestFollowerSnapshot()
    }, delayMs)
  }

  /**
   * Follower cold start: open the EventBus patch gate via coordinator snapshot handshake.
   * @return {Promise<void>}
   * @private
   */
  async _requestFollowerSnapshot()
  {
    if (this._documentSuspended || !this._reactorBus?.requestSnapshot) {
      return
    }
    if (this._kernel?.isCoordinator()) {
      this._reactorBus.markSnapshotReady?.()
      this._cancelFollowerSnapshotRetry()
      return
    }
    if (this._followerSnapshotInFlight) {
      return
    }
    this._followerSnapshotInFlight = true
    try {
      let sinceSeq = this._lastAppliedPatchSeq > 0 ? this._lastAppliedPatchSeq : 0
      let response = await this._reactorBus.requestSnapshot(sinceSeq)
      this._applyFollowerSnapshotResponse(response)
      this._cancelFollowerSnapshotRetry()
    } catch (_e) {
      if (!this._documentSuspended && !this._kernel?.isCoordinator()) {
        this._scheduleFollowerSnapshotRetry()
      }
    } finally {
      this._followerSnapshotInFlight = false
    }
  }

  /**
   * Update dock progress counters/bars. Remount slide-out only when pending work
   * appears or clears; refresh Start/Pause chrome before painting the live node.
   * @param {{refreshAllItems?: boolean}} [options]
   * @return {Promise<void>}
   * @private
   */
  async _paintDockProgress(options = {})
  {
    // Early DM init runs before `_buildDock` — painting then fills a detached slot and
    // leaves the live rail empty until a later wake. Skip until the dock exists.
    if (!document.querySelector('.bv-dock .bv-dock-rail-body')) {
      return
    }
    let refreshAllItems = options.refreshAllItems === true
    let downloadCount = this.getPendingDownloadCountSync()
    let resolutionCount = this.getPendingResolutionCountSync()
    let prevDownload = this._lastPaintedDownloadPending ?? downloadCount
    let prevResolution = this._lastPaintedResolutionPending ?? resolutionCount

    let slideOutBefore = prevDownload > 0 || prevResolution > 0
    let slideOutAfter = downloadCount > 0 || resolutionCount > 0
    // Chrome first so `_refreshDockRootSlot` cannot detach the node after we paint it.
    if (slideOutBefore !== slideOutAfter) {
      globalThis.__brazenReactor?.mark?.('effect', 'dock-refresh-interface', {
        key: 'slideOut',
        data: {cause: globalThis.__brazenReactor?.activeCauseLabel?.()},
      })
      this._framework.refreshDockInterface(undefined, {layout: false})
    } else {
      globalThis.__brazenReactor?.mark?.('effect', 'dock-refresh-buttons', {
        data: {cause: globalThis.__brazenReactor?.activeCauseLabel?.()},
      })
      this._cm.refreshDockButtonStates()
    }

    let showProgress = (resolutionCount > 0 || downloadCount > 0) && this.isDownloadManagerEnabled()
    let progressElement = this.getOrCreateProgressSlot()
    if (showProgress && !this._isProgressSlotConnected(progressElement)) {
      this._remountDownloadStartPauseSlot()
      progressElement = this.getOrCreateProgressSlot()
    }
    BrazenViewLayer.setDownloadManagerProgressSlotVisible(progressElement, showProgress)
    if (showProgress) {
      let state = this.readDmState()
      if (!state) {
        state = await this._repos().downloadManagerState.get()
      }
      let completedResolution = Number(state.completedResolutionCount) || 0
      let completedDownload = Number(state.completedDownloadCount) || 0
      BrazenViewLayer.updateDownloadManagerProgressSlot(progressElement, {
        resolution: {
          current: completedResolution,
          total: completedResolution + resolutionCount,
        },
        download: {
          current: completedDownload,
          total: completedDownload + downloadCount,
        },
      })
    }

    this._lastPaintedDownloadPending = downloadCount
    this._lastPaintedResolutionPending = resolutionCount

    if (refreshAllItems) {
      this._refreshAllItemProgress()
    }
  }

  // -------------------------------------------------------------------------
  // Page helpers
  // -------------------------------------------------------------------------

  /**
   * @return {string|null}
   * @private
   */
  _getActivePageName()
  {
    for (let pageName of Object.keys(this._config.pages ?? {})) {
      if (this._framework.isPage(pageName)) {
        return pageName
      }
    }
    return null
  }

  /**
   * @return {object|null}
   * @private
   */
  _getActivePageConfig()
  {
    let pageName = this._getActivePageName()
    return pageName ? this._config.pages[pageName] : null
  }

  /**
   * @return {BrazenStorageRepositories}
   * @private
   */
  _repos()
  {
    return this._cm.getRepos()
  }
}

/** @type {Readonly<Record<string, string>>} */
BrazenDownloadManager.FIELD_DETAILED_HELP = DOWNLOAD_MANAGER_FIELD_DETAILED_HELP
/** @type {Readonly<typeof BRAZEN_DOWNLOAD_PATH_FIELD_KEYS>} */
BrazenDownloadManager.BRAZEN_DOWNLOAD_PATH_FIELD_KEYS = BRAZEN_DOWNLOAD_PATH_FIELD_KEYS