Arena.ai | DOM Performance Optimizer

Fixes sluggish scrolling in Arena by virtualizing old messages, flattening 30k+ node Shiki codeblocks, and neutralizing CSS blurs and transitions.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Arena.ai | DOM Performance Optimizer
// @namespace    https://greasyfork.org/en/users/1462137-piknockyou
// @version      2.4
// @author       Piknockyou (vibe-coded)
// @license      AGPL-3.0
// @description  Fixes sluggish scrolling in Arena by virtualizing old messages, flattening 30k+ node Shiki codeblocks, and neutralizing CSS blurs and transitions.
// @match        *://*.arena.ai/*
// @match        *://*.canaryarena.ai/*
// @match        *://chat.lmsys.org/*
// @icon         https://arena.ai/favicon.ico
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-start
// ==/UserScript==

(() => {
	let hasStarted = false;
	let pendingStart = false;

	// ═══════════════════════════════════════════════════════════════════
	// CONFIGURATION
	// ═══════════════════════════════════════════════════════════════════
	const DEFAULT_CONFIG = {
		visibleMessages: 6, // Number of recent messages to keep fully rendered
		flattenCodeblocks: true, // Strip <span> tags from old code blocks (massive React perf win)
		killTransitions: true, // Disable expensive transition:all globally
		killBlurs: true, // Disable backdrop-filter:blur (GPU win)
		addContainment: true, // Add CSS containment for scroll isolation
		autoClean: true, // Automatically prune on new messages
		autoCleanDelay: 1000, // ms delay after DOM change before cleaning
		showToolbar: true, // Show the bottom status toolbar
	};

	function getConfig() {
		const stored = GM_getValue("arenaPerfConfigDOM", null);
		if (stored) {
			try {
				return { ...DEFAULT_CONFIG, ...JSON.parse(stored) };
			} catch (_e) {}
		}
		return { ...DEFAULT_CONFIG };
	}

	function saveConfig(cfg) {
		GM_setValue("arenaPerfConfigDOM", JSON.stringify(cfg));
	}

	const config = getConfig();

	// ⚡ INSTANT CSS INJECTION (Before body loads)
	// This prevents the "flash" of transitions, blurs, and old messages.
	try {
		const SURGEON_ID = "arena-perf-surgeon";
		const buildInitialCSS = () => {
			const hideThreshold = config.visibleMessages + 1;
			let css = `ol.flex-col-reverse > div:nth-child(n+${hideThreshold + 1}), ol.flex-col-reverse > li:nth-child(n+${hideThreshold + 1}) { display: none !important; }`;
			if (config.killTransitions)
				css += `*, *::before, *::after { transition: none !important; animation-duration: 0s !important; }`;
			if (config.killBlurs)
				css += `[class*="backdrop-blur"], [style*="backdrop-filter"] { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; }`;
			return css;
		};
		const style = document.createElement("style");
		style.id = SURGEON_ID;
		style.textContent = buildInitialCSS();
		(document.head || document.documentElement).appendChild(style);
	} catch (e) {
		console.error("[ArenaPerf] Early CSS fail", e);
	}

	// ═══════════════════════════════════════════════════════════════════
	// INTERNAL STATE
	// ═══════════════════════════════════════════════════════════════════
	let isPaused = false;
	const stats = { hiddenMsgs: 0, totalMsgs: 0, nodesFlattened: 0 };
	let observer = null;
	let cleanTimeout = null;
	let _settingsPopover = null;

	// ═══════════════════════════════════════════════════════════════════
	// MODULE A — CSS SURGEON
	// ═══════════════════════════════════════════════════════════════════
	const SURGEON_ID = "arena-perf-surgeon";

	function buildSurgeonCSS() {
		let css = `/* ── Arena Perf: Base ── */\n`;

		// INSTANT VIRTUALIZATION: Hide messages via CSS before JS even runs
		// We add +1 to account for the .h-0 spacer at index 0
		const hideThreshold = config.visibleMessages + 1;
		css += `
            ol.flex-col-reverse > div:nth-child(n+${hideThreshold + 1}),
            ol.flex-col-reverse > li:nth-child(n+${hideThreshold + 1}) {
                display: none !important;
            }
        `;

		if (config.killTransitions) {
			css += `
                /* Replace global transition:all with safe, cheap subset */
                *, *::before, *::after {
                    transition-property: color, background-color, border-color, opacity, transform !important;
                    transition-duration: 150ms !important;
                    animation-duration: 0s !important;
                }
                /* Kill React max-height layout thrashing */
                [class*="transition-[max-height]"] {
                    transition: none !important;
                }
                /* Kill scroll-behavior jank */[class*="overflow-"], [class*="scroller"], [data-radix-scroll-area-viewport] {
                    scroll-behavior: auto !important;
                }
            `;
		}

		if (config.killBlurs) {
			css += `
                /* Blurs destroy GPU composition on huge DOMs */[class*="backdrop-blur"], [style*="backdrop-filter"] {
                    backdrop-filter: none !important;
                    -webkit-backdrop-filter: none !important;
                }
                .bg-white\\/50 { background-color: rgba(255,255,255,0.95) !important; }
                .bg-black\\/50 { background-color: rgba(0,0,0,0.95) !important; }
            `;
		}

		if (config.addContainment) {
			css += `
                /* CSS Containment fences off layout calculations */
                [data-radix-scroll-area-viewport] { 
                    contain: layout paint; 
                }
                ol.flex-col-reverse > div {
                    contain: content;
                }
                /* Isolate Markdown and Code elements */
                pre, code,[class*="prose"] {
                    contain: content;
                }
            `;
		}

		return css;
	}

	function applySurgeryCSS() {
		let el = document.getElementById(SURGEON_ID);
		if (!el) {
			el = document.createElement("style");
			el.id = SURGEON_ID;
			(document.head || document.documentElement).appendChild(el);
		}
		el.textContent = buildSurgeonCSS();
	}

	// ═══════════════════════════════════════════════════════════════════
	// MODULE B — DOM VIRTUALIZER & FLATTENER
	// ═══════════════════════════════════════════════════════════════════

	// Arena uses `flex-col-reverse` for the chat container.
	// This means DOM index [0] is the NEWEST message (visually at bottom),
	// and DOM index[length - 1] is the OLDEST message (visually at top).
	function getMessageContainer() {
		return document.querySelector("ol.flex-col-reverse");
	}

	function getMessages(container) {
		if (!container) return [];
		return Array.from(container.children).filter((el) => {
			const isMsg = el.tagName === "DIV" || el.tagName === "LI";
			const isNotSpacer = !el.classList.contains("h-0");
			return isMsg && isNotSpacer;
		});
	}

	// Strips out Shiki <span> tags and leaves pure raw text.
	// This stops React from reconciling 25,000+ nodes on every app state change.
	function flattenShikiBlocks(msgNode) {
		if (!config.flattenCodeblocks) return 0;
		let flattened = 0;

		const codes = msgNode.querySelectorAll(
			'pre.shiki code:not([data-perf-flat="1"])',
		);
		codes.forEach((code) => {
			if (code.children.length > 0) {
				// Extract text line by line to preserve line breaks, preventing user messages from being mashed together
				const lines = code.querySelectorAll(".line");
				let rawText = "";
				if (lines.length > 0) {
					rawText = Array.from(lines)
						.map((l) => l.textContent)
						.join("\n");
				} else {
					rawText = code.textContent;
				}

				code.innerHTML = "";
				code.textContent = rawText;
				code.dataset.perfFlat = "1";
				flattened++;
			}
		});
		return flattened;
	}

	function cleanMessages() { 
		if (isPaused || !config.autoClean) return; 
 
		if (observer) observer.disconnect();

		const container = getMessageContainer();
		if (!container) return;

		const msgs = getMessages(container);
		stats.totalMsgs = msgs.length;

		if (msgs.length <= config.visibleMessages) {
			msgs.forEach((m) => {
				m.style.display = "";
			});
			stats.hiddenMsgs = 0;
			updateToolbar();
			return;
		}

		let hidden = 0;
		let newFlattened = 0;

		// Because it's flex-col-reverse, indices 0 to (visibleMessages-1) are the newest.
		// Everything after that is old and should be virtualized/hidden.
		msgs.forEach((msg, index) => {
			if (index < config.visibleMessages) {
				// Keep visible
				if (msg.style.display === "none") {
					msg.style.display = "";
				}
			} else {
				// Flatten and Hide
				newFlattened += flattenShikiBlocks(msg);

				if (msg.style.display !== "none") {
					msg.style.display = "none";
				}
				hidden++;
			}
		});

		stats.hiddenMsgs = hidden;
		stats.nodesFlattened += newFlattened; 

		startObserver();

		if (newFlattened > 0) {
			console.log( 
				`[ArenaPerf] Flattened ${newFlattened} codeblocks in hidden messages.`, 
			); 
		}
		updateToolbar(); 
	}

	function showAllMessages() {
		const container = getMessageContainer();
		if (!container) return;

		getMessages(container).forEach((msg) => {
			if (msg.style.display === "none") {
				msg.style.display = "";
			}
		});

		stats.hiddenMsgs = 0;
		updateToolbar();
	}

	// ═══════════════════════════════════════════════════════════════════
	// MODULE C — TOOLBAR UI
	// ═══════════════════════════════════════════════════════════════════
	const TOOLBAR_ID = "arena-perf-toolbar";
	const POPOVER_ID = "arena-perf-settings";

	let toolbarEl, statsEl, toggleBtn;

	function createToolbar() {
		if (!config.showToolbar || document.getElementById(TOOLBAR_ID)) return;

		GM_addStyle(`
            #${TOOLBAR_ID} {
                position: fixed; bottom: 0; left: 50%; transform: translateX(-50%);
                z-index: 2147483647; background: #09090b; color: #4ade80;
                padding: 0 4px; border-radius: 6px 6px 0 0;
                font: 11px/1.4 system-ui, sans-serif; display: flex; align-items: center; gap: 2px;
                border: 1px solid #27272a; border-bottom: none; box-shadow: 0 -2px 10px rgba(0,0,0,0.5);
                user-select: none;
            }
            #${TOOLBAR_ID} button {
                all: unset; cursor: pointer; padding: 2px 8px; border-radius: 4px;
                color: #e4e4e7; white-space: nowrap; transition: background 0.1s;
            }
            #${TOOLBAR_ID} button:hover { background: rgba(255,255,255,0.1); }
            #${TOOLBAR_ID} .perf-stats { padding: 3px 6px; pointer-events: none; }

            #${POPOVER_ID} {
                position: fixed; bottom: 28px; left: 50%; transform: translateX(-50%);
                z-index: 2147483647; background: #18181b; color: #e4e4e7;
                border-radius: 10px; padding: 14px 18px; font: 13px/1.5 system-ui, sans-serif;
                box-shadow: 0 -4px 24px rgba(0,0,0,0.6); border: 1px solid #27272a;
                display: flex; flex-direction: column; gap: 8px; min-width: 260px;
            }
            #${POPOVER_ID} .perf-row { 
                display: flex; align-items: center; justify-content: space-between; gap: 12px;
                padding: 6px 8px; border-radius: 6px; cursor: pointer;
            }
            #${POPOVER_ID} .perf-row:hover { background: rgba(255,255,255,0.05); }
            #${POPOVER_ID} .perf-on { color: #4ade80; font-weight: bold; }
            #${POPOVER_ID} .perf-off { color: #a1a1aa; font-weight: bold; }
            #${POPOVER_ID} hr { border: 0; border-top: 1px solid #27272a; margin: 4px 0; }
        `);

		toolbarEl = document.createElement("div");
		toolbarEl.id = TOOLBAR_ID;

		toggleBtn = document.createElement("button");
		toggleBtn.addEventListener("click", () => {
			isPaused = !isPaused;
			if (isPaused) showAllMessages();
			else cleanMessages();
			updateToolbar();
		});

		statsEl = document.createElement("span");
		statsEl.className = "perf-stats";

		const forceBtn = document.createElement("button");
		forceBtn.textContent = "🧹";
		forceBtn.title = "Force Clean Now";
		forceBtn.addEventListener("click", () => {
			isPaused = false;
			cleanMessages();
		});

		const settingsBtn = document.createElement("button");
		settingsBtn.textContent = "⚙️";
		settingsBtn.addEventListener("click", toggleSettingsPopover);

		toolbarEl.appendChild(toggleBtn);
		toolbarEl.appendChild(statsEl);
		toolbarEl.appendChild(forceBtn);
		toolbarEl.appendChild(settingsBtn);

		document.body.appendChild(toolbarEl);
		updateToolbar();

		// Start watchdog to prevent React from permanently removing the toolbar
		setInterval(() => {
			if (toolbarEl && !document.body.contains(toolbarEl)) {
				console.log("[ArenaPerf] Watchdog: Re-attaching toolbar");
				document.body.appendChild(toolbarEl);
			}
		}, 2000);
	}

	function updateToolbar() {
		if (!statsEl) return;

		toggleBtn.textContent = isPaused ? "👁️ All" : "🔒 Hide";
		toggleBtn.style.color = isPaused ? "#fcd34d" : "#e4e4e7";

		const statText = isPaused
			? `${stats.totalMsgs} msgs · paused`
			: `${stats.totalMsgs - stats.hiddenMsgs}/${stats.totalMsgs} msgs`;

		statsEl.textContent = statText;
		statsEl.style.color = isPaused
			? "#fcd34d"
			: stats.hiddenMsgs > 0
				? "#4ade80"
				: "#a1a1aa";
	}

	function toggleSettingsPopover() {
		if (_settingsPopover) {
			_settingsPopover.remove();
			_settingsPopover = null;
			return;
		}

		const panel = document.createElement("div");
		panel.id = POPOVER_ID;

		function addToggle(icon, label, getValue, onToggle) {
			const row = document.createElement("div");
			row.className = "perf-row";
			const render = () => {
				const on = getValue();
				row.innerHTML = `<span>${icon} ${label}</span><span class="${on ? "perf-on" : "perf-off"}">${on ? "ON" : "OFF"}</span>`;
			};
			render();
			row.addEventListener("click", () => {
				onToggle();
				render();
				saveConfig(config);
			});
			panel.appendChild(row);
		}

		const head = document.createElement("div");
		head.style.cssText =
			"font-weight:bold; font-size:14px; margin-bottom:4px; text-align:center;";
		head.textContent = "DOM Optimizer Settings";
		panel.appendChild(head);

		addToggle(
			"⚡",
			"Kill Transitions",
			() => config.killTransitions,
			() => {
				config.killTransitions = !config.killTransitions;
				applySurgeryCSS();
			},
		);

		addToggle(
			"🌫️",
			"Kill GPU Blurs",
			() => config.killBlurs,
			() => {
				config.killBlurs = !config.killBlurs;
				applySurgeryCSS();
			},
		);

		addToggle(
			"🔥",
			"Flatten Shiki Spans",
			() => config.flattenCodeblocks,
			() => {
				config.flattenCodeblocks = !config.flattenCodeblocks;
				if (config.flattenCodeblocks) cleanMessages();
			},
		);

		addToggle(
			"📦",
			"CSS Containment",
			() => config.addContainment,
			() => {
				config.addContainment = !config.addContainment;
				applySurgeryCSS();
			},
		);

		panel.appendChild(document.createElement("hr"));

		const numRow = document.createElement("div");
		numRow.className = "perf-row";
		numRow.innerHTML = `<span>📝 Visible Messages</span><span style="color:#60a5fa;font-weight:bold">${config.visibleMessages}</span>`;
		numRow.addEventListener("click", () => {
			const val = prompt(
				"Number of recent messages to keep visible?",
				config.visibleMessages,
			);
			const num = parseInt(val, 10);
			if (num && num > 0) {
				config.visibleMessages = num;
				saveConfig(config);
				cleanMessages();
				panel.remove();
				_settingsPopover = null;
			}
		});
		panel.appendChild(numRow);

		document.body.appendChild(panel);
		_settingsPopover = panel;

		// Auto-close on click outside
		setTimeout(() => {
			const closeHandler = (e) => {
				if (!panel.contains(e.target) && !toolbarEl.contains(e.target)) {
					panel.remove();
					_settingsPopover = null;
					document.removeEventListener("mousedown", closeHandler);
				}
			};
			document.addEventListener("mousedown", closeHandler);
		}, 0);
	}

	// ═══════════════════════════════════════════════════════════════════
	// MODULE D — OBSERVER & ORCHESTRATOR
	// ═══════════════════════════════════════════════════════════════════

	function scheduleClean() {
		if (cleanTimeout) clearTimeout(cleanTimeout);
		cleanTimeout = setTimeout(cleanMessages, config.autoCleanDelay);
	}

	function startObserver() {
		if (observer) observer.disconnect();

		observer = new MutationObserver((mutations) => {
			let shouldClean = false;
			for (const m of mutations) {
				// If a new message is added to the list
				if (m.type === "childList" && m.addedNodes.length > 0) {
					shouldClean = true;
					break;
				}
			}
			if (shouldClean) scheduleClean();
		});

		// Observe the body to handle SPA navigations where the ol container swaps
		observer.observe(document.body, { childList: true, subtree: true });
	}

	function initializeUI() {
		if (document.visibilityState !== "visible") {
			pendingStart = true;
			console.log("[ArenaPerf] Deferring init until tab is visible");
			return;
		}

		if (hasStarted) return;
		hasStarted = true;

		console.log("[ArenaPerf] Booting DOM Virtualizer & Flattener...");
		applySurgeryCSS();
		if (config.showToolbar) createToolbar();
		startObserver();

		// Initial passes
		setTimeout(cleanMessages, 1000);
		setTimeout(cleanMessages, 3000);
	}

	function scheduleInit() {
		if (document.visibilityState !== "visible") {
			pendingStart = true;
			return;
		}

		if (window.requestIdleCallback) {
			const idleId = window.requestIdleCallback(initializeUI, {
				timeout: 1000,
			});
			setTimeout(() => {
				if (!hasStarted) {
					window.cancelIdleCallback(idleId);
					initializeUI();
				}
			}, 1500);
		} else {
			setTimeout(initializeUI, 100);
		}
	}

	function waitForReady() {
		const READY_SELECTOR = "form textarea";

		if (document.querySelector(READY_SELECTOR)) {
			scheduleInit();
			return;
		}

		const readyObserver = new MutationObserver(() => {
			if (document.querySelector(READY_SELECTOR)) {
				readyObserver.disconnect();
				setTimeout(scheduleInit, 200);
			}
		});

		readyObserver.observe(document.body, { childList: true, subtree: true });
	}

	document.addEventListener("visibilitychange", () => {
		if (document.visibilityState !== "visible") return;

		if (pendingStart && !hasStarted) {
			pendingStart = false;
			setTimeout(initializeUI, 250);
		}
	});

	setTimeout(() => {
		if (document.visibilityState !== "visible") {
			pendingStart = true;
			return;
		}
		if (!hasStarted && document.querySelector("form textarea")) {
			console.warn("[ArenaPerf] Emergency init after 5s");
			initializeUI();
		}
	}, 5000);

	// Ultra-fast Body Detection (Faster than DOMContentLoaded)
	if (document.body) {
		waitForReady();
	} else {
		const bodyObserver = new MutationObserver(() => {
			if (document.body) {
				bodyObserver.disconnect();
				waitForReady();
			}
		});
		bodyObserver.observe(document.documentElement, { childList: true });
	}
})();