Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent lobortis lorem sapien, eu viverra tellus auctor nec. Nulla aliquet iaculis tellus, in tempus massa eleifend a. Vestibulum viverra, massa nec aliquet ultrices, felis libero eleifend est, eu elementum sapien libero vitae lectus. Quisque rhoncus mollis felis, sed finibus ex pharetra ultricies. Praesent viverra nulla id congue ullamcorper.

# Header 1

## Header 2

**Lorem** ipsum.

- list1
- list2
- list3

`var a = 1;`

<think>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent lobortis lorem sapien, eu viverra tellus auctor nec. Nulla aliquet iaculis tellus, in tempus massa eleifend a. Vestibulum viverra, massa nec aliquet ultrices, felis libero eleifend est, eu elementum sapien libero vitae lectus. Quisque rhoncus mollis felis, sed finibus ex pharetra ultricies. Praesent viverra nulla id congue ullamcorper.</think>

<execute>
a = 1
b = 2
c = a + b
print(c)
class ChatWebOutput(QWebEngineView):
    def __init__(self, window=None):
        """
        HTML output (WebEngine)

        :param window: Window instance
        """
        super(ChatWebOutput, self).__init__(window)
        self.window = window
        self.finder = WebFinder(window, self)
        self.loadFinished.connect(self.on_page_loaded)
        self.customContextMenuRequested.connect(self.on_context_menu)
        self.signals = WebEngineSignals(self)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.filter = FocusEventFilter(self, self.on_focus)
        self.installEventFilter(self)
        self.plain = None
        self.html_content = None
        self.meta = None
        self.tab = None
        self.setProperty('class', 'layout-output-web')
</execute>

<tool>{"cmd": "fake_cmd", "params": {"foo": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."}}</tool>

```python
from pygpt_net.core.events import RenderEvent


@dataclass(slots=True)
class RenderBlock:
    """
    JSON payload for node rendering in JS templates.

    Keep only raw data here. HTML is avoided except where there is
    no easy way to keep raw (e.g. plugin-provided tool extras), which are
    carried under extra.tool_extra_html.
    """
    id: int
    meta_id: Optional[int] = None
    input: Optional[dict] = None
    output: Optional[dict] = None
    files: dict = field(default_factory=dict)
    images: dict = field(default_factory=dict)
    urls: dict = field(default_factory=dict)
    tools: dict = field(default_factory=dict)
    tools_outputs: dict = field(default_factory=dict)
    extra: dict = field(default_factory=dict)

    def to_dict(self) -> dict:
        return {
            "id": self.id,
            "meta_id": self.meta_id,
            "input": self.input,
            "output": self.output,
            "files": self.files,
            "images": self.images,
            "urls": self.urls,
            "tools": self.tools,
            "tools_outputs": self.tools_outputs,
            "extra": self.extra,
        }

```
Interdum et malesuada fames ac ante ipsum primis in faucibus. Nam auctor tempor erat. Nam augue nisl, venenatis at semper ac, tristique ut lacus.

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Lorem ipsum dolor sit amet.</title>
</head>
<body>
    <h1>Lorem ipsum dolor sit amet!</h1>
    <p>Aliquam rutrum, nisi eget egestas vehicula, lectus nulla vestibulum odio, eget bibendum arcu nisi id diam. Praesent a blandit justo. Duis tristique leo in faucibus tempor</p>
    <ul>
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
    </ul>
</body>
</html>
```

Nunc nec ex vel justo varius sagittis lobortis quis urna. Vivamus cursus faucibus augue, vitae eleifend ligula auctor a. Morbi arcu eros, porttitor eget bibendum vitae, pellentesque quis mauris. Fusce imperdiet leo a elit viverra porta pellentesque bibendum est.

```js

(function () {
  'use strict';

  // ==========================================================================
  // 0) Utils & Config
  // ==========================================================================

  // Small helper utilities used across the runtime.
  class Utils {
    static g(name, dflt) { return (typeof window[name] !== 'undefined') ? window[name] : dflt; }
    static now() { return (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now(); }

    // reuse a single detached element to reduce allocations on hot path.
    static escapeHtml(s) {
      const d = Utils._escDiv || (Utils._escDiv = document.createElement('div'));
      d.textContent = String(s ?? '');
      return d.innerHTML;
    }

    static countNewlines(s) {
      if (!s) return 0;
      let c = 0, i = -1; while ((i = s.indexOf('\n', i + 1)) !== -1) c++;
      return c;
    }
    static reEscape(s) { return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
    // Schedule a function in idle time (falls back to setTimeout).
    static idle(fn, timeout) {
      if ('requestIdleCallback' in window) return requestIdleCallback(fn, { timeout: timeout || 800 });
      return setTimeout(fn, 50);
    }
    // Cancel idle callback if possible (safe for fallback).
    static cancelIdle(id) {
      try {
        if ('cancelIdleCallback' in window) cancelIdleCallback(id);
        else clearTimeout(id);
      } catch (_) {}
    }
    static get SE() { return document.scrollingElement || document.documentElement; }

    // shared UTF-8 decoder to avoid per-call allocations.
    static utf8Decode(bytes) {
      if (!Utils._td) Utils._td = new TextDecoder('utf-8');
      return Utils._td.decode(bytes);
    }
  }

  // ==========================================================================
  // 0.1) Unified Logger (bridge-based; injected into classes)
  // ==========================================================================

  class Logger {
    // Production-grade logger with queue, batch sending and soft caps.
    constructor(cfg) {
      this.cfg = cfg || { LOG: {} };
      // Queue holds strings waiting to be sent over QWebChannel to Python.
      this.queue = [];
      this.queueBytes = 0; // approximate UTF-16 bytes (chars * 2)
      this.armed = false;
      this.maxTries = 480; // ~8s @60fps
      this.tries = 0;
      this.bridge = null;
      this._idleId = 0;              // idle-callback handle (backoff-friendly)
      this._lastFlushQueueBytes = 0; // last known bytes to detect progress

      // Centralized rAF manager handle (bound by runtime); falls back to microtasks until available.
      this.raf = null;
      this._rafScheduled = false;
      this._rafKey = { t: 'Logger:tick' };

      // Soft limits; tunable from window.* if desired.
      const L = this.cfg.LOG || {};
      this.MAX_QUEUE = Utils.g('LOG_MAX_QUEUE', L.MAX_QUEUE ?? 400);
      this.MAX_BYTES = Utils.g('LOG_MAX_BYTES', L.MAX_BYTES ?? 256 * 1024); // 256 KiB
      this.BATCH_MAX = Utils.g('LOG_BATCH_MAX', L.BATCH_MAX ?? 64);
      this.RATE_LIMIT_PER_SEC = Utils.g('LOG_RATE_LIMIT_PER_SEC', L.RATE_LIMIT_PER_SEC ?? 0); // 0 == off
      this._rlWindowMs = 1000;
      this._rlCount = 0;
      this._rlWindowStart = Utils.now();
    }
    // Connect a QWebChannel bridge and flush any pending messages.
    bindBridge(bridge) {
      this.bridge = bridge || null;
      // When bridge arrives, flush pending messages respecting caps.
      this.flush();
    }
    // Bind RafManager instance – ensures no direct requestAnimationFrame usage in Logger.
    bindRaf(raf) {
      this.raf = raf || null;
    }
    // Check if debug logging is enabled for a namespace.
    isEnabled(ns) {
      if (!ns) return !!(window.STREAM_DEBUG || window.MD_LANG_DEBUG);
      const key1 = ns + '_DEBUG';
      const key2 = ns.toUpperCase() + '_DEBUG';
      return !!(window[key1] || window[key2] || window.STREAM_DEBUG || window.MD_LANG_DEBUG);
    }
    // Pretty-view string (safe truncation for logs).
    pv(s, n = 120) {
      if (!s) return '';
      s = String(s);
      if (s.length <= n) return s.replace(/\n/g, '\\n');
      const k = Math.floor(n / 2);
      return (s.slice(0, k) + ' … ' + s.slice(-k)).replace(/\n/g, '\\n');
    }
    j(o) { try { return JSON.stringify(o); } catch (_) { try { return String(o); } catch(__){ return '[unserializable]'; } } }
    _emit(msg) {
      // Attempt batch-capable sink if provided by the bridge
      try {
        if (this.bridge) {
          if (typeof this.bridge.log_batch === 'function') { this.bridge.log_batch([String(msg)]); return true; }
          if (typeof this.bridge.logBatch === 'function') { this.bridge.logBatch([String(msg)]); return true; }
          if (typeof this.bridge.log === 'function') { this.bridge.log(String(msg)); return true; }
        }
        if (window.runtime && runtime.bridge && typeof runtime.bridge.log === 'function') {
          runtime.bridge.log(String(msg)); return true;
        }
      } catch (_) {}
      return false;
    }
    _emitBatch(arr) {
      try {
        if (!arr || !arr.length) return 0;
        // Prefer batch API if present; otherwise fallback to per-line
        if (this.bridge && typeof this.bridge.log_batch === 'function') { this.bridge.log_batch(arr.map(String)); return arr.length; }
        if (this.bridge && typeof this.bridge.logBatch === 'function') { this.bridge.logBatch(arr.map(String)); return arr.length; }
        if (this.bridge && typeof this.bridge.log === 'function') {
          for (let i = 0; i < arr.length; i++) this.bridge.log(String(arr[i]));
          return arr.length;
        }
      } catch (_) {}
      return 0;
    }
    _maybeDropForCaps(len) {
      // Hard cap queue size and memory to guard Python process via QWebChannel.
      if (this.queue.length <= this.MAX_QUEUE && this.queueBytes <= this.MAX_BYTES) return;
      const targetLen = Math.floor(this.MAX_QUEUE * 0.8);
      const targetBytes = Math.floor(this.MAX_BYTES * 0.8);
      // Drop oldest first; keep newest events
      while ((this.queue.length > targetLen || this.queueBytes > targetBytes) && this.queue.length) {
        const removed = this.queue.shift();
        this.queueBytes -= (removed ? removed.length * 2 : 0);
      }
      // Add one synthetic notice to indicate dropped logs (avoid recursion)
      const notice = '[LOGGER] queue trimmed due to caps';
      this.queue.unshift(notice);
      this.queueBytes += notice.length * 2;
    }
    _passRateLimit() {
      if (!this.RATE_LIMIT_PER_SEC || this.RATE_LIMIT_PER_SEC <= 0) return true;
      const now = Utils.now();
      if (now - this._rlWindowStart > this._rlWindowMs) {
        this._rlWindowStart = now;
        this._rlCount = 0;
      }
      if (this._rlCount >= this.RATE_LIMIT_PER_SEC) return false;
      return true;
    }
    // Push a log line; try immediate send, otherwise enqueue.
    log(text) {
      const msg = String(text);
      // If bridge is available, try immediate emit to avoid queueing overhead.
      if (this.bridge && (typeof this.bridge.log === 'function' || typeof this.bridge.log_batch === 'function' || typeof this.bridge.logBatch === 'function')) {
        if (this._passRateLimit()) {
          const ok = this._emit(msg);
          if (ok) return true;
        }
      }
      // Enqueue with caps; avoid unbounded growth
      this.queue.push(msg);
      this.queueBytes += msg.length * 2;
      this._maybeDropForCaps(msg.length);
      this._arm();
      return false;
    }
    // Debug wrapper respecting per-namespace switch.
    debug(ns, line, ctx) {
      if (!this.isEnabled(ns)) return false;
      let msg = `[${ns}] ${line || ''}`;
      if (typeof ctx !== 'undefined') msg += ' ' + this.j(ctx);
      return this.log(msg);
    }
    // Send a batch of lines if bridge is ready.
    flush(maxPerTick = this.BATCH_MAX) {
      // Centralized flush – no direct cancelAnimationFrame here; RafManager governs frames.
      if (!this.bridge && !(window.runtime && runtime.bridge && typeof runtime.bridge.log === 'function')) return 0;
      const n = Math.min(maxPerTick, this.queue.length);
      if (!n) return 0;
      const batch = this.queue.splice(0, n);
      // Update memory accounting before attempting emit to eagerly unlock memory
      let bytes = 0; for (let i = 0; i < batch.length; i++) bytes += batch[i].length * 2;
      this.queueBytes = Math.max(0, this.queueBytes - bytes);
      const sent = this._emitBatch(batch);

      // fix byte accounting for partial sends (re-queue remaining with accurate bytes).
      if (sent < batch.length) {
        const remain = batch.slice(sent);
        let remBytes = 0; for (let i = 0; i < remain.length; i++) remBytes += remain[i].length * 2;
        // Prepend remaining back to the queue preserving order
        for (let i = remain.length - 1; i >= 0; i--) this.queue.unshift(remain[i]);
        this.queueBytes += remBytes;
      }
      return sent;
    }
    _scheduleTick(tick) {
      // Prefer idle scheduling when bridge isn't available yet – avoids 60fps RAF churn.
      const preferIdle = !this.bridge;

      const scheduleIdle = () => {
        try { if (this._idleId) Utils.cancelIdle(this._idleId); } catch (_) {}
        // Use requestIdleCallback when available, fallback to small timeout. Keeps UI cold on idle.
        this._idleId = Utils.idle(() => { this._idleId = 0; tick(); }, 800);
      };

      if (preferIdle) { scheduleIdle(); return; }

      if (this._rafScheduled) return;
      this._rafScheduled = true;
      const run = () => { this._rafScheduled = false; tick(); };
      try {
        if (this.raf && typeof this.raf.schedule === 'function') {
          this.raf.schedule(this._rafKey, run, 'Logger', 3);
        } else if (typeof runtime !== 'undefined' && runtime.raf && typeof runtime.raf.schedule === 'function') {
          runtime.raf.schedule(this._rafKey, run, 'Logger', 3);
        } else {
          Promise.resolve().then(run);
        }
      } catch (_) {
        Promise.resolve().then(run);
      }
    }
    _arm() {
      if (this.armed) return;
      this.armed = true; this.tries = 0;
      const tick = () => {
        if (!this.armed) return;
        this.flush();
        this.tries++;
        if (this.queue.length === 0 || this.tries > this.maxTries) {
          this.armed = false;
          try { if (this._idleId) Utils.cancelIdle(this._idleId); } catch (_) {}
          this._idleId = 0;
          return;
        }
        this._scheduleTick(tick);
      };
      this._scheduleTick(tick);
    }
  }

  // ==========================================================================
  // RafManager (rAF-only)
  // ==========================================================================

  class RafManager {
    // rAF-only task pump with soft budget per flush to prevent long frames.
    constructor(cfg) {
      this.cfg = cfg || { RAF: {}, ASYNC: {} };
      this.tasks = new Map();
      this.groups = new Map();
      // rAF pump state
      this.tickId = 0;
      this._mode = 'raf';
      this.scheduled = false;
      this._flushInProgress = false;

      const R = (this.cfg && this.cfg.RAF) || {};
      this.FLUSH_BUDGET_MS = Utils.g('RAF_FLUSH_BUDGET_MS', R.FLUSH_BUDGET_MS ?? 7);
      this.MAX_TASKS_PER_FLUSH = Utils.g('RAF_MAX_TASKS_PER_FLUSH', R.MAX_TASKS_PER_FLUSH ?? 120);
    }
    // Start the pumping loop if needed.
    _armPump() {
      if (this.scheduled) return;
      this.scheduled = true;

      // Focus/visibility-agnostic, rAF-only scheduling (no timers, no watchdogs).
      const canRAF = typeof requestAnimationFrame === 'function';
      if (canRAF) {
        this._mode = 'raf';
        try {
          this.tickId = requestAnimationFrame(() => this.flush());
          return;
        } catch (_) {}
      }
      // Fallback without timers: schedule a microtask flush.
      this._mode = 'raf';
      Promise.resolve().then(() => this.flush());
    }
    // Schedule a function with an optional group and priority.
    schedule(key, fn, group = 'default', priority = 0) {
      if (!key) key = { k: 'anon' };
      this.tasks.set(key, { fn, group, priority });
      if (group) {
        let set = this.groups.get(group);
        if (!set) { set = new Set(); this.groups.set(group, set); }
        set.add(key);
      }
      this._armPump();
    }
    // Run pending tasks within a time and count budget.
    flush() {
      // Cancel rAF handle if any; nothing else (timers removed).
      try { if (this.tickId) cancelAnimationFrame(this.tickId); } catch (_) {}
      this.tickId = 0;
      this.scheduled = false;

      // Snapshot tasks and clear the map; re-queue leftovers if we exceed budget.
      const list = Array.from(this.tasks.entries()).map(([key, v]) => ({ key, ...v }));
      this.tasks.clear();
      list.sort((a, b) => a.priority - b.priority);

      const start = Utils.now();
      let processed = 0;

      for (let idx = 0; idx < list.length; idx++) {
        const t = list[idx];
        try { t.fn(); } catch (_) {}
        processed++;
        if (t.group) {
          const set = this.groups.get(t.group);
          if (set) { set.delete(t.key); if (set.size === 0) this.groups.delete(t.group); }
        }

        // Soft budget: if long, re-queue the rest for next rAF.
        const elapsed = Utils.now() - start;
        if (processed >= this.MAX_TASKS_PER_FLUSH || elapsed >= this.FLUSH_BUDGET_MS) {
          for (let j = idx + 1; j < list.length; j++) {
            const r = list[j];
            this.tasks.set(r.key, { fn: r.fn, group: r.group, priority: r.priority });
          }
          this._armPump();
          return;
        }
      }

      if (this.tasks.size) {
        this._armPump();
      }
    }
    // Force immediate flush or schedule next frame.
    kick(forceImmediate = true) {
      if (forceImmediate && this.tasks.size) {
        if (this._flushInProgress) return;
        this._flushInProgress = true;
        try { this.scheduled = true; this.flush(); } catch (_) {} finally { this._flushInProgress = false; }
        return;
      }
      this._armPump();
    }
    // Cancel a specific scheduled task by key.
    cancel(key) {
      const t = this.tasks.get(key);
      if (!t) return;
      this.tasks.delete(key);
      if (t.group) {
        const set = this.groups.get(t.group);
        if (set) { set.delete(key); if (set.size === 0) this.groups.delete(t.group); }
      }
    }
    // Cancel all tasks in a group.
    cancelGroup(group) {
      const set = this.groups.get(group);
      if (!set) return;
      for (const key of set) this.tasks.delete(key);
      this.groups.delete(group);
    }
    // Cancel everything and reset the pump.
    cancelAll() {
      this.tasks.clear();
      this.groups.clear();
      try { if (this.tickId) cancelAnimationFrame(this.tickId); } catch (_) {}
      this.tickId = 0;
      this.scheduled = false;
    }
    isScheduled(key) { return this.tasks.has(key); }

    // Awaitable "next frame" helper – resolves on next flush.
    nextFrame() {
      return new Promise((resolve) => {
        const key = { t: 'raf:nextFrame', i: Math.random() };
        this.schedule(key, () => resolve(), 'RafNext', 0);
      });
    }
  }

  // Return math rendering policy from window.MATH_STREAM_MODE.
  function getMathMode() {
    const v = String(window.MATH_STREAM_MODE || 'finalize-only').toLowerCase();
    return (v === 'idle' || v === 'always' || v === 'finalize-only') ? v : 'finalize-only';
  }

  // ==========================================================================
  // 0.2) Async runner (cooperative yielding for heavy tasks)
  // ==========================================================================

  class AsyncRunner {
    // Cooperative scheduler (rAF-based) for CPU-heavy tasks (keeps UI responsive).
    constructor(cfg, raf) {
      this.cfg = cfg || {};
      this.raf = raf || null;
      const A = this.cfg.ASYNC || {};
      this.SLICE_MS = Utils.g('ASYNC_SLICE_MS', A.SLICE_MS ?? 12);
      this.MIN_YIELD_MS = Utils.g('ASYNC_MIN_YIELD_MS', A.MIN_YIELD_MS ?? 0); // kept only for config compatibility
    }
    // Check whether we should yield control to the browser.
    _inputPending() {
      try {
        const s = navigator && navigator.scheduling;
        return !!(s && s.isInputPending && s.isInputPending({ includeContinuous: true }));
      } catch (_) { return false; }
    }
    shouldYield(startTs) {
      if (this._inputPending()) return true;
      return (Utils.now() - startTs) >= this.SLICE_MS;
    }
    // Yield cooperatively via next rAF frame (with a safe fallback).
    async yield() {
      // Centralized rAF yield; fallback to a tiny timer if raf manager is not ready yet.
      if (this.raf && typeof this.raf.nextFrame === 'function') {
        await this.raf.nextFrame();
        return;
      }
      if (typeof runtime !== 'undefined' && runtime.raf && typeof runtime.raf.nextFrame === 'function') {
        await runtime.raf.nextFrame();
        return;
      }
      await new Promise(r => setTimeout(r, 16));
    }
    // Process items in small batches with periodic yields.
    async forEachChunk(arr, fn, label) {
      if (!arr || !arr.length) return;
      let start = Utils.now();
      for (let i = 0; i < arr.length; i++) {
        await fn(arr[i], i);
        if (this.shouldYield(start)) { await this.yield(); start = Utils.now(); }
      }
    }
  }

  // ==========================================================================
  // Config
  // ==========================================================================

  class Config {
    constructor() {
      // Process identifier (passed from host).
      this.PID = Utils.g('PID', 0);

      // UI: scroll behavior and busy/zoom signalling thresholds (milliseconds / pixels).
      this.UI = {
        AUTO_FOLLOW_REENABLE_PX: Utils.g('AUTO_FOLLOW_REENABLE_PX', 8),     // Enable auto-follow when near bottom
        SCROLL_NEAR_MARGIN_PX: Utils.g('SCROLL_NEAR_MARGIN_PX', 450),      // How close to bottom is considered "near"
        INTERACTION_BUSY_MS: Utils.g('UI_INTERACTION_BUSY_MS', 140),       // Minimum busy time for interactions
        ZOOM_BUSY_MS: Utils.g('UI_ZOOM_BUSY_MS', 300)                      // Minimum busy time for zoom
      };

      // FAB (floating action button) visibility and debounce.
      this.FAB = {
        SHOW_DOWN_THRESHOLD_PX: Utils.g('SHOW_DOWN_THRESHOLD_PX', 0),      // Show "down" arrow when scroll distance is large
        TOGGLE_DEBOUNCE_MS: Utils.g('FAB_TOGGLE_DEBOUNCE_MS', 100)         // Debounce for icon/text toggles
      };

      // Highlighting controls and per-frame budget.
      this.HL = {
        PER_FRAME: Utils.g('HL_PER_FRAME', 2),                             // How many code blocks to highlight per frame
        DISABLE_ALL: Utils.g('DISABLE_SYNTAX_HIGHLIGHT', false)            // Global off switch for hljs
      };

      // Intersection-like margins (we do our own scan, but these guide budgets).
      this.OBSERVER = {
        CODE_ROOT_MARGIN: Utils.g('CODE_ROOT_MARGIN', '1000px 0px 1000px 0px'),
        BOX_ROOT_MARGIN: Utils.g('BOX_ROOT_MARGIN', '1500px 0px 1500px 0px'),
        CODE_THRESHOLD: [0, 0.001], BOX_THRESHOLD: 0
      };

      // Viewport scan preload distance (in pixels).
      this.SCAN = { PRELOAD_PX: Utils.g('SCAN_PRELOAD_PX', 1000) };

      // Code scroll behavior (auto-follow re-enable margin and near-bottom margin).
      this.CODE_SCROLL = {
        AUTO_FOLLOW_REENABLE_PX: Utils.g('CODE_AUTO_FOLLOW_REENABLE_PX', 8),
        NEAR_MARGIN_PX: Utils.g('CODE_SCROLL_NEAR_MARGIN_PX', 48)
      };

      // Stream (snapshot) budgets and queue limits.
      this.STREAM = {
        MAX_PER_FRAME: Utils.g('STREAM_MAX_PER_FRAME', 8),                 // How many chunks to process per frame
        EMERGENCY_COALESCE_LEN: Utils.g('STREAM_EMERGENCY_COALESCE_LEN', 300), // If queue grows beyond, coalesce
        COALESCE_MODE: Utils.g('STREAM_COALESCE_MODE', 'fixed'),           // fixed | adaptive
        SNAPSHOT_MAX_STEP: Utils.g('STREAM_SNAPSHOT_MAX_STEP', 8000),      // Upper bound for adaptive step
        // Bounds for queue to prevent memory growth on bursty streams
        QUEUE_MAX_ITEMS: Utils.g('STREAM_QUEUE_MAX_ITEMS', 1200),
        PRESERVE_CODES_MAX: Utils.g('STREAM_PRESERVE_CODES_MAX', 120)      // Max code blocks to attempt reuse
      };

      // Math (KaTeX) idle batching and per-batch hint.
      this.MATH = {
        IDLE_TIMEOUT_MS: Utils.g('MATH_IDLE_TIMEOUT_MS', 800),
        BATCH_HINT: Utils.g('MATH_BATCH_HINT', 24)
      };

      // Icon URLs (provided by host app).
      this.ICONS = {
        EXPAND: Utils.g('ICON_EXPAND', ''), COLLAPSE: Utils.g('ICON_COLLAPSE', ''),
        CODE_MENU: Utils.g('ICON_CODE_MENU', ''), CODE_COPY: Utils.g('ICON_CODE_COPY', ''),
        CODE_RUN: Utils.g('ICON_CODE_RUN', ''), CODE_PREVIEW: Utils.g('ICON_CODE_PREVIEW', '')
      };

      // Localized UI strings.
      this.LOCALE = {
        PREVIEW: Utils.g('LOCALE_PREVIEW', 'Preview'),
        RUN: Utils.g('LOCALE_RUN', 'Run'),
        COLLAPSE: Utils.g('LOCALE_COLLAPSE', 'Collapse'),
        EXPAND: Utils.g('LOCALE_EXPAND', 'Expand'),
        COPY: Utils.g('LOCALE_COPY', 'Copy'),
        COPIED: Utils.g('LOCALE_COPIED', 'Copied')
      };

      // Code block styling theme (hljs theme key or custom).
      this.CODE_STYLE = Utils.g('CODE_SYNTAX_STYLE', 'default');

      // Adaptive snapshot profile for plain text.
      this.PROFILE_TEXT = {
        base: Utils.g('PROFILE_TEXT_BASE', 4),                 // Base step (chars) between snapshots
        growth: Utils.g('PROFILE_TEXT_GROWTH', 1.28),          // Growth factor for adaptive
        minInterval: Utils.g('PROFILE_TEXT_MIN_INTERVAL', 4),  // Minimum time between snapshots (ms)
        softLatency: Utils.g('PROFILE_TEXT_SOFT_LATENCY', 60), // If idle for this long, force a snapshot
        adaptiveStep: Utils.g('PROFILE_TEXT_ADAPTIVE_STEP', false)
      };

      // Adaptive snapshot profile for code (line-aware).
      this.PROFILE_CODE = {
        base: 2048,                 // Fewer snapshots during fence-open warm-up (reduce transient fragments)
        growth: 2.6,                // Ramp step quickly if adaptive is enabled
        minInterval: 500,           // Minimum time between snapshots (ms) to avoid churn
        softLatency: 1200,          // Force snapshot only after a noticeable idle (ms)
        minLinesForHL: Utils.g('PROFILE_CODE_HL_N_LINE', 25),
        minCharsForHL: Utils.g('PROFILE_CODE_HL_N_CHARS', 5000),
        promoteMinInterval: 300, promoteMaxLatency: 800, promoteMinLines: Utils.g('PROFILE_CODE_HL_N_LINE', 25),
        adaptiveStep: Utils.g('PROFILE_CODE_ADAPTIVE_STEP', true),
        // Hard switches to plain streaming (no incremental hljs, minimal DOM churn)
        stopAfterLines: Utils.g('PROFILE_CODE_STOP_HL_AFTER_LINES', 300),   // Stop incremental hljs after this many lines
        streamPlainAfterLines: 0,   // Belt-and-suspenders: enforce plain mode soon after
        streamPlainAfterChars: 0,   // Also guard huge single-line code (chars cap)
        maxFrozenChars: 32000,      // If promotions slipped through, cap before spans grow too large
        finalHighlightMaxLines: Utils.g('PROFILE_CODE_FINAL_HL_MAX_LINES', 1500),
        finalHighlightMaxChars: Utils.g('PROFILE_CODE_FINAL_HL_MAX_CHARS', 350000)
      };

      // Debounce for heavy resets (ms).
      this.RESET = {
        HEAVY_DEBOUNCE_MS: Utils.g('RESET_HEAVY_DEBOUNCE_MS', 24)
      };

      // Logging caps (used by Logger).
      this.LOG = {
        MAX_QUEUE: Utils.g('LOG_MAX_QUEUE', 400),
        MAX_BYTES: Utils.g('LOG_MAX_BYTES', 256 * 1024),
        BATCH_MAX: Utils.g('LOG_BATCH_MAX', 64),
        RATE_LIMIT_PER_SEC: Utils.g('LOG_RATE_LIMIT_PER_SEC', 0)
      };

      // Async tuning for background work.
      this.ASYNC = {
        SLICE_MS: Utils.g('ASYNC_SLICE_MS', 12),
        MIN_YIELD_MS: Utils.g('ASYNC_MIN_YIELD_MS', 0),
        MD_NODES_PER_SLICE: Utils.g('ASYNC_MD_NODES_PER_SLICE', 12)
      };

      // RAF pump tuning (budget per frame).
      this.RAF = {
        FLUSH_BUDGET_MS: Utils.g('RAF_FLUSH_BUDGET_MS', 7),
        MAX_TASKS_PER_FLUSH: Utils.g('RAF_MAX_TASKS_PER_FLUSH', 120)
      };

      // Markdown tuning – allow/disallow indented code blocks.
      this.MD = {
        ALLOW_INDENTED_CODE: Utils.g('MD_ALLOW_INDENTED_CODE', false)
      };

      // Custom markup rules for simple tags in text.
      this.CUSTOM_MARKUP_RULES = Utils.g('CUSTOM_MARKUP_RULES', [
          { name: 'cmd',        open: '[!cmd]',     close: '[/!cmd]',     tag: 'div',   className: 'cmd', innerMode: 'text' },
          { name: 'think_md',   open: '[!think]',   close: '[/!think]',   tag: 'think', className: '',    innerMode: 'text' },
          { name: 'think_html', open: '<think>',    close: '</think>',    tag: 'think', className: '',    innerMode: 'text', stream: true },
          { name: 'tool', open: '<tool>',    close: '</tool>',    tag: 'div', className: 'cmd',    innerMode: 'text', stream: true },

          // Streams+final: convert [!exec]... into fenced python code BEFORE markdown-it
          { name: 'exec_md',    open: '[!exec]',    close: '[/!exec]',    innerMode: 'text', stream: true,
            openReplace: '```python\n', closeReplace: '\n```', phase: 'source' },

          // Streams+final: convert <execute>...</execute> into fenced python code BEFORE markdown-it
          { name: 'exec_html',  open: '<execute>',  close: '</execute>',  innerMode: 'text', stream: true,
            openReplace: '```python\n', closeReplace: '\n```', phase: 'source' }
        ]);
    }
  }

  // ==========================================================================
  // 1) DOM references
  // ==========================================================================

  class DOMRefs {
    constructor() { this.els = {}; this.domOutputStream = null; this.domStreamMsg = null; }
    // Cache frequently used elements by id.
    init() {
      const ids = [
        'container','_nodes_','_append_input_','_append_output_before_','_append_output_',
        '_append_live_','_footer_','_loader_','tips','scrollFab','scrollFabIcon'
      ];
      ids.forEach(id => { this.els[id] = document.getElementById(id); });
    }
    // Get element by id (reads cache first).
    get(id) { return this.els[id] || document.getElementById(id); }
    // Reset ephemeral pointers (used during stream).
    resetEphemeral() { this.domStreamMsg = null; }
    // Release refs and restore default scroll behavior.
    cleanup() {
      this.resetEphemeral(); this.domOutputStream = null; this.els = {};
      try { history.scrollRestoration = "auto"; } catch (_) {}
    }
    // Faster clear of a container by avoiding innerHTML='' (which is slow on large trees).
    fastClear(id) {
      const el = this.get(id);
      if (!el) return null;
      // Fast paths:
      if (el.firstChild) {
        // Prefer replaceChildren, fallback to textContent.
        if (el.replaceChildren) el.replaceChildren();
        else el.textContent = '';
      }
      return el;
    }

    // Clear and ensure paint on next frame (await before reading layout).
    async fastClearAndPaint(id) {
      const el = this.fastClear(id);
      if (!el) return null;
      // Yield to the next frame through the centralized RafManager to ensure repaint.
      try {
        if (typeof runtime !== 'undefined' && runtime.raf && typeof runtime.raf.nextFrame === 'function') {
          await runtime.raf.nextFrame();
        } else {
          await new Promise(r => setTimeout(r, 16));
        }
      } catch (_) {}
      return el;
    }

    // Hard clear by temporarily hiding element to avoid intermediate paints.
    fastClearHidden(id) {
      const el = this.get(id);
      if (!el) return null;
      const prevDisplay = el.style.display;
      el.style.display = 'none';       // pause paints
      if (el.replaceChildren) el.replaceChildren();
      else el.textContent = '';
      el.style.display = prevDisplay;  // resume paints
      return el;
    }
    // Replace element node by a shallow clone (drops children).
    hardReplaceByClone(id) {
      const el = this.get(id);
      if (!el || !el.parentNode) return null;
      const clone = el.cloneNode(false);
      try { el.replaceWith(clone); } catch (_) { el.innerHTML = ''; }
      this.els[id] = clone;
      if (id === '_append_output_') this.domOutputStream = clone;
      return clone;
    }
    // Clear streaming containers and transient state.
    hardResetStreamContainers() {
      this.resetEphemeral();
      this.domOutputStream = null;
      this.fastClearHidden('_append_output_before_');
      this.fastClearHidden('_append_output_');
    }
    // Return output stream container, caching reference.
    getStreamContainer() {
      if (this.domOutputStream && this.domOutputStream.isConnected) return this.domOutputStream;
      const el = this.get('_append_output_'); if (el) this.domOutputStream = el; return el;
    }
    // Get or create current streaming message container (.msg-box > .msg > .md-snapshot-root).
    getStreamMsg(create, name_header) {
      const container = this.getStreamContainer(); if (!container) return null;
      if (this.domStreamMsg && this.domStreamMsg.isConnected) return this.domStreamMsg;

      let box = container.querySelector('.msg-box'); let msg = null;
      if (!box && create) {
        box = document.createElement('div'); box.classList.add('msg-box', 'msg-bot');
        if (name_header) {
          const name = document.createElement('div'); name.classList.add('name-header','name-bot'); name.innerHTML = name_header; box.appendChild(name);
        }
        msg = document.createElement('div'); msg.classList.add('msg');
        const snap = document.createElement('div'); snap.className = 'md-snapshot-root';
        msg.appendChild(snap); box.appendChild(msg); container.appendChild(box);
      } else if (box) {
        msg = box.querySelector('.msg');
        if (msg && !msg.querySelector('.md-snapshot-root')) {
          const snap = document.createElement('div'); snap.className = 'md-snapshot-root'; msg.appendChild(snap);
        }
      }
      if (msg) this.domStreamMsg = msg;
      return msg;
    }
    // Clear the "before" area (older messages area).
    clearStreamBefore() {
      if (typeof window.hideTips === 'function') { window.hideTips(); }
      const el = this.fastClearHidden('_append_output_before_');
      if (el) { /* no-op */ }
    }
    // Clear output stream area.
    clearOutput() { this.hardResetStreamContainers(); }
    // Clear messages list and reset state.
    clearNodes() {
      this.clearStreamBefore();
      const el = this.fastClearHidden('_nodes_'); if (el) { el.classList.add('empty_list'); }
      this.resetEphemeral();
    }
    // Clear input area.
    clearInput() { this.fastClearHidden('_append_input_'); }
    // Clear live area and hide it.
    clearLive() {
      const el = this.fastClearHidden('_append_live_'); if (!el) return;
      el.classList.remove('visible');
      el.classList.add('hidden');
      this.resetEphemeral();
    }
  }

    // ==========================================================================
    // 2) Code scroll state manager
    // ==========================================================================

    class CodeScrollState {
      constructor(cfg, raf) {
        this.cfg = cfg;
        this.raf = raf;
        this.map = new WeakMap();
        this.rafMap = new WeakMap();
        this.rafIds = new Set(); // legacy
        this.rafKeyMap = new WeakMap();
      }
      // Get or create per-code element state.
      state(el) {
        let s = this.map.get(el);
        if (!s) { s = { autoFollow: false, lastScrollTop: 0, userInteracted: false, freezeUntil: 0 }; this.map.set(el, s); }
        return s;
      }
      // Check if code block is already finalized (not streaming).
      isFinalizedCode(el) {
        if (!el || el.tagName !== 'CODE') return false;
        if (el.dataset && el.dataset._active_stream === '1') return false;
        const highlighted = (el.getAttribute('data-highlighted') === 'yes') || el.classList.contains('hljs');
        return highlighted;
      }
      // Is element scrolled close to the bottom by a margin?
      isNearBottomEl(el, margin = 100) {
        if (!el) return true;
        const distance = el.scrollHeight - el.clientHeight - el.scrollTop;
        return distance <= margin;
      }
      // Scroll code element to the bottom respecting interaction state.
      scrollToBottom(el, live = false, force = false) {
        if (!el || !el.isConnected) return;
        if (!force && this.isFinalizedCode(el)) return;

        const st = this.state(el);
        const now = Utils.now();
        if (!force && st.freezeUntil && now < st.freezeUntil) return;

        const distNow = el.scrollHeight - el.clientHeight - el.scrollTop;
        if (!force && distNow <= 1) { st.lastScrollTop = el.scrollTop; return; }

        const marginPx = live ? 96 : this.cfg.CODE_SCROLL.NEAR_MARGIN_PX;
        const behavior = 'instant';

        if (!force) {
          if (live && st.autoFollow !== true) return;
          if (!live && !(st.autoFollow === true || this.isNearBottomEl(el, marginPx) || !st.userInteracted)) return;
        }

        try { el.scrollTo({ top: el.scrollHeight, behavior }); } catch (_) { el.scrollTop = el.scrollHeight; }
        st.lastScrollTop = el.scrollTop;
      }
      // Schedule bottom scroll in rAF (coalesces multiple calls).
      scheduleScroll(el, live = false, force = false) {
        if (!el || !el.isConnected) return;
        if (!force && this.isFinalizedCode(el)) return;
        if (this.rafMap.get(el)) return;
        this.rafMap.set(el, true);

        let key = this.rafKeyMap.get(el);
        if (!key) { key = { t: 'codeScroll', el }; this.rafKeyMap.set(el, key); }

        this.raf.schedule(key, () => {
          this.rafMap.delete(el);
          this.scrollToBottom(el, live, force);
        }, 'CodeScroll', 0);
      }
      // Attach scroll/wheel/touch handlers to manage auto-follow state.
      attachHandlers(codeEl) {
        if (!codeEl || codeEl.dataset.csListeners === '1') return;
        codeEl.dataset.csListeners = '1';
        const st = this.state(codeEl);

        const onScroll = (ev) => {
          const top = codeEl.scrollTop;
          const isUser = !!(ev && ev.isTrusted === true);
          const now = Utils.now();

          if (this.isFinalizedCode(codeEl)) {
            if (isUser) st.userInteracted = true;
            st.autoFollow = false;
            st.lastScrollTop = top;
            return;
          }

          if (isUser) {
            if (top + 1 < st.lastScrollTop) {
              st.autoFollow = false; st.userInteracted = true; st.freezeUntil = now + 1000;
            } else if (this.isNearBottomEl(codeEl, this.cfg.CODE_SCROLL.AUTO_FOLLOW_REENABLE_PX)) {
              st.autoFollow = true;
            }
          } else {
            if (this.isNearBottomEl(codeEl, this.cfg.CODE_SCROLL.AUTO_FOLLOW_REENABLE_PX)) st.autoFollow = true;
          }
          st.lastScrollTop = top;
        };

        const onWheel = (ev) => {
          st.userInteracted = true;
          const now = Utils.now();

          if (this.isFinalizedCode(codeEl)) { st.autoFollow = false; return; }

          if (ev.deltaY < 0) { st.autoFollow = false; st.freezeUntil = now + 1000; }
          else if (this.isNearBottomEl(codeEl, this.cfg.CODE_SCROLL.AUTO_FOLLOW_REENABLE_PX)) { st.autoFollow = true; }
        };

        codeEl.addEventListener('scroll', onScroll, { passive: true });
        codeEl.addEventListener('wheel', onWheel, { passive: true });
        codeEl.addEventListener('touchstart', function () { st.userInteracted = true; }, { passive: true });
      }
      // Attach handlers to all bot code blocks under root (or document).
      // IMPORTANT: We intentionally do NOT auto-scroll finalized/static code blocks to the bottom.
      // Only actively streaming code blocks (data-_active_stream="1") are auto-followed live.
      initScrollableBlocks(root) {
        const scope = root || document;
        let nodes = [];
        if (scope.nodeType === 1 && scope.closest && scope.closest('.msg-box.msg-bot')) {
          nodes = scope.querySelectorAll('pre code');
        } else {
          nodes = document.querySelectorAll('.msg-box.msg-bot pre code');
        }
        if (!nodes.length) return;

        nodes.forEach((code) => {
          this.attachHandlers(code);
          // Live streaming blocks: enable auto-follow and keep them glued to bottom.
          if (code.dataset._active_stream === '1') {
            const st = this.state(code);
            st.autoFollow = true;
            this.scheduleScroll(code, true, false);
          }
          // Finalized/static blocks: do nothing (no initial scroll-to-bottom).
          // This avoids surprising jumps when static content is rendered.
        });
      }
      // Transfer stored scroll state between elements (after replace).
      transfer(oldEl, newEl) {
        if (!oldEl || !newEl || oldEl === newEl) return;
        const oldState = this.map.get(oldEl);
        if (oldState) this.map.set(newEl, { ...oldState });
        this.attachHandlers(newEl);
      }
      // Cancel any scheduled scroll tasks for code blocks.
      cancelAllScrolls() {
        try { this.raf.cancelGroup('CodeScroll'); } catch (_) {}
        this.rafMap = new WeakMap();
        this.rafIds.clear();
      }
    }

  // ==========================================================================
  // 3) Highlighter (hljs) + rAF viewport scan
  // ==========================================================================

  class Highlighter {
    constructor(cfg, codeScroll, raf) {
      this.cfg = cfg;
      this.codeScroll = codeScroll;
      this.raf = raf;
      this.hlScheduled = false;
      this.hlQueue = [];
      this.hlQueueSet = new Set();
      this.scanScheduled = false;

      // Global scanning state for budgeted viewport scans (prevents long frames).
      this._globalScanState = null;
      // Budget per scan step (ms) – based on RAF budget hint with a small clamp.
      const hint = (cfg && cfg.RAF && cfg.RAF.FLUSH_BUDGET_MS) ? cfg.RAF.FLUSH_BUDGET_MS : 7;
      this.SCAN_STEP_BUDGET_MS = Math.max(3, Math.min(12, hint));
    }
    _decodeEntitiesDeep(text, maxPasses = 2) {
        if (!text || text.indexOf('&') === -1) return text || '';
        const ta = Highlighter._decTA || (Highlighter._decTA = document.createElement('textarea'));
        const decodeOnce = (s) => { ta.innerHTML = s; return ta.value; };
        let prev = String(text);
        let cur = decodeOnce(prev);
        let passes = 1;
        while (passes < maxPasses && cur !== prev) {
          prev = cur;
          cur = decodeOnce(prev);
          passes++;
        }
        return cur;
      }
    // Global switch to skip all highlighting.
    isDisabled() { return !!this.cfg.HL.DISABLE_ALL; }
    // Configure hljs once (safe if hljs not present).
    initHLJS() {
      if (this.isDisabled()) return;
      if (typeof hljs !== 'undefined' && hljs) { try { hljs.configure({ ignoreUnescapedHTML: true }); } catch (_) {} }
    }
    // Check if code is near viewport (with preload).
    _nearViewport(el) {
      const preload = this.cfg.SCAN.PRELOAD_PX;
      const vh = window.innerHeight || Utils.SE.clientHeight || 800;
      const r = el.getBoundingClientRect();
      return r.bottom >= -preload && r.top <= (vh + preload);
    }
    // Queue a code element for highlight; skip active streaming code and heavy-known cases.
    queue(codeEl, activeCode) {
      if (this.isDisabled()) return;
      if (!codeEl || !codeEl.isConnected) return;
      if (activeCode && codeEl === activeCode.codeEl) return;
      if (codeEl.getAttribute('data-highlighted') === 'yes') return;
      if (codeEl.dataset && (codeEl.dataset.hlStreamSuspended === '1' || codeEl.dataset.finalHlSkip === '1')) return; // skip heavy blocks intentionally left plain
      if (!codeEl.closest('.msg-box.msg-bot')) return;
      if (!this.hlQueueSet.has(codeEl)) { this.hlQueueSet.add(codeEl); this.hlQueue.push(codeEl); }
      if (!this.hlScheduled) {
        this.hlScheduled = true;
        this.raf.schedule('HL:flush', () => this.flush(activeCode), 'Highlighter', 1);
      }
    }
    // Process a small batch of code elements per frame.
    flush(activeCode) {
      if (this.isDisabled()) { this.hlScheduled = false; this.hlQueueSet.clear(); this.hlQueue.length = 0; return; }
      this.hlScheduled = false;
      let count = 0;
      while (this.hlQueue.length && count < this.cfg.HL.PER_FRAME) {
        const el = this.hlQueue.shift();
        if (el && el.isConnected) this.safeHighlight(el, activeCode);
        if (el) this.hlQueueSet.delete(el);
        count++;
        try {
          const sched = (navigator && navigator.scheduling && navigator.scheduling.isInputPending) ? navigator.scheduling : null;
          if (sched && sched.isInputPending({ includeContinuous: true })) {
            if (this.hlQueue.length) {
              this.hlScheduled = true;
              this.raf.schedule('HL:flush', () => this.flush(activeCode),  'Highlighter', 1);
            }
            return;
          }
        } catch (_) {}
      }
      if (this.hlQueue.length) {
        this.hlScheduled = true;
        this.raf.schedule('HL:flush', () => this.flush(activeCode), 'Highlighter', 1);
      }
    }
    // Highlight a single code block with safety checks and scroll preservation.
    _needsDeepDecode(text) {
        if (!text) return false;
        const s = String(text);
        return (s.indexOf('&amp;') !== -1) || (s.indexOf('&#') !== -1);
      }

      safeHighlight(codeEl, activeCode) {
        if (this.isDisabled()) return;
        if (!window.hljs || !codeEl || !codeEl.isConnected) return;
        if (!codeEl.closest('.msg-box.msg-bot')) return;
        if (codeEl.getAttribute('data-highlighted') === 'yes') return;
        if (activeCode && codeEl === activeCode.codeEl) return;

        // fast-skip final highlight for gigantic blocks using precomputed meta.
        try {
          const wrap = codeEl.closest('.code-wrapper');
          const maxLines = this.cfg.PROFILE_CODE.finalHighlightMaxLines | 0;
          const maxChars = this.cfg.PROFILE_CODE.finalHighlightMaxChars | 0;

          let lines = NaN, chars = NaN;
          if (wrap) {
            const nlAttr = wrap.getAttribute('data-code-nl');
            const lenAttr = wrap.getAttribute('data-code-len');
            if (nlAttr) lines = parseInt(nlAttr, 10);
            if (lenAttr) chars = parseInt(lenAttr, 10);
          }

          if ((Number.isFinite(lines) && maxLines > 0 && lines > maxLines) ||
              (Number.isFinite(chars) && maxChars > 0 && chars > maxChars)) {
            // NEW: normalize entities for readability even if we skip final highlight
            try {
              const raw = codeEl.textContent || '';
              if (this._needsDeepDecode(raw)) {
                const dec = this._decodeEntitiesDeep(raw);
                if (dec !== raw) codeEl.textContent = dec;
              }
            } catch (_) {}

            codeEl.classList.add('hljs');
            codeEl.setAttribute('data-highlighted', 'yes');
            codeEl.dataset.finalHlSkip = '1';
            try { this.codeScroll.attachHandlers(codeEl); } catch (_) {}
            this.codeScroll.scheduleScroll(codeEl, false, false);
            return;
          }

          // Fallback to reading actual text only if wrapper meta is missing.
          if (!Number.isFinite(lines) || !Number.isFinite(chars)) {
            const txt0 = codeEl.textContent || '';
            const ln0 = Utils.countNewlines(txt0);
            if ((maxLines > 0 && ln0 > maxLines) || (maxChars > 0 && txt0.length > maxChars)) {
              // NEW: normalize entities here as well
              try {
                if (this._needsDeepDecode(txt0)) {
                  const dec = this._decodeEntitiesDeep(txt0);
                  if (dec !== txt0) codeEl.textContent = dec;
                }
              } catch (_) {}

              codeEl.classList.add('hljs');
              codeEl.setAttribute('data-highlighted', 'yes');
              codeEl.dataset.finalHlSkip = '1';
              try { this.codeScroll.attachHandlers(codeEl); } catch (_) {}
              this.codeScroll.scheduleScroll(codeEl, false, false);
              return;
            }
          }
        } catch (_) { /* safe fallback */ }

        const wasNearBottom = this.codeScroll.isNearBottomEl(codeEl, 16);
        const st = this.codeScroll.state(codeEl);
        const shouldAutoScrollAfter = (st.autoFollow === true) || wasNearBottom;

        try {
          try { codeEl.classList.remove('hljs'); codeEl.removeAttribute('data-highlighted'); } catch (_) {}

          // NEW: deep-decode text before highlighting (fixes &amp;#x27; → ' etc.)
          let txt = codeEl.textContent || '';
          if (this._needsDeepDecode(txt)) {
            try { txt = this._decodeEntitiesDeep(txt); } catch (_) {}
          }
          codeEl.textContent = txt; // ensure no stale spans remain and normalized text provided

          hljs.highlightElement(codeEl);
          codeEl.setAttribute('data-highlighted', 'yes');
        } catch (_) {
          if (!codeEl.classList.contains('hljs')) codeEl.classList.add('hljs');
        } finally {
          try { this.codeScroll.attachHandlers(codeEl); } catch (_) {}
          const needInitForce = (codeEl.dataset && (codeEl.dataset.csInitBtm === '1' || codeEl.dataset.justFinalized === '1'));
          const mustScroll = shouldAutoScrollAfter || needInitForce;
          if (mustScroll) this.codeScroll.scheduleScroll(codeEl, false, !!needInitForce);
          if (codeEl.dataset) {
            if (codeEl.dataset.csInitBtm === '1') codeEl.dataset.csInitBtm = '0';
            if (codeEl.dataset.justFinalized === '1') codeEl.dataset.justFinalized = '0';
          }
        }
      }

    // Start a budgeted global scan – split across frames to avoid long blocking.
    _startGlobalScan(activeCode) {
      if (this.isDisabled()) return;
      const preload = this.cfg.SCAN_PRELOAD_PX || this.cfg.SCAN.PRELOAD_PX;
      const vh = window.innerHeight || Utils.SE.clientHeight || 800;
      const rectTop = 0 - preload, rectBottom = vh + preload;
      const nodes = Array.from(document.querySelectorAll('.msg-box.msg-bot pre code:not([data-highlighted="yes"])'));
      this._globalScanState = { nodes, idx: 0, rectTop, rectBottom, activeCode };
      this._scanGlobalStep();
    }
    // Continue global scan for visible code elements under a time budget.
    _scanGlobalStep() {
      const state = this._globalScanState;
      if (!state || !state.nodes || state.idx >= state.nodes.length) { this._globalScanState = null; return; }
      const start = Utils.now();
      while (state.idx < state.nodes.length) {
        const code = state.nodes[state.idx++];
        if (!code || !code.isConnected) continue;
        if (state.activeCode && code === state.activeCode.codeEl) continue;
        try {
          const r = code.getBoundingClientRect();
          if (r.bottom >= state.rectTop && r.top <= state.rectBottom) this.queue(code, state.activeCode);
        } catch (_) {}
        if ((Utils.now() - start) >= this.SCAN_STEP_BUDGET_MS) {
          // Schedule next slice to keep UI responsive.
          this.raf.schedule('HL:scanStep', () => this._scanGlobalStep(), 'Highlighter', 2);
          return;
        }
      }
      this._globalScanState = null;
    }

    // Observe new code blocks and queue those near the viewport.
    observeNewCode(root, opts, activeCode) {
      const scope = root || document;
      let nodes;
      if (scope.nodeType === 1 && scope.closest && scope.closest('.msg-box.msg-bot')) nodes = scope.querySelectorAll('pre code');
      else nodes = document.querySelectorAll('.msg-box.msg-bot pre code');
      if (!nodes || !nodes.length) return;

      const options = Object.assign({ deferLastIfStreaming: false, minLinesForLast: 2, minCharsForLast: 120 }, (opts || {}));
      nodes.forEach((code) => {
        if (!code.closest('.msg-box.msg-bot')) return;
        this.codeScroll.attachHandlers(code);
        if (this.isDisabled()) return;
        if (activeCode && code === activeCode.codeEl) return;

        if (options.deferLastIfStreaming && activeCode && code === activeCode.codeEl) {
          const tailLen = (activeCode.tailEl && activeCode.tailEl.textContent) ? activeCode.tailEl.textContent.length : 0;
          const tailLines = (typeof activeCode.tailLines === 'number') ? activeCode.tailLines : 0;
          if (tailLines < options.minLinesForLast && tailLen < options.minCharsForLast) return;
        }
        if (this._nearViewport(code)) this.queue(code, activeCode);
      });
    }
    // Schedule a viewport scan in a budgeted way.
    scheduleScanVisibleCodes(activeCode) {
      if (this.isDisabled()) return;

      // Fast bail-out: nothing to highlight and no active streaming code.
      try {
        const anyCandidate = document.querySelector('.msg-box.msg-bot pre code:not([data-highlighted="yes"])');
        const hasActive = !!(activeCode && activeCode.codeEl && activeCode.codeEl.isConnected);
        if (!anyCandidate && !hasActive) return;
      } catch (_) { /* safe fallback */ }

      // If a scan is already in progress, just ensure next step is scheduled; otherwise schedule start.
      if (this._globalScanState) {
        this.raf.schedule('HL:scanStep', () => this._scanGlobalStep(), 'Highlighter', 2);
        return;
      }
      if (this.scanScheduled) return;
      this.scanScheduled = true;
      this.raf.schedule('HL:scan', () => {
        this.scanScheduled = false;
        this._startGlobalScan(activeCode || null);
      }, 'Highlighter', 2);
    }

    // Direct scan (synchronous) – used in places where root scope is small.
    scanVisibleCodes(activeCode) {
      this._startGlobalScan(activeCode || null);
    }
    // Scan only inside a given root (synchronous, small scopes).
    scanVisibleCodesInRoot(root, activeCode) {
      if (this.isDisabled()) return;
      const preload = this.cfg.SCAN_PRELOAD_PX || this.cfg.SCAN.PRELOAD_PX;
      const vh = window.innerHeight || Utils.SE.clientHeight || 800;
      const rectTop = 0 - preload, rectBottom = vh + preload;
      const scope = root || document;
      const nodes = scope.querySelectorAll('.msg-box.msg-bot pre code:not([data-highlighted="yes"])');
      // Local root scans tend to be small – keep them synchronous for simplicity.
      nodes.forEach((code) => {
        if (!code.isConnected) return;
        if (activeCode && code === activeCode.codeEl) return;
        const r = code.getBoundingClientRect();
        if (r.bottom >= rectTop && r.top <= rectBottom) this.queue(code, activeCode);
      });
    }
    installBoxObserver() { /* no-op */ }
    // Visit bot message boxes and call callback (used for local scans).
    observeMsgBoxes(root, onBoxIntersect) {
      const scope = root || document;
      let boxes;
      if (scope.nodeType === 1) boxes = scope.querySelectorAll('.msg-box.msg-bot');
      else boxes = document.querySelectorAll('.msg-box.msg-bot');
      boxes.forEach((box) => { onBoxIntersect && onBoxIntersect(box); });
    }
    // Clear all internal queues and scheduled jobs.
    cleanup() {
      try { this.raf.cancelGroup('Highlighter'); } catch (_) {}
      this.hlScheduled = false;
      this.scanScheduled = false;
      this._globalScanState = null;
      this.hlQueueSet.clear(); this.hlQueue.length = 0;
    }
  }

     // ==========================================================================
     // 4) Custom Markup Processor
     // ==========================================================================

     class CustomMarkup {
       constructor(cfg, logger) {
         this.cfg = cfg || { CUSTOM_MARKUP_RULES: [] };
         this.logger = logger || new Logger(cfg);
         this.__compiled = null;
         this.__hasStreamRules = false; // Fast flag to skip stream work if not needed
       }
       _d(line, ctx) { try { this.logger.debug('CM', line, ctx); } catch (_) {} }

       // Decode HTML entities once (safe)
       // This addresses cases when linkify/full markdown path leaves literal "&quot;" etc. in text nodes.
       // We decode only for rules that explicitly opt-in (see compile()) to avoid changing semantics globally.
       decodeEntitiesOnce(s) {
         if (!s || s.indexOf('&') === -1) return String(s || '');
         const ta = CustomMarkup._decTA || (CustomMarkup._decTA = document.createElement('textarea'));
         ta.innerHTML = s;
         return ta.value;
       }

       // Small helper: escape text to safe HTML (shared Utils or fallback)
       _escHtml(s) {
         try { return Utils.escapeHtml(s); } catch (_) {
           return String(s || '').replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[m]));
         }
       }

       // quick check if any rule's open token is present in text (used to skip expensive work early)
       hasAnyOpenToken(text, rules) {
         if (!text || !rules || !rules.length) return false;
         for (let i = 0; i < rules.length; i++) {
           const r = rules[i];
           if (!r || !r.open) continue;
           if (text.indexOf(r.open) !== -1) return true;
         }
         return false;
       }

       // Build inner HTML from text according to rule's mode (markdown-inline | text) with optional entity decode.
       _materializeInnerHTML(rule, text, MD) {
         let payload = String(text || '');
         if (rule && rule.decodeEntities && payload && payload.indexOf('&') !== -1) {
           try { payload = this.decodeEntitiesOnce(payload); } catch (_) { /* keep original */ }
         }
         if (rule && rule.innerMode === 'markdown-inline' && MD && typeof MD.renderInline === 'function') {
           try { return MD.renderInline(payload); } catch (_) { return this._escHtml(payload); }
         }
         return this._escHtml(payload);
       }

       // Make a DOM Fragment from HTML string (robust across contexts).
       _fragmentFromHTML(html, ctxNode) {
         let frag = null;
         try {
           const range = document.createRange();
           const ctx = (ctxNode && ctxNode.parentNode) ? ctxNode.parentNode : (document.body || document.documentElement);
           range.selectNode(ctx);
           frag = range.createContextualFragment(String(html || ''));
           return frag;
         } catch (_) {
           const tmp = document.createElement('div');
           tmp.innerHTML = String(html || '');
           frag = document.createDocumentFragment();
           while (tmp.firstChild) frag.appendChild(tmp.firstChild);
           return frag;
         }
       }

       // Replace one element in DOM with HTML string (keeps siblings intact).
       _replaceElementWithHTML(el, html) {
         if (!el || !el.parentNode) return;
         const parent = el.parentNode;
         const frag = this._fragmentFromHTML(html, el);
         try {
           // Insert new nodes before the old element, then remove the old element (widely supported).
           parent.insertBefore(frag, el);
           parent.removeChild(el);
         } catch (_) {
           // Conservative fallback: wrap in a span if direct fragment insertion failed for some reason.
           const tmp = document.createElement('span');
           tmp.innerHTML = String(html || '');
           while (tmp.firstChild) parent.insertBefore(tmp.firstChild, el);
           parent.removeChild(el);
         }
       }

       // Compile rules once; also precompile strict and whitespace-tolerant "full match" regexes.
       compile(rules) {
         const src = Array.isArray(rules) ? rules : (window.CUSTOM_MARKUP_RULES || this.cfg.CUSTOM_MARKUP_RULES || []);
         const compiled = [];
         let hasStream = false;

         for (const r of src) {
           if (!r || typeof r.open !== 'string' || typeof r.close !== 'string') continue;

           const tag = (r.tag || 'span').toLowerCase();
           const className = (r.className || r.class || '').trim();
           const innerMode = (r.innerMode === 'markdown-inline' || r.innerMode === 'text') ? r.innerMode : 'text';

           const stream = !!(r.stream === true);
           const openReplace = String((r.openReplace != null ? r.openReplace : (r.openReplace || '')) || '');
           const closeReplace = String((r.closeReplace != null ? r.closeReplace : (r.closeReplace || '')) || '');

           // Back-compat: decode entities default true for cmd-like
           const decodeEntities = (typeof r.decodeEntities === 'boolean')
             ? r.decodeEntities
             : ((r.name || '').toLowerCase() === 'cmd' || className === 'cmd');

           // Optional application phase (where replacement should happen)
           // - 'source' => before markdown-it
           // - 'html'   => after markdown-it (DOM fragment)
           // - 'both'
           let phaseRaw = (typeof r.phase === 'string') ? r.phase.toLowerCase() : '';
           if (phaseRaw !== 'source' && phaseRaw !== 'html' && phaseRaw !== 'both') phaseRaw = '';
           // Heuristic: if replacement contains fenced code backticks, default to 'source'
           const looksLikeFence = (openReplace.indexOf('```') !== -1) || (closeReplace.indexOf('```') !== -1);
           const phase = phaseRaw || (looksLikeFence ? 'source' : 'html');

           const re = new RegExp(Utils.reEscape(r.open) + '([\\s\\S]*?)' + Utils.reEscape(r.close), 'g');
           const reFull = new RegExp('^' + Utils.reEscape(r.open) + '([\\s\\S]*?)' + Utils.reEscape(r.close) + '$');
           const reFullTrim = new RegExp('^\\s*' + Utils.reEscape(r.open) + '([\\s\\S]*?)' + Utils.reEscape(r.close) + '\\s*$');

           const item = {
             name: r.name || tag,
             tag, className, innerMode,
             open: r.open, close: r.close,
             decodeEntities,
             re, reFull, reFullTrim,
             stream,
             openReplace, closeReplace,
             phase,                 // NEW: where this rule should be applied
             isSourceFence: looksLikeFence // NEW: hints StreamEngine to treat as custom fence
           };
           compiled.push(item);
           if (stream) hasStream = true;
           this._d('COMPILE_RULE', { name: item.name, phase: item.phase, stream: item.stream });
         }

         if (compiled.length === 0) {
           const open = '[!cmd]', close = '[/!cmd]';
           const item = {
             name: 'cmd', tag: 'p', className: 'cmd', innerMode: 'text', open, close,
             decodeEntities: true,
             re: new RegExp(Utils.reEscape(open) + '([\\s\\S]*?)' + Utils.reEscape(close), 'g'),
             reFull: new RegExp('^' + Utils.reEscape(open) + '([\\s\\S]*?)' + Utils.reEscape(close) + '$'),
             reFullTrim: new RegExp('^\\s*' + Utils.reEscape(open) + '([\\s\\S]*?)' + Utils.reEscape(close) + '\\s*$'),
             stream: false,
             openReplace: '', closeReplace: '',
             phase: 'html', isSourceFence: false
           };
           compiled.push(item);
           this._d('COMPILE_RULE_FALLBACK', { name: item.name });
         }

         this.__hasStreamRules = hasStream;
         return compiled;
       }

       // pre-markdown source transformer – applies only rules for 'source'/'both' with replacements
       // IMPORTANT CHANGE:
       // - Skips replacements inside fenced code blocks (``` / ~~~).
       // - Applies only when the rule opener is at top-level of the line (no list markers/blockquote).
       transformSource(src, opts) {
         let s = String(src || '');
         this.ensureCompiled();
         const rules = this.__compiled;
         if (!rules || !rules.length) return s;

         // Pick only source-phase rules with explicit replacements
         const candidates = [];
         for (let i = 0; i < rules.length; i++) {
           const r = rules[i];
           if (!r) continue;
           if ((r.phase === 'source' || r.phase === 'both') && (r.openReplace || r.closeReplace)) candidates.push(r);
         }
         if (!candidates.length) return s;

         // Compute fenced-code ranges once to exclude them from replacements (production-safe).
         const fences = this._findFenceRanges(s);
         if (!fences.length) {
           // No code fences in source; apply top-level guarded replacements globally.
           return this._applySourceReplacementsInChunk(s, s, 0, candidates);
         }

         // Apply replacements only in segments outside fenced code.
         let out = '';
         let last = 0;
         for (let k = 0; k < fences.length; k++) {
           const [a, b] = fences[k];
           if (a > last) {
             const chunk = s.slice(last, a);
             out += this._applySourceReplacementsInChunk(s, chunk, last, candidates);
           }
           out += s.slice(a, b); // pass fenced code verbatim
           last = b;
         }
         if (last < s.length) {
           const tail = s.slice(last);
           out += this._applySourceReplacementsInChunk(s, tail, last, candidates);
         }
         return out;
       }

       // expose custom fence specs (to StreamEngine)
       getSourceFenceSpecs() {
         this.ensureCompiled();
         const rules = this.__compiled || [];
         const out = [];
         for (let i = 0; i < rules.length; i++) {
           const r = rules[i];
           if (!r || !r.isSourceFence) continue;
           // Only expose when they actually look like fences in source phase
           if (r.phase !== 'source' && r.phase !== 'both') continue;
           out.push({ open: r.open, close: r.close });
         }
         return out;
       }

       // Ensure rules are compiled and cached.
       ensureCompiled() {
         if (!this.__compiled) {
           this.__compiled = this.compile(window.CUSTOM_MARKUP_RULES || this.cfg.CUSTOM_MARKUP_RULES);
           this._d('ENSURE_COMPILED', { count: this.__compiled.length, hasStream: this.__hasStreamRules });
         }
         return this.__compiled;
       }

       // Replace rules set (also exposes rules on window).
       setRules(rules) {
         this.__compiled = this.compile(rules);
         window.CUSTOM_MARKUP_RULES = Array.isArray(rules) ? rules.slice() : (this.cfg.CUSTOM_MARKUP_RULES || []).slice();
         this._d('SET_RULES', { count: this.__compiled.length, hasStream: this.__hasStreamRules });
       }

       // Return current rules as array.
       getRules() {
         const list = (window.CUSTOM_MARKUP_RULES ? window.CUSTOM_MARKUP_RULES.slice()
                                                  : (this.cfg.CUSTOM_MARKUP_RULES || []).slice());
         this._d('GET_RULES', { count: list.length });
         return list;
       }

       // Fast switch: do we have any rules that want streaming parsing?
       hasStreamRules() {
         this.ensureCompiled();
         return !!this.__hasStreamRules;
       }

       // Context guards
       isInsideForbiddenContext(node) {
         const p = node.parentElement; if (!p) return true;
         // IMPORTANT: exclude code/math/hljs/wrappers AND list contexts (ul/ol/li/dl/dt/dd)
         return !!p.closest('pre, code, kbd, samp, var, script, style, textarea, .math-pending, .hljs, .code-wrapper, ul, ol, li, dl, dt, dd');
       }
       isInsideForbiddenElement(el) {
         if (!el) return true;
         // IMPORTANT: exclude code/math/hljs/wrappers AND list contexts (ul/ol/li/dl/dt/dd)
         return !!el.closest('pre, code, kbd, samp, var, script, style, textarea, .math-pending, .hljs, .code-wrapper, ul, ol, li, dl, dt, dd');
       }

       // Global finder on a single text blob (original per-text-node logic).
       findNextMatch(text, from, rules) {
         let best = null;
         for (const rule of rules) {
           rule.re.lastIndex = from;
           const m = rule.re.exec(text);
           if (m) {
             const start = m.index, end = rule.re.lastIndex;
             if (!best || start < best.start) best = { rule, start, end, inner: m[1] || '' };
           }
         }
         return best;
       }

       // Strict full match of a pure text node (legacy path).
       findFullMatch(text, rules) {
         for (const rule of rules) {
           if (rule.reFull) {
             const m = rule.reFull.exec(text);
             if (m) return { rule, inner: m[1] || '' };
           } else {
             rule.re.lastIndex = 0;
             const m = rule.re.exec(text);
             if (m && m.index === 0 && (rule.re.lastIndex === text.length)) {
               const m2 = rule.re.exec(text);
               if (!m2) return { rule, inner: m[1] || '' };
             }
           }
         }
         return null;
       }

       // Set inner content according to the rule's mode, with optional entity decode (element mode).
       setInnerByMode(el, mode, text, MD, decodeEntities = false) {
         let payload = String(text || '');
         if (decodeEntities && payload && payload.indexOf('&') !== -1) {
           try { payload = this.decodeEntitiesOnce(payload); } catch (_) {}
         }

         if (mode === 'markdown-inline' && typeof window.markdownit !== 'undefined') {
           try {
             if (MD && typeof MD.renderInline === 'function') { el.innerHTML = MD.renderInline(payload); return; }
             const tempMD = window.markdownit({ html: false, linkify: true, breaks: true, highlight: () => '' });
             el.innerHTML = tempMD.renderInline(payload); return;
           } catch (_) {}
         }
         el.textContent = payload;
       }

       // Try to replace an entire <p> that is a full custom markup match.
       _tryReplaceFullParagraph(el, rules, MD) {
         if (!el || el.tagName !== 'P') return false;
         if (this.isInsideForbiddenElement(el)) {
           this._d('P_SKIP_FORBIDDEN', { tag: el.tagName });
           return false;
         }
         const t = el.textContent || '';
         if (!this.hasAnyOpenToken(t, rules)) return false;

         for (const rule of rules) {
           if (!rule) continue;
           const m = rule.reFullTrim ? rule.reFullTrim.exec(t) : null;
           if (!m) continue;

           const innerText = m[1] || '';

           if (rule.phase !== 'html' && rule.phase !== 'both') continue; // element materialization is html-phase only

           if (rule.openReplace || rule.closeReplace) {
             const innerHTML = this._materializeInnerHTML(rule, innerText, MD);
             const html = String(rule.openReplace || '') + innerHTML + String(rule.closeReplace || '');
             this._replaceElementWithHTML(el, html);
             this._d('P_REPLACED_AS_HTML', { rule: rule.name });
             return true;
           }

           const outTag = (rule.tag && typeof rule.tag === 'string') ? rule.tag.toLowerCase() : 'span';
           const out = document.createElement(outTag === 'p' ? 'p' : outTag);
           if (rule.className) out.className = rule.className;
           out.setAttribute('data-cm', rule.name);
           this.setInnerByMode(out, rule.innerMode, innerText, MD, !!rule.decodeEntities);

           try { el.replaceWith(out); } catch (_) {
             const par = el.parentNode; if (par) par.replaceChild(out, el);
           }
           this._d('P_REPLACED', { rule: rule.name, asTag: outTag });
           return true;
         }
         this._d('P_NO_FULL_MATCH', { preview: this.logger.pv(t, 160) });
         return false;
       }

       // Core implementation shared by static and streaming passes.
       applyRules(root, MD, rules) {
         if (!root || !rules || !rules.length) return;

         const scope = (root.nodeType === 1 || root.nodeType === 11) ? root : document;

         // Phase 1: tolerant <p> replacements
         try {
           const paragraphs = (typeof scope.querySelectorAll === 'function') ? scope.querySelectorAll('p') : [];
           this._d('P_TOLERANT_SCAN_START', { count: paragraphs.length });

           if (paragraphs && paragraphs.length) {
             for (let i = 0; i < paragraphs.length; i++) {
               const p = paragraphs[i];
               if (p && p.getAttribute && p.getAttribute('data-cm')) continue;
               const tc = p && (p.textContent || '');
               if (!tc || !this.hasAnyOpenToken(tc, rules)) continue;
               // Skip paragraphs inside forbidden contexts (includes lists now)
               if (this.isInsideForbiddenElement(p)) continue;
               this._tryReplaceFullParagraph(p, rules, MD);
             }
           }
         } catch (e) {
           this._d('P_TOLERANT_SCAN_ERR', String(e));
         }

         // Phase 2: legacy per-text-node pass for partial inline cases.
         const self = this;
         const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
           acceptNode: (node) => {
             const val = node && node.nodeValue ? node.nodeValue : '';
             if (!val || !self.hasAnyOpenToken(val, rules)) return NodeFilter.FILTER_SKIP;
             if (self.isInsideForbiddenContext(node)) return NodeFilter.FILTER_REJECT;
             return NodeFilter.FILTER_ACCEPT;
           }
         });

         let node;
         while ((node = walker.nextNode())) {
           const text = node.nodeValue;
           if (!text || !this.hasAnyOpenToken(text, rules)) continue; // quick skip
           const parent = node.parentElement;

           // Entire text node equals one full match and parent is <p>.
           if (parent && parent.tagName === 'P' && parent.childNodes.length === 1) {
             const fm = this.findFullMatch(text, rules);
             if (fm) {
               // If explicit HTML replacements are provided, swap <p> for exact HTML (only for html/both phase).
               if ((fm.rule.phase === 'html' || fm.rule.phase === 'both') && (fm.rule.openReplace || fm.rule.closeReplace)) {
                 const innerHTML = this._materializeInnerHTML(fm.rule, fm.inner, MD);
                 const html = String(fm.rule.openReplace || '') + innerHTML + String(fm.rule.closeReplace || '');
                 this._replaceElementWithHTML(parent, html);
                 this._d('WALKER_FULL_REPLACE_HTML', { rule: fm.rule.name, preview: this.logger.pv(text, 160) });
                 continue;
               }

               // Backward-compatible: only replace as <p> when rule tag is 'p'
               if (fm.rule.tag === 'p') {
                 const out = document.createElement('p');
                 if (fm.rule.className) out.className = fm.rule.className;
                 out.setAttribute('data-cm', fm.rule.name);
                 this.setInnerByMode(out, fm.rule.innerMode, fm.inner, MD, !!fm.rule.decodeEntities);
                 try { parent.replaceWith(out); } catch (_) {
                   const par = parent.parentNode; if (par) par.replaceChild(out, parent);
                 }
                 this._d('WALKER_FULL_REPLACE', { rule: fm.rule.name, preview: this.logger.pv(text, 160) });
                 continue;
               }
             }
           }

           // General inline replacement inside the text node (span-like or HTML-replace).
           let i = 0;
           let didReplace = false;
           const frag = document.createDocumentFragment();

           while (i < text.length) {
             const m = this.findNextMatch(text, i, rules);
             if (!m) break;

             if (m.start > i) {
               frag.appendChild(document.createTextNode(text.slice(i, m.start)));
             }

             // If HTML replacements are provided, build exact HTML around processed inner – only for html/both phase.
             if ((m.rule.openReplace || m.rule.closeReplace) && (m.rule.phase === 'html' || m.rule.phase === 'both')) {
               const innerHTML = this._materializeInnerHTML(m.rule, m.inner, MD);
               const html = String(m.rule.openReplace || '') + innerHTML + String(m.rule.closeReplace || '');
               const part = this._fragmentFromHTML(html, node);
               frag.appendChild(part);
               this._d('WALKER_INLINE_MATCH_HTML', { rule: m.rule.name, start: m.start, end: m.end });
               i = m.end; didReplace = true; continue;
             }

             // If rule is not html-phase, do NOT inject open/close replacements here (source-only rules are handled pre-md).
             if (m.rule.openReplace || m.rule.closeReplace) {
               // Source-only replacement met in DOM pass – keep original text verbatim for this match.
               frag.appendChild(document.createTextNode(text.slice(m.start, m.end)));
               this._d('WALKER_INLINE_SKIP_SOURCE_PHASE_HTML', { rule: m.rule.name, start: m.start, end: m.end });
               i = m.end; didReplace = true; continue;
             }

             // Element-based inline replacement (original behavior).
             const tag = (m.rule.tag === 'p') ? 'span' : m.rule.tag;
             const el = document.createElement(tag);
             if (m.rule.className) el.className = m.rule.className;
             el.setAttribute('data-cm', m.rule.name);
             this.setInnerByMode(el, m.rule.innerMode, m.inner, MD, !!m.rule.decodeEntities);
             frag.appendChild(el);
             this._d('WALKER_INLINE_MATCH', { rule: m.rule.name, start: m.start, end: m.end });

             i = m.end;
             didReplace = true;
           }

           if (!didReplace) continue;

           if (i < text.length) {
             frag.appendChild(document.createTextNode(text.slice(i)));
           }

           const parentNode = node.parentNode;
           if (parentNode) {
             parentNode.replaceChild(frag, node);
             this._d('WALKER_INLINE_DONE', { preview: this.logger.pv(text, 120) });
           }
         }
       }

       // Public API: apply custom markup for full (static) paths – unchanged behavior.
       apply(root, MD) {
         this.ensureCompiled();
         this.applyRules(root, MD, this.__compiled);
       }

       // Public API: apply only stream-enabled rules (used in snapshots).
       applyStream(root, MD) {
         this.ensureCompiled();
         if (!this.__hasStreamRules) return;
         const rules = this.__compiled.filter(r => !!r.stream);
         if (!rules.length) return;
         this.applyRules(root, MD, rules);
       }

       // -----------------------------
       // INTERNAL HELPERS (NEW)
       // -----------------------------

       // Scan source and return ranges [start, end) of fenced code blocks (``` or ~~~).
       // Matches Markdown fences at line-start with up to 3 spaces/tabs indentation.
       _findFenceRanges(s) {
         const ranges = [];
         const n = s.length;
         let i = 0;
         let inFence = false;
         let fenceMark = '';
         let fenceLen = 0;
         let startLineStart = 0;

         while (i < n) {
           const lineStart = i;
           // Find line end and newline length
           let j = lineStart;
           while (j < n && s.charCodeAt(j) !== 10 && s.charCodeAt(j) !== 13) j++;
           const lineEnd = j;
           let nl = 0;
           if (j < n) {
             if (s.charCodeAt(j) === 13 && j + 1 < n && s.charCodeAt(j + 1) === 10) nl = 2;
             else nl = 1;
           }

           // Compute indentation up to 3 "spaces" (tabs count as 1 here – safe heuristic)
           let k = lineStart;
           let indent = 0;
           while (k < lineEnd) {
             const c = s.charCodeAt(k);
             if (c === 32 /* space */) { indent++; if (indent > 3) break; k++; }
             else if (c === 9 /* tab */) { indent++; if (indent > 3) break; k++; }
             else break;
           }

           if (!inFence) {
             if (indent <= 3 && k < lineEnd) {
               const ch = s.charCodeAt(k);
               if (ch === 0x60 /* ` */ || ch === 0x7E /* ~ */) {
                 const mark = String.fromCharCode(ch);
                 let m = k;
                 while (m < lineEnd && s.charCodeAt(m) === ch) m++;
                 const run = m - k;
                 if (run >= 3) {
                   inFence = true;
                   fenceMark = mark;
                   fenceLen = run;
                   startLineStart = lineStart;
                 }
               }
             }
           } else {
             if (indent <= 3 && k < lineEnd && s.charCodeAt(k) === fenceMark.charCodeAt(0)) {
               let m = k;
               while (m < lineEnd && s.charCodeAt(m) === fenceMark.charCodeAt(0)) m++;
               const run = m - k;
               if (run >= fenceLen) {
                 // Only whitespace is allowed after closing fence on the same line
                 let onlyWS = true;
                 for (let t = m; t < lineEnd; t++) {
                   const cc = s.charCodeAt(t);
                   if (cc !== 32 && cc !== 9) { onlyWS = false; break; }
                 }
                 if (onlyWS) {
                   const endIdx = lineEnd + nl; // include trailing newline if present
                   ranges.push([startLineStart, endIdx]);
                   inFence = false; fenceMark = ''; fenceLen = 0; startLineStart = 0;
                 }
               }
             }
           }
           i = lineEnd + nl;
         }

         // If EOF while still in fence, mark until end of string.
         if (inFence) ranges.push([startLineStart, n]);
         return ranges;
       }

       // Check if match starts at "top-level" of a line:
       // - up to 3 leading spaces/tabs allowed
       // - not a list item marker ("- ", "+ ", "* ", "1. ", "1) ") and not a blockquote ("> ")
       // - nothing else precedes the token on the same line
       _isTopLevelLineInSource(s, absIdx) {
         let ls = absIdx;
         while (ls > 0) {
           const ch = s.charCodeAt(ls - 1);
           if (ch === 10 /* \n */ || ch === 13 /* \r */) break;
           ls--;
         }
         const prefix = s.slice(ls, absIdx);

         // Strip up to 3 leading "spaces" (tabs treated as 1 – acceptable heuristic)
         let i = 0, indent = 0;
         while (i < prefix.length) {
           const c = prefix.charCodeAt(i);
           if (c === 32) { indent++; if (indent > 3) break; i++; }
           else if (c === 9) { indent++; if (indent > 3) break; i++; }
           else break;
         }
         if (indent > 3) return false;
         const rest = prefix.slice(i);

         // Reject lists/blockquote
         if (/^>\s?/.test(rest)) return false;
         if (/^[-+*]\s/.test(rest)) return false;
         if (/^\d+[.)]\s/.test(rest)) return false;

         // If any other non-whitespace text precedes the token on this line – not top-level
         if (rest.trim().length > 0) return false;

         return true;
       }

       // Apply source-phase replacements to one outside-of-fence chunk with top-level guard.
       _applySourceReplacementsInChunk(full, chunk, baseOffset, rules) {
         let t = chunk;
         for (let i = 0; i < rules.length; i++) {
           const r = rules[i];
           if (!r || !(r.openReplace || r.closeReplace)) continue;
           try {
             r.re.lastIndex = 0;
             t = t.replace(r.re, (match, inner, offset /*, ...rest*/) => {
               const abs = baseOffset + (offset | 0);
               // Only apply when opener is at top-level on that line (not in lists/blockquote)
               if (!this._isTopLevelLineInSource(full, abs)) return match;
               const open = r.openReplace || '';
               const close = r.closeReplace || '';
               return open + (inner || '') + close;
             });
           } catch (_) { /* keep chunk as is on any error */ }
         }
         return t;
       }
     }

  // ==========================================================================
  // 5) Markdown runtime (markdown-it + code wrapper + math placeholders)
  // ==========================================================================

  class MarkdownRenderer {
      constructor(cfg, customMarkup, logger, asyncer, raf) {
        this.cfg = cfg; this.customMarkup = customMarkup; this.MD = null;
        this.logger = logger || new Logger(cfg);
        // Cooperative async utilities available in renderer for heavy decode/render paths
        this.asyncer = asyncer || new AsyncRunner(cfg, raf);
        this.raf = raf || null;

        // Fast-path streaming renderer without linkify to reduce regex work on hot path.
        this.MD_STREAM = null;

        this.hooks = {
          observeNewCode: () => {},
          observeMsgBoxes: () => {},
          scheduleMathRender: () => {},
          codeScrollInit: () => {}
        };
      }
      // Initialize markdown-it instances and plugins.
      init() {
        if (!window.markdownit) { this.logger.log('[MD] markdown-it not found – rendering skipped.'); return; }
        // Full renderer (used for non-hot paths, final results)
        this.MD = window.markdownit({ html: false, linkify: true, breaks: true, highlight: () => '' });
        // Streaming renderer (no linkify) – hot path
        this.MD_STREAM = window.markdownit({ html: false, linkify: false, breaks: true, highlight: () => '' });

        // SAFETY: disable CommonMark "indented code blocks" unless explicitly enabled.
        if (!this.cfg.MD || this.cfg.MD.ALLOW_INDENTED_CODE !== true) {
          try { this.MD.block.ruler.disable('code'); } catch (_) {}
          try { this.MD_STREAM.block.ruler.disable('code'); } catch (_) {}
        }

        const escapeHtml = Utils.escapeHtml;

        // Dollar and bracket math placeholder plugins: generate lightweight placeholders to be picked up by KaTeX later.
        const mathDollarPlaceholderPlugin = (md) => {
          function notEscaped(src, pos) { let back = 0; while (pos - back - 1 >= 0 && src.charCodeAt(pos - back - 1) === 0x5C) back++; return (back % 2) === 0; }
          function math_block_dollar(state, startLine, endLine, silent) {
            const pos = state.bMarks[startLine] + state.tShift[startLine];
            const max = state.eMarks[startLine];
            if (pos + 1 >= max) return false;
            if (state.src.charCodeAt(pos) !== 0x24 || state.src.charCodeAt(pos + 1) !== 0x24) return false;
            let nextLine = startLine + 1, found = false;
            for (; nextLine < endLine; nextLine++) {
              let p = state.bMarks[nextLine] + state.tShift[nextLine];
              const pe = state.eMarks[nextLine];
              if (p + 1 < pe && state.src.charCodeAt(p) === 0x24 && state.src.charCodeAt(p + 1) === 0x24) { found = true; break; }
            }
            if (!found) return false;
            if (silent) return true;

            const contentStart = state.bMarks[startLine] + state.tShift[startLine] + 2;
            const contentEndLine = nextLine - 1;
            let content = '';
            if (contentEndLine >= startLine + 1) {
              const startIdx = state.bMarks[startLine + 1];
              const endIdx = state.eMarks[contentEndLine];
              content = state.src.slice(startIdx, endIdx);
            } else content = '';

            const token = state.push('math_block_dollar', '', 0);
            token.block = true; token.content = content; state.line = nextLine + 1; return true;
          }
          function math_inline_dollar(state, silent) {
            const pos = state.pos, src = state.src, max = state.posMax;
            if (pos >= max) return false;
            if (src.charCodeAt(pos) !== 0x24) return false;
            if (pos + 1 < max && src.charCodeAt(pos + 1) === 0x24) return false;
            const after = pos + 1 < max ? src.charCodeAt(pos + 1) : 0;
            if (after === 0x20 || after === 0x0A || after === 0x0D) return false;
            let i = pos + 1;
            while (i < max) {
              const ch = src.charCodeAt(i);
              if (ch === 0x24 && notEscaped(src, i)) {
                const before = i - 1 >= 0 ? src.charCodeAt(i - 1) : 0;
                if (before === 0x20 || before === 0x0A || before === 0x0D) { i++; continue; }
                break;
              }
              i++;
            }
            if (i >= max || src.charCodeAt(i) !== 0x24) return false;

            if (!silent) {
              const token = state.push('math_inline_dollar', '', 0);
              token.block = false; token.content = src.slice(pos + 1, i);
            }
            state.pos = i + 1; return true;
          }

          md.block.ruler.before('fence', 'math_block_dollar', math_block_dollar, { alt: ['paragraph', 'reference', 'blockquote', 'list'] });
          md.inline.ruler.before('escape', 'math_inline_dollar', math_inline_dollar);

          md.renderer.rules.math_inline_dollar = (tokens, idx) => {
            const tex = tokens[idx].content || '';
            return `<span class="math-pending" data-display="0"><span class="math-fallback">$${escapeHtml(tex)}$</span><script type="math/tex">${escapeHtml(tex)}</script></span>`;
          };
          md.renderer.rules.math_block_dollar = (tokens, idx) => {
            const tex = tokens[idx].content || '';
            return `<div class="math-pending" data-display="1"><div class="math-fallback">$$${escapeHtml(tex)}$$</div><script type="math/tex; mode=display">${escapeHtml(tex)}</script></div>`;
          };
        };

        const mathBracketsPlaceholderPlugin = (md) => {
          function math_brackets(state, silent) {
            const src = state.src, pos = state.pos, max = state.posMax;
            if (pos + 1 >= max || src.charCodeAt(pos) !== 0x5C) return false;
            const next = src.charCodeAt(pos + 1);
            if (next !== 0x28 && next !== 0x5B) return false;
            const isInline = (next === 0x28); const close = isInline ? '\\)' : '\\]';
            const start = pos + 2; const end = src.indexOf(close, start);
            if (end < 0) return false;
            const content = src.slice(start, end);
            if (!silent) {
              const t = state.push(isInline ? 'math_inline_bracket' : 'math_block_bracket', '', 0);
              t.content = content; t.block = !isInline;
            }
            state.pos = end + 2; return true;
          }
          md.inline.ruler.before('escape', 'math_brackets', math_brackets);
          md.renderer.rules.math_inline_bracket = (tokens, idx) => {
            const tex = tokens[idx].content || '';
            return `<span class="math-pending" data-display="0"><span class="math-fallback">\\(${escapeHtml(tex)}\\)</span><script type="math/tex">${escapeHtml(tex)}</script></span>`;
          };
          md.renderer.rules.math_block_bracket = (tokens, idx) => {
            const tex = tokens[idx].content || '';
            return `<div class="math-pending" data-display="1"><div class="math-fallback">\\[${escapeHtml(tex)}\\]</div><script type="math/tex; mode=display">${escapeHtml(tex)}</script></div>`;
          };
        };

        this.MD.use(mathDollarPlaceholderPlugin);
        this.MD.use(mathBracketsPlaceholderPlugin);
        this.MD_STREAM.use(mathDollarPlaceholderPlugin);
        this.MD_STREAM.use(mathBracketsPlaceholderPlugin);

        const cfg = this.cfg; const logger = this.logger;

        // STREAMING wrapper plugin (modified header label guard)
        (function codeWrapperPlugin(md, logger) {
          let CODE_IDX = 1;
          const log = (line, ctx) => logger.debug('MD_LANG', line, ctx);

          const DEDUP = (window.MD_LANG_LOG_DEDUP !== false);
          const seenFP = new Set();
          const makeFP = (info, raw) => {
            const head = (raw || '').slice(0, 96);
            return String(info || '') + '|' + String((raw || '').length) + '|' + head;
          };

          const ALIAS = {
            txt: 'plaintext', text: 'plaintext', plaintext: 'plaintext',
            sh: 'bash', shell: 'bash', zsh: 'bash', 'shell-session': 'bash',
            py: 'python', python3: 'python', py3: 'python',
            js: 'javascript', node: 'javascript', nodejs: 'javascript',
            ts: 'typescript', 'ts-node': 'typescript',
            yml: 'yaml', kt: 'kotlin', rs: 'rust',
            csharp: 'csharp', 'c#': 'csharp', 'c++': 'cpp',
            ps: 'powershell', ps1: 'powershell', pwsh: 'powershell', powershell7: 'powershell',
            docker: 'dockerfile'
          };
          function normLang(s) { if (!s) return ''; const v = String(s).trim().toLowerCase(); return ALIAS[v] || v; }
          function isSupportedByHLJS(lang) { try { return !!(window.hljs && hljs.getLanguage && hljs.getLanguage(lang)); } catch (_) { return false; } }
          function classForHighlight(lang) { if (!lang) return 'plaintext'; return isSupportedByHLJS(lang) ? lang : 'plaintext'; }
          function stripBOM(s) { return (s && s.charCodeAt(0) === 0xFEFF) ? s.slice(1) : s; }

          function detectFromFirstLine(raw, rid) {
            if (!raw) return { lang: '', content: raw, isOutput: false };
            const lines = raw.split(/\r?\n/);
            if (!lines.length) return { lang: '', content: raw, isOutput: false };
            let i = 0; while (i < lines.length && !lines[i].trim()) i++;
            if (i >= lines.length) { log(`#${rid} first-line: only whitespace`); return { lang: '', content: raw, isOutput: false }; }
            let first = stripBOM(lines[i]).trim();
            first = first.replace(/^\s*lang(?:uage)?\s*[:=]\s*/i, '').trim();
            let token = first.split(/\s+/)[0].replace(/:$/, '');
            if (!/^[A-Za-z][\w#+\-\.]{0,30}$/.test(token)) { log(`#${rid} first-line: no token match`, { first }); return { lang: '', content: raw, isOutput: false }; }
            let cand = normLang(token);
            if (cand === 'output') {
              const content = lines.slice(i + 1).join('\n');
              log(`#${rid} first-line: output header`);
              return { lang: 'python', headerLabel: 'output', content, isOutput: true };
            }
            const rest = lines.slice(i + 1).join('\n');
            if (!rest.trim()) { log(`#${rid} first-line: directive but no content after, ignore`, { cand }); return { lang: '', content: raw, isOutput: false }; }
            log(`#${rid} first-line: directive accepted`, { cand, restLen: rest.length, hljs: isSupportedByHLJS(cand) });
            return { lang: cand, headerLabel: cand, content: rest, isOutput: false };
          }

          md.renderer.rules.fence = (tokens, idx) => renderFence(tokens[idx]);
          md.renderer.rules.code_block = (tokens, idx) => renderFence({ info: '', content: tokens[idx].content || '' });

          function resolveLanguageAndContent(info, raw, rid) {
            const infoLangRaw = (info || '').trim().split(/\s+/)[0] || '';
            let cand = normLang(infoLangRaw);
            if (cand === 'output') {
              log(`#${rid} info: output header`);
              return { lang: 'python', headerLabel: 'output', content: raw, isOutput: true };
            }
            if (cand) {
              log(`#${rid} info: token`, { infoLangRaw, cand, hljs: isSupportedByHLJS(cand) });
              return { lang: cand, headerLabel: cand, content: raw, isOutput: false };
            }
            const det = detectFromFirstLine(raw, rid);
            if (det && (det.lang || det.isOutput)) return det;
            log(`#${rid} resolve: fallback`);
            return { lang: '', headerLabel: 'code', content: raw, isOutput: false };
          }

          function renderFence(token) {
            const raw = token.content || '';
            const rid = String(CODE_IDX + '');
            const fp = makeFP(token.info || '', raw);
            const canLog = !DEDUP || !seenFP.has(fp);
            if (canLog) log(`FENCE_ENTER #${rid}`, { info: (token.info || ''), rawHead: logger.pv(raw) });

            const res = resolveLanguageAndContent(token.info || '', raw, rid);
            const isOutput = !!res.isOutput;

            // Choose class and a safe header label (avoid 'on', 'ml', 's' etc.)
            const rawToken = (res.lang || '').trim();
            const langClass = isOutput ? 'python' : classForHighlight(rawToken);

            // Guard against tiny unsupported tokens – show temporary 'code' instead of partial suffix.
            let headerLabel = isOutput ? 'output' : (res.headerLabel || (rawToken || 'code'));
            if (!isOutput) {
              if (rawToken && !isSupportedByHLJS(rawToken) && rawToken.length < 3) {
                headerLabel = 'code';
              }
            }

            if (canLog) {
              log(`FENCE_RESOLVE #${rid}`, { headerLabel, langToken: (res.lang || ''), langClass, hljsSupported: isSupportedByHLJS(res.lang || ''), contentLen: (res.content || '').length });
              if (DEDUP) seenFP.add(fp);
            }

            // precompute code meta to avoid expensive .textContent on next phases
            const content = res.content || '';
            const len = content.length;
            const head = content.slice(0, 64);
            const tail = content.slice(-64);
            const headEsc = Utils.escapeHtml(head);
            const tailEsc = Utils.escapeHtml(tail);
            // Note: for full renderer we will also persist data-code-nl (see below).

            const inner = Utils.escapeHtml(content);
            const idxLocal = CODE_IDX++;

            let actions = '';
            if (langClass === 'html') {
              actions += `<a href="empty:${idxLocal}" class="code-header-action code-header-preview"><img src="${cfg.ICONS.CODE_PREVIEW}" class="action-img" data-id="${idxLocal}"><span>${Utils.escapeHtml(cfg.LOCALE.PREVIEW)}</span></a>`;
            } else if (langClass === 'python' && headerLabel !== 'output') {
              actions += `<a href="empty:${idxLocal}" class="code-header-action code-header-run"><img src="${cfg.ICONS.CODE_RUN}" class="action-img" data-id="${idxLocal}"><span>${Utils.escapeHtml(cfg.LOCALE.RUN)}</span></a>`;
            }
            actions += `<a href="empty:${idxLocal}" class="code-header-action code-header-collapse"><img src="${cfg.ICONS.CODE_MENU}" class="action-img" data-id="${idxLocal}"><span>${Utils.escapeHtml(cfg.LOCALE.COLLAPSE)}</span></a>`;
            actions += `<a href="empty:${idxLocal}" class="code-header-action code-header-copy"><img src="${cfg.ICONS.CODE_COPY}" class="action-img" data-id="${idxLocal}"><span>${Utils.escapeHtml(cfg.LOCALE.COPY)}</span></a>`;

            // attach precomputed meta (len/head/tail) on wrapper for downstream optimizations
            return (
              `<div class="code-wrapper highlight" data-index="${idxLocal}"` +
              ` data-code-lang="${Utils.escapeHtml(res.lang || '')}"` +
              ` data-code-len="${String(len)}" data-code-head="${headEsc}" data-code-tail="${tailEsc}"` + // meta (no nl here – only in full renderer)
              ` data-locale-collapse="${Utils.escapeHtml(cfg.LOCALE.COLLAPSE)}" data-locale-expand="${Utils.escapeHtml(cfg.LOCALE.EXPAND)}"` +
              ` data-locale-copy="${Utils.escapeHtml(cfg.LOCALE.COPY)}" data-locale-copied="${Utils.escapeHtml(cfg.LOCALE.COPIED)}" data-style="${Utils.escapeHtml(cfg.CODE_STYLE)}">` +
                `<p class="code-header-wrapper"><span><span class="code-header-lang">${Utils.escapeHtml(headerLabel)}   </span>${actions}</span></p>` +
                `<pre><code class="language-${Utils.escapeHtml(langClass)} hljs">${inner}</code></pre>` +
              `</div>`
            );
          }
        })(this.MD_STREAM, this.logger);

        // FULL renderer wrapper plugin (modified header label guard)
        (function codeWrapperPlugin(md, logger) {
          // identical core logic – augmented with data-code-nl for full renderer
          let CODE_IDX = 1;
          const log = (line, ctx) => logger.debug('MD_LANG', line, ctx);

          const DEDUP = (window.MD_LANG_LOG_DEDUP !== false);
          const seenFP = new Set();
          const makeFP = (info, raw) => {
            const head = (raw || '').slice(0, 96);
            return String(info || '') + '|' + String((raw || '').length) + '|' + head;
          };

          const ALIAS = {
            txt: 'plaintext', text: 'plaintext', plaintext: 'plaintext',
            sh: 'bash', shell: 'bash', zsh: 'bash', 'shell-session': 'bash',
            py: 'python', python3: 'python', py3: 'python',
            js: 'javascript', node: 'javascript', nodejs: 'javascript',
            ts: 'typescript', 'ts-node': 'typescript',
            yml: 'yaml', kt: 'kotlin', rs: 'rust',
            csharp: 'csharp', 'c#': 'csharp', 'c++': 'cpp',
            ps: 'powershell', ps1: 'powershell', pwsh: 'powershell', powershell7: 'powershell',
            docker: 'dockerfile'
          };
          function normLang(s) { if (!s) return ''; const v = String(s).trim().toLowerCase(); return ALIAS[v] || v; }
          function isSupportedByHLJS(lang) { try { return !!(window.hljs && hljs.getLanguage && hljs.getLanguage(lang)); } catch (_) { return false; } }
          function classForHighlight(lang) { if (!lang) return 'plaintext'; return isSupportedByHLJS(lang) ? lang : 'plaintext'; }
          function stripBOM(s) { return (s && s.charCodeAt(0) === 0xFEFF) ? s.slice(1) : s; }

          function detectFromFirstLine(raw, rid) {
            if (!raw) return { lang: '', content: raw, isOutput: false };
            const lines = raw.split(/\r?\n/);
            if (!lines.length) return { lang: '', content: raw, isOutput: false };
            let i = 0; while (i < lines.length && !lines[i].trim()) i++;
            if (i >= lines.length) { log(`#${rid} first-line: only whitespace`); return { lang: '', content: raw, isOutput: false }; }
            let first = stripBOM(lines[i]).trim();
            first = first.replace(/^\s*lang(?:uage)?\s*[:=]\s*/i, '').trim();
            let token = first.split(/\s+/)[0].replace(/:$/, '');
            if (!/^[A-Za-z][\w#+\-\.]{0,30}$/.test(token)) { log(`#${rid} first-line: no token match`, { first }); return { lang: '', content: raw, isOutput: false }; }
            let cand = normLang(token);
            if (cand === 'output') {
              const content = lines.slice(i + 1).join('\n');
              log(`#${rid} first-line: output header`);
              return { lang: 'python', headerLabel: 'output', content, isOutput: true };
            }
            const rest = lines.slice(i + 1).join('\n');
            if (!rest.trim()) { log(`#${rid} first-line: directive but no content after, ignore`, { cand }); return { lang: '', content: raw, isOutput: false }; }
            log(`#${rid} first-line: directive accepted`, { cand, restLen: rest.length, hljs: isSupportedByHLJS(cand) });
            return { lang: cand, headerLabel: cand, content: rest, isOutput: false };
          }

          md.renderer.rules.fence = (tokens, idx) => renderFence(tokens[idx]);
          md.renderer.rules.code_block = (tokens, idx) => renderFence({ info: '', content: tokens[idx].content || '' });

          function resolveLanguageAndContent(info, raw, rid) {
            const infoLangRaw = (info || '').trim().split(/\s+/)[0] || '';
            let cand = normLang(infoLangRaw);
            if (cand === 'output') {
              log(`#${rid} info: output header`);
              return { lang: 'python', headerLabel: 'output', content: raw, isOutput: true };
            }
            if (cand) {
              log(`#${rid} info: token`, { infoLangRaw, cand, hljs: isSupportedByHLJS(cand) });
              return { lang: cand, headerLabel: cand, content: raw, isOutput: false };
            }
            const det = detectFromFirstLine(raw, rid);
            if (det && (det.lang || det.isOutput)) return det;
            log(`#${rid} resolve: fallback`);
            return { lang: '', headerLabel: 'code', content: raw, isOutput: false };
          }

          function renderFence(token) {
            const raw = token.content || '';
            const rid = String(CODE_IDX + '');
            const fp = makeFP(token.info || '', raw);
            const canLog = !DEDUP || !seenFP.has(fp);
            if (canLog) log(`FENCE_ENTER #${rid}`, { info: (token.info || ''), rawHead: logger.pv(raw) });

            const res = resolveLanguageAndContent(token.info || '', raw, rid);
            const isOutput = !!res.isOutput;

            // Choose class and a safe header label (avoid 'on', 'ml', 's' etc.)
            const rawToken = (res.lang || '').trim();
            const langClass = isOutput ? 'python' : classForHighlight(rawToken);

            // Guard against tiny unsupported tokens – show temporary 'code' instead of partial suffix.
            let headerLabel = isOutput ? 'output' : (res.headerLabel || (rawToken || 'code'));
            if (!isOutput) {
              if (rawToken && !isSupportedByHLJS(rawToken) && rawToken.length < 3) {
                headerLabel = 'code';
              }
            }

            if (canLog) {
              log(`FENCE_RESOLVE #${rid}`, { headerLabel, langToken: (res.lang || ''), langClass, hljsSupported: isSupportedByHLJS(res.lang || ''), contentLen: (res.content || '').length });
              if (DEDUP) seenFP.add(fp);
            }

            // precompute code meta
            const content = res.content || '';
            const len = content.length;
            const head = content.slice(0, 64);
            const tail = content.slice(-64);
            const headEsc = Utils.escapeHtml(head);
            const tailEsc = Utils.escapeHtml(tail);
            const nl = Utils.countNewlines(content);

            const inner = Utils.escapeHtml(content);
            const idxLocal = CODE_IDX++;

            let actions = '';
            if (langClass === 'html') {
              actions += `<a href="empty:${idxLocal}" class="code-header-action code-header-preview"><img src="${cfg.ICONS.CODE_PREVIEW}" class="action-img" data-id="${idxLocal}"><span>${Utils.escapeHtml(cfg.LOCALE.PREVIEW)}</span></a>`;
            } else if (langClass === 'python' && headerLabel !== 'output') {
              actions += `<a href="empty:${idxLocal}" class="code-header-action code-header-run"><img src="${cfg.ICONS.CODE_RUN}" class="action-img" data-id="${idxLocal}"><span>${Utils.escapeHtml(cfg.LOCALE.RUN)}</span></a>`;
            }
            actions += `<a href="empty:${idxLocal}" class="code-header-action code-header-collapse"><img src="${cfg.ICONS.CODE_MENU}" class="action-img" data-id="${idxLocal}"><span>${Utils.escapeHtml(cfg.LOCALE.COLLAPSE)}</span></a>`;
            actions += `<a href="empty:${idxLocal}" class="code-header-action code-header-copy"><img src="${cfg.ICONS.CODE_COPY}" class="action-img" data-id="${idxLocal}"><span>${Utils.escapeHtml(cfg.LOCALE.COPY)}</span></a>`;

            return (
              `<div class="code-wrapper highlight" data-index="${idxLocal}"` +
              ` data-code-lang="${Utils.escapeHtml(res.lang || '')}"` +
              ` data-code-len="${String(len)}" data-code-head="${headEsc}" data-code-tail="${tailEsc}" data-code-nl="${String(nl)}"` +
              ` data-locale-collapse="${Utils.escapeHtml(cfg.LOCALE.COLLAPSE)}" data-locale-expand="${Utils.escapeHtml(cfg.LOCALE.EXPAND)}"` +
              ` data-locale-copy="${Utils.escapeHtml(cfg.LOCALE.COPY)}" data-locale-copied="${Utils.escapeHtml(cfg.LOCALE.COPIED)}" data-style="${Utils.escapeHtml(cfg.CODE_STYLE)}">` +
                `<p class="code-header-wrapper"><span><span class="code-header-lang">${Utils.escapeHtml(headerLabel)}   </span>${actions}</span></p>` +
                `<pre><code class="language-${Utils.escapeHtml(langClass)} hljs">${inner}</code></pre>` +
              `</div>`
            );
          }
        })(this.MD, this.logger);
      }
      // Replace "sandbox:" links with file:// in markdown source (host policy).
      preprocessMD(s) { return (s || '').replace(/\]\(sandbox:/g, '](file://'); }
      // Decode base64 UTF-8 to string (shared TextDecoder).
      b64ToUtf8(b64) {
        const bin = atob(b64);
        const bytes = new Uint8Array(bin.length);
        for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
        return Utils.utf8Decode(bytes);
      }

      // Apply custom markup for bot messages only (method name kept for API).
      applyCustomMarkupForBots(root) {
        const MD = this.MD;
        try {
          const scope = root || document;
          const targets = [];

          // If scope itself is a bot message box
          if (scope && scope.nodeType === 1 && scope.classList && scope.classList.contains('msg-box') &&
              scope.classList.contains('msg-bot')) {
            targets.push(scope);
          }

          // Collect bot message boxes within the scope
          if (scope && typeof scope.querySelectorAll === 'function') {
            const list = scope.querySelectorAll('.msg-box.msg-bot');
            for (let i = 0; i < list.length; i++) targets.push(list[i]);
          }

          // If scope is inside a bot message, include the closest ancestor as well
          if (scope && scope.nodeType === 1 && typeof scope.closest === 'function') {
            const closestMsg = scope.closest('.msg-box.msg-bot');
            if (closestMsg) targets.push(closestMsg);
          }

          // Deduplicate and apply rules only to bot messages
          const seen = new Set();
          for (const el of targets) {
            if (!el || !el.isConnected || seen.has(el)) continue;
            seen.add(el);
            this.customMarkup.apply(el, MD);
          }
        } catch (_) {
          // Keep render path resilient
        }
      }

      // Helper: choose renderer (hot vs full) for snapshot use.
      _md(streamingHint) {
        return streamingHint ? (this.MD_STREAM || this.MD) : (this.MD || this.MD_STREAM);
      }

      // Async, batched processing of [data-md64] / [md-block-markdown] to keep UI responsive on heavy loads.
      // Note: user messages are rendered as plain text (no markdown-it, no custom markup, no KaTeX).
      async renderPendingMarkdown(root) {
        const MD = this.MD; if (!MD) return;
        const scope = root || document;

        // Collect both legacy base64 holders and new native Markdown holders
        const nodes = Array.from(scope.querySelectorAll('[data-md64], [md-block-markdown]'));
        if (nodes.length === 0) {
          // Nothing to materialize right now. Avoid arming rAF work unless there is
          // actually something present that needs highlight/scroll/math.
          try {
            const hasBots = !!(scope && scope.querySelector && scope.querySelector('.msg-box.msg-bot'));
            const hasWrappers = !!(scope && scope.querySelector && scope.querySelector('.code-wrapper'));
            const hasCodes = !!(scope && scope.querySelector && scope.querySelector('.msg-box.msg-bot pre code'));
            const hasUnhighlighted = !!(scope && scope.querySelector && scope.querySelector('.msg-box.msg-bot pre code:not([data-highlighted="yes"])'));
            const hasMath = !!(scope && scope.querySelector && scope.querySelector('script[type^="math/tex"]'));

            // Apply Custom Markup only if bot messages are present.
            if (hasBots) { this.applyCustomMarkupForBots(scope); }

            // Restore collapsed state only if we can actually find wrappers.
            if (hasWrappers) { this.restoreCollapsedCode(scope); }

            // Initialize code scroll helpers for current root.
            this.hooks.codeScrollInit(scope);

            // Init code-scroll/highlight observers only when there are codes in DOM.
            if (hasCodes) {
              this.hooks.observeMsgBoxes(scope);
              this.hooks.observeNewCode(scope, {
                deferLastIfStreaming: true,
                minLinesForLast: this.cfg.PROFILE_CODE.minLinesForHL,
                minCharsForLast: this.cfg.PROFILE_CODE.minCharsForHL
              });
              if (hasUnhighlighted && typeof runtime !== 'undefined' && runtime.highlighter) {
                runtime.highlighter.scanVisibleCodesInRoot(scope, runtime.stream.activeCode || null);
              }
            }

            // Schedule KaTeX render only if there are math scripts present.
            if (hasMath) { this.hooks.scheduleMathRender(scope); }
            this.hooks.codeScrollInit(scope);

          } catch (_) { /* swallow: keep idle path safe */ }

          return;
        }

        // Track which bot message boxes actually changed to avoid a heavy global Custom Markup pass.
        const touchedBoxes = new Set();

        // Budgeted, cooperative loop: process nodes one-by-one with per-frame yield when needed.
        const perSlice = (this.cfg.ASYNC && this.cfg.ASYNC.MD_NODES_PER_SLICE) || 12; // upper bound per frame
        let sliceCount = 0;
        let startedAt = Utils.now();

        for (let j = 0; j < nodes.length; j++) {
          const el = nodes[j];
          if (!el || !el.isConnected) continue;

          let md = '';
          const isNative = el.hasAttribute('md-block-markdown');
          const msgBox = (el.closest && el.closest('.msg-box.msg-bot, .msg-box.msg-user')) || null;
          const isUserMsg = !!(msgBox && msgBox.classList.contains('msg-user'));
          const isBotMsg = !!(msgBox && msgBox.classList.contains('msg-bot'));

          // Read source text (do not preprocess for user messages to keep it raw)
          if (isNative) {
            try { md = isUserMsg ? (el.textContent || '') : this.preprocessMD(el.textContent || ''); } catch (_) { md = ''; }
            try { el.removeAttribute('md-block-markdown'); } catch (_) {}
          } else {
            const b64 = el.getAttribute('data-md64'); if (!b64) continue;
            try { md = this.b64ToUtf8(b64); } catch (_) { md = ''; }
            el.removeAttribute('data-md64');
            if (!isUserMsg) { try { md = this.preprocessMD(md); } catch (_) {} }
          }

          if (isUserMsg) {
            // User message: replace placeholder with raw plain text only.
            const span = document.createElement('span');
            span.textContent = md;
            el.replaceWith(span);
            // Intentionally do NOT add to touchedBoxes; no Custom Markup for user.
          } else if (isBotMsg) {
            // Bot message: full markdown-it render with Custom Markup.
            let html = '';
              try {
                let src = md;
                // Pre-md transforms for source-phase rules
                if (this.customMarkup && typeof this.customMarkup.transformSource === 'function') {
                  src = this.customMarkup.transformSource(src, { streaming: false });
                }
                html = MD.render(src);
              } catch (_) { html = Utils.escapeHtml(md); }

            // build fragment directly (avoid intermediate container allocations).
            let frag = null;
            try {
              const range = document.createRange();
              const ctx = el.parentNode || document.body || document.documentElement;
              range.selectNode(ctx);
              frag = range.createContextualFragment(html);
            } catch (_) {
              const tmp = document.createElement('div');
              tmp.innerHTML = html;
              frag = document.createDocumentFragment();
              while (tmp.firstChild) frag.appendChild(tmp.firstChild);
            }

            // Apply Custom Markup on a lightweight DocumentFragment
            try { this.customMarkup.apply(frag, MD); } catch (_) {}

            el.replaceWith(frag);
            touchedBoxes.add(msgBox);
          } else {
            // Outside of any message box: materialize as plain text.
            const span = document.createElement('span');
            span.textContent = md;
            el.replaceWith(span);
          }

          sliceCount++;
          // Yield by time budget or by count to keep frame short and reactive.
          if (sliceCount >= perSlice || this.asyncer.shouldYield(startedAt)) {
            await this.asyncer.yield();
            startedAt = Utils.now();
            sliceCount = 0;
          }
        }

        // Apply Custom Markup only to actually modified BOT messages (keeps this pass light).
        try {
          touchedBoxes.forEach(box => { try { this.customMarkup.apply(box, MD); } catch (_) {} });
        } catch (_) {}

        // Same post-processing as before (idempotent with external calls).
        this.restoreCollapsedCode(scope);
        this.hooks.observeNewCode(scope, {
          deferLastIfStreaming: true,
          minLinesForLast: this.cfg.PROFILE_CODE.minLinesForHL,
          minCharsForLast: this.cfg.PROFILE_CODE.minCharsForHL
        });
        this.hooks.observeMsgBoxes(scope);
        this.hooks.scheduleMathRender(scope);
        this.hooks.codeScrollInit(scope);

        if (typeof runtime !== 'undefined' && runtime.highlighter) {
         runtime.highlighter.scanVisibleCodesInRoot(scope, runtime.stream.activeCode || null);
        }
      }

      // Render streaming snapshot.
      renderStreamingSnapshot(src) {
        const md = this._md(true);
        if (!md) return '';
        try {
          let s = String(src || '');
          // Pre-markdown custom transforms (e.g. [!exec]/<execute> => ```python fences)
          if (this.customMarkup && typeof this.customMarkup.transformSource === 'function') {
            s = this.customMarkup.transformSource(s, { streaming: true });
          }
          return md.render(s);
        } catch (_) { return Utils.escapeHtml(src); }
      }

      renderFinalSnapshot(src) {
        const md = this._md(false);
        if (!md) return '';
        try {
          let s = String(src || '');
          if (this.customMarkup && typeof this.customMarkup.transformSource === 'function') {
            s = this.customMarkup.transformSource(s, { streaming: false });
          }
          return md.render(s);
        } catch (_) { return Utils.escapeHtml(src); }
      }

      // Restore collapse/expand state of code blocks after DOM updates.
      restoreCollapsedCode(root) {
        const scope = root || document;
        const wrappers = scope.querySelectorAll('.code-wrapper');
        wrappers.forEach((wrapper) => {
          const index = wrapper.getAttribute('data-index');
          const localeCollapse = wrapper.getAttribute('data-locale-collapse');
          const localeExpand = wrapper.getAttribute('data-locale-expand');
          const source = wrapper.querySelector('code');
          const isCollapsed = (window.__collapsed_idx || []).includes(index);
          if (!source) return;
          const btn = wrapper.querySelector('.code-header-collapse');
          if (isCollapsed) {
            source.style.display = 'none';
            if (btn) { const span = btn.querySelector('span'); if (span) span.textContent = localeExpand; }
          } else {
            source.style.display = 'block';
            if (btn) { const span = btn.querySelector('span'); if (span) span.textContent = localeCollapse; }
          }
        });
      }
    }
  window.__collapsed_idx = window.__collapsed_idx || [];

  // ==========================================================================
  // 6) Math renderer (async, chunked)
  // ==========================================================================
  class MathRenderer {
    constructor(cfg, raf, asyncer) {
      this.cfg = cfg; this.raf = raf; this.asyncer = asyncer;
      this.scheduled = false;

      // rAF key used by the central pump (do not change – API compatibility).
      this.rafKey = { t: 'Math:render' };

      // Pending roots aggregation: if document-level render is requested, it supersedes others.
      this._pendingRoots = new Set();
      this._pendingDoc = false;
    }

    // Async, cooperative KaTeX rendering to avoid long blocking on many formulas.
    async renderAsync(root) {
      if (typeof katex === 'undefined') return;
      const scope = root || document;
      const scripts = Array.from(scope.querySelectorAll('script[type^="math/tex"]'));
      const useToString = (typeof katex.renderToString === 'function');

      const batchFn = async (script) => {
        if (!script || !script.isConnected) return;
        // Only render math in bot messages
        if (!script.closest('.msg-box.msg-bot')) return;
        const t = script.getAttribute('type') || '';
        const displayMode = t.indexOf('mode=display') > -1;
        // avoid innerText (it may trigger layout). textContent is sufficient here.
        const mathContent = script.textContent || '';
        const parent = script.parentNode; if (!parent) return;

        try {
          if (useToString) {
            let html = '';
            try {
              html = katex.renderToString(mathContent, { displayMode, throwOnError: false });
            } catch (_) {
              const fb = displayMode ? `\\[${mathContent}\\]` : `\\(${mathContent}\\)`;
              html = (displayMode ? `<div>${Utils.escapeHtml(fb)}</div>` : `<span>${Utils.escapeHtml(fb)}</span>`);
            }
            const host = document.createElement(displayMode ? 'div' : 'span');
            host.innerHTML = html;
            const el = host.firstElementChild || host;
            if (parent.classList && parent.classList.contains('math-pending')) parent.replaceWith(el);
            else parent.replaceChild(el, script);
          } else {
            const el = document.createElement(displayMode ? 'div' : 'span');
            try { katex.render(mathContent, el, { displayMode, throwOnError: false }); }
            catch (_) { el.textContent = (displayMode ? `\\[${mathContent}\\]` : `\\(${mathContent}\\)`); }
            if (parent.classList && parent.classList.contains('math-pending')) parent.replaceWith(el);
            else parent.replaceChild(el, script);
          }
        } catch (_) {
          // Keep fallback text intact on any error
        }
      };

      // Process formulas cooperatively (rAF yields).
      await this.asyncer.forEachChunk(scripts, batchFn, 'MathRenderer');
    }

    // Schedule math rendering for a root. Coalesces multiple calls.
    schedule(root, _delayIgnored = 0, forceNow = false) {
      // If KaTeX is not available, honor no-op. API stays intact.
      if (typeof katex === 'undefined') return;

      // Normalize root (default to whole document).
      const targetRoot = root || document;

      // Fast existence check to avoid arming rAF when nothing to do, but still
      // keep aggregation semantics: if a job is already scheduled we can still
      // merge new roots into the pending set when they actually contain math.
      let hasMath = true;
      if (!forceNow) {
        try {
          hasMath = !!(targetRoot && targetRoot.querySelector && targetRoot.querySelector('script[type^="math/tex"]'));
        } catch (_) { hasMath = false; }
        if (!hasMath) return; // nothing to render for this root; safe early exit
      }

      // Aggregate roots so nothing is lost while one job is already scheduled.
      if (targetRoot === document || targetRoot === document.documentElement || targetRoot === document.body) {
        this._pendingDoc = true;                 // promote to a full-document sweep
        this._pendingRoots.clear();              // small optimization (document covers all)
      } else if (!this._pendingDoc) {
        this._pendingRoots.add(targetRoot);
      }

      // If a task is already scheduled, do not arm another – coalescing will take care of it.
      if (this.scheduled && this.raf && typeof this.raf.isScheduled === 'function' && this.raf.isScheduled(this.rafKey)) return;

      this.scheduled = true;
      const priority = forceNow ? 0 : 2;

      // Single rAF job drains all pending roots; renderAsync remains public and unchanged.
      this.raf.schedule(this.rafKey, () => {
        this.scheduled = false;

        const useDoc = this._pendingDoc;
        const roots = [];

        if (useDoc) {
          roots.push(document);
        } else {
          this._pendingRoots.forEach((r) => {
            // Only keep connected elements to avoid useless work.
            try {
              if (r && (r.isConnected === undefined || r.isConnected)) roots.push(r);
            } catch (_) {
              // Conservative: keep the root; renderAsync guards internally.
              roots.push(r);
            }
          });
        }

        // Reset aggregation state before running (new calls can aggregate afresh).
        this._pendingDoc = false;
        this._pendingRoots.clear();

        // Fire-and-forget async drain; keep renderAsync API intact.
        (async () => {
          for (let i = 0; i < roots.length; i++) {
            try { await this.renderAsync(roots[i]); } catch (_) { /* swallow – resilient */ }
          }
        })();
      }, 'Math', priority);
    }
    // Cleanup pending work and state.
    cleanup() {
      try { this.raf.cancelGroup('Math'); } catch (_) {}
      this.scheduled = false;

      // Ensure pending state is fully cleared on cleanup.
      try { this._pendingRoots.clear(); } catch (_) {}
      this._pendingDoc = false;
    }
  }

  // ==========================================================================
  // 7) Scroll manager + FAB
  // ==========================================================================

  class ScrollManager {
    constructor(cfg, dom, raf) {
      this.cfg = cfg; this.dom = dom; this.raf = raf;
      this.autoFollow = true; this.userInteracted = false;
      this.lastScrollTop = 0; this.prevScroll = 0;
      this.currentFabAction = 'none'; this.fabFreezeUntil = 0;
      this.scrollScheduled = false; this.scrollFabUpdateScheduled = false;
      this.scrollRAF = 0; this.scrollFabRAF = 0;
    }
    // Is page near the bottom by given margin?
    isNearBottom(marginPx = 100) {
      const el = Utils.SE; const distance = el.scrollHeight - el.clientHeight - el.scrollTop;
      return distance <= marginPx;
    }
    // Schedule a page scroll to bottom if auto-follow allows it.
    scheduleScroll(live = false) {
      if (live === true && this.autoFollow !== true) return;
      if (this.scrollScheduled) return;
      this.scrollScheduled = true;
      this.raf.schedule('SM:scroll', () => { this.scrollScheduled = false; this.scrollToBottom(live); this.scheduleScrollFabUpdate(); }, 'ScrollManager', 1);
    }
    // Cancel any pending page scroll.
    cancelPendingScroll() {
      try { this.raf.cancelGroup('ScrollManager'); } catch (_) {}
      this.scrollScheduled = false;
      this.scrollFabUpdateScheduled = false;
      this.scrollRAF = 0; this.scrollFabRAF = 0;
    }
    // Jump to bottom immediately (no smooth behavior).
    forceScrollToBottomImmediate() {
      const el = Utils.SE; el.scrollTop = el.scrollHeight; this.prevScroll = el.scrollHeight;
    }
    // Scroll window to bottom based on auto-follow and margins.
    scrollToBottom(live = false, force = false) {
      const el = Utils.SE; const marginPx = this.cfg.UI.SCROLL_NEAR_MARGIN_PX; const behavior = 'instant';
      if (live === true && this.autoFollow !== true) { this.prevScroll = el.scrollHeight; return; }
      if ((live === true && this.userInteracted === false) || this.isNearBottom(marginPx) || live === false || force) {
        try { el.scrollTo({ top: el.scrollHeight, behavior }); } catch (_) { el.scrollTop = el.scrollHeight; }
      }
      this.prevScroll = el.scrollHeight;
    }
    // Check if window has vertical scroll bar.
    hasVerticalScroll() { const el = Utils.SE; return (el.scrollHeight - el.clientHeight) > 1; }
    // Compute the current FAB action (none/up/down).
    computeFabAction() {
      const el = Utils.SE; const hasScroll = (el.scrollHeight - el.clientHeight) > 1;
      if (!hasScroll) return 'none';
      const dist = el.scrollHeight - el.clientHeight - el.scrollTop;
      if (dist <= 2) return 'up';
      if (dist >= this.cfg.FAB.SHOW_DOWN_THRESHOLD_PX) return 'down';
      return 'none';
    }
    // Update FAB to show correct direction and label.
    updateScrollFab(force = false, actionOverride = null, bypassFreeze = false) {
      const btn = this.dom.get('scrollFab'); const icon = this.dom.get('scrollFabIcon');
      if (!btn || !icon) return;
      const now = Utils.now(); const action = actionOverride || this.computeFabAction();
      if (!force && !bypassFreeze && now < this.fabFreezeUntil && action !== this.currentFabAction) return;
      if (action === 'none') {
        if (this.currentFabAction !== 'none' || force) { btn.classList.remove('visible'); this.currentFabAction = 'none'; }
        return;
      }
      if (action !== this.currentFabAction || force) {
        if (action === 'up') {
          if (icon.dataset.dir !== 'up') { icon.src = this.cfg.ICONS.COLLAPSE; icon.dataset.dir = 'up'; }
          btn.title = "Go to top";
        } else {
          if (icon.dataset.dir !== 'down') { icon.src = this.cfg.ICONS.EXPAND; icon.dataset.dir = 'down'; }
          btn.title = "Go to bottom";
        }
        btn.setAttribute('aria-label', btn.title);
        this.currentFabAction = action; btn.classList.add('visible');
      } else if (!btn.classList.contains('visible')) btn.classList.add('visible');
    }
    // Schedule a FAB state refresh.
    scheduleScrollFabUpdate() {
      if (this.scrollFabUpdateScheduled) return;
      this.scrollFabUpdateScheduled = true;
      this.raf.schedule('SM:fab', () => {
        this.scrollFabUpdateScheduled = false;
        const action = this.computeFabAction(); if (action !== this.currentFabAction) this.updateScrollFab(false, action);
      }, 'ScrollManager', 2);
    }
    // If user is near bottom, enable auto-follow again.
    maybeEnableAutoFollowByProximity() {
      const el = Utils.SE;
      if (!this.autoFollow) {
        const dist = el.scrollHeight - el.clientHeight - el.scrollTop;
        if (dist <= this.cfg.UI.AUTO_FOLLOW_REENABLE_PX) this.autoFollow = true;
      }
    }
    // User-triggered scroll to top; disables auto-follow.
    scrollToTopUser() {
      this.userInteracted = true; this.autoFollow = false;
      try { const el = Utils.SE; el.scrollTo({ top: 0, behavior: 'instant' }); this.lastScrollTop = el.scrollTop; }
      catch (_) { const el = Utils.SE; el.scrollTop = 0; this.lastScrollTop = 0; }
    }
    // User-triggered scroll to bottom; may re-enable auto-follow if near bottom.
    scrollToBottomUser() {
      this.userInteracted = true; this.autoFollow = false;
      try { const el = Utils.SE; el.scrollTo({ top: el.scrollHeight, behavior: 'instant' }); this.lastScrollTop = el.scrollTop; }
      catch (_) { const el = Utils.SE; el.scrollTop = el.scrollHeight; this.lastScrollTop = el.scrollTop; }
      this.maybeEnableAutoFollowByProximity();
    }
  }

  // ==========================================================================
  // 8) Tips manager
  // ==========================================================================

  // Tips manager (drop-in replacement): rotates small hint messages in a top overlay.
  class TipsManager {
    // Lightweight tips rotator that works with your CSS (.tips/.visible)
    // and is backward-compatible with legacy `let tips = [...]` injection.
    constructor(dom) {
      this.dom = dom;
      this.hidden = false;
      this._timers = [];
      this._running = false;
      this._idx = 0;
    }

    // Resolve tips list from multiple legacy/new sources.
    _getList() {
      // New preferred: window.TIPS (array)
      const upper = (typeof window !== 'undefined') ? window.TIPS : undefined;
      if (Array.isArray(upper) && upper.length) return upper;

      // Legacy inline: window.tips (array or JSON string)
      const lower = (typeof window !== 'undefined') ? window.tips : undefined;
      if (Array.isArray(lower) && lower.length) return lower;
      if (typeof lower === 'string' && lower.trim().length) {
        try { const arr = JSON.parse(lower); if (Array.isArray(arr)) return arr; } catch (_) {}
      }

      // Optional: data-tips='["...","..."]' on #tips
      const host = this._host();
      if (host && host.dataset && typeof host.dataset.tips === 'string') {
        try { const arr = JSON.parse(host.dataset.tips); if (Array.isArray(arr)) return arr; } catch (_) {}
      }

      return [];
    }

    _host() {
      return this.dom.get('tips') || document.getElementById('tips');
    }

    _clearTimers() {
      for (const t of this._timers) { try { clearTimeout(t); } catch (_) {} }
      this._timers.length = 0;
    }

    // Stop any running rotation timers.
    stopTimers() {
      this._clearTimers();
      this._running = false;
    }

    _applyBaseStyle(el) {
      if (!el) return;
      // Keep your flex layout and sizing; do not overwrite width/height.
      // Ensure it renders above other layers.
      const z = (typeof window !== 'undefined' && typeof window.TIPS_ZINDEX !== 'undefined')
        ? String(window.TIPS_ZINDEX) : '2147483000';
      el.style.zIndex = z;
    }

    // Hide tips layer and stop rotation.
    hide() {
      if (this.hidden) return;
      this.stopTimers();
      const el = this._host();
      if (el) {
        // Remove visibility class and hide hard (used when stream starts etc.)
        el.classList.remove('visible');
        el.classList.remove('hidden'); // in case it was set elsewhere
        el.style.display = 'none';
      }
      this.hidden = true;
    }

    // Show tips layer (does not start rotation).
    show() {
      const list = this._getList(); if (!list.length) return;
      const el = this._host(); if (!el) return;

      this.hidden = false;
      this._applyBaseStyle(el);
      el.classList.remove('hidden');
      el.style.display = 'block'; // CSS handles opacity via .tips/.visible
      // Do not add 'visible' yet – cycle() takes care of fade-in steps.
    }

    // Show one tip (by index) and fade it in next frame.
    _showOne(idx) {
      const list = this._getList(); if (!list.length) return;
      const el = this._host(); if (!el || this.hidden) return;

      this._applyBaseStyle(el);
      el.innerHTML = list[idx % list.length];

      // Centralize "next-frame" visibility toggle through RafManager to guarantee CSS transition.
      try {
        if (typeof runtime !== 'undefined' && runtime.raf && typeof runtime.raf.schedule === 'function') {
          const key = { t: 'Tips:show', el, i: Math.random() };
          runtime.raf.schedule(key, () => {
            if (this.hidden || !el.isConnected) return;
            el.classList.add('visible');
          }, 'Tips', 2);
        } else {
          // Fallback: no frame delay – still functional, transition may not play.
          el.classList.add('visible');
        }
      } catch (_) {
        el.classList.add('visible');
      }
    }

    // Internal loop: show, wait, hide, wait fade, next.
    _cycleLoop() {
      if (this.hidden) return;
      const el = this._host(); if (!el) return;

      const VISIBLE_MS = (typeof window !== 'undefined' && window.TIPS_VISIBLE_MS) ? window.TIPS_VISIBLE_MS : 15000;
      const FADE_MS    = (typeof window !== 'undefined' && window.TIPS_FADE_MS) ? window.TIPS_FADE_MS : 1000;

      this._showOne(this._idx);

      // Sequence: visible -> wait -> remove 'visible' -> wait fade -> next
      this._timers.push(setTimeout(() => {
        if (this.hidden) return;
        el.classList.remove('visible');
        this._timers.push(setTimeout(() => {
          if (this.hidden) return;
          const list = this._getList(); if (!list.length) return;
          this._idx = (this._idx + 1) % list.length;
          this._cycleLoop();
        }, FADE_MS));
      }, VISIBLE_MS));
    }

    // Start rotation with initial delay.
    cycle() {
      const list = this._getList(); if (!list.length || this._running) return;
      this._running = true; this._idx = 0;
      this.show(); // make sure the host is visible and centered

      const INIT_DELAY = (typeof window !== 'undefined' && window.TIPS_INIT_DELAY_MS) ? window.TIPS_INIT_DELAY_MS : 10000;
      this._timers.push(setTimeout(() => {
        if (this.hidden) return;
        this._cycleLoop();
      }, Math.max(0, INIT_DELAY)));
    }

    // Stop and reset.
    cleanup() {
      this.stopTimers();
      const el = this._host();
      if (el) el.classList.remove('visible');
    }
  }

  // ==========================================================================
  // 9) Tool output + Nodes manager
  // ==========================================================================

  class ToolOutput {
    // Placeholder for loader show (can be extended by host).
    showLoader() { return; }
    // Hide spinner elements in bot messages.
    hideLoader() {
      const elements = document.querySelectorAll('.msg-bot');
      if (elements.length > 0) elements.forEach(el => { const s = el.querySelector('.spinner'); if (s) s.style.display = 'none'; });
    }
    begin() { this.showLoader(); }
    end() { this.hideLoader(); }
    enable() { const els = document.querySelectorAll('.tool-output'); if (els.length) els[els.length - 1].style.display = 'block'; }
    disable() { const els = document.querySelectorAll('.tool-output'); if (els.length) els[els.length - 1].style.display = 'none'; }
    // Append HTML into the latest tool-output content area.
    append(content) {
      this.hideLoader(); this.enable();
      const els = document.querySelectorAll('.tool-output');
      if (els.length) { const contentEl = els[els.length - 1].querySelector('.content'); if (contentEl) contentEl.insertAdjacentHTML('beforeend', content); }
    }
    // Replace inner HTML for the latest tool-output content area.
    update(content) {
      this.hideLoader(); this.enable();
      const els = document.querySelectorAll('.tool-output');
      if (els.length) { const contentEl = els[els.length - 1].querySelector('.content'); if (contentEl) contentEl.innerHTML = content; }
    }
    // Remove children from the latest tool-output content area.
    clear() {
      this.hideLoader(); this.enable();
      const els = document.querySelectorAll('.tool-output');
      if (els.length) { const contentEl = els[els.length - 1].querySelector('.content'); if (contentEl) contentEl.replaceChildren(); }
    }
    // Toggle visibility of a specific tool output block by message id.
    toggle(id) {
      const el = document.getElementById('msg-bot-' + id); if (!el) return;
      const outputEl = el.querySelector('.tool-output'); if (!outputEl) return;
      const contentEl = outputEl.querySelector('.content');
      if (contentEl) contentEl.style.display = (contentEl.style.display === 'none') ? 'block' : 'none';
      const toggleEl = outputEl.querySelector('.toggle-cmd-output img'); if (toggleEl) toggleEl.classList.toggle('toggle-expanded');
    }
  }

    // UserCollapseManager – collapsible user messages (msg-box.msg-user)
    class UserCollapseManager {
      constructor(cfg) {
        this.cfg = cfg || {};
        // Collapse threshold in pixels (can be overridden via window.USER_MSG_COLLAPSE_HEIGHT_PX).
        this.threshold = Utils.g('USER_MSG_COLLAPSE_HEIGHT_PX', 1000);
        // Track processed .msg elements to allow cheap remeasure on resize if needed.
        this._processed = new Set();

        // Visual indicator attached while collapsed (does not modify original text).
        this.ellipsisText = ' [...]';
      }

      _icons() {
        const I = (this.cfg && this.cfg.ICONS) || {};
        return { expand: I.EXPAND || '', collapse: I.COLLAPSE || '' };
      }
      _labels() {
        const L = (this.cfg && this.cfg.LOCALE) || {};
        return { expand: L.EXPAND || 'Expand', collapse: L.COLLAPSE || 'Collapse' };
      }

      // Schedule a function for next frame (ensures layout is up-to-date before scrolling).
      _afterLayout(fn) {
        try {
          if (typeof runtime !== 'undefined' && runtime.raf && typeof runtime.raf.schedule === 'function') {
            const key = { t: 'UC:afterLayout', i: Math.random() };
            runtime.raf.schedule(key, () => { try { fn && fn(); } catch (_) {} }, 'UserCollapse', 0);
            return;
          }
        } catch (_) {}
        try { requestAnimationFrame(() => { try { fn && fn(); } catch (_) {} }); }
        catch (_) { setTimeout(() => { try { fn && fn(); } catch (__){ } }, 0); }
      }

      // Bring toggle into view with minimal scroll (upwards if it moved above after collapse).
      _scrollToggleIntoView(toggleEl) {
        if (!toggleEl || !toggleEl.isConnected) return;
        try { if (runtime && runtime.scrollMgr) { runtime.scrollMgr.userInteracted = true; runtime.scrollMgr.autoFollow = false; } } catch (_) {}
        this._afterLayout(() => {
          try {
            if (toggleEl.scrollIntoView) {
              // Prefer minimal movement; keep behavior non-animated and predictable.
              try { toggleEl.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'instant' }); }
              catch (_) { toggleEl.scrollIntoView(false); }
            }
          } catch (_) {}
        });
      }

      // Ensure wrapper and toggle exist for a given .msg element.
      _ensureStructure(msg) {
        if (!msg || !msg.isConnected) return null;

        // Wrap all direct children into a dedicated content container to measure height accurately.
        let content = msg.querySelector('.uc-content');
        if (!content) {
          content = document.createElement('div');
          content.className = 'uc-content';
          const frag = document.createDocumentFragment();
          while (msg.firstChild) frag.appendChild(msg.firstChild);
          content.appendChild(frag);
          msg.appendChild(content);
        }

        // Ensure a single toggle exists (click and keyboard accessible).
        let toggle = msg.querySelector('.uc-toggle');
        if (!toggle) {
          const icons = this._icons();
          const labels = this._labels();

          toggle = document.createElement('div');
          toggle.className = 'uc-toggle';
          toggle.tabIndex = 0;
          toggle.setAttribute('role', 'button');
          toggle.setAttribute('aria-expanded', 'false');
          toggle.title = labels.expand;

          const img = document.createElement('img');
          img.className = 'uc-toggle-icon';
          img.alt = labels.expand;
          img.src = icons.expand;

          // Provide a sane default size even if CSS did not load yet (CSS will override when present).
          img.width = 26;   // keep in sync with CSS fallback var(--uc-toggle-icon-size, 26px)
          img.height = 26;  // ensures a consistent, non-tiny control from the first paint

          toggle.appendChild(img);

          // Attach local listeners (no global handler change; production-safe).
          toggle.addEventListener('click', (ev) => {
            ev.preventDefault();
            ev.stopPropagation();
            this.toggleFromToggle(toggle);
          });
          toggle.addEventListener('keydown', (ev) => {
            if (ev.key === 'Enter' || ev.key === ' ') {
              ev.preventDefault();
              ev.stopPropagation();
              this.toggleFromToggle(toggle);
            }
          }, { passive: false });

          msg.appendChild(toggle);
        }

        this._processed.add(msg);
        msg.dataset.ucInit = '1';
        return { content, toggle };
      }

      // Create or update the ellipsis indicator inside content (absolute in the bottom-right corner).
      _ensureEllipsisEl(msg, contentEl) {
        const content = contentEl || (msg && msg.querySelector('.uc-content'));
        if (!content) return null;

        // Ensure the content becomes a positioning context only when needed.
        if (getComputedStyle(content).position === 'static') {
          content.style.position = 'relative';
        }

        let dot = content.querySelector('.uc-ellipsis');
        if (!dot) {
          dot = document.createElement('span');
          dot.className = 'uc-ellipsis';
          dot.textContent = this.ellipsisText;
          // Inline, theme-agnostic styles; kept minimal and non-interactive.
          dot.style.position = 'absolute';
          dot.style.right = '0';
          dot.style.bottom = '0';
          dot.style.paddingLeft = '6px';
          dot.style.pointerEvents = 'none';
          dot.style.zIndex = '1';
          dot.style.fontWeight = '500';
          dot.style.opacity = '0.75';

          content.appendChild(dot);
        }
        return dot;
      }

      // Show ellipsis only when there is hidden overflow (collapsed).
      _showEllipsis(msg, contentEl) {
        const dot = this._ensureEllipsisEl(msg, contentEl);
        if (dot) dot.style.display = 'inline';
      }
      // Hide and clean ellipsis when not needed (expanded or short content).
      _hideEllipsis(msg) {
        const content = msg && msg.querySelector('.uc-content');
        if (!content) return;
        const dot = content.querySelector('.uc-ellipsis');
        if (dot && dot.parentNode) {
          // Remove the indicator to avoid accidental copy/select and keep DOM lean.
          dot.parentNode.removeChild(dot);
        }
        // Drop positioning context when no indicator is present (keep styles minimal).
        try {
          if (content && content.style && content.querySelector('.uc-ellipsis') == null) {
            content.style.position = '';
          }
        } catch (_) {}
      }

      // Apply collapse to all user messages under root.
      apply(root) {
        const scope = root || document;
        let list;
        if (scope.nodeType === 1) list = scope.querySelectorAll('.msg-box.msg-user .msg');
        else list = document.querySelectorAll('.msg-box.msg-user .msg');
        if (!list || !list.length) return;

        for (let i = 0; i < list.length; i++) {
          const msg = list[i];
          const st = this._ensureStructure(msg);
          if (!st) continue;
          this._update(msg, st.content, st.toggle);
        }
      }

      // Update collapsed/expanded state depending on content height.
      _update(msg, contentEl, toggleEl) {
        const c = contentEl || (msg && msg.querySelector('.uc-content'));
        if (!msg || !c) return;

        // Special-case: when threshold = 0 (or '0'), auto-collapse is globally disabled.
        // We avoid any measurement, force the content to be fully expanded, and ensure the toggle is hidden.
        // This preserves public API while providing an explicit opt-out, without impacting existing behavior.
        if (this.threshold === 0 || this.threshold === '0') {
          const t = toggleEl || msg.querySelector('.uc-toggle');
          const labels = this._labels();

          // Ensure expanded state and remove any limiting classes.
          c.classList.remove('uc-collapsed');
          c.classList.remove('uc-expanded'); // No class => fully expanded by default CSS.
          msg.dataset.ucState = 'expanded';

          // Hide ellipsis in disabled mode.
          this._hideEllipsis(msg);

          // Hide toggle in disabled mode to avoid user interaction.
          if (t) {
            t.classList.remove('visible');
            t.setAttribute('aria-expanded', 'false');
            t.title = labels.expand;
            const img = t.querySelector('img');
            if (img) { img.alt = labels.expand; }
          }
          return; // Do not proceed with measuring or collapsing.
        }

        // Temporarily remove limiting classes for precise measurement.
        c.classList.remove('uc-collapsed');
        c.classList.remove('uc-expanded');

        const fullHeight = Math.ceil(c.scrollHeight);
        const labels = this._labels();
        const icons = this._icons();
        const t = toggleEl || msg.querySelector('.uc-toggle');

        if (fullHeight > this.threshold) {
          if (t) t.classList.add('visible');
          const desired = msg.dataset.ucState || 'collapsed';
          const expand = (desired === 'expanded');

          if (expand) {
            c.classList.add('uc-expanded');
            this._hideEllipsis(msg); // Expanded => no ellipsis
          } else {
            c.classList.add('uc-collapsed');
            this._showEllipsis(msg, c); // Collapsed => show ellipsis overlay
          }

          if (t) {
            const img = t.querySelector('img');
            if (img) {
              if (expand) { img.src = icons.collapse; img.alt = labels.collapse; }
              else { img.src = icons.expand; img.alt = labels.expand; }
            }
            t.setAttribute('aria-expanded', expand ? 'true' : 'false');
            t.title = expand ? labels.collapse : labels.expand;
          }
        } else {
          // Short content – ensure fully expanded and hide toggle + ellipsis.
          c.classList.remove('uc-collapsed');
          c.classList.remove('uc-expanded');
          msg.dataset.ucState = 'expanded';
          this._hideEllipsis(msg);
          if (t) {
            t.classList.remove('visible');
            t.setAttribute('aria-expanded', 'false');
            t.title = labels.expand;
          }
        }
      }

      // Toggle handler via the toggle element (div.uc-toggle).
      toggleFromToggle(toggleEl) {
        const msg = toggleEl && toggleEl.closest ? toggleEl.closest('.msg-box.msg-user .msg') : null;
        if (!msg) return;
        this.toggle(msg);
      }

      // Core toggle logic.
      toggle(msg) {
        if (!msg || !msg.isConnected) return;
        const c = msg.querySelector('.uc-content'); if (!c) return;
        const t = msg.querySelector('.uc-toggle');
        const labels = this._labels();
        const icons = this._icons();

        const isCollapsed = c.classList.contains('uc-collapsed');
        if (isCollapsed) {
          // Expand – leave scroll as-is; remove ellipsis.
          c.classList.remove('uc-collapsed');
          c.classList.add('uc-expanded');
          msg.dataset.ucState = 'expanded';
          this._hideEllipsis(msg);
          if (t) {
            t.setAttribute('aria-expanded', 'true');
            t.title = labels.collapse;
            const img = t.querySelector('img'); if (img) { img.src = icons.collapse; img.alt = labels.collapse; }
          }
        } else {
          // Collapse – apply classes, show ellipsis, then bring toggle into view (scroll up if needed).
          c.classList.remove('uc-expanded');
          c.classList.add('uc-collapsed');
          msg.dataset.ucState = 'collapsed';
          this._showEllipsis(msg, c);
          if (t) {
            t.setAttribute('aria-expanded', 'false');
            t.title = labels.expand;
            const img = t.querySelector('img'); if (img) { img.src = icons.expand; img.alt = labels.expand; }
            // Follow the collapsing content upward – keep the toggle visible.
            this._scrollToggleIntoView(t);
          }
        }
      }

      // Optional public method to re-evaluate height after layout/resize.
      remeasureAll() {
        const arr = Array.from(this._processed || []);
        for (let i = 0; i < arr.length; i++) {
          const msg = arr[i];
          if (!msg || !msg.isConnected) { this._processed.delete(msg); continue; }
          this._update(msg);
        }
      }
    }

  class NodesManager {
      constructor(dom, renderer, highlighter, math) {
        this.dom = dom;
        this.renderer = renderer;
        this.highlighter = highlighter;
        this.math = math;
        // User message collapse manager
        this._userCollapse = new UserCollapseManager(this.renderer.cfg);
      }

      // Check if HTML contains only user messages without any markdown or code features.
      _isUserOnlyContent(html) {
        try {
          const tmp = document.createElement('div');
          tmp.innerHTML = html;
          const hasBot = !!tmp.querySelector('.msg-box.msg-bot');
          const hasUser = !!tmp.querySelector('.msg-box.msg-user');
          const hasMD64 = !!tmp.querySelector('[data-md64]');
          const hasMDNative = !!tmp.querySelector('[md-block-markdown]');
          const hasCode = !!tmp.querySelector('pre code');
          const hasMath = !!tmp.querySelector('script[type^="math/tex"]');
          return hasUser && !hasBot && !hasMD64 && !hasMDNative && !hasCode && !hasMath;
        } catch (_) { return false; }
      }

      // Convert user markdown placeholders into plain text nodes.
      _materializeUserMdAsPlainText(scopeEl) {
        try {
          const nodes = scopeEl.querySelectorAll('.msg-box.msg-user [data-md64], .msg-box.msg-user [md-block-markdown]');
          nodes.forEach(el => {
            let txt = '';
            if (el.hasAttribute('data-md64')) {
              const b64 = el.getAttribute('data-md64') || '';
              el.removeAttribute('data-md64');
              try { txt = this.renderer.b64ToUtf8(b64); } catch (_) { txt = ''; }
            } else {
              // Native Markdown block in user message: keep as plain text (no markdown-it)
              try { txt = el.textContent || ''; } catch (_) { txt = ''; }
              try { el.removeAttribute('md-block-markdown'); } catch (_) {}
            }
            const span = document.createElement('span'); span.textContent = txt; el.replaceWith(span);
          });
        } catch (_) {}
      }

      // Append HTML/text into the message input container.
        // If plain text is provided, wrap it into a minimal msg-user box to keep layout consistent.
        appendToInput(content) {
          const el = this.dom.get('_append_input_'); if (!el) return;

          let html = String(content || '');
          const trimmed = html.trim();

          // If already a full msg-user wrapper, append as-is; otherwise wrap the plain text.
          const isWrapped = (trimmed.startsWith('<div') && /class=["']msg-box msg-user["']/.test(trimmed));
          if (!isWrapped) {
            // Treat incoming payload as plain text (escape + convert newlines to <br>).
            const safe = (typeof Utils !== 'undefined' && Utils.escapeHtml)
              ? Utils.escapeHtml(html)
              : String(html).replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[m]));
            const body = safe.replace(/\r?\n/g, '<br>');
            // Minimal, margin-less user message (no empty msg-extra to avoid extra spacing).
            html = `<div class="msg-box msg-user"><div class="msg"><p style="margin:0">${body}</p></div></div>`;
          }

          // Synchronous DOM update.
          el.insertAdjacentHTML('beforeend', html);

          // Apply collapse to any user messages in input area (now or later).
          try { this._userCollapse.apply(el); } catch (_) {}
        }

      // Append nodes into messages list and perform post-processing (markdown, code, math).
      appendNode(content, scrollMgr) {
        // Keep scroll behavior consistent with existing logic
        scrollMgr.userInteracted = false; scrollMgr.prevScroll = 0;
        this.dom.clearStreamBefore();

        const el = this.dom.get('_nodes_'); if (!el) return;
        el.classList.remove('empty_list');

        const userOnly = this._isUserOnlyContent(content);
        if (userOnly) {
          el.insertAdjacentHTML('beforeend', content);
          this._materializeUserMdAsPlainText(el);
          // Collapse before scrolling to ensure final height is used for scroll computations.
          try { this._userCollapse.apply(el); } catch (_) {}
          scrollMgr.scrollToBottom(false);
          scrollMgr.scheduleScrollFabUpdate();
          return;
        }

        el.insertAdjacentHTML('beforeend', content);

        try {
          // Defer post-processing (highlight/math/collapse) and perform scroll AFTER collapse.
          const maybePromise = this.renderer.renderPendingMarkdown(el);
          const post = () => {
            // Viewport highlight scheduling
            try { this.highlighter.scheduleScanVisibleCodes(null); } catch (_) {}

            // In finalize-only mode we must explicitly schedule KaTeX
            try { if (getMathMode() === 'finalize-only') this.math.schedule(el, 0, true); } catch (_) {}

            // Collapse user messages now that DOM is materialized (ensures correct height).
            try { this._userCollapse.apply(el); } catch (_) {}

            // Only now scroll to bottom and update FAB – uses post-collapse heights.
            scrollMgr.scrollToBottom(false);
            scrollMgr.scheduleScrollFabUpdate();
          };

          if (maybePromise && typeof maybePromise.then === 'function') {
            maybePromise.then(post);
          } else {
            post();
          }
        } catch (_) {
          // In case of error, do a conservative scroll to keep UX responsive.
          scrollMgr.scrollToBottom(false);
          scrollMgr.scheduleScrollFabUpdate();
        }
      }

      // Replace messages list content entirely and re-run post-processing.
      replaceNodes(content, scrollMgr) {
        // Same semantics as appendNode, but using a hard clone reset
        scrollMgr.userInteracted = false; scrollMgr.prevScroll = 0;
        this.dom.clearStreamBefore();

        const el = this.dom.hardReplaceByClone('_nodes_'); if (!el) return;
        el.classList.remove('empty_list');

        const userOnly = this._isUserOnlyContent(content);
        if (userOnly) {
          el.insertAdjacentHTML('beforeend', content);
          this._materializeUserMdAsPlainText(el);
          // Collapse before scrolling to ensure final height is used for scroll computations.
          try { this._userCollapse.apply(el); } catch (_) {}
          scrollMgr.scrollToBottom(false, true);
          scrollMgr.scheduleScrollFabUpdate();
          return;
        }

        el.insertAdjacentHTML('beforeend', content);

        try {
          // Defer KaTeX schedule to post-Markdown to avoid races and collapse before scroll.
          const maybePromise = this.renderer.renderPendingMarkdown(el);
          const post = () => {
            try { this.highlighter.scheduleScanVisibleCodes(null); } catch (_) {}
            try { if (getMathMode() === 'finalize-only') this.math.schedule(el, 0, true); } catch (_) {}

            // Collapse after materialization to compute final heights correctly.
            try { this._userCollapse.apply(el); } catch (_) {}

            // Now scroll and update FAB using the collapsed layout.
            scrollMgr.scrollToBottom(false, true);
            scrollMgr.scheduleScrollFabUpdate();
          };

          if (maybePromise && typeof maybePromise.then === 'function') {
            maybePromise.then(post);
          } else {
            post();
          }
        } catch (_) {
          scrollMgr.scrollToBottom(false, true);
          scrollMgr.scheduleScrollFabUpdate();
        }
      }

      // Append "extra" content into a specific bot message and post-process locally.
      appendExtra(id, content, scrollMgr) {
        const el = document.getElementById('msg-bot-' + id); if (!el) return;
        const extra = el.querySelector('.msg-extra'); if (!extra) return;

        extra.insertAdjacentHTML('beforeend', content);

        try {
          const maybePromise = this.renderer.renderPendingMarkdown(extra);

          const post = () => {
            const activeCode = (typeof runtime !== 'undefined' && runtime.stream) ? runtime.stream.activeCode : null;

            // Attach observers after Markdown produced the nodes
            try {
              this.highlighter.observeNewCode(extra, {
                deferLastIfStreaming: true,
                minLinesForLast: this.renderer.cfg.PROFILE_CODE.minLinesForHL,
                minCharsForLast: this.renderer.cfg.PROFILE_CODE.minCharsForHL
              }, activeCode);
              this.highlighter.observeMsgBoxes(extra, (box) => this._onBox(box));
            } catch (_) {}

            // KaTeX: honor stream mode; in finalize-only force immediate schedule
            try {
              const mm = getMathMode();
              if (mm === 'finalize-only') this.math.schedule(extra, 0, true);
              else this.math.schedule(extra);
            } catch (_) {}
          };

          if (maybePromise && typeof maybePromise.then === 'function') {
            maybePromise.then(post);
          } else {
            post();
          }
        } catch (_) { /* swallow */ }

        scrollMgr.scheduleScroll(true);
      }

      // When a new message box appears, hook up code/highlight handlers.
      _onBox(box) {
        const activeCode = (typeof runtime !== 'undefined' && runtime.stream) ? runtime.stream.activeCode : null;
        this.highlighter.observeNewCode(box, {
          deferLastIfStreaming: true,
          minLinesForLast: this.renderer.cfg.PROFILE_CODE.minLinesForHL,
          minCharsForLast: this.renderer.cfg.PROFILE_CODE.minCharsForHL
        }, activeCode);
        this.renderer.hooks.codeScrollInit(box);
      }

      // Remove message by id and keep scroll consistent.
      removeNode(id, scrollMgr) {
        scrollMgr.prevScroll = 0;
        let el = document.getElementById('msg-user-' + id); if (el) el.remove();
        el = document.getElementById('msg-bot-' + id); if (el) el.remove();
        this.dom.resetEphemeral();
        try { this.renderer.renderPendingMarkdown(); } catch (_) {}
        scrollMgr.scheduleScroll(true);
      }

      // Remove all messages from (and including) a given message id.
      removeNodesFromId(id, scrollMgr) {
        scrollMgr.prevScroll = 0;
        const container = this.dom.get('_nodes_'); if (!container) return;
        const elements = container.querySelectorAll('.msg-box');
        let remove = false;
        elements.forEach((element) => {
          if (element.id && element.id.endsWith('-' + id)) remove = true;
          if (remove) element.remove();
        });
        this.dom.resetEphemeral();
        try { this.renderer.renderPendingMarkdown(container); } catch (_) {}
        scrollMgr.scheduleScroll(true);
      }
    }

   // ==========================================================================
  // 9a) Template engine for JSON nodes
  // ==========================================================================

  class NodeTemplateEngine {
    // JS-side templates for nodes rendered from JSON payload (RenderBlock).
    constructor(cfg, logger) {
      this.cfg = cfg || {};
      this.logger = logger || { debug: () => {} };
    }

    _esc(s) { return (s == null) ? '' : String(s); }
    _escapeHtml(s) { return (typeof Utils !== 'undefined') ? Utils.escapeHtml(s) : String(s).replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[m])); }

    // Render name header given role
    _nameHeader(role, name, avatarUrl) {
      if (!name && !avatarUrl) return '';
      const cls = (role === 'user') ? 'name-user' : 'name-bot';
      const img = avatarUrl ? `<img src="${this._esc(avatarUrl)}" class="avatar"> ` : '';
      return `<div class="name-header ${cls}">${img}${this._esc(name || '')}</div>`;
    }

    // Render user message block
    _renderUser(block) {
      const id = block.id;
      const inp = block.input || {};
      const msgId = `msg-user-${id}`;

      // NOTE: timestamps intentionally disabled on frontend
      // let ts = '';
      // if (inp.timestamp) { ... }

      const personalize = !!(block && block.extra && block.extra.personalize === true);
      const nameHeader = personalize ? this._nameHeader('user', inp.name || '', inp.avatar_img || null) : '';

      const content = this._escapeHtml(inp.text || '').replace(/\r?\n/g, '<br>');
      return `<div class="msg-box msg-user" id="${msgId}">${nameHeader}<div class="msg"><p style="margin:0">${content}</p></div></div>`;
    }

    // Render extra blocks (images/files/urls/docs/tool-extra)
    _renderExtras(block) {
      const parts = [];

      // images
      const images = block.images || {};
      const keysI = Object.keys(images);
      if (keysI.length) {
        keysI.forEach((k) => {
          const it = images[k];
          if (!it) return;
          const url = this._esc(it.url); const path = this._esc(it.path); const bn = this._esc(it.basename || '');
          if (it.is_video) {
            const src = (it.ext === '.webm' || !it.webm_path) ? path : this._esc(it.webm_path);
            const ext = (src.endsWith('.webm') ? 'webm' : (path.split('.').pop() || 'mp4'));
            parts.push(
              `<div class="extra-src-video-box" title="${url}">` +
                `<video class="video-player" controls>` +
                  `<source src="${src}" type="video/${ext}">` +
                `</video>` +
                `<p><a href="bridge://play_video/${url}" class="title">${this._escapeHtml(bn)}</a></p>` +
              `</div>`
            );
          } else {
            parts.push(
              `<div class="extra-src-img-box" title="${url}">` +
                `<div class="img-outer"><div class="img-wrapper"><a href="${url}"><img src="${path}" class="image"></a></div>` +
                `<a href="${url}" class="title">${this._escapeHtml(bn)}</a></div>` +
              `</div><br/>`
            );
          }
        });
      }

      // files
      const files = block.files || {};
      const kF = Object.keys(files);
      if (kF.length) {
        const rows = [];
        kF.forEach((k) => {
          const it = files[k]; if (!it) return;
          const url = this._esc(it.url); const path = this._esc(it.path);
          const icon = (typeof window !== 'undefined' && window.ICON_ATTACHMENTS) ? `<img src="${window.ICON_ATTACHMENTS}" class="extra-src-icon">` : '';
          rows.push(`${icon} <b> [${k}] </b> <a href="${url}">${path}</a>`);
        });
        if (rows.length) parts.push(`<div>${rows.join("<br/><br/>")}</div>`);
      }

      // urls
      const urls = block.urls || {};
      const kU = Object.keys(urls);
      if (kU.length) {
        const rows = [];
        kU.forEach((k) => {
          const it = urls[k]; if (!it) return;
          const url = this._esc(it.url);
          const icon = (typeof window !== 'undefined' && window.ICON_URL) ? `<img src="${window.ICON_URL}" class="extra-src-icon">` : '';
          rows.push(`${icon}<a href="${url}" title="${url}">${url}</a> <small> [${k}] </small>`);
        });
        if (rows.length) parts.push(`<div>${rows.join("<br/><br/>")}</div>`);
      }

      // docs (render on JS) or fallback to docs_html
      const extra = block.extra || {};
      const docsRaw = Array.isArray(extra.docs) ? extra.docs : null;

      if (docsRaw && docsRaw.length) {
        const icon = (typeof window !== 'undefined' && window.ICON_DB) ? `<img src="${window.ICON_DB}" class="extra-src-icon">` : '';
        const prefix = (typeof window !== 'undefined' && window.LOCALE_DOC_PREFIX) ? String(window.LOCALE_DOC_PREFIX) : 'Doc:';
        const limit = 3;

        // normalize: [{uuid, meta}] OR [{ uuid: {...} }]
        const normalized = [];
        docsRaw.forEach((it) => {
          if (!it || typeof it !== 'object') return;
          if ('uuid' in it && 'meta' in it && typeof it.meta === 'object') {
            normalized.push({ uuid: String(it.uuid), meta: it.meta || {} });
          } else {
            const keys = Object.keys(it);
            if (keys.length === 1) {
              const uuid = keys[0];
              const meta = it[uuid];
              if (meta && typeof meta === 'object') {
                normalized.push({ uuid: String(uuid), meta });
              }
            }
          }
        });

        const rows = [];
        for (let i = 0; i < Math.min(limit, normalized.length); i++) {
          const d = normalized[i];
          const meta = d.meta || {};
          const entries = Object.keys(meta).map(k => `<b>${this._escapeHtml(k)}:</b> ${this._escapeHtml(String(meta[k]))}`).join(', ');
          rows.push(`<p><small>[${i + 1}] ${this._escapeHtml(d.uuid)}: ${entries}</small></p>`);
        }
        if (rows.length) {
          parts.push(`<p>${icon}<small><b>${this._escapeHtml(prefix)}:</b></small></p>`);
          parts.push(`<div class="cmd"><p>${rows.join('')}</p></div>`);
        }
      } else {
        // backward compat
        const docs_html = extra && extra.docs_html ? String(extra.docs_html) : '';
        if (docs_html) parts.push(docs_html);
      }

      // plugin-driven tool extra HTML
      const tool_extra_html = extra && extra.tool_extra_html ? String(extra.tool_extra_html) : '';
      if (tool_extra_html) parts.push(`<div class="msg-extra">${tool_extra_html}</div>`);

      return parts.join('');
    }

    // Render message-level actions
    _renderActions(block) {
      const extra = block.extra || {};
      const actions = extra.actions || [];
      if (!actions || !actions.length) return '';
      const parts = actions.map((a) => {
        const href = this._esc(a.href || '#');
        const title = this._esc(a.title || '');
        const icon = this._esc(a.icon || '');
        const id = this._esc(a.id || block.id);
        return `<a href="${href}" class="action-icon" data-id="${id}" role="button"><span class="cmd"><img src="${icon}" class="action-img" title="${title}" alt="${title}" data-id="${id}"></span></a>`;
      });
      return `<div class="action-icons" data-id="${this._esc(block.id)}">${parts.join('')}</div>`;
    }

    // Render tool output wrapper (always collapsed by default; wrapper visibility depends on flag)
    _renderToolOutputWrapper(block) {
      const extra = block.extra || {};
      const tool_output = this._esc(extra.tool_output || '');

      // Wrapper visibility: show/hide based on tool_output_visible...
      const wrapperDisplay = (extra.tool_output_visible === true) ? '' : 'display:none';

      const toggleTitle = (typeof trans !== 'undefined' && trans) ? trans('action.cmd.expand') : 'Expand';
      const expIcon = (typeof window !== 'undefined' && window.ICON_EXPAND) ? window.ICON_EXPAND : '';

      return (
        `<div class='tool-output' style='${wrapperDisplay}'>` +
          `<span class='toggle-cmd-output' onclick='toggleToolOutput(${this._esc(block.id)});' ` +
          `title='${this._escapeHtml(toggleTitle)}' role='button'>` +
            `<img src='${this._esc(expIcon)}' width='25' height='25' valign='middle'>` +
          `</span>` +
          // IMPORTANT: content is always collapsed initially
          `<div class='content' style='display:none'>${this._escapeHtml(tool_output)}</div>` +
        `</div>`
      );
    }

    // Render bot message block (md-block-markdown)
   _renderBot(block) {
      const id = block.id;
      const out = block.output || {};
      const msgId = `msg-bot-${id}`;

      // NOTE: timestamps intentionally disabled on frontend
      // let ts = '';
      // if (out.timestamp) { ... }

      const personalize = !!(block && block.extra && block.extra.personalize === true);
      const nameHeader = personalize ? this._nameHeader('bot', out.name || '', out.avatar_img || null) : '';

      const mdText = this._escapeHtml(out.text || '');
      const toolWrap = this._renderToolOutputWrapper(block);
      const extras = this._renderExtras(block);
      const actions = (block.extra && block.extra.footer_icons) ? this._renderActions(block) : '';
      const debug = (block.extra && block.extra.debug_html) ? String(block.extra.debug_html) : '';

      return (
        `<div class='msg-box msg-bot' id='${msgId}'>` +
          `${nameHeader}` +
          `<div class='msg'>` +
            `<div class='md-block' md-block-markdown='1'>${mdText}</div>` +
            `<div class='msg-tool-extra'></div>` +
            `${toolWrap}` +
            `<div class='msg-extra'>${extras}</div>` +
            `${actions}${debug}` +
          `</div>` +
        `</div>`
      );
    }

    // Render one RenderBlock into HTML (may produce 1 or 2 messages – input and/or output)
    renderNode(block) {
      const parts = [];
      if (block && block.input && block.input.text) parts.push(this._renderUser(block));
      if (block && block.output && block.output.text) parts.push(this._renderBot(block));
      return parts.join('');
    }

    // Render array of blocks
    renderNodes(blocks) {
      if (!Array.isArray(blocks)) return '';
      const out = [];
      for (let i = 0; i < blocks.length; i++) {
        const b = blocks[i] || null;
        if (!b) continue;
        out.push(this.renderNode(b));
      }
      return out.join('');
    }
  }

  // ==========================================================================
  // 9b) Data receiver for append/replace nodes
  // ==========================================================================

  class DataReceiver {
    // Normalizes payload (HTML string or JSON) and delegates to NodesManager.
    constructor(cfg, templates, nodes, scrollMgr) {
      this.cfg = cfg || {};
      this.templates = templates;
      this.nodes = nodes;
      this.scrollMgr = scrollMgr;
    }

    _tryParseJSON(s) {
      if (typeof s !== 'string') return s;
      const t = s.trim();
      if (!t) return null;
      // If it's like HTML, don't parse as JSON
      if (t[0] === '<') return null;
      try { return JSON.parse(t); } catch (_) { return null; }
    }

    _normalizeToBlocks(obj) {
      if (!obj) return [];
      if (Array.isArray(obj)) return obj;
      if (obj.node) return [obj.node];
      if (obj.nodes) return (Array.isArray(obj.nodes) ? obj.nodes : []);
      // single node-like object
      if (typeof obj === 'object' && (obj.input || obj.output || obj.id)) return [obj];
      return [];
    }

    append(payload) {
      // Legacy HTML string?
      if (typeof payload === 'string' && payload.trim().startsWith('<')) {
        this.nodes.appendNode(payload, this.scrollMgr);
        return;
      }
      // Try JSON
      const obj = this._tryParseJSON(payload);
      if (!obj) {
        // Not JSON – pass through
        this.nodes.appendNode(String(payload), this.scrollMgr);
        return;
      }
      const blocks = this._normalizeToBlocks(obj);
      if (!blocks.length) {
        this.nodes.appendNode('', this.scrollMgr);
        return;
      }
      const html = this.templates.renderNodes(blocks);
      this.nodes.appendNode(html, this.scrollMgr);
    }

    replace(payload) {
      // Legacy HTML string?
      if (typeof payload === 'string' && payload.trim().startsWith('<')) {
        this.nodes.replaceNodes(payload, this.scrollMgr);
        return;
      }
      // Try JSON
      const obj = this._tryParseJSON(payload);
      if (!obj) {
        this.nodes.replaceNodes(String(payload), this.scrollMgr);
        return;
      }
      const blocks = this._normalizeToBlocks(obj);
      if (!blocks.length) {
        this.nodes.replaceNodes('', this.scrollMgr);
        return;
      }
      const html = this.templates.renderNodes(blocks);
      this.nodes.replaceNodes(html, this.scrollMgr);
    }
  }

  // ==========================================================================
  // 10) UI manager
  // ==========================================================================

  class UIManager {
      // Replace or insert app-level CSS in a <style> tag.
      updateCSS(styles) {
        let style = document.getElementById('app-style');
        if (!style) { style = document.createElement('style'); style.id = 'app-style'; document.head.appendChild(style); }
        style.textContent = styles;
      }
      // Ensure base styles for code header sticky behavior exist.
      ensureStickyHeaderStyle() {
        let style = document.getElementById('code-sticky-style');
        if (style) return;
        style = document.createElement('style'); style.id = 'code-sticky-style';
        style.textContent = [
          '.code-wrapper { position: relative; }',
          '.code-wrapper .code-header-wrapper { position: sticky; top: var(--code-header-sticky-top, 0px); z-index: 2; box-shadow: 0 1px 0 rgba(0,0,0,.06); }',
          '.code-wrapper pre { overflow: visible; margin-top: 0; }',
          '.code-wrapper pre code { display: block; white-space: pre; max-height: 100dvh; overflow: auto;',
          '  overscroll-behavior: contain; -webkit-overflow-scrolling: touch; overflow-anchor: none; scrollbar-gutter: stable both-edges; scroll-behavior: auto; }',
          '#_loader_.hidden { display: none !important; visibility: hidden !important; }',
          '#_loader_.visible { display: block; visibility: visible; }',

          /* User message collapse (uc-*) */
          '.msg-box.msg-user .msg { position: relative; }',
          '.msg-box.msg-user .msg > .uc-content { display: block; overflow: visible; }',
          '.msg-box.msg-user .msg > .uc-content.uc-collapsed { max-height: 1000px; overflow: hidden; }',
          '.msg-box.msg-user .msg > .uc-toggle { display: none; margin-top: 8px; text-align: center; cursor: pointer; user-select: none; }',
          '.msg-box.msg-user .msg > .uc-toggle.visible { display: block; }',

          /* Increased toggle icon size to a comfortable/default size.
             Overridable via CSS var --uc-toggle-icon-size to keep host-level control. */
          '.msg-box.msg-user .msg > .uc-toggle img { width: var(--uc-toggle-icon-size, 26px); height: var(--uc-toggle-icon-size, 26px); opacity: .8; }',
          '.msg-box.msg-user .msg > .uc-toggle:hover img { opacity: 1; }'
        ].join('\n');
        document.head.appendChild(style);
      }
      // Toggle classes controlling optional UI features.
      enableEditIcons() { document.body && document.body.classList.add('display-edit-icons'); }
      disableEditIcons() { document.body && document.body.classList.remove('display-edit-icons'); }
      enableTimestamp() { document.body && document.body.classList.add('display-timestamp'); }
      disableTimestamp() { document.body && document.body.classList.remove('display-timestamp'); }
      enableBlocks() { document.body && document.body.classList.add('display-blocks'); }
      disableBlocks() { document.body && document.body.classList.remove('display-blocks'); }
    }

  // ==========================================================================
  // 11) Stream snapshot engine + incremental code streaming
  // ==========================================================================

  class StreamEngine {
    constructor(cfg, dom, renderer, math, highlighter, codeScroll, scrollMgr, raf, asyncer, logger) {
      this.cfg = cfg; this.dom = dom; this.renderer = renderer; this.math = math;
      this.highlighter = highlighter; this.codeScroll = codeScroll; this.scrollMgr = scrollMgr; this.raf = raf;
      this.asyncer = asyncer;
      this.logger = logger || new Logger(cfg);

      // Streaming buffer (rope-like) – avoids O(n^2) string concatenation when many small chunks arrive.
      // streamBuf holds the already materialized prefix; _sbParts keeps recent tail parts; _sbLen tracks their length.
      this.streamBuf = '';     // materialized prefix (string used by render)
      this._sbParts = [];      // pending string chunks (array) not yet joined
      this._sbLen = 0;         // length of pending chunks

      this.fenceOpen = false; this.fenceMark = '`'; this.fenceLen = 3;
      this.fenceTail = ''; this.fenceBuf = '';
      this.lastSnapshotTs = 0; this.nextSnapshotStep = cfg.PROFILE_TEXT.base;
      this.snapshotScheduled = false; this.snapshotRAF = 0;

      this.codeStream = { open: false, lines: 0, chars: 0 };
      this.activeCode = null;

      this.suppressPostFinalizePass = false;

      this._promoteScheduled = false;

      // Guard to ensure first fence-open is materialized immediately when stream starts with code.
      this._firstCodeOpenSnapDone = false;

      // Streaming mode flag – controls reduced rendering (no linkify etc.) on hot path.
      this.isStreaming = false;

      // Tracks whether renderSnapshot injected a one-off synthetic EOL for parsing an open fence
      // (used to strip it from the initial streaming tail to avoid "#\n foo" on first line).
      this._lastInjectedEOL = false;

      this._customFenceSpecs = [];   // [{ open, close }, ...]
      this._fenceCustom = null;      // currently active custom fence spec or null
    }
    _d(tag, data) { this.logger.debug('STREAM', tag, data); }

    setCustomFenceSpecs(specs) {
      this._customFenceSpecs = Array.isArray(specs) ? specs.slice() : [];
    }

    // --- Rope buffer helpers (internal) ---

    // Append a chunk into the rope without immediately touching the large string.
    _appendChunk(s) {
      if (!s) return;
      this._sbParts.push(s);
      this._sbLen += s.length;
    }
    // Current logical length of the stream text (materialized prefix + pending tail).
    getStreamLength() {
      return (this.streamBuf.length + this._sbLen);
    }
    // Materialize the rope into a single string for rendering (cheap if nothing pending).
    getStreamText() {
      if (this._sbLen > 0) {
        // Join pending parts into the materialized prefix and clear the tail.
        // Single-part fast path avoids a temporary array join.
        this.streamBuf += (this._sbParts.length === 1 ? this._sbParts[0] : this._sbParts.join(''));
        this._sbParts.length = 0;
        this._sbLen = 0;
      }
      return this.streamBuf;
    }
    // Reset the rope to an empty state.
    _clearStreamBuffer() {
      this.streamBuf = '';
      this._sbParts.length = 0;
      this._sbLen = 0;
    }

    // Reset all streaming state and counters.
    reset() {
      this._clearStreamBuffer();
      this.fenceOpen = false; this.fenceMark = '`'; this.fenceLen = 3;
      this.fenceTail = ''; this.fenceBuf = '';
      this.lastSnapshotTs = 0; this.nextSnapshotStep = this.profile().base;
      this.snapshotScheduled = false; this.snapshotRAF = 0;
      this.codeStream = { open: false, lines: 0, chars: 0 };
      this.activeCode = null; this.suppressPostFinalizePass = false;
      this._promoteScheduled = false;
      this._firstCodeOpenSnapDone = false;

      // Clear any previous synthetic EOL marker.
      this._lastInjectedEOL = false;
      this._fenceCustom = null;

      this._d('RESET', { });
    }
    // Turn active streaming code block into plain text (safety on abort).
    defuseActiveToPlain() {
      if (!this.activeCode || !this.activeCode.codeEl || !this.activeCode.codeEl.isConnected) return;
      const codeEl = this.activeCode.codeEl;
      const fullText = (this.activeCode.frozenEl?.textContent || '') + (this.activeCode.tailEl?.textContent || '');
      try {
        codeEl.textContent = fullText;
        codeEl.removeAttribute('data-highlighted');
        codeEl.classList.remove('hljs');
        codeEl.dataset._active_stream = '0';
        const st = this.codeScroll.state(codeEl); st.autoFollow = false;
      } catch (_) {}
      this._d('DEFUSE_ACTIVE_TO_PLAIN', { len: fullText.length });
      this.activeCode = null;
    }
    // If there are orphan streaming code blocks in DOM, finalize them as plain text.
    defuseOrphanActiveBlocks(root) {
      try {
        const scope = root || document;
        const nodes = scope.querySelectorAll('pre code[data-_active_stream="1"]');
        let n = 0;
        nodes.forEach(codeEl => {
          if (!codeEl.isConnected) return;
          let text = '';
          const frozen = codeEl.querySelector('.hl-frozen');
          const tail = codeEl.querySelector('.hl-tail');
          if (frozen || tail) text = (frozen?.textContent || '') + (tail?.textContent || '');
          else text = codeEl.textContent || '';
          codeEl.textContent = text;
          codeEl.removeAttribute('data-highlighted');
          codeEl.classList.remove('hljs');
          codeEl.dataset._active_stream = '0';
          try { this.codeScroll.attachHandlers(codeEl); } catch (_) {}
          n++;
        });
        if (n) this._d('DEFUSE_ORPHAN_ACTIVE_BLOCKS', { count: n });
      } catch (e) { this._d('DEFUSE_ORPHAN_ACTIVE_ERR', String(e)); }
    }
    // Abort streaming and clear state with options.
    abortAndReset(opts) {
      const o = Object.assign({
        finalizeActive: true,
        clearBuffer: true,
        clearMsg: false,
        defuseOrphans: true,
        reason: '',
        suppressLog: false
      }, (opts || {}));

      try { this.raf.cancelGroup('StreamEngine'); } catch (_) {}
      try { this.raf.cancel('SE:snapshot'); } catch (_) {}
      this.snapshotScheduled = false; this.snapshotRAF = 0;

      const hadActive = !!this.activeCode;
      try {
        if (this.activeCode) {
          if (o.finalizeActive === true) this.finalizeActiveCode();
          else this.defuseActiveToPlain();
        }
      } catch (e) {
        this._d('ABORT_FINALIZE_ERR', String(e));
      }

      if (o.defuseOrphans) {
        try { this.defuseOrphanActiveBlocks(); }
        catch (e) { this._d('ABORT_DEFUSE_ORPHANS_ERR', String(e)); }
      }

      if (o.clearBuffer) {
        this._clearStreamBuffer();
        this.fenceOpen = false; this.fenceMark = '`'; this.fenceLen = 3;
        this.fenceTail = ''; this.fenceBuf = '';
        this.codeStream.open = false; this.codeStream.lines = 0; this.codeStream.chars = 0;
        window.__lastSnapshotLen = 0;
      }
      if (o.clearMsg === true) {
        try { this.dom.resetEphemeral(); } catch (_) {}
      }
      if (!o.suppressLog) this._d('ABORT_AND_RESET', { hadActive, ...o });
    }
    // Select profile for current stream state (code vs text).
    profile() { return this.fenceOpen ? this.cfg.PROFILE_CODE : this.cfg.PROFILE_TEXT; }
    // Reset adaptive snapshot budget to base.
    resetBudget() { this.nextSnapshotStep = this.profile().base; }
    // Check whether [from, end) contains only spaces/tabs.
    onlyTrailingWhitespace(s, from, end) {
      for (let i = from; i < end; i++) { const c = s.charCodeAt(i); if (c !== 0x20 && c !== 0x09) return false; }
      return true;
    }
    // Update fence state based on a fresh chunk and buffer tail; detect openings and closings.
    updateFenceHeuristic(chunk) {
      const prev = (this.fenceBuf || '');
      const s = prev + (chunk || '');
      const preLen = prev.length;
      const n = s.length; let i = 0;
      let opened = false; let closed = false; let splitAt = -1;
      let atLineStart = (preLen === 0) ? true : /[\n\r]$/.test(prev);

      const inNewOrCrosses = (j, k) => (j >= preLen) || (k > preLen);

      while (i < n) {
        const ch = s[i];
        if (ch === '\r' || ch === '\n') { atLineStart = true; i++; continue; }
        if (!atLineStart) { i++; continue; }
        atLineStart = false;

        // Skip list/blockquote/indent normalization (existing logic)
        let j = i;
        while (j < n) {
          let localSpaces = 0;
          while (j < n && (s[j] === ' ' || s[j] === '\t')) { localSpaces += (s[j] === '\t') ? 4 : 1; j++; if (localSpaces > 3) break; }
          if (j < n && s[j] === '>') { j++; if (j < n && s[j] === ' ') j++; continue; }

          let saved = j;
          if (j < n && (s[j] === '-' || s[j] === '*' || s[j] === '+')) {
            let jj = j + 1; if (jj < n && s[jj] === ' ') { j = jj + 1; } else { j = saved; }
          } else {
            let k2 = j; let hasDigit = false;
            while (k2 < n && s[k2] >= '0' && s[k2] <= '9') { hasDigit = true; k2++; }
            if (hasDigit && k2 < n && (s[k2] === '.' || s[k2] === ')')) {
              k2++; if (k2 < n && s[k2] === ' ') { j = k2 + 1; } else { j = saved; }
            } else { j = saved; }
          }
          break;
        }

        let indent = 0;
        while (j < n && (s[j] === ' ' || s[j] === '\t')) {
          indent += (s[j] === '\t') ? 4 : 1; j++; if (indent > 3) break;
        }
        if (indent > 3) { i = j; continue; }

        // 1) Custom fences first (e.g. [!exec] ... [/!exec], <execute>...</execute>)
        if (!this.fenceOpen && this._customFenceSpecs && this._customFenceSpecs.length) {
          for (let ci = 0; ci < this._customFenceSpecs.length; ci++) {
            const spec = this._customFenceSpecs[ci];
            const open = spec && spec.open ? spec.open : '';
            if (!open) continue;
            const k = j + open.length;
            if (k <= n && s.slice(j, k) === open) {
              if (!inNewOrCrosses(j, k)) { /* seen fully in previous prefix */ }
              else {
                this.fenceOpen = true; this._fenceCustom = spec; opened = true; i = k;
                this._d('FENCE_OPEN_DETECTED_CUSTOM', { open, idxStart: j, idxEnd: k, region: (j >= preLen) ? 'new' : 'cross' });
                continue; // main while
              }
            }
          }
        } else if (this.fenceOpen && this._fenceCustom && this._fenceCustom.close) {
          const close = this._fenceCustom.close;
          const k = j + close.length;
          if (k <= n && s.slice(j, k) === close) {
            // Require only trailing whitespace on the line (consistent with ``` logic)
            let eol = k; while (eol < n && s[eol] !== '\n' && s[eol] !== '\r') eol++;
            const onlyWS = this.onlyTrailingWhitespace(s, k, eol);
            if (onlyWS) {
              if (!inNewOrCrosses(j, k)) { /* seen in previous prefix */ }
              else {
                this.fenceOpen = false; this._fenceCustom = null; closed = true;
                const endInS = k;
                const rel = endInS - preLen;
                splitAt = Math.max(0, Math.min((chunk ? chunk.length : 0), rel));
                i = k;
                this._d('FENCE_CLOSE_DETECTED_CUSTOM', { close, idxStart: j, idxEnd: k, splitAt, region: (j >= preLen) ? 'new' : 'cross' });
                continue; // main while
              }
            } else {
              this._d('FENCE_CLOSE_REJECTED_CUSTOM_NON_WS_AFTER', { close, idxStart: j, idxEnd: k });
            }
          }
        }

        // 2) Standard markdown-it fences (``` or ~~~) – leave your original logic intact
        if (j < n && (s[j] === '`' || s[j] === '~')) {
          const mark = s[j]; let k = j; while (k < n && s[k] === mark) k++; const run = k - j;

          if (!this.fenceOpen) {
            if (run >= 3) {
              if (!inNewOrCrosses(j, k)) { i = k; continue; }
              this.fenceOpen = true; this.fenceMark = mark; this.fenceLen = run; opened = true; i = k;
              this._d('FENCE_OPEN_DETECTED', { mark, run, idxStart: j, idxEnd: k, region: (j >= preLen) ? 'new' : 'cross' });
              continue;
            }
          } else if (!this._fenceCustom) {
            if (mark === this.fenceMark && run >= this.fenceLen) {
              if (!inNewOrCrosses(j, k)) { i = k; continue; }
              let eol = k; while (eol < n && s[eol] !== '\n' && s[eol] !== '\r') eol++;
              if (this.onlyTrailingWhitespace(s, k, eol)) {
                this.fenceOpen = false; closed = true;
                const endInS = k;
                const rel = endInS - preLen;
                splitAt = Math.max(0, Math.min((chunk ? chunk.length : 0), rel));
                i = k;
                this._d('FENCE_CLOSE_DETECTED', { mark, run, idxStart: j, idxEnd: k, splitAt, region: (j >= preLen) ? 'new' : 'cross' });
                continue;
              } else {
                this._d('FENCE_CLOSE_REJECTED_NON_WS_AFTER', { mark, run, idxStart: j, idxEnd: k });
              }
            }
          }
        }

        i = j + 1;
      }

      const MAX_TAIL = 512;
      this.fenceBuf = s.slice(-MAX_TAIL);
      this.fenceTail = s.slice(-3);
      return { opened, closed, splitAt };
    }

    // Ensure message snapshot container exists.
    getMsgSnapshotRoot(msg) {
      if (!msg) return null;
      let snap = msg.querySelector('.md-snapshot-root');
      if (!snap) { snap = document.createElement('div'); snap.className = 'md-snapshot-root'; msg.appendChild(snap); }
      return snap;
    }
    // Detect structural boundaries in a chunk (for snapshot decisions).
    hasStructuralBoundary(chunk) { if (!chunk) return false; return /\n(\n|[-*]\s|\d+\.\s|#{1,6}\s|>\s)/.test(chunk); }
    // Decide whether we should snapshot on this chunk.
    shouldSnapshotOnChunk(chunk, chunkHasNL, hasBoundary) {
      const prof = this.profile(); const now = Utils.now();
      if (this.activeCode && this.fenceOpen) return false;
      if ((now - this.lastSnapshotTs) < prof.minInterval) return false;
      if (hasBoundary) return true;

      const delta = Math.max(0, this.getStreamLength() - (window.__lastSnapshotLen || 0));
      if (this.fenceOpen) { if (chunkHasNL && delta >= this.nextSnapshotStep) return true; return false; }
      if (delta >= this.nextSnapshotStep) return true;
      return false;
    }
    // If we are getting slow, schedule a soft snapshot based on time.
    maybeScheduleSoftSnapshot(msg, chunkHasNL) {
      const prof = this.profile(); const now = Utils.now();
      if (this.activeCode && this.fenceOpen) return;
      if (this.fenceOpen && this.codeStream.lines < 1 && !chunkHasNL) return;
      if ((now - this.lastSnapshotTs) >= prof.softLatency) this.scheduleSnapshot(msg);
    }
    // Schedule snapshot rendering (coalesced via rAF).
    scheduleSnapshot(msg, force = false) {
      if (this.snapshotScheduled && !this.raf.isScheduled('SE:snapshot')) this.snapshotScheduled = false;
      if (!force) {
        if (this.snapshotScheduled) return;
        if (this.activeCode && this.fenceOpen) return;
      } else {
        if (this.snapshotScheduled && this.raf.isScheduled('SE:snapshot')) return;
      }
      this.snapshotScheduled = true;
      this.raf.schedule('SE:snapshot', () => { this.snapshotScheduled = false; this.renderSnapshot(msg); }, 'StreamEngine', 0);
    }
    // Split code element into frozen and tail spans if needed.
    ensureSplitCodeEl(codeEl) {
      if (!codeEl) return null;
      let frozen = codeEl.querySelector('.hl-frozen'); let tail = codeEl.querySelector('.hl-tail');
      if (frozen && tail) return { codeEl, frozenEl: frozen, tailEl: tail };
      const text = codeEl.textContent || ''; codeEl.innerHTML = '';
      frozen = document.createElement('span'); frozen.className = 'hl-frozen';
      tail = document.createElement('span'); tail.className = 'hl-tail';
      codeEl.appendChild(frozen); codeEl.appendChild(tail);
      if (text) tail.textContent = text; return { codeEl, frozenEl: frozen, tailEl: tail };
    }
    // Create active code context from the latest snapshot.
    setupActiveCodeFromSnapshot(snap) {
      const codes = snap.querySelectorAll('pre code'); if (!codes.length) return null;
      const last = codes[codes.length - 1];
      const cls = Array.from(last.classList).find(c => c.startsWith('language-')) || 'language-plaintext';
      const lang = (cls.replace('language-', '') || 'plaintext');
      const parts = this.ensureSplitCodeEl(last); if (!parts) return null;

      // If we injected a synthetic EOL for parsing an open fence, remove it from the streaming tail now.
      // This prevents breaking the very first code line into "#\n foo" when the next chunk starts with " foo".
      if (this._lastInjectedEOL && parts.tailEl && parts.tailEl.textContent && parts.tailEl.textContent.endsWith('\n')) {
        parts.tailEl.textContent = parts.tailEl.textContent.slice(0, -1);
        // Reset the marker so we don't accidentally trim again in this snapshot lifecycle.
        this._lastInjectedEOL = false;
      }

      const st = this.codeScroll.state(parts.codeEl); st.autoFollow = true; st.userInteracted = false;
      parts.codeEl.dataset._active_stream = '1';
      const baseFrozenNL = Utils.countNewlines(parts.frozenEl.textContent || ''); const baseTailNL = Utils.countNewlines(parts.tailEl.textContent || '');
      const ac = { codeEl: parts.codeEl, frozenEl: parts.frozenEl, tailEl: parts.tailEl, lang, frozenLen: parts.frozenEl.textContent.length, lastPromoteTs: 0,
                   lines: 0, tailLines: baseTailNL, linesSincePromote: 0, initialLines: baseFrozenNL + baseTailNL, haltHL: false, plainStream: false };
      this._d('ACTIVE_CODE_SETUP', { lang, frozenLen: ac.frozenLen, tailLines: ac.tailLines, initialLines: ac.initialLines });
      return ac;
    }
    // Copy previous active code state into the new one (after snapshot).
    rehydrateActiveCode(oldAC, newAC) {
      if (!oldAC || !newAC) return;
      newAC.frozenEl.innerHTML = oldAC.frozenEl ? oldAC.frozenEl.innerHTML : '';
      const fullText = newAC.codeEl.textContent || ''; const remainder = fullText.slice(oldAC.frozenLen);
      newAC.tailEl.textContent = remainder;
      newAC.frozenLen = oldAC.frozenLen; newAC.lang = oldAC.lang;
      newAC.lines = oldAC.lines; newAC.tailLines = Utils.countNewlines(remainder);
      newAC.lastPromoteTs = oldAC.lastPromoteTs; newAC.linesSincePromote = oldAC.linesSincePromote || 0;
      newAC.initialLines = oldAC.initialLines || 0; newAC.haltHL = !!oldAC.haltHL;
      newAC.plainStream = !!oldAC.plainStream;
      this._d('ACTIVE_CODE_REHYDRATE', { lang: newAC.lang, frozenLen: newAC.frozenLen, tailLines: newAC.tailLines, initialLines: newAC.initialLines, halted: newAC.haltHL, plainStream: newAC.plainStream });
    }
    // Append text to active tail span and update counters.
    appendToActiveTail(text) {
      if (!this.activeCode || !this.activeCode.tailEl || !text) return;
      this.activeCode.tailEl.insertAdjacentText('beforeend', text);
      const nl = Utils.countNewlines(text);
      this.activeCode.tailLines += nl; this.activeCode.linesSincePromote += nl;
      this.codeScroll.scheduleScroll(this.activeCode.codeEl, true, false);
      if (this.logger.isEnabled('STREAM') && (nl > 0 || text.length >= 64)) {
        this._d('TAIL_APPEND', { addLen: text.length, addNL: nl, totalTailNL: this.activeCode.tailLines });
      }
    }
    // Enforce budgets: stop incremental hljs and switch to plain streaming if needed.
    enforceHLStopBudget() {
      if (!this.activeCode) return;
      // If global disable was requested, halt early and switch to plain streaming.
      if (this.cfg.HL.DISABLE_ALL) { this.activeCode.haltHL = true; this.activeCode.plainStream = true; return; }
      const stop = (this.cfg.PROFILE_CODE.stopAfterLines | 0);
      const streamPlainLines = (this.cfg.PROFILE_CODE.streamPlainAfterLines | 0);
      const streamPlainChars = (this.cfg.PROFILE_CODE.streamPlainAfterChars | 0);
      const maxFrozenChars = (this.cfg.PROFILE_CODE.maxFrozenChars | 0);

      const totalLines = (this.activeCode.initialLines || 0) + (this.activeCode.lines || 0);
      const frozenChars = this.activeCode.frozenLen | 0;
      const tailChars = (this.activeCode.tailEl?.textContent || '').length | 0;
      const totalStreamedChars = frozenChars + tailChars;

      // Switch to plain streaming after budgets – no incremental hljs
      if ((streamPlainLines > 0 && totalLines >= streamPlainLines) ||
          (streamPlainChars > 0 && totalStreamedChars >= streamPlainChars) ||
          (maxFrozenChars > 0 && frozenChars >= maxFrozenChars)) {
        this.activeCode.haltHL = true;
        this.activeCode.plainStream = true;
        try { this.activeCode.codeEl.dataset.hlStreamSuspended = '1'; } catch (_) {}
        this._d('STREAM_HL_SUSPENDED', { totalLines, totalStreamedChars, frozenChars, reason: 'budget' });
        return;
      }

      if (stop > 0 && totalLines >= stop) {
        this.activeCode.haltHL = true;
        this.activeCode.plainStream = true;
        try { this.activeCode.codeEl.dataset.hlStreamSuspended = '1'; } catch (_) {}
        this._d('STREAM_HL_SUSPENDED', { totalLines, stopAfter: stop, reason: 'stopAfterLines' });
      }
    }
    _aliasLang(token) {
      const ALIAS = {
        txt: 'plaintext', text: 'plaintext', plaintext: 'plaintext',
        sh: 'bash', shell: 'bash', zsh: 'bash', 'shell-session': 'bash',
        py: 'python', python3: 'python', py3: 'python',
        js: 'javascript', node: 'javascript', nodejs: 'javascript',
        ts: 'typescript', 'ts-node': 'typescript',
        yml: 'yaml', kt: 'kotlin', rs: 'rust',
        csharp: 'csharp', 'c#': 'csharp', 'c++': 'cpp',
        ps: 'powershell', ps1: 'powershell', pwsh: 'powershell', powershell7: 'powershell',
        docker: 'dockerfile'
      };
      const v = String(token || '').trim().toLowerCase();
      return ALIAS[v] || v;
    }
    _isHLJSSupported(lang) {
      try { return !!(window.hljs && hljs.getLanguage && hljs.getLanguage(lang)); } catch (_) { return false; }
    }
    // Try to detect language from a "language: X" style first line directive.
    _detectDirectiveLangFromText(text) {
      if (!text) return null;
      let s = String(text);
      if (s.charCodeAt(0) === 0xFEFF) s = s.slice(1);
      const lines = s.split(/\r?\n/);
      let i = 0; while (i < lines.length && !lines[i].trim()) i++;
      if (i >= lines.length) return null;
      let first = lines[i].trim();
      first = first.replace(/^\s*lang(?:uage)?\s*[:=]\s*/i, '').trim();
      let token = first.split(/\s+/)[0].replace(/:$/, '');
      if (!/^[A-Za-z][\w#+\-\.]{0,30}$/.test(token)) return null;

      let cand = this._aliasLang(token);
      const rest = lines.slice(i + 1).join('\n');
      if (!rest.trim()) return null;

      let pos = 0, seen = 0;
      while (seen < i && pos < s.length) { const nl = s.indexOf('\n', pos); if (nl === -1) return null; pos = nl + 1; seen++; }
      let end = s.indexOf('\n', pos);
      if (end === -1) end = s.length; else end = end + 1;
      return { lang: cand, deleteUpto: end };
    }
    // Update code element class to reflect new lang (language-xxx).
    _updateCodeLangClass(codeEl, newLang) {
      try {
        Array.from(codeEl.classList).forEach(c => { if (c.startsWith('language-')) codeEl.classList.remove(c); });
        codeEl.classList.add('language-' + (newLang || 'plaintext'));
      } catch (_) {}
    }
    // Update code header label and data attribute.
    _updateCodeHeaderLabel(codeEl, newLabel, newLangToken) {
      try {
        const wrap = codeEl.closest('.code-wrapper');
        if (!wrap) return;
        const span = wrap.querySelector('.code-header-lang');
        if (span) span.textContent = newLabel || (newLangToken || 'code');
        wrap.setAttribute('data-code-lang', newLangToken || '');
      } catch (_) {}
    }
    // Try to promote language from a directive and remove its header line.
    maybePromoteLanguageFromDirective() {
      if (!this.activeCode || !this.activeCode.codeEl) return;
      if (this.activeCode.lang && this.activeCode.lang !== 'plaintext') return;

      const frozenTxt = this.activeCode.frozenEl ? this.activeCode.frozenEl.textContent : '';
      const tailTxt = this.activeCode.tailEl ? this.activeCode.tailEl.textContent : '';
      const combined = frozenTxt + tailTxt;
      if (!combined) return;

      const det = this._detectDirectiveLangFromText(combined);
      if (!det || !det.lang) return;

      const newLang = det.lang;
      const newCombined = combined.slice(det.deleteUpto);

      try {
        const codeEl = this.activeCode.codeEl;
        codeEl.innerHTML = '';
        const frozen = document.createElement('span'); frozen.className = 'hl-frozen';
        const tail = document.createElement('span'); tail.className = 'hl-tail';
        tail.textContent = newCombined;
        codeEl.appendChild(frozen); codeEl.appendChild(tail);
        this.activeCode.frozenEl = frozen; this.activeCode.tailEl = tail;
        this.activeCode.frozenLen = 0;
        this.activeCode.tailLines = Utils.countNewlines(newCombined);
        this.activeCode.linesSincePromote = 0;

        this.activeCode.lang = newLang;
        this._updateCodeLangClass(codeEl, newLang);
        this._updateCodeHeaderLabel(codeEl, newLang, newLang);

        this._d('LANG_PROMOTE', { to: newLang, removedChars: det.deleteUpto, tailLines: this.activeCode.tailLines });
        this.schedulePromoteTail(true);
      } catch (e) {
        this._d('LANG_PROMOTE_ERR', String(e));
      }
    }
    // Highlight a small piece of text based on language (safe fallback to escapeHtml).
    highlightDeltaText(lang, text) {
      if (this.cfg.HL.DISABLE_ALL) return Utils.escapeHtml(text);
      if (window.hljs && lang && hljs.getLanguage && hljs.getLanguage(lang)) {
        try { return hljs.highlight(text, { language: lang, ignoreIllegals: true }).value; }
        catch (_) { return Utils.escapeHtml(text); }
      }
      return Utils.escapeHtml(text);
    }
    // Schedule cooperative tail promotion (async) to avoid blocking UI on each chunk.
    schedulePromoteTail(force = false) {
      if (!this.activeCode || !this.activeCode.tailEl) return;
      if (this._promoteScheduled) return;
      this._promoteScheduled = true;
      this.raf.schedule('SE:promoteTail', () => {
        this._promoteScheduled = false;
        this._promoteTailWork(force);
      }, 'StreamEngine', 1);
    }
    // Move a full-line part of tail into frozen region (with highlight if budgets allow).
    async _promoteTailWork(force = false) {
      if (!this.activeCode || !this.activeCode.tailEl) return;

      // If plain streaming mode is on, or incremental hljs is disabled, promote as plain text only.
      const now = Utils.now(); const prof = this.cfg.PROFILE_CODE;
      const tailText0 = this.activeCode.tailEl.textContent || ''; if (!tailText0) return;

      if (!force) {
        if ((now - this.activeCode.lastPromoteTs) < prof.promoteMinInterval) return;
        const enoughLines = (this.activeCode.linesSincePromote || 0) >= (prof.promoteMinLines || 10);
        const enoughChars = tailText0.length >= prof.minCharsForHL;
        if (!enoughLines && !enoughChars) return;
      }

      // Cut at last full line to avoid moving partial tokens
      const idx = tailText0.lastIndexOf('\n');
      if (idx <= -1 && !force) return;
      const cut = (idx >= 0) ? (idx + 1) : tailText0.length;
      const delta = tailText0.slice(0, cut); if (!delta) return;

      // Re-evaluate budgets before performing any heavy work
      this.enforceHLStopBudget();
      const usePlain = this.activeCode.haltHL || this.activeCode.plainStream || !this._isHLJSSupported(this.activeCode.lang);

      // Cooperative rAF yield before heavy highlight
      if (!usePlain) await this.asyncer.yield();

      // If tail changed since we captured it, validate prefix to avoid duplication
      if (!this.activeCode || !this.activeCode.tailEl) return;
      const tailNow = this.activeCode.tailEl.textContent || '';
      if (!tailNow.startsWith(delta)) {
        // New data arrived; reschedule for next frame without touching DOM
        this.schedulePromoteTail(false);
        return;
      }

      // Apply DOM updates: either highlighted HTML delta or plain text
      if (usePlain) {
        // Plain text promotion – extremely cheap, no spans created.
        this.activeCode.frozenEl.insertAdjacentText('beforeend', delta);
      } else {
        // Highlighted promotion – still capped by budgets above.
        let html = Utils.escapeHtml(delta);
        try { html = this.highlightDeltaText(this.activeCode.lang, delta); } catch (_) { html = Utils.escapeHtml(delta); }
        this.activeCode.frozenEl.insertAdjacentHTML('beforeend', html);
      }

      // Update tail and counters
      this.activeCode.tailEl.textContent = tailNow.slice(delta.length);
      this.activeCode.frozenLen += delta.length;
      const promotedLines = Utils.countNewlines(delta);
      this.activeCode.tailLines = Math.max(0, (this.activeCode.tailLines || 0) - promotedLines);
      this.activeCode.linesSincePromote = Math.max(0, (this.activeCode.linesSincePromote || 0) - promotedLines);
      this.activeCode.lastPromoteTs = Utils.now();
      this.codeScroll.scheduleScroll(this.activeCode.codeEl, true, false);
      this._d(usePlain ? 'TAIL_PROMOTE_PLAIN' : 'TAIL_PROMOTE_ASYNC', { cut, promotedLines, lang: this.activeCode.lang, plain: usePlain });
    }
    // Finalize the current active code block. Keep it plain for now and schedule highlight lazily.
    finalizeActiveCode() {
      if (!this.activeCode) return;
      const codeEl = this.activeCode.codeEl;
      const fromBottomBefore = Math.max(0, codeEl.scrollHeight - codeEl.clientHeight - codeEl.scrollTop);
      const wasNearBottom = this.codeScroll.isNearBottomEl(codeEl, this.cfg.CODE_SCROLL.NEAR_MARGIN_PX);
      const fullText = (this.activeCode.frozenEl.textContent || '') + (this.activeCode.tailEl.textContent || '');

      // Non-blocking finalize: place plain text now, schedule highlight via Highlighter later.
      try {
        codeEl.innerHTML = '';
        codeEl.textContent = fullText;
        codeEl.classList.add('hljs');           // keep visual parity until highlight applies
        codeEl.removeAttribute('data-highlighted');
      } catch (_) {}

      const st = this.codeScroll.state(codeEl); st.autoFollow = false;
      const maxScrollTop = Math.max(0, codeEl.scrollHeight - codeEl.clientHeight);
      const target = wasNearBottom ? maxScrollTop : Math.max(0, maxScrollTop - fromBottomBefore);
      try { codeEl.scrollTop = target; } catch (_) {}
      st.lastScrollTop = codeEl.scrollTop;
      codeEl.dataset._active_stream = '0';

      try { codeEl.dataset.justFinalized = '1'; } catch (_) {}
      this.codeScroll.scheduleScroll(codeEl, false, true);

      // Schedule async highlight on the finalized element (viewport-aware).
      try { if (!this.cfg.HL.DISABLE_ALL) this.highlighter.queue(codeEl, null); } catch (_) {}

      this.suppressPostFinalizePass = true;

      this._d('FINALIZE_CODE_NONBLOCK', { lang: this.activeCode.lang, len: fullText.length, highlighted: false });
      this.activeCode = null;
    }
    // Make a simple fingerprint to reuse identical closed code blocks between snapshots.
    codeFingerprint(codeEl) {
      const cls = Array.from(codeEl.classList).find(c => c.startsWith('language-')) || 'language-plaintext';
      const lang = cls.replace('language-', '') || 'plaintext';
      const t = codeEl.textContent || ''; const len = t.length; const head = t.slice(0, 64); const tail = t.slice(-64);
      return `${lang}|${len}|${head}|${tail}`;
    }
    // fingerprint using precomputed meta on wrapper (avoids .textContent for heavy blocks).
    codeFingerprintFromWrapper(codeEl) {
      try {
        const wrap = codeEl.closest('.code-wrapper'); if (!wrap) return null;
        const cls = Array.from(codeEl.classList).find(c => c.startsWith('language-')) || 'language-plaintext';
        const lang = (cls.replace('language-', '') || 'plaintext');
        const len = wrap.getAttribute('data-code-len') || '';
        const head = wrap.getAttribute('data-code-head') || '';
        const tail = wrap.getAttribute('data-code-tail') || '';
        if (!len) return null; // ensure at least length exists
        return `${lang}|${len}|${head}|${tail}`;
      } catch (_) {
        return null;
      }
    }
    // Try to reuse old finalized code block DOM nodes to avoid re-highlighting.
    preserveStableClosedCodes(oldSnap, newRoot, skipLastIfStreaming) {
      try {
        const oldCodes = Array.from(oldSnap.querySelectorAll('pre code')); if (!oldCodes.length) return;
        // Safety guard: avoid heavy fingerprint work on extremely large outputs
        const newCodesPre = Array.from(newRoot.querySelectorAll('pre code'));
        if (newCodesPre.length > this.cfg.STREAM.PRESERVE_CODES_MAX || oldCodes.length > this.cfg.STREAM.PRESERVE_CODES_MAX) return;

        const map = new Map();
        for (const el of oldCodes) {
          if (el.querySelector('.hl-frozen')) continue;               // skip streaming blocks
          if (this.activeCode && el === this.activeCode.codeEl) continue;
          // Try wrapper-based fingerprint first, fallback to text-based
          let fp = this.codeFingerprintFromWrapper(el);
          if (!fp) fp = el.dataset.fp || (el.dataset.fp = this.codeFingerprint(el));
          const arr = map.get(fp) || []; arr.push(el); map.set(fp, arr);
        }
        const newCodes = newCodesPre;
        const end = (skipLastIfStreaming && newCodes.length > 0) ? (newCodes.length - 1) : newCodes.length;
        let reuseCount = 0;
        for (let i = 0; i < end; i++) {
          const nc = newCodes[i];
          if (nc.getAttribute('data-highlighted') === 'yes') continue;
          // Fingerprint new code: prefer wrapper meta (no .textContent read)
          let fp = this.codeFingerprintFromWrapper(nc);
          if (!fp) fp = this.codeFingerprint(nc);
          const arr = map.get(fp);
          if (arr && arr.length) {
            const oldEl = arr.shift();
            if (oldEl && oldEl.isConnected) {
              try {
                nc.replaceWith(oldEl);
                this.codeScroll.attachHandlers(oldEl);
                // Preserve whatever final state the old element had
                if (!oldEl.getAttribute('data-highlighted')) oldEl.setAttribute('data-highlighted', 'yes');
                const st = this.codeScroll.state(oldEl); st.autoFollow = false;
                reuseCount++;
              } catch (_) {}
            }
            if (!arr.length) map.delete(fp);
          }
        }
        if (reuseCount) this._d('PRESERVE_CODES_REUSED', { reuseCount, skipLastIfStreaming });
      } catch (e) {
        this._d('PRESERVE_CODES_ERROR', String(e));
      }
    }
    // Ensure blocks marked as just-finalized are scrolled to bottom.
    _ensureBottomForJustFinalized(root) {
      try {
        const scope = root || document;
        const nodes = scope.querySelectorAll('pre code[data-just-finalized="1"]');
        if (!nodes || !nodes.length) return;
        nodes.forEach((codeEl) => {
          this.codeScroll.scheduleScroll(codeEl, false, true);
          const key = { t: 'JF:forceBottom', el: codeEl, n: Math.random() };
          this.raf.schedule(key, () => {
            this.codeScroll.scrollToBottom(codeEl, false, true);
            try { codeEl.dataset.justFinalized = '0'; } catch (_) {}
          }, 'CodeScroll', 2);
        });
      } catch (_) {}
    }
    // If stream is visible but something got stuck, force a quick refresh.
    kickVisibility() {
      const msg = this.getMsg(false, '');
      if (!msg) return;
      if (this.codeStream.open && !this.activeCode) {
        this.scheduleSnapshot(msg, true);
        return;
      }
      const needSnap = (this.getStreamLength() !== (window.__lastSnapshotLen || 0));
      if (needSnap) this.scheduleSnapshot(msg, true);
      if (this.activeCode && this.activeCode.codeEl) {
        this.codeScroll.scheduleScroll(this.activeCode.codeEl, true, false);
        this.schedulePromoteTail(true);
      }
    }
      // Keep language header stable across snapshots for the active streaming code.
      // If the current snapshot produced a tiny/unsupported token (e.g. 'on', 'ml', 's'),
      // reuse the last known good language (from previous active state or sticky attribute).
      stabilizeHeaderLabel(prevAC, newAC) {
        try {
          if (!newAC || !newAC.codeEl || !newAC.codeEl.isConnected) return;

          const wrap = newAC.codeEl.closest('.code-wrapper');
          if (!wrap) return;

          const span = wrap.querySelector('.code-header-lang');
          const curLabel = (span && span.textContent ? span.textContent.trim() : '').toLowerCase();

          // Do not touch tool/output blocks
          if (curLabel === 'output') return;

          const tokNow = (wrap.getAttribute('data-code-lang') || '').trim().toLowerCase();
          const sticky = (wrap.getAttribute('data-lang-sticky') || '').trim().toLowerCase();
          const prev = (prevAC && prevAC.lang && prevAC.lang !== 'plaintext') ? prevAC.lang.toLowerCase() : '';

          const valid = (t) => !!t && t !== 'plaintext' && this._isHLJSSupported(t);

          let finalTok = '';
          if (valid(tokNow)) finalTok = tokNow;
          else if (valid(prev)) finalTok = prev;
          else if (valid(sticky)) finalTok = sticky;

          if (finalTok) {
            // Update code class and header label consistently
            this._updateCodeLangClass(newAC.codeEl, finalTok);
            this._updateCodeHeaderLabel(newAC.codeEl, finalTok, finalTok);
            try { wrap.setAttribute('data-code-lang', finalTok); } catch (_) {}
            try { wrap.setAttribute('data-lang-sticky', finalTok); } catch (_) {}
            newAC.lang = finalTok; // keep AC state in sync
          } else {
            // If current label looks like a tiny/incomplete token, normalize to 'code'
            if (span && curLabel && curLabel.length < 3) {
              span.textContent = 'code';
            }
          }
        } catch (_) { /* defensive: never break streaming path */ }
      }
    // Render a snapshot of current stream buffer into the DOM.
    renderSnapshot(msg) {
      const streaming = !!this.isStreaming;
      const snap = this.getMsgSnapshotRoot(msg); if (!snap) return;

      // No-op if nothing changed and no active code
      const prevLen = (window.__lastSnapshotLen || 0);
      const curLen = this.getStreamLength();
      if (!this.fenceOpen && !this.activeCode && curLen === prevLen) {
        this.lastSnapshotTs = Utils.now();
        this._d('SNAPSHOT_SKIPPED_NO_DELTA', { bufLen: curLen });
        return;
      }

      const t0 = Utils.now();

      // When an open fence is present, append a synthetic EOL only if the current buffer
      // does not already end with EOL. This stabilizes markdown-it parsing without polluting
      // the real code tail (we will strip this EOL from the active tail right after snapshot).
      const allText = this.getStreamText();
      const needSyntheticEOL = (this.fenceOpen && !/[\r\n]$/.test(allText));
      this._lastInjectedEOL = !!needSyntheticEOL;
      const src = needSyntheticEOL ? (allText + '\n') : allText;

      // Use streaming renderer (no linkify) on hot path to reduce CPU/allocs
      const html = streaming ? this.renderer.renderStreamingSnapshot(src) : this.renderer.renderFinalSnapshot(src);

      // parse HTML into a DocumentFragment directly to avoid intermediate container allocations.
      let frag = null;
      try {
        const range = document.createRange();
        range.selectNodeContents(snap);
        frag = range.createContextualFragment(html);
      } catch (_) {
        const tmp = document.createElement('div');
        tmp.innerHTML = html;
        frag = document.createDocumentFragment();
        while (tmp.firstChild) frag.appendChild(tmp.firstChild);
      }

      // (stream-aware custom markup):
      // Apply Custom Markup on the fragment only if at least one rule opted-in for stream.
      try {
        if (this.renderer && this.renderer.customMarkup && this.renderer.customMarkup.hasStreamRules()) {
          const MDinline = this.renderer.MD_STREAM || this.renderer.MD || null;
          this.renderer.customMarkup.applyStream(frag, MDinline);
        }
      } catch (_) { /* keep snapshot path resilient */ }

      // Reuse closed, stable code blocks from previous snapshot to avoid re-highlighting
      this.preserveStableClosedCodes(snap, frag, this.fenceOpen === true);

      // Replace content
      snap.replaceChildren(frag);

      // Restore code UI state and ensure bottoming for freshly finalized elements
      this.renderer.restoreCollapsedCode(snap);
      this._ensureBottomForJustFinalized(snap);

      // Setup active streaming code if fence is open, otherwise clear active state
        const prevAC = this.activeCode; // remember previous active streaming state (if any)

        if (this.fenceOpen) {
          const newAC = this.setupActiveCodeFromSnapshot(snap);

          // preserve previous frozen/tail state and stable lang/header across snapshots
          if (prevAC && newAC) {
            this.rehydrateActiveCode(prevAC, newAC);
            this.stabilizeHeaderLabel(prevAC, newAC);
          }

          this.activeCode = newAC || null;
        } else {
          this.activeCode = null;
        }

      // Attach scroll/highlight observers (viewport aware)
      if (!this.fenceOpen) {
        this.codeScroll.initScrollableBlocks(snap);
      }
      this.highlighter.observeNewCode(snap, {
        deferLastIfStreaming: true,
        minLinesForLast: this.cfg.PROFILE_CODE.minLinesForHL,
        minCharsForLast: this.cfg.PROFILE_CODE.minCharsForHL
      }, this.activeCode);
      this.highlighter.observeMsgBoxes(snap, (box) => {
        this.highlighter.observeNewCode(box, {
          deferLastIfStreaming: true,
          minLinesForLast: this.cfg.PROFILE_CODE.minLinesForHL,
          minCharsForLast: this.cfg.PROFILE_CODE.minCharsForHL
        }, this.activeCode);
        this.codeScroll.initScrollableBlocks(box);
      });

      // Schedule math render according to mode; keep "finalize-only" cheap on hot path
      const mm = getMathMode();
      if (!this.suppressPostFinalizePass) {
        if (mm === 'idle') this.math.schedule(snap);
        else if (mm === 'always') this.math.schedule(snap, 0, true);
      }

      // If streaming code is visible, keep it glued to bottom
      if (this.fenceOpen && this.activeCode && this.activeCode.codeEl) {
        this.codeScroll.attachHandlers(this.activeCode.codeEl);
        this.codeScroll.scheduleScroll(this.activeCode.codeEl, true, false);
      } else if (!this.fenceOpen) {
        this.codeScroll.initScrollableBlocks(snap);
      }

      // Advance snapshot budget and remember progress
      window.__lastSnapshotLen = this.getStreamLength();
      this.lastSnapshotTs = Utils.now();

      const prof = this.profile();
      if (prof.adaptiveStep) {
        const maxStep = this.cfg.STREAM.SNAPSHOT_MAX_STEP || 8000;
        this.nextSnapshotStep = Math.min(Math.ceil(this.nextSnapshotStep * prof.growth), maxStep);
      } else {
        this.nextSnapshotStep = prof.base;
      }

      // Keep page scroll/fab in sync
      this.scrollMgr.scheduleScroll(true);
      this.scrollMgr.fabFreezeUntil = Utils.now() + this.cfg.FAB.TOGGLE_DEBOUNCE_MS;
      this.scrollMgr.scheduleScrollFabUpdate();

      if (this.suppressPostFinalizePass) this.suppressPostFinalizePass = false;

      const dt = Utils.now() - t0;
      this._d('SNAPSHOT', { fenceOpen: this.fenceOpen, activeCode: !!this.activeCode, bufLen: this.getStreamLength(), timeMs: Math.round(dt), streaming });
    }

    // Get current message container (.msg) or create if allowed.
    getMsg(create, name_header) { return this.dom.getStreamMsg(create, name_header); }
    // Start a new streaming session (clear state and display loader, if any).
    beginStream(chunk = false) {
      this.isStreaming = true; // engage streaming mode (no linkify etc.)
      if (chunk) runtime.loading.hide();
      this.scrollMgr.userInteracted = false;
      this.dom.clearOutput();
      this.reset();
      this.scrollMgr.forceScrollToBottomImmediate();
      this.scrollMgr.scheduleScroll();
      this._d('BEGIN_STREAM', { chunkFlag: !!chunk });
    }
    // End streaming session, finalize active code if present, and complete math/highlight.
    endStream() {
      // Switch to final mode before the last snapshot to allow full renderer (linkify etc.)
      this.isStreaming = false;

      const msg = this.getMsg(false, '');
      if (msg) this.renderSnapshot(msg);

      this.snapshotScheduled = false;
      try { this.raf.cancel('SE:snapshot'); } catch (_) {}
      this.snapshotRAF = 0;

      const hadActive = !!this.activeCode;
      if (this.activeCode) this.finalizeActiveCode();

      if (!hadActive) {
        if (this.highlighter.hlQueue && this.highlighter.hlQueue.length) {
          this.highlighter.flush(this.activeCode);
        }
        const snap = msg ? this.getMsgSnapshotRoot(msg) : null;
        if (snap) this.math.renderAsync(snap); // ensure math completes eagerly but async
      }

      this.fenceOpen = false; this.codeStream.open = false; this.activeCode = null; this.lastSnapshotTs = Utils.now();
      this.suppressPostFinalizePass = false;
      this._d('END_STREAM', { hadActive });
    }
    // Apply incoming chunk to stream buffer and update DOM when needed.
    applyStream(name_header, chunk, alreadyBuffered = false) {
      if (!this.activeCode && !this.fenceOpen) {
        try { if (document.querySelector('pre code[data-_active_stream="1"]')) this.defuseOrphanActiveBlocks(); } catch (_) {}
      }
      if (this.snapshotScheduled && !this.raf.isScheduled('SE:snapshot')) this.snapshotScheduled = false;

      const msg = this.getMsg(true, name_header); if (!msg || !chunk) return;
      const s = String(chunk);
      if (!alreadyBuffered) this._appendChunk(s);

      const change = this.updateFenceHeuristic(s);
      const nlCount = Utils.countNewlines(s); const chunkHasNL = nlCount > 0;

      this._d('APPLY_CHUNK', { len: s.length, nl: nlCount, opened: change.opened, closed: change.closed, splitAt: change.splitAt, fenceOpenBefore: this.fenceOpen || false, codeOpenBefore: this.codeStream.open || false, rebroadcast: !!alreadyBuffered });

      // Track if we just materialized the first code-open snapshot synchronously.
      let didImmediateOpenSnap = false;

      if (change.opened) {
        this.codeStream.open = true; this.codeStream.lines = 0; this.codeStream.chars = 0;
        this.resetBudget();
        this.scheduleSnapshot(msg);
        this._d('CODE_STREAM_OPEN', { });

        // Fast-path: if stream starts with a code fence and no snapshot was made yet,
        // immediately materialize the code block so tail streaming can proceed without click.
        if (!this._firstCodeOpenSnapDone && !this.activeCode && ((window.__lastSnapshotLen || 0) === 0)) {
          try {
            this.renderSnapshot(msg);
            try { this.raf.cancel('SE:snapshot'); } catch (_) {}
            this.snapshotScheduled = false;
            this._firstCodeOpenSnapDone = true;
            didImmediateOpenSnap = true;
            this._d('CODE_OPEN_IMMEDIATE_SNAPSHOT', { bufLen: this.getStreamLength() });
          } catch (_) {
            // Keep going; normal scheduled snapshot will land soon.
          }
        }
      }

      if (this.codeStream.open) {
        this.codeStream.lines += nlCount; this.codeStream.chars += s.length;

        if (this.activeCode && this.activeCode.codeEl && this.activeCode.codeEl.isConnected) {
          let partForCode = s; let remainder = '';

          if (didImmediateOpenSnap) {
            partForCode = '';
          } else {
            if (change.closed && change.splitAt >= 0 && change.splitAt <= s.length) {
              partForCode = s.slice(0, change.splitAt); remainder = s.slice(change.splitAt);
            }
          }

          if (partForCode) {
            this.appendToActiveTail(partForCode);
            this.activeCode.lines += Utils.countNewlines(partForCode);

            this.maybePromoteLanguageFromDirective();
            this.enforceHLStopBudget();

            if (!this.activeCode.haltHL) {
              if (partForCode.indexOf('\n') >= 0 || (this.activeCode.tailEl.textContent || '').length >= this.cfg.PROFILE_CODE.minCharsForHL) {
                this.schedulePromoteTail(false);
              }
            }
          }
          this.scrollMgr.scrollFabUpdateScheduled = false;
          this.scrollMgr.scheduleScroll(true);
          this.scrollMgr.fabFreezeUntil = Utils.now() + this.cfg.FAB.TOGGLE_DEBOUNCE_MS;
          this.scrollMgr.scheduleScrollFabUpdate();

          if (change.closed) {
            this.finalizeActiveCode();
            this.codeStream.open = false; this.resetBudget(); this.scheduleSnapshot(msg);
            this._d('CODE_STREAM_CLOSE_FINALIZED', { remainderLen: remainder.length });
            if (remainder && remainder.length) { this.applyStream(name_header, remainder, true); }
          }
          return;
        } else {
          if (!this.activeCode && (this.codeStream.lines >= 2 || this.codeStream.chars >= 80)) {
            this.scheduleSnapshot(msg, true);
            return;
          }
          if (change.closed) {
            this.codeStream.open = false; this.resetBudget(); this.scheduleSnapshot(msg);
            this._d('CODE_CLOSED_WITHOUT_ACTIVE', { sinceLastSnapMs: Math.round(Utils.now() - this.lastSnapshotTs), snapshotScheduled: this.snapshotScheduled });
          } else {
            const boundary = this.hasStructuralBoundary(s);
            if (this.shouldSnapshotOnChunk(s, chunkHasNL, boundary)) this.scheduleSnapshot(msg);
            else this.maybeScheduleSoftSnapshot(msg, chunkHasNL);
          }
          return;
        }
      }

      if (change.closed) {
        this.codeStream.open = false; this.resetBudget(); this.scheduleSnapshot(msg);
        this._d('CODE_STREAM_CLOSE', { });
      } else {
        const boundary = this.hasStructuralBoundary(s);
        if (this.shouldSnapshotOnChunk(s, chunkHasNL, boundary)) {
          this.scheduleSnapshot(msg);
          this._d('SCHEDULE_SNAPSHOT_BOUNDARY', { boundary });
        } else {
          this.maybeScheduleSoftSnapshot(msg, chunkHasNL);
        }
      }
    }
  }

  // ==========================================================================
  // 12) Stream queue
  // ==========================================================================

  class StreamQueue {
    constructor(cfg, engine, scrollMgr, raf) {
      this.cfg = cfg; this.engine = engine; this.scrollMgr = scrollMgr; this.raf = raf;
      this.q = []; this.drainScheduled = false;
      this.batching = false; this.needScroll = false;
    }
    // Coalesce contiguous entries for the same header to reduce overhead.
    _compactContiguousSameName() {
      // Coalesce contiguous entries for the same message header to reduce string objects count.
      if (this.q.length < 2) return;
      const out = [];
      let last = this.q[0];
      for (let i = 1; i < this.q.length; i++) {
        const cur = this.q[i];
        if (cur.name_header === last.name_header) {
          // Merge payloads; reduce object overhead without changing semantics
          last.chunk = (last.chunk || '') + (cur.chunk || '');
        }
        else {
          out.push(last);
          last = cur;
        }
      }
      out.push(last);
      this.q = out;
    }
    // Push new chunk into the queue and schedule drain.
    enqueue(name_header, chunk) {
      this.q.push({ name_header, chunk });
      // Guard against unbounded growth during bursts
      if (this.q.length > this.cfg.STREAM.EMERGENCY_COALESCE_LEN) this._compactContiguousSameName();
      if (this.q.length > this.cfg.STREAM.QUEUE_MAX_ITEMS) this._compactContiguousSameName();
      if (!this.drainScheduled) {
        this.drainScheduled = true;
        this.raf.schedule('SQ:drain', () => this.drain(), 'StreamQueue', 0);
      }
    }
    // Drain a limited number of chunks per frame (adaptive if configured).
    drain() {
      this.drainScheduled = false; let processed = 0;
      const adaptive = (this.cfg.STREAM.COALESCE_MODE === 'adaptive');
      const coalesceAggressive = adaptive && (this.q.length >= this.cfg.STREAM.EMERGENCY_COALESCE_LEN);

      // Adaptive per-frame budget: increase throughput if queue is long
      const basePerFrame = this.cfg.STREAM.MAX_PER_FRAME | 0;
      const perFrame = adaptive ? Math.min(basePerFrame + Math.floor(this.q.length / 20), basePerFrame * 4) : basePerFrame;

      this.batching = true;
      while (this.q.length && processed < perFrame) {
        let { name_header, chunk } = this.q.shift();
        if (chunk && chunk.length > 0) {
          const chunks = [chunk];
          while (this.q.length) {
            const next = this.q[0];
            if (next.name_header === name_header) {
              chunks.push(next.chunk); this.q.shift();
              if (!coalesceAggressive) break;
            } else break;
          }
          chunk = chunks.join('');
        }
        this.engine.applyStream(name_header, chunk);
        processed++;
      }
      this.batching = false;
      if (this.needScroll) { this.scrollMgr.scheduleScroll(true); this.needScroll = false; }
      if (this.q.length) {
        this.drainScheduled = true;
        this.raf.schedule('SQ:drain', () => this.drain(), 'StreamQueue', 0);
      }
    }
    // Force a drain soon.
    kick() {
      if (this.q.length || this.drainScheduled) {
        this.drainScheduled = true;
        this.raf.schedule('SQ:drain', () => this.drain(), 'StreamQueue', 0);
      }
    }
    // Clear queued work and cancel scheduled drains.
    clear() {
      this.q.length = 0;
      try { this.raf.cancelGroup('StreamQueue'); } catch (_) {}
      this.drainScheduled = false;
    }
  }

  // ==========================================================================
  // 13) Bridge manager (QWebChannel)
  // ==========================================================================

  class BridgeManager {
    constructor(cfg, logger) {
      this.cfg = cfg; this.logger = logger || new Logger(cfg);
      this.bridge = null; this.connected = false;
    }
    log(text) { try { if (this.bridge && this.bridge.log) this.bridge.log(text); } catch (_) {} }
    connect(onChunk, onNode, onNodeReplace, onNodeInput) {
      if (!this.bridge) return false; if (this.connected) return true;
      try {
        if (this.bridge.chunk) this.bridge.chunk.connect((name, chunk, type) => onChunk(name, chunk, type));
        if (this.bridge.node) this.bridge.node.connect(onNode);
        if (this.bridge.nodeReplace) this.bridge.nodeReplace.connect(onNodeReplace);
        if (this.bridge.nodeInput) this.bridge.nodeInput.connect(onNodeInput);
        this.connected = true; return true;
      } catch (e) { this.log(e); return false; }
    }
    disconnect() {
      if (!this.bridge) return false; if (!this.connected) return true;
      try {
        if (this.bridge.chunk) this.bridge.chunk.disconnect();
        if (this.bridge.node) this.bridge.node.disconnect();
        if (this.bridge.nodeReplace) this.bridge.nodeReplace.disconnect();
        if (this.bridge.nodeInput) this.bridge.nodeInput.disconnect();
      } catch (_) {}
      this.connected = false; return true;
    }
    initQWebChannel(pid, onReady) {
      try {
        new QWebChannel(qt.webChannelTransport, (channel) => {
          this.bridge = channel.objects.bridge;
          try { this.logger.bindBridge(this.bridge); } catch (_) {}
          onReady && onReady(this.bridge);
          if (this.bridge && this.bridge.js_ready) this.bridge.js_ready(pid);
        });
      } catch (e) { /* swallow */ }
    }
    copyCode(text) { if (this.bridge && this.bridge.copy_text) this.bridge.copy_text(text); }
    previewCode(text) { if (this.bridge && this.bridge.preview_text) this.bridge.preview_text(text); }
    runCode(text) { if (this.bridge && this.bridge.run_text) this.bridge.run_text(text); }
    updateScrollPosition(pos) { if (this.bridge && this.bridge.update_scroll_position) this.bridge.update_scroll_position(pos); }
  }

  // ==========================================================================
  // 14) Loading indicator
  // ==========================================================================

  class Loading {
    constructor(dom) { this.dom = dom; }
    // Show loader element (and hide tips if visible).
    show() { if (typeof window.hideTips === 'function') { window.hideTips(); } const el = this.dom.get('_loader_'); if (!el) return; if (el.classList.contains('hidden')) el.classList.remove('hidden'); el.classList.add('visible'); }
    // Hide loader element.
    hide() { const el = this.dom.get('_loader_'); if (!el) return; if (el.classList.contains('visible')) el.classList.remove('visible'); el.classList.add('hidden'); }
  }

  // ==========================================================================
  // 15) Event manager
  // ==========================================================================

  class EventManager {
    constructor(cfg, dom, scrollMgr, highlighter, codeScroll, toolOutput, bridge) {
      this.cfg = cfg; this.dom = dom; this.scrollMgr = scrollMgr; this.highlighter = highlighter;
      this.codeScroll = codeScroll; this.toolOutput = toolOutput; this.bridge = bridge;
      this.handlers = { wheel: null, scroll: null, resize: null, fabClick: null, mouseover: null, mouseout: null, click: null, keydown: null, docClickFocus: null, visibility: null, focus: null, pageshow: null };
    }
    _findWrapper(target) { if (!target || typeof target.closest !== 'function') return null; return target.closest('.code-wrapper'); }
    _getCodeEl(wrapper) { if (!wrapper) return null; return wrapper.querySelector('pre > code'); }
    _collectCodeText(codeEl) {
      if (!codeEl) return '';
      const frozen = codeEl.querySelector('.hl-frozen'); const tail = codeEl.querySelector('.hl-tail');
      if (frozen || tail) return (frozen?.textContent || '') + (tail?.textContent || '');
      return codeEl.textContent || '';
    }
    // Copy to clipboard via bridge if available, otherwise use browser APIs.
    async _copyTextRobust(text) {
      try { if (this.bridge && typeof this.bridge.copyCode === 'function') { this.bridge.copyCode(text); return true; } } catch (_) {}
      try { if (navigator && navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(text); return true; } } catch (_) {}
      try {
        const ta = document.createElement('textarea');
        ta.value = text; ta.setAttribute('readonly', ''); ta.style.position = 'fixed'; ta.style.top = '-9999px'; ta.style.opacity = '0';
        document.body.appendChild(ta); ta.select(); const ok = document.execCommand && document.execCommand('copy'); document.body.removeChild(ta); return !!ok;
      } catch (_) { return false; }
    }
    // Flash "Copied" feedback on the copy button.
    _flashCopied(btn, wrapper) {
      if (!btn || !wrapper) return;
      const span = btn.querySelector('span'); if (!span) return;
      const L_COPY = wrapper.getAttribute('data-locale-copy') || 'Copy';
      const L_COPIED = wrapper.getAttribute('data-locale-copied') || 'Copied';
      try { if (btn.__copyTimer) { clearTimeout(btn.__copyTimer); btn.__copyTimer = 0; } } catch (_) {}
      span.textContent = L_COPIED; btn.classList.add('copied');
      btn.__copyTimer = setTimeout(() => { try { span.textContent = L_COPY; btn.classList.remove('copied'); } catch (_) {} btn.__copyTimer = 0; }, 1200);
    }
    // Toggle code collapse/expand and remember collapsed indices.
    _toggleCollapse(wrapper) {
      if (!wrapper) return;
      const codeEl = this._getCodeEl(wrapper); if (!codeEl) return;
      const btn = wrapper.querySelector('.code-header-collapse');
      const span = btn ? btn.querySelector('span') : null;
      const L_COLLAPSE = wrapper.getAttribute('data-locale-collapse') || 'Collapse';
      const L_EXPAND = wrapper.getAttribute('data-locale-expand') || 'Expand';
      const idx = String(wrapper.getAttribute('data-index') || '');
      const arr = window.__collapsed_idx || (window.__collapsed_idx = []);
      const isHidden = (codeEl.style.display === 'none');

      if (isHidden) {
        codeEl.style.display = 'block';
        if (span) span.textContent = L_COLLAPSE;
        const p = arr.indexOf(idx); if (p !== -1) arr.splice(p, 1);
      } else {
        codeEl.style.display = 'none';
        if (span) span.textContent = L_EXPAND;
        if (!arr.includes(idx)) arr.push(idx);
      }
    }
    // Attach global UI event handlers and container-level interactions.
    install() {
      try { history.scrollRestoration = "manual"; } catch (_) {}

      this.handlers.keydown = (event) => {
        if (event.ctrlKey && event.key === 'f') { window.location.href = 'bridge://open_find:' + runtime.cfg.PID; event.preventDefault(); }
        if (event.key === 'Escape') { window.location.href = 'bridge://escape'; event.preventDefault(); }
      };
      document.addEventListener('keydown', this.handlers.keydown, { passive: false });

      // Removed global click-to-focus navigation and visibility/focus wakeups to keep the pump rAF-only and click-agnostic.

      const container = this.dom.get('container');
      const addClassToMsg = (id, className) => { const el = document.getElementById('msg-bot-' + id); if (el) el.classList.add(className); };
      const removeClassFromMsg = (id, className) => { const el = document.getElementById('msg-bot-' + id); if (el) el.classList.remove(className); };

      this.handlers.mouseover = (event) => { if (event.target.classList.contains('action-img')) { const id = event.target.getAttribute('data-id'); addClassToMsg(id, 'msg-highlight'); } };
      this.handlers.mouseout = (event) => { if (event.target.classList.contains('action-img')) { const id = event.target.getAttribute('data-id'); const el = document.getElementById('msg-bot-' + id); if (el) el.classList.remove('msg-highlight'); } };
      if (container) {
        container.addEventListener('mouseover', this.handlers.mouseover, { passive: true });
        container.addEventListener('mouseout', this.handlers.mouseout, { passive: true });
      }

      this.handlers.click = async (ev) => {
        const a = ev.target && (ev.target.closest ? ev.target.closest('a.code-header-action') : null);
        if (!a) return;
        const wrapper = this._findWrapper(a);
        if (!wrapper) return;

        ev.preventDefault();
        ev.stopPropagation();

        const isCopy = a.classList.contains('code-header-copy');
        const isCollapse = a.classList.contains('code-header-collapse');
        const isRun = a.classList.contains('code-header-run');
        const isPreview = a.classList.contains('code-header-preview');

        let codeEl = null, text = '';
        if (isCopy || isRun || isPreview) {
          codeEl = this._getCodeEl(wrapper);
          text = this._collectCodeText(codeEl);
        }

        try {
          if (isCopy) {
            const ok = await this._copyTextRobust(text);
            if (ok) this._flashCopied(a, wrapper);
          } else if (isCollapse) {
            this._toggleCollapse(wrapper);
          } else if (isRun) {
            if (this.bridge && typeof this.bridge.runCode === 'function') this.bridge.runCode(text);
          } else if (isPreview) {
            if (this.bridge && typeof this.bridge.previewCode === 'function') this.bridge.previewCode(text);
          }
        } catch (_) { /* swallow */ }
      };
      if (container) container.addEventListener('click', this.handlers.click, { passive: false });

      this.handlers.wheel = (ev) => {
        runtime.scrollMgr.userInteracted = true;
        if (ev.deltaY < 0) runtime.scrollMgr.autoFollow = false;
        else runtime.scrollMgr.maybeEnableAutoFollowByProximity();
        this.highlighter.scheduleScanVisibleCodes(runtime.stream.activeCode);
      };
      document.addEventListener('wheel', this.handlers.wheel, { passive: true });

      this.handlers.scroll = () => {
        const el = Utils.SE; const top = el.scrollTop;
        if (top + 1 < runtime.scrollMgr.lastScrollTop) runtime.scrollMgr.autoFollow = false;
        runtime.scrollMgr.maybeEnableAutoFollowByProximity();
        runtime.scrollMgr.lastScrollTop = top;
        const action = runtime.scrollMgr.computeFabAction();
        if (action !== runtime.scrollMgr.currentFabAction) runtime.scrollMgr.updateScrollFab(false, action, true);
        this.highlighter.scheduleScanVisibleCodes(runtime.stream.activeCode);
      };
      window.addEventListener('scroll', this.handlers.scroll, { passive: true });

      const fab = this.dom.get('scrollFab');
      if (fab) {
        this.handlers.fabClick = (ev) => {
          ev.preventDefault(); ev.stopPropagation();
          const action = runtime.scrollMgr.computeFabAction();
          if (action === 'up') runtime.scrollMgr.scrollToTopUser();
          else if (action === 'down') runtime.scrollMgr.scrollToBottomUser();
          runtime.scrollMgr.fabFreezeUntil = Utils.now() + this.cfg.FAB.TOGGLE_DEBOUNCE_MS;
          runtime.scrollMgr.updateScrollFab(true);
        };
        fab.addEventListener('click', this.handlers.fabClick, { passive: false });
      }

      this.handlers.resize = () => {
        runtime.scrollMgr.maybeEnableAutoFollowByProximity();
        runtime.scrollMgr.scheduleScrollFabUpdate();
        this.highlighter.scheduleScanVisibleCodes(runtime.stream.activeCode);
      };
      window.addEventListener('resize', this.handlers.resize, { passive: true });

      // Note: visibility/focus/pageshow kickers removed intentionally.
    }
    // Detach all installed handlers and reset local refs.
    cleanup() {
      const container = this.dom.get('container');
      if (this.handlers.wheel) document.removeEventListener('wheel', this.handlers.wheel);
      if (this.handlers.scroll) window.removeEventListener('scroll', this.handlers.scroll);
      if (this.handlers.resize) window.removeEventListener('resize', this.handlers.resize);
      const fab = this.dom.get('scrollFab'); if (fab && this.handlers.fabClick) fab.removeEventListener('click', this.handlers.fabClick);
      if (container && this.handlers.mouseover) container.removeEventListener('mouseover', this.handlers.mouseover);
      if (container && this.handlers.mouseout) container.removeEventListener('mouseout', this.handlers.mouseout);
      if (container && this.handlers.click) container.removeEventListener('click', this.handlers.click);
      if (this.handlers.keydown) document.removeEventListener('keydown', this.handlers.keydown);
      if (this.handlers.docClickFocus) document.removeEventListener('click', this.handlers.docClickFocus);
      if (this.handlers.visibility) document.removeEventListener('visibilitychange', this.handlers.visibility);
      if (this.handlers.focus) window.removeEventListener('focus', this.handlers.focus);
      if (this.handlers.pageshow) window.removeEventListener('pageshow', this.handlers.pageshow);
      this.handlers = {};
    }
  }

  // ==========================================================================
  // 16) Orchestrator runtime
  // ==========================================================================

  class Runtime {
    constructor() {
      this.cfg = new Config();
      this.logger = new Logger(this.cfg);

      this.dom = new DOMRefs();
      this.customMarkup = new CustomMarkup(this.cfg, this.logger);
      this.raf = new RafManager(this.cfg);

      // Ensure logger uses central RafManager for its internal tick pump.
      try { this.logger.bindRaf(this.raf); } catch (_) {}

      this.async = new AsyncRunner(this.cfg, this.raf);
      this.renderer = new MarkdownRenderer(this.cfg, this.customMarkup, this.logger, this.async, this.raf);

      this.math = new MathRenderer(this.cfg, this.raf, this.async);
      this.codeScroll = new CodeScrollState(this.cfg, this.raf);
      this.highlighter = new Highlighter(this.cfg, this.codeScroll, this.raf);
      this.scrollMgr = new ScrollManager(this.cfg, this.dom, this.raf);
      this.toolOutput = new ToolOutput();
      this.loading = new Loading(this.dom);
      this.nodes = new NodesManager(this.dom, this.renderer, this.highlighter, this.math);
      this.bridge = new BridgeManager(this.cfg, this.logger);
      this.ui = new UIManager();
      this.stream = new StreamEngine(this.cfg, this.dom, this.renderer, this.math, this.highlighter, this.codeScroll, this.scrollMgr, this.raf, this.async, this.logger);
      this.streamQ = new StreamQueue(this.cfg, this.stream, this.scrollMgr, this.raf);
      this.events = new EventManager(this.cfg, this.dom, this.scrollMgr, this.highlighter, this.codeScroll, this.toolOutput, this.bridge);

      try {
        this.stream.setCustomFenceSpecs(this.customMarkup.getSourceFenceSpecs());
      } catch (_) {}

      this.templates = new NodeTemplateEngine(this.cfg, this.logger);
      this.data = new DataReceiver(this.cfg, this.templates, this.nodes, this.scrollMgr);

      this.tips = null;
      this._lastHeavyResetMs = 0;

      this.renderer.hooks.observeNewCode = (root, opts) => this.highlighter.observeNewCode(root, opts, this.stream.activeCode);
      this.renderer.hooks.observeMsgBoxes = (root) => this.highlighter.observeMsgBoxes(root, (box) => {
        this.highlighter.observeNewCode(box, {
          deferLastIfStreaming: true,
          minLinesForLast: this.cfg.PROFILE_CODE.minLinesForHL,
          minCharsForLast: this.cfg.PROFILE_CODE.minCharsForHL
        }, this.stream.activeCode);
        this.codeScroll.initScrollableBlocks(box);
      });
      this.renderer.hooks.scheduleMathRender = (root) => {
        const mm = getMathMode();
        if (mm === 'idle') this.math.schedule(root);
        else if (mm === 'always') this.math.schedule(root, 0, true);
      };
      this.renderer.hooks.codeScrollInit = (root) => this.codeScroll.initScrollableBlocks(root);
    }

    // Reset stream state and optionally perform a heavy reset of schedulers and observers.
    resetStreamState(origin, opts) {
      try { this.streamQ.clear(); } catch (_) {}

      const def = Object.assign({
        finalizeActive: true, clearBuffer: true, clearMsg: false, defuseOrphans: true, forceHeavy: false, reason: String(origin || 'external-op')
      }, (opts || {}));

      const now = Utils.now();
      const withinDebounce = (now - (this._lastHeavyResetMs || 0)) <= (this.cfg.RESET.HEAVY_DEBOUNCE_MS || 24);
      const mustHeavyByOrigin =
        def.forceHeavy === true || def.clearMsg === true ||
        origin === 'beginStream' || origin === 'nextStream' ||
        origin === 'clearStream' || origin === 'replaceNodes' ||
        origin === 'clearNodes' || origin === 'clearOutput' ||
        origin === 'clearLive' || origin === 'clearInput';
      const shouldHeavy = mustHeavyByOrigin || !withinDebounce;
      const suppressLog = withinDebounce && origin !== 'beginStream';

      try { this.stream.abortAndReset({ ...def, suppressLog }); } catch (_) {}

      if (shouldHeavy) {
        try { this.highlighter.cleanup(); } catch (_) {}
        try { this.math.cleanup(); } catch (_) {}
        try { this.codeScroll.cancelAllScrolls(); } catch (_) {}
        try { this.scrollMgr.cancelPendingScroll(); } catch (_) {}
        try { this.raf.cancelAll(); } catch (_) {}
        this._lastHeavyResetMs = now;
      } else {
        try { this.raf.cancelGroup('StreamQueue'); } catch (_) {}
      }

      try { this.tips && this.tips.hide(); } catch (_) {}
    }
    // API: handle incoming chunk (from bridge).
    api_onChunk = (name, chunk, type) => {
      const t = String(type || 'text_delta');
      if (t === 'text_delta') {
        this.api_appendStream(name, chunk);
        return;
      }
      // Future-proof: add other chunk types here (attachments, status, etc.)
      // No-op for unknown types to keep current behavior.
      this.logger.debug('STREAM', 'IGNORED_NON_TEXT_CHUNK', { type: t, len: (chunk ? String(chunk).length : 0) });
    };
    // API: begin stream.
    api_beginStream = (chunk = false) => { this.tips && this.tips.hide(); this.resetStreamState('beginStream', { clearMsg: true, finalizeActive: false, forceHeavy: true }); this.stream.beginStream(chunk); };
    // API: end stream.
    api_endStream = () => { this.stream.endStream(); };
    // API: apply chunk.
    api_applyStream = (name, chunk) => { this.stream.applyStream(name, chunk); };
    // API: enqueue chunk (drained on rAF).
    api_appendStream = (name, chunk) => { this.streamQ.enqueue(name, chunk); };
    // API: move current output to "before" area and prepare for next stream.
    api_nextStream = () => {
      this.tips && this.tips.hide();
      const element = this.dom.get('_append_output_'); const before = this.dom.get('_append_output_before_');
      if (element && before) {
        const frag = document.createDocumentFragment();
        while (element.firstChild) frag.appendChild(element.firstChild);
        before.appendChild(frag);
      }
      this.resetStreamState('nextStream', { clearMsg: true, finalizeActive: false, forceHeavy: true });
      this.scrollMgr.scheduleScroll();
    };
    // API: clear streaming output area entirely.
    api_clearStream = () => { this.tips && this.tips.hide(); this.resetStreamState('clearStream', { clearMsg: true, forceHeavy: true }); const el = this.dom.getStreamContainer(); if (!el) return; el.replaceChildren(); };

    // API: append/replace messages (non-streaming).
    api_appendNode = (payload) => { this.resetStreamState('appendNode'); this.data.append(payload); };
    api_replaceNodes = (payload) => { this.resetStreamState('replaceNodes', { clearMsg: true, forceHeavy: true }); this.dom.clearNodes(); this.data.replace(payload); };

    // API: append to input area.
      api_appendToInput = (payload) => {
        this.nodes.appendToInput(payload);

        // Ensure initial auto-follow is ON for the next stream that will start right after user input.
        // Rationale: previously, if the user had scrolled up, autoFollow could remain false and the
        // live stream would not follow even though we just sent a new input.
        this.scrollMgr.autoFollow = true;       // explicitly re-enable page auto-follow
        this.scrollMgr.userInteracted = false;  // Reset interaction so live scroll is allowed

        // Keep lastScrollTop in sync to avoid misclassification in the next onscroll handler.
        try { this.scrollMgr.lastScrollTop = Utils.SE.scrollTop | 0; } catch (_) {}

        // Non-live scroll to bottom right away, independent of autoFollow state.
        this.scrollMgr.scheduleScroll();
        // NOTE: No resetStreamState() here to avoid flicker/reflow issues while previewing user input.
      };

    // API: clear messages list.
    api_clearNodes = () => { this.dom.clearNodes(); this.resetStreamState('clearNodes', { clearMsg: true, forceHeavy: true }); };
    // API: clear input area.
    api_clearInput = () => { this.resetStreamState('clearInput', { forceHeavy: true }); this.dom.clearInput(); };
    // API: clear output area.
    api_clearOutput = () => { this.dom.clearOutput(); this.resetStreamState('clearOutput', { clearMsg: true, forceHeavy: true }); };
    // API: clear live area.
    api_clearLive = () => { this.dom.clearLive(); this.resetStreamState('clearLive', { forceHeavy: true }); };

    // API: tool output helpers.
    api_appendToolOutput = (c) => this.toolOutput.append(c);
    api_updateToolOutput = (c) => this.toolOutput.update(c);
    api_clearToolOutput = () => this.toolOutput.clear();
    api_beginToolOutput = () => this.toolOutput.begin();
    api_endToolOutput = () => this.toolOutput.end();
    api_enableToolOutput = () => this.toolOutput.enable();
    api_disableToolOutput = () => this.toolOutput.disable();
    api_toggleToolOutput = (id) => this.toolOutput.toggle(id);

    // API: append extra content to a bot message.
    api_appendExtra = (id, c) => this.nodes.appendExtra(id, c, this.scrollMgr);
    // API: remove one message by id.
    api_removeNode = (id) => this.nodes.removeNode(id, this.scrollMgr);
    // API: remove all messages starting from id.
    api_removeNodesFromId = (id) => this.nodes.removeNodesFromId(id, this.scrollMgr);

    // API: replace live area content (with local post-processing).
    api_replaceLive = (content) => {
      const el = this.dom.get('_append_live_'); if (!el) return;
      if (el.classList.contains('hidden')) { el.classList.remove('hidden'); el.classList.add('visible'); }
      el.innerHTML = content;

      try {
        const maybePromise = this.renderer.renderPendingMarkdown(el);

        const post = () => {
          try {
            this.highlighter.observeNewCode(el, {
              deferLastIfStreaming: true,
              minLinesForLast: this.cfg.PROFILE_CODE.minLinesForHL,
              minCharsForLast: this.cfg.PROFILE_CODE.minCharsForHL
            }, this.stream.activeCode);

            this.highlighter.observeMsgBoxes(el, (box) => {
              this.highlighter.observeNewCode(box, {
                deferLastIfStreaming: true,
                minLinesForLast: this.cfg.PROFILE_CODE.minLinesForHL,
                minCharsForLast: this.cfg.PROFILE_CODE.minCharsForHL
              }, this.stream.activeCode);
              this.codeScroll.initScrollableBlocks(box);
            });
          } catch (_) {}

          try {
            const mm = getMathMode();
            // In finalize-only we must force now; otherwise normal schedule is fine.
            if (mm === 'finalize-only') this.math.schedule(el, 0, true);
            else this.math.schedule(el);
          } catch (_) {}

          this.scrollMgr.scheduleScroll();
        };

        if (maybePromise && typeof maybePromise.then === 'function') {
          maybePromise.then(post);
        } else {
          post();
        }
      } catch (_) {
        // Worst-case: keep UX responsive even if something throws before post-processing
        this.scrollMgr.scheduleScroll();
      }
    };

    // API: update footer content.
    api_updateFooter = (html) => { const el = this.dom.get('_footer_'); if (el) el.innerHTML = html; };

    // API: toggle UI features.
    api_enableEditIcons = () => this.ui.enableEditIcons();
    api_disableEditIcons = () => this.ui.disableEditIcons();
    api_enableTimestamp = () => this.ui.enableTimestamp();
    api_disableTimestamp = () => this.ui.disableTimestamp();
    api_enableBlocks = () => this.ui.enableBlocks();
    api_disableBlocks = () => this.ui.disableBlocks();
    api_updateCSS = (styles) => this.ui.updateCSS(styles);

    // API: sync scroll position with host.
    api_getScrollPosition = () => { this.bridge.updateScrollPosition(window.scrollY); };
    api_setScrollPosition = (pos) => { try { window.scrollTo(0, pos); this.scrollMgr.prevScroll = parseInt(pos); } catch (_) {} };

    // API: show/hide loading overlay.
    api_showLoading = () => this.loading.show();
    api_hideLoading = () => this.loading.hide();

    // API: restore collapsed state of codes in a given root.
    api_restoreCollapsedCode = (root) => this.renderer.restoreCollapsedCode(root);

    // API: user-triggered page scroll.
    api_scrollToTopUser = () => this.scrollMgr.scrollToTopUser();
    api_scrollToBottomUser = () => this.scrollMgr.scrollToBottomUser();

    // API: tips visibility control.
    api_showTips = () => this.tips.show();
    api_hideTips = () => this.tips.hide();

    // API: custom markup rules control.
    api_getCustomMarkupRules = () => this.customMarkup.getRules();
    api_setCustomMarkupRules = (rules) => {
      this.customMarkup.setRules(rules);
      // Keep StreamEngine in sync with rules producing fenced code
      try { this.stream.setCustomFenceSpecs(this.customMarkup.getSourceFenceSpecs()); } catch (_) {}
    };

    // Initialize runtime (called on DOMContentLoaded).
    init() {
      this.highlighter.initHLJS();
      this.dom.init();
      this.ui.ensureStickyHeaderStyle();

      this.tips = new TipsManager(this.dom);
      this.events.install();

      this.bridge.initQWebChannel(this.cfg.PID, (bridge) => {
        const onChunk = (name, chunk, type) => this.api_onChunk(name, chunk, type);
        const onNode = (payload) => this.api_appendNode(payload);
        const onNodeReplace = (payload) => this.api_replaceNodes(payload);
        const onNodeInput = (html) => this.api_appendToInput(html);
        this.bridge.connect(onChunk, onNode, onNodeReplace, onNodeInput);
        try { this.logger.bindBridge(this.bridge.bridge || this.bridge); } catch (_) {}
      });

      this.renderer.init();
      try { this.renderer.renderPendingMarkdown(document); } catch (_) {}

      this.highlighter.observeMsgBoxes(document, (box) => {
        this.highlighter.observeNewCode(box, {
          deferLastIfStreaming: true,
          minLinesForLast: this.cfg.PROFILE_CODE.minLinesForHL,
          minCharsForLast: this.cfg.PROFILE_CODE.minCharsForHL
        }, this.stream.activeCode);
        this.codeScroll.initScrollableBlocks(box);
      });
      this.highlighter.observeNewCode(document, {
        deferLastIfStreaming: true,
        minLinesForLast: this.cfg.PROFILE_CODE.minLinesForHL,
        minCharsForLast: this.cfg.PROFILE_CODE.minCharsForHL
      }, this.stream.activeCode);
      this.highlighter.scheduleScanVisibleCodes(this.stream.activeCode);

      this.tips.cycle();
      this.scrollMgr.updateScrollFab(true);
    }

    // Cleanup runtime and detach from DOM/bridge.
    cleanup() {
      this.tips.cleanup();
      try { this.bridge.disconnect(); } catch (_) {}
      this.events.cleanup();
      this.highlighter.cleanup();
      this.math.cleanup();
      this.streamQ.clear();
      this.dom.cleanup();
    }
  }

  // Ensure RafManager.cancel uses the correct group key cleanup.
    if (typeof RafManager !== 'undefined' && RafManager.prototype && typeof RafManager.prototype.cancel === 'function') {
        RafManager.prototype.cancel = function(key) {
          const t = this.tasks.get(key);
          if (!t) return;
          this.tasks.delete(key);
          if (t.group) {
            const set = this.groups.get(t.group);
            if (set) { set.delete(key); if (set.size === 0) this.groups.delete(t.group); }
          }
        };
      }

  const runtime = new Runtime();

  document.addEventListener('DOMContentLoaded', () => runtime.init());

  Object.defineProperty(window, 'SE', { get() { return Utils.SE; } });

  window.beginStream = (chunk) => runtime.api_beginStream(chunk);
  window.endStream = () => runtime.api_endStream();
  window.applyStream = (name, chunk) => runtime.api_applyStream(name, chunk);
  window.appendStream = (name, chunk) => runtime.api_appendStream(name, chunk);
  window.appendStreamTyped = (type, name, chunk) => runtime.api_onChunk(name, chunk, type);
  window.nextStream = () => runtime.api_nextStream();
  window.clearStream = () => runtime.api_clearStream();

  window.appendNode = (payload) => runtime.api_appendNode(payload);
  window.replaceNodes = (payload) => runtime.api_replaceNodes(payload);
  window.appendToInput = (html) => runtime.api_appendToInput(html);

  window.clearNodes = () => runtime.api_clearNodes();
  window.clearInput = () => runtime.api_clearInput();
  window.clearOutput = () => runtime.api_clearOutput();
  window.clearLive = () => runtime.api_clearLive();

  window.appendToolOutput = (c) => runtime.api_appendToolOutput(c);
  window.updateToolOutput = (c) => runtime.api_updateToolOutput(c);
  window.clearToolOutput = () => runtime.api_clearToolOutput();
  window.beginToolOutput = () => runtime.api_beginToolOutput();
  window.endToolOutput = () => runtime.api_endToolOutput();
  window.enableToolOutput = () => runtime.api_enableToolOutput();
  window.disableToolOutput = () => runtime.api_disableToolOutput();
  window.toggleToolOutput = (id) => runtime.api_toggleToolOutput(id);

  window.appendExtra = (id, c) => runtime.api_appendExtra(id, c);
  window.removeNode = (id) => runtime.api_removeNode(id);
  window.removeNodesFromId = (id) => runtime.api_removeNodesFromId(id);

  window.replaceLive = (c) => runtime.api_replaceLive(c);
  window.updateFooter = (c) => runtime.api_updateFooter(c);

  window.enableEditIcons = () => runtime.api_enableEditIcons();
  window.disableEditIcons = () => runtime.api_disableEditIcons();
  window.enableTimestamp = () => runtime.api_enableTimestamp();
  window.disableTimestamp = () => runtime.api_disableTimestamp();
  window.enableBlocks = () => runtime.api_enableBlocks();
  window.disableBlocks = () => runtime.api_disableBlocks();
  window.updateCSS = (s) => runtime.api_updateCSS(s);

  window.getScrollPosition = () => runtime.api_getScrollPosition();
  window.setScrollPosition = (pos) => runtime.api_setScrollPosition(pos);

  window.showLoading = () => runtime.api_showLoading();
  window.hideLoading = () => runtime.api_hideLoading();

  window.restoreCollapsedCode = (root) => runtime.api_restoreCollapsedCode(root);
  window.scrollToTopUser = () => runtime.api_scrollToTopUser();
  window.scrollToBottomUser = () => runtime.api_scrollToBottomUser();

  window.showTips = () => runtime.api_showTips();
  window.hideTips = () => runtime.api_hideTips();

  window.getCustomMarkupRules = () => runtime.api_getCustomMarkupRules();
  window.setCustomMarkupRules = (rules) => runtime.api_setCustomMarkupRules(rules);

  window.__pygpt_cleanup = () => runtime.cleanup();

})();
```

Interdum et malesuada fames ac ante ipsum primis in faucibus. Nam auctor tempor erat. Nam augue nisl, venenatis at semper ac, tristique ut lacus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras convallis volutpat est condimentum varius. 