Improves ChatGPT performance by virtualizing off-screen messages.
// ==UserScript==
// @name GPT Optimum
// @namespace https://github.com/YashRana738/GptOptimum
// @version 2.2
// @description Improves ChatGPT performance by virtualizing off-screen messages.
// @author Yash Rana
// @license MIT
// @homepageURL https://github.com/YashRana738/GptOptimum
// @supportURL https://github.com/YashRana738/GptOptimum/issues
// @match *://chatgpt.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
// ── Helpers ──────────────────────────────────────────────────
const $ = (s, root = document) => root.querySelector(s);
const $$ = (s, root = document) => root.querySelectorAll(s);
const isChatRoute = () => location.pathname.startsWith('/c/');
const getChatId = () => {
const m = location.pathname.match(/^\/c\/([a-f0-9-]+)/);
return m ? m[1] : null;
};
// ── Settings (GM storage) ───────────────────────────────────
const DEFAULTS = { enabled: true, debug: false, aggressive: false, instantNewChat: true, topCenter: false, uiAlpha: 0.5, alwaysExpand: false };
const cfg = {
get(k) { return GM_getValue(k, DEFAULTS[k]); },
set(k, v) { GM_setValue(k, v); },
all() { return { enabled: this.get('enabled'), debug: this.get('debug'), aggressive: this.get('aggressive'), instantNewChat: this.get('instantNewChat'), topCenter: this.get('topCenter'), uiAlpha: this.get('uiAlpha'), alwaysExpand: this.get('alwaysExpand') }; },
};
// ── Early UI lock (document-start) ──────────────────────────
if (cfg.get('enabled') && isChatRoute()) {
const bootId = getChatId();
const storedBootId = cfg.get('lastChatId');
const isNewChat = bootId && storedBootId && storedBootId < bootId && cfg.get('instantNewChat');
if (!isNewChat) {
document.documentElement.classList.add('cgv-loading');
}
if (bootId && (!storedBootId || bootId > storedBootId)) cfg.set('lastChatId', bootId);
}
// ── CSS ─────────────────────────────────────────────────────
const CSS_VARS = `
:root{--cgv-bg:#fff;--cgv-text:#0d0d0d;--cgv-muted:#666;--cgv-border:#e5e5e5;--cgv-shadow:0 4px 6px -1px rgb(0 0 0/.1);--cgv-primary:#10a37f;--cgv-ui-alpha:0.5}
html.dark,:root{--cgv-bg:#fff;--cgv-text:#0d0d0d;--cgv-muted:#666;--cgv-border:#e5e5e5}
html.dark{--cgv-bg:#2f2f2f;--cgv-text:#ececec;--cgv-muted:#b4b4b4;--cgv-border:#424242;--cgv-shadow:0 4px 6px -1px rgb(0 0 0/.5)}
@media(prefers-color-scheme:dark){html:not(.light){--cgv-bg:#2f2f2f;--cgv-text:#ececec;--cgv-muted:#b4b4b4;--cgv-border:#424242;--cgv-shadow:0 4px 6px -1px rgb(0 0 0/.5)}}
`;
const GLOBAL_CSS = `
${CSS_VARS}
html.cgv-loading body { pointer-events: none !important; user-select: none !important; cursor: wait !important; opacity: 0.7 !important; transition: opacity 0.3s; }
.cgv-off { content-visibility: hidden !important; }
body.cgv-debug .cgv-off { background: rgba(255,0,0,.1) !important; border: 1px dashed red !important; position: relative; }
body.cgv-debug .cgv-off::after { content: "Unloaded"; position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); color: red; font-weight: 700; font-size: 14px; pointer-events: none; }
`;
const SHADOW_CSS = `
${CSS_VARS}
.cgv-wrap { position: fixed; bottom: 24px; right: 24px; z-index: 999999; display: flex; flex-direction: column; align-items: flex-end; pointer-events: auto; }
.cgv-pill { display: flex; align-items: center; background: var(--cgv-bg); color: var(--cgv-text); border: 1px solid var(--cgv-border); box-shadow: var(--cgv-shadow); padding: 8px 14px; border-radius: 20px; font: 500 13px/1 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; cursor: pointer; white-space: nowrap; box-sizing: border-box; opacity: var(--cgv-ui-alpha); transform: translateY(10px); gap: 8px; max-width: 220px; transition: opacity .25s ease, transform .4s cubic-bezier(.34,1.56,.64,1), max-width .4s cubic-bezier(.4,0,.2,1), padding .4s cubic-bezier(.4,0,.2,1), height .4s cubic-bezier(.4,0,.2,1), border-radius .4s cubic-bezier(.4,0,.2,1), border-color .25s, gap .4s cubic-bezier(.4,0,.2,1); height: 34px; overflow: hidden; }
.cgv-pill.on { opacity: var(--cgv-ui-alpha); transform: translateY(0); }
.cgv-pill.on:hover, .cgv-pill.busy, .cgv-pill.dead { opacity: 1 !important; }
.cgv-pill.mini { max-width: 30px; padding: 10px; border-radius: 50%; gap: 0; height: 30px; }
.cgv-pill.mini .cgv-dot { margin: 0; }
.cgv-wrap:hover .cgv-pill.mini { max-width: 220px; padding: 8px 14px; border-radius: 20px; height: 34px; gap: 8px; }
.cgv-wrap:hover .cgv-pill.mini .cgv-label { max-width: 150px; }
.cgv-dot { width: 8px; height: 8px; min-width: 8px; min-height: 8px; border-radius: 50%; flex-shrink: 0; position: relative; z-index: 2; background: var(--cgv-primary); box-shadow: 0 0 6px var(--cgv-primary); animation: cgv-pulse 2s infinite; transition: background .25s, box-shadow .25s, border .25s; }
.cgv-label { display: inline-block; overflow: hidden; max-width: 150px; transition: max-width .4s cubic-bezier(.4,0,.2,1); }
.cgv-pill.mini .cgv-label { max-width: 0; }
.cgv-pill.busy { border-color: var(--cgv-primary); opacity: 1 !important; }
.cgv-pill.busy .cgv-dot { animation: cgv-spin .8s linear infinite; background: 0 0; border: 2px solid var(--cgv-primary); border-top-color: transparent; width: 10px; height: 10px; box-shadow: none; }
.cgv-pill.dead { border-color: #ef4444; }
.cgv-pill.dead .cgv-dot { background: #ef4444; box-shadow: 0 0 6px #ef4444; animation: none; }
.cgv-block { position: fixed; bottom: 24px; right: 24px; background: #ef4444; color: #fff; padding: 10px 18px; border-radius: 25px; font: 600 13px -apple-system,sans-serif; box-shadow: 0 10px 25px -5px rgba(239,68,68,.4); z-index: 1000000; display: flex; align-items: center; gap: 8px; transform: translateY(120px); opacity: 0; transition: transform .6s cubic-bezier(.68,-.6,.32,1.6), opacity .35s; }
.cgv-block.on { transform: translateY(0); opacity: 1; }
.cgv-block svg { width: 16px; height: 16px; stroke: currentColor; stroke-width: 2.5; fill: none; }
.cgv-pill.pushed { transform: translateY(-64px) !important; }
.cgv-panel { position: absolute; bottom: calc(100% + 14px); right: 0; width: 290px; background: var(--cgv-bg); color: var(--cgv-text); border: 1px solid var(--cgv-border); border-radius: 18px; box-shadow: 0 8px 28px rgba(0,0,0,.16); font-family: 'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; opacity: 0; transform: translateY(6px) scale(.97); pointer-events: none; transition: opacity .18s, transform .22s cubic-bezier(.34,1.56,.64,1); z-index: 1000001; overflow: visible; }
.cgv-panel::after { content: ""; position: absolute; top: 100%; left: 0; right: 0; height: 22px; }
.cgv-wrap:hover .cgv-panel { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
.cgv-top-pos { top: 24px; bottom: auto; left: 50%; right: auto; transform: translateX(-50%); align-items: center; }
.cgv-top-pos .cgv-pill.pushed { transform: translateY(0) !important; }
.cgv-top-pos .cgv-panel { top: calc(100% + 14px); bottom: auto; right: auto; left: 50%; transform: translate(-50%, -6px) scale(.97); }
.cgv-top-pos .cgv-panel::after { bottom: 100%; top: auto; height: 22px; }
.cgv-top-pos:hover .cgv-panel { transform: translate(-50%, 0) scale(1); }
.cgv-block.cgv-top-pos { top: 74px; bottom: auto; left: 50%; right: auto; transform: translate(-50%, -120px); }
.cgv-block.cgv-top-pos.on { transform: translate(-50%, 0); }
.cgv-ph { padding: 18px 16px 8px; text-align: center; }
.cgv-ph h3 { margin: 0; font-size: 15px; font-weight: 600; display: flex; align-items: center; justify-content: center; gap: 8px; }
.cgv-ph p { margin: 3px 0 0; font-size: 11px; color: var(--cgv-muted); }
.cgv-ph .cgv-hd { width: 9px; height: 9px; background: var(--cgv-primary); border-radius: 50%; box-shadow: 0 0 6px var(--cgv-primary); flex-shrink: 0; }
.cgv-pc { padding: 8px 16px 16px; }
.cgv-loading-msg { display: none; padding: 28px 16px; text-align: center; font-size: 13px; font-weight: 500; color: var(--cgv-muted); }
.cgv-loading-msg .cgv-lspin { display: inline-block; width: 18px; height: 18px; border: 2px solid var(--cgv-border); border-top-color: var(--cgv-primary); border-radius: 50%; animation: cgv-spin .7s linear infinite; margin-bottom: 10px; }
.cgv-panel.loading .cgv-pc { display: none; }
.cgv-panel.loading .cgv-loading-msg { display: block; }
.cgv-card { background: var(--cgv-bg); border: 1px solid var(--cgv-border); border-radius: 14px; padding: 12px 14px; margin-bottom: 8px; display: flex; align-items: center; justify-content: space-between; box-shadow: 0 1px 3px rgba(0,0,0,.05); cursor: pointer; transition: border-color .2s, transform .15s; }
.cgv-card:last-child { margin-bottom: 0; }
.cgv-card:hover { border-color: var(--cgv-primary); transform: translateY(-1px); }
.cgv-card.off { opacity: .45; pointer-events: none; filter: grayscale(.5); }
.cgv-card.main { border-color: var(--cgv-primary); margin-bottom: 14px; }
.cgv-ci { display: flex; flex-direction: column; gap: 1px; }
.cgv-cl { font-size: 13px; font-weight: 500; }
.cgv-cd { font-size: 10.5px; color: var(--cgv-muted); }
.cgv-sw { position: relative; display: inline-block; width: 34px; height: 19px; flex-shrink: 0; }
.cgv-sw input { opacity: 0; width: 0; height: 0; }
.cgv-sl { position: absolute; cursor: pointer; inset: 0; background: var(--cgv-border); border-radius: 19px; transition: .25s cubic-bezier(.4, 0, .2, 1); }
.cgv-sl::before { content: ""; position: absolute; width: 13px; height: 13px; left: 3px; bottom: 3px; background: #fff; border-radius: 50%; box-shadow: 0 1px 3px rgba(0,0,0,.2); transition: .25s cubic-bezier(.4, 0, .2, 1); }
.cgv-sw input:checked + .cgv-sl { background: var(--cgv-primary); }
.cgv-sw input:checked + .cgv-sl::before { transform: translateX(15px); }
.cgv-range { width: 100%; height: 4px; background: var(--cgv-border); border-radius: 2px; appearance: none; outline: none; margin-top: 8px; }
.cgv-range::-webkit-slider-thumb { appearance: none; width: 12px; height: 12px; background: var(--cgv-primary); border-radius: 50%; cursor: pointer; transition: transform .1s; }
.cgv-range::-webkit-slider-thumb:hover { transform: scale(1.2); }
.cgv-ft { padding: 0 16px 12px; text-align: center; font-size: 10px; color: var(--cgv-muted); opacity: .5; }
@keyframes cgv-spin { to { transform: rotate(360deg); } }
@keyframes cgv-pulse { 0%, 100% { opacity: .5; } 50% { opacity: 1; } }
`;
GM_addStyle(GLOBAL_CSS);
// ── Virtualizer ─────────────────────────────────────────────
class Virtualizer {
constructor() {
this.enabled = false;
this.aggressive = false;
this.io = null;
}
boot(enabled, aggressive) {
this.enabled = enabled;
this.aggressive = aggressive;
if (enabled) this._createObserver();
}
_margin() { return this.aggressive ? '5% 0px' : '100% 0px'; }
_createObserver() {
this.io?.disconnect();
this.io = new IntersectionObserver(entries => {
if (!this.enabled) return;
for (const e of entries) {
if (e.isIntersecting) this._restore(e.target);
else this._unload(e.target);
}
}, { rootMargin: this._margin(), threshold: 0 });
$$('.cgv-msg').forEach(el => this.io.observe(el));
}
setEnabled(v) {
this.enabled = v;
if (v) this._createObserver();
else { this.io?.disconnect(); $$('.cgv-off').forEach(el => this._restore(el)); }
}
setAggressive(v) {
const changed = this.aggressive !== v;
this.aggressive = v;
if (changed && this.enabled) this._createObserver();
}
setDebug(v) {
document.body?.classList.toggle('cgv-debug', v);
}
observe(el) { this.io?.observe(el); }
_unload(el) {
if (el.classList.contains('cgv-off')) return;
const h = Math.round(el.getBoundingClientRect().height);
if (h < 60) return;
el.style.containIntrinsicSize = `auto ${h}px`;
el.classList.add('cgv-off');
}
_restore(el) {
if (!el.classList.contains('cgv-off')) return;
el.classList.remove('cgv-off');
el.style.containIntrinsicSize = '';
}
}
const virt = new Virtualizer();
// ── Selectors ───────────────────────────────────────────────
const MSG_SEL = '[data-message-author-role],article,[data-testid^="conversation-turn-"],.w-full.text-token-text-primary,.group.w-full.border-b';
// ── App ─────────────────────────────────────────────────────
class App {
constructor() {
this.tracked = new Set();
this.pill = null;
this.wrap = null;
this.blocker = null;
this.panel = null;
this.busy = isChatRoute();
this.lastUrl = location.href;
this.collapseTimer = null;
this.statusLock = false;
this.shadow = null;
this.host = null;
}
async run() {
await this._waitBody();
const s = cfg.all();
virt.boot(s.enabled, s.aggressive);
virt.setDebug(s.debug);
this._buildUI(s);
this._observe();
this._watchUrl();
this._guard(); // Persistence guard
if (s.enabled && this.busy) this._optimise();
else { this.busy = false; this._unlock(); this._sync(s.enabled); }
}
// ── Input blocker during optimisation ──────────────────────
_blockInputs() {
const handler = e => {
if (!this.busy || !virt.enabled) return;
// Never block events inside our UI wrapper
if (this.wrap?.contains(e.target)) return;
e.stopImmediatePropagation();
e.preventDefault();
};
for (const t of ['keydown', 'keyup', 'keypress', 'contextmenu', 'mousedown', 'click', 'dblclick', 'auxclick'])
window.addEventListener(t, handler, true);
}
// ── URL change detection (SPA) ────────────────────────────
_watchUrl() {
setInterval(() => {
if (location.href === this.lastUrl) return;
const prevUrl = this.lastUrl;
this.lastUrl = location.href;
// Was the previous page the homepage (not a /c/ chat route)?
const cameFromHome = !prevUrl.includes('/c/');
if (isChatRoute() && virt.enabled) {
const newId = getChatId();
// ── Instant optimise new chats ──
if (newId) {
const storedId = cfg.get('lastChatId');
if (!storedId || newId > storedId) {
cfg.set('lastChatId', newId);
}
if (cfg.get('instantNewChat') && cameFromHome && storedId && storedId < newId) {
this.tracked.clear();
this.busy = false;
this._unlock();
this.pill?.classList.remove('pushed');
this.blocker?.classList.remove('on');
this.panel?.classList.remove('loading');
const lbl = $('.cgv-label', this.pill);
if (lbl) lbl.textContent = 'New chat detected';
this.statusLock = true;
this.pill?.classList.remove('busy', 'dead', 'mini');
this.pill?.classList.add('on');
this._scheduleCollapse(3000);
this._observeNewChat();
return;
}
}
this.tracked.clear();
this.busy = true;
document.documentElement.classList.add('cgv-loading');
this._showBusy();
setTimeout(() => { if (this.busy) { this.pill?.classList.add('pushed'); this.blocker?.classList.add('on'); } }, 400);
this._optimise(true);
} else {
this.busy = false;
this._unlock();
this._sync(virt.enabled);
this.pill?.classList.remove('pushed');
this.blocker?.classList.remove('on');
}
}, 500);
}
// ── Wait for <body> ───────────────────────────────────────
_waitBody() {
return new Promise(r => {
if (document.body) return r();
new MutationObserver((_, o) => { if (document.body) { o.disconnect(); r(); } })
.observe(document.documentElement, { childList: true });
});
}
// ── Build all UI elements (using Shadow Root) ──────────────
_buildUI(s) {
if (this.host && document.contains(this.host)) return;
this.host = document.createElement('div');
this.host.id = 'cgv-host';
this.shadow = this.host.attachShadow({ mode: 'closed' });
const style = document.createElement('style');
style.textContent = SHADOW_CSS;
this.shadow.appendChild(style);
// Wrapper
this.wrap = document.createElement('div');
this.wrap.className = 'cgv-wrap';
// Pill
this.pill = document.createElement('div');
this.pill.className = 'cgv-pill';
this.pill.innerHTML = '<div class="cgv-dot"></div><span class="cgv-label">Optimised</span>';
this.pill.addEventListener('click', () => {
if (this.busy) return;
this.pill.classList.toggle('mini');
if (this.pill.classList.contains('mini')) { clearTimeout(this.collapseTimer); }
else this._scheduleCollapse();
});
// Blocker
this.blocker = document.createElement('div');
this.blocker.className = 'cgv-block';
this.blocker.innerHTML = '<svg viewBox="0 0 24 24"><path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg><span>Input temporarily blocked</span>';
// Panel
this.panel = this._buildPanel(s);
// Initial state
if (s.topCenter) {
this.wrap.classList.add('cgv-top-pos');
this.blocker.classList.add('cgv-top-pos');
}
if (this.busy && s.enabled) {
this._showBusy();
setTimeout(() => { if (this.busy) { this.pill?.classList.add('pushed'); this.blocker?.classList.add('on'); } }, 400);
}
this.wrap.append(this.panel, this.pill);
this.shadow.append(this.wrap, this.blocker);
document.body.appendChild(this.host);
this._blockInputs();
}
// ── Build settings panel ──────────────────────────────────
_buildPanel(s) {
const el = document.createElement('div');
el.className = 'cgv-panel' + (this.busy ? ' loading' : '');
el.innerHTML = `
<div class="cgv-ph"><h3><div class="cgv-hd"></div>GPT Optimum</h3><p>Optimize ChatGPT Performance</p></div>
<div class="cgv-loading-msg"><div class="cgv-lspin"></div><br>Please wait for chat to load</div>
<div class="cgv-pc">
${this._card('enabled', 'Enable Optimise', 'Unload off-screen items', s.enabled, 'main')}
${this._card('alwaysExpand', 'Always Expanded', 'Prevent pill from collapsing', s.alwaysExpand, 'dep')}
${this._card('topCenter', 'Top Center Position', 'Move the indicator to the top', s.topCenter, 'dep')}
${this._slider('uiAlpha', 'UI Alpha', 'Adjust Indicator Transparency', s.uiAlpha, 'dep')}
${this._card('instantNewChat', 'Instant New Chats', 'Skip optimisation on new chats', s.instantNewChat, 'dep')}
${this._card('aggressive', 'Aggressive Mode', 'Tighter margins, more unloading', s.aggressive, 'dep')}
${this._card('debug', 'Debug Mode', 'Highlight unloaded elements', s.debug, 'dep')}
</div>
<div class="cgv-ft"><a href="https://github.com/YashRana738/GptOptimum" target="_blank" style="color:inherit;text-decoration:none;opacity:0.7">v2.1 by Yash Rana</a></div>`;
// Wire toggles
const enabledTgl = $('[data-key="enabled"] input', el);
const expandTgl = $('[data-key="alwaysExpand"] input', el);
const topTgl = $('[data-key="topCenter"] input', el);
const alphaSld = $('[data-key="uiAlpha"] input', el);
const instantTgl = $('[data-key="instantNewChat"] input', el);
const aggrTgl = $('[data-key="aggressive"] input', el);
const debugTgl = $('[data-key="debug"] input', el);
const deps = $$('.dep', el);
const syncDeps = () => {
deps.forEach(c => c.classList.toggle('off', !enabledTgl.checked));
};
syncDeps();
enabledTgl.addEventListener('change', () => {
const on = enabledTgl.checked;
cfg.set('enabled', on);
virt.setEnabled(on);
syncDeps();
if (on && isChatRoute()) {
this.busy = true;
this.tracked.clear();
this.scanMessages();
this._optimise();
} else if (!on) {
this.busy = false;
this._unlock();
}
this._sync(on);
});
expandTgl.addEventListener('change', () => {
const on = expandTgl.checked;
cfg.set('alwaysExpand', on);
if (on) {
clearTimeout(this.collapseTimer);
this.pill?.classList.remove('mini');
} else {
this._scheduleCollapse(500);
}
});
topTgl.addEventListener('change', () => {
const on = topTgl.checked;
cfg.set('topCenter', on);
this.wrap?.classList.toggle('cgv-top-pos', on);
this.blocker?.classList.toggle('cgv-top-pos', on);
syncDeps();
});
alphaSld.addEventListener('input', () => {
const val = alphaSld.value;
cfg.set('uiAlpha', val);
document.documentElement.style.setProperty('--cgv-ui-alpha', val);
const valEl = $('.cgv-range-val', el);
if (valEl) valEl.textContent = Math.round(val * 100) + '%';
});
instantTgl.addEventListener('change', () => {
cfg.set('instantNewChat', instantTgl.checked);
});
aggrTgl.addEventListener('change', () => {
cfg.set('aggressive', aggrTgl.checked);
virt.setAggressive(aggrTgl.checked);
});
debugTgl.addEventListener('change', () => {
cfg.set('debug', debugTgl.checked);
virt.setDebug(debugTgl.checked);
});
// Card click → toggle
$$('.cgv-card', el).forEach(card => {
card.addEventListener('click', e => {
if (e.target.tagName === 'INPUT' || e.target.classList.contains('cgv-sl')) return;
if (card.classList.contains('off')) return;
const inp = $('input', card);
inp.checked = !inp.checked;
inp.dispatchEvent(new Event('change'));
});
});
return el;
}
_card(key, label, desc, checked, cls) {
return `<div class="cgv-card ${cls}" data-key="${key}"><div class="cgv-ci"><span class="cgv-cl">${label}</span><span class="cgv-cd">${desc}</span></div><label class="cgv-sw"><input type="checkbox"${checked ? ' checked' : ''}><span class="cgv-sl"></span></label></div>`;
}
_slider(key, label, desc, value, cls) {
return `<div class="cgv-card ${cls}" data-key="${key}" style="flex-direction:column;align-items:flex-start;cursor:default"><div style="display:flex;justify-content:space-between;width:100%"><div class="cgv-ci"><span class="cgv-cl">${label}</span><span class="cgv-cd">${desc}</span></div><span class="cgv-range-val" style="font-size:11px;font-weight:600;color:var(--cgv-primary)">${Math.round(value * 100)}%</span></div><input type="range" class="cgv-range" min="0" max="1" step="0.05" value="${value}"></div>`;
}
// ── Indicator state management ────────────────────────────
_sync(enabled) {
if (!this.pill) return;
const lbl = $('.cgv-label', this.pill);
this.pill.classList.remove('busy', 'dead', 'mini', 'pushed');
clearTimeout(this.collapseTimer);
this.blocker?.classList.remove('on');
// Update panel loading state
this.panel?.classList.toggle('loading', this.busy);
if (this.busy && virt.enabled) {
lbl.textContent = 'Optimising';
this.pill.classList.add('busy', 'on');
return;
}
// Always show the pill — green when enabled, red dot when disabled
this.pill.classList.add('on');
if (enabled) {
if (!this.statusLock) lbl.textContent = 'Optimised';
this._scheduleCollapse();
} else {
if (!this.statusLock) lbl.textContent = 'Disabled';
this.pill.classList.add('dead');
// Collapse to compact dot when disabled
this._scheduleCollapse(800);
}
}
_showBusy() {
if (!this.pill) return;
const lbl = $('.cgv-label', this.pill);
lbl.textContent = 'Optimising';
this.pill.classList.remove('mini', 'dead');
this.pill.classList.add('busy', 'on');
this.panel?.classList.add('loading');
}
_scheduleCollapse(delay = 4000) {
clearTimeout(this.collapseTimer);
if (cfg.get('alwaysExpand')) return;
this.collapseTimer = setTimeout(() => {
this.statusLock = false;
if (!this.busy && this.pill) this.pill.classList.add('mini');
}, delay);
}
// ── Optimisation cycle ────────────────────────────────────
_optimise(requireClear = false) {
let ticks = 0, cleared = !requireClear;
const iv = setInterval(() => {
ticks++;
const msgs = $$(MSG_SEL);
if (requireClear && !cleared) {
if (msgs.length === 0 || ticks > 50) { cleared = true; ticks = 0; }
return;
}
if (msgs.length > 0 || ticks > 600) {
clearInterval(iv);
if (msgs.length > 0) this.scanMessages();
setTimeout(() => {
this.busy = false;
this.pill?.classList.remove('pushed');
this.blocker?.classList.remove('on');
this.panel?.classList.remove('loading');
this._unlock();
this._sync(virt.enabled);
}, 400);
}
}, 100);
}
// ── Background observer for new chats (no blocking) ──────
_observeNewChat() {
let ticks = 0;
const iv = setInterval(() => {
ticks++;
const msgs = $$(MSG_SEL);
if (msgs.length > 0) {
clearInterval(iv);
this.scanMessages();
this._sync(virt.enabled);
}
if (ticks > 300) clearInterval(iv); // 30s timeout
}, 100);
}
_unlock() {
document.documentElement.classList.remove('cgv-loading');
// Failsafe: Ensure it's removed even if the above fails or script re-runs
setTimeout(() => document.documentElement.classList.remove('cgv-loading'), 5000);
}
// ── Monitor UI persistence ──────────────────────────────
_guard() {
new MutationObserver(() => {
if (!document.getElementById('cgv-host')) {
this._buildUI(cfg.all());
}
}).observe(document.body, { childList: true });
}
// ── Message tracking ──────────────────────────────────────
scanMessages() { $$(MSG_SEL).forEach(m => this._track(m)); }
_track(el) {
if (this.tracked.has(el)) return;
this.tracked.add(el);
el.classList.add('cgv-msg');
virt.observe(el);
}
_observe() {
new MutationObserver(muts => {
for (const m of muts) {
if (m.type !== 'childList') continue;
for (const n of m.addedNodes) {
if (n.nodeType !== 1) continue;
if (n.matches?.(MSG_SEL)) this._track(n);
$$(MSG_SEL, n).forEach(c => this._track(c));
}
}
}).observe(document.body, { childList: true, subtree: true });
}
}
new App().run();
})();