"""Browserless regression for transparent stream live row reconciliation.""" import json import os import shutil import subprocess from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[1] NODE = shutil.which("node") def _run_node_script(script, ui_js_path=None): assert NODE, "node is required for DOM-executed anchor render tests" env = os.environ.copy() if ui_js_path is not None: env["UI_JS_PATH"] = ui_js_path result = subprocess.run([NODE, "-e", script], env=env, text=True, capture_output=True, check=False) assert result.returncode == 0, result.stderr return json.loads(result.stdout) def test_transparent_thinking_scroll_container_reserves_gutter(): css = (ROOT / "static" / "style.css").read_text(encoding="utf-8") # Find the transparent thinking scroll container block and verify scrollbar-gutter:stable is present. # There are multiple blocks with this selector — check that at least one has the property. import re selector = ".transparent-event-row .thinking-card.open .thinking-card-body{" pattern = re.escape(selector) + r'([\s\S]*?)\}' for match in re.finditer(pattern, css): if "scrollbar-gutter:stable" in match.group(1): return # Test passes — found the property in a matching block assert False, f"scrollbar-gutter:stable not found in any {selector} block" @pytest.mark.skipif(NODE is None, reason="node not on PATH") def test_transparent_live_scene_reuses_matching_rows_and_removes_stale_rows(): script = """ const fs = require('fs'); const src = fs.readFileSync(process.env.UI_JS_PATH, 'utf8'); function extractFunc(name){{ const marker = new RegExp('function\\\\s+' + name + '\\\\s*\\\\('); const start = src.search(marker); if(start < 0) throw new Error(name + ' not found'); let i = src.indexOf('{{', start) + 1; let depth = 1; while(depth > 0 && i < src.length){{ if(src[i] === '{{') depth += 1; else if(src[i] === '}}') depth -= 1; i += 1; }} return src.slice(start, i); }} class FakeElement {{ static moves = 0; constructor(tag='div'){{ this.tagName = String(tag).toUpperCase(); this.children = []; this.parentNode = null; this.attributes = Object.create(null); this.dataset = Object.create(null); this.style = Object.create(null); this.hidden = false; this.id = ''; this._textContent = ''; this._innerHTML = ''; this._classes = new Set(); const self = this; this.classList = {{ add(...names){{ names.forEach(name=>self._classes.add(name)); }}, remove(...names){{ names.forEach(name=>self._classes.delete(name)); }}, contains(name){{ return self._classes.has(name); }}, toggle(name, force){{ if(force === true){{ self._classes.add(name); return true; }} if(force === false){{ self._classes.delete(name); return false; }} if(self._classes.has(name)){{ self._classes.delete(name); return false; }} self._classes.add(name); return true; }}, }}; }} get parentElement(){{ return this.parentNode; }} get firstChild(){{ return this.children[0]||null; }} get nextSibling(){{ if(!this.parentNode) return null; const siblings = this.parentNode.children; const idx = siblings.indexOf(this); return idx >= 0 ? (siblings[idx + 1] || null) : null; }} get className(){{ return Array.from(this._classes).join(' '); }} set className(value){{ this._classes = new Set(String(value).trim().split(/\\s+/).filter(Boolean)); }} get textContent(){{ return this._textContent; }} set textContent(value){{ this._textContent = String(value ?? ''); this._innerHTML = this._textContent; this.children = []; }} get innerHTML(){{ return this._innerHTML; }} set innerHTML(value){{ this._innerHTML = String(value ?? ''); this._textContent = this._innerHTML; this.children = []; }} setAttribute(name, value){{ const key = String(name); const val = String(value); this.attributes[key] = val; if(key === 'id') this.id = val; if(key.startsWith('data-')){{ const dataKey = key.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase()); this.dataset[dataKey] = val; }} if(key === 'class'){ this.className = val; }} }} getAttribute(name){{ return Object.prototype.hasOwnProperty.call(this.attributes, name) ? this.attributes[name] : null; }} getAttributeNames(){{ return Object.keys(this.attributes); }} removeAttribute(name){{ delete this.attributes[name]; if(name === 'id') this.id = ''; if(name.startsWith('data-')){{ const dataKey = name.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase()); delete this.dataset[dataKey]; }} if(name === 'class'){ this._classes = new Set(); }} }} appendChild(child){{ this.appendChildCount = (this.appendChildCount || 0) + 1; if(child && child.tagName === '#FRAGMENT'){{ child.children.slice().forEach(grandchild=>this.appendChild(grandchild)); child.children = []; return child; }} if(child && child.parentNode){{ FakeElement.moves += 1; child.remove(); }} if(!child) return null; child.parentNode = this; this.children.push(child); return child; }} insertBefore(child, refNode){{ this.insertBeforeCount = (this.insertBeforeCount || 0) + 1; if(child && child.parentNode){{ FakeElement.moves += 1; child.remove(); }} if(!child) return null; const idx = this.children.indexOf(refNode); child.parentNode = this; if(idx < 0) this.children.push(child); else this.children.splice(idx, 0, child); return child; }} remove(){{ if(!this.parentNode) return; const siblings = this.parentNode.children; const idx = siblings.indexOf(this); if(idx >= 0) siblings.splice(idx, 1); this.parentNode = null; }} matches(selector){{ return matchesSelector(this, selector); }} querySelector(selector){{ return this.querySelectorAll(selector)[0] || null; }} querySelectorAll(selector){{ const out = []; const walk = (node)=>{ for(const child of node.children){ if(matchesSelector(child, selector)) out.push(child); walk(child); } }; walk(this); return out; }} closest(selector){{ let node = this; while(node){ if(matchesSelector(node, selector)) return node; node = node.parentNode; }} return null; }} }} function matchesSelector(el, selector){{ if(!selector) return false; const options = selector.split(',').map(part=>part.trim()).filter(Boolean); return options.some(part=>matchesSimple(el, part)); }} function matchesSimple(el, selector){{ selector = selector.replace(/^:scope\\s*>\\s*/, '').trim(); if(!selector) return false; const idMatch = selector.match(/#([^.\\[#]+)/); if(idMatch && el.id !== idMatch[1]) return false; const clsMatches = selector.match(/\\.([A-Za-z0-9_-]+)/g) || []; for(const cls of clsMatches){{ const name = cls.slice(1); if(!el.classList.contains(name)) return false; }} const attrMatches = selector.match(/\\[([^=\\]]+)(?:=\\"([^\\"]*)\\")?\\]/g) || []; for(const attrMatch of attrMatches){{ const [, name, expected] = attrMatch.match(/\\[([^=\\]]+)(?:=\\"([^\\"]*)\\")?\\]/); const value = el.getAttribute(name); if(value === null) return false; if(expected !== undefined && String(value) !== String(expected)) return false; }} return !!(idMatch || clsMatches.length || attrMatches.length); }} global.window = {{}}; global.document = {{ createElement:(tag)=>new FakeElement(tag), createTextNode:(text)=>{{ const node = new FakeElement('#text'); node.textContent = text; return node; }}, createDocumentFragment:()=>new FakeElement('#fragment'), }}; global.CSS = {{ escape:(value)=>String(value) }}; global.requestAnimationFrame = (fn)=>fn(); global.S = {{ session:{{ session_id: 'session-1', pending_started_at: 123 }}, activeStreamId:'stream-1' }}; global._captureMessageScrollSnapshot = () => ({{ scrollHeight: 1000 }}); global._prepareLiveAnchorScrollRebuildGuard = () => ({{ readerAwayFromBottom:false, release:null }}); global._restoreMessageScrollSnapshotSameFrame = () => {{}}; global.scrollIfPinned = () => {{}}; global._moveLiveRunStatusToTurnEnd = () => {{}}; global._messageUserUnpinned = false; global._syncTransparentEventControls = () => {{}}; global._anchorSceneRowsForRendering = (scene) => scene && scene.activity_rows || []; global._anchorSceneNodeForRow = (row) => {{ const node = new FakeElement('div'); node.classList.add('assistant-segment'); node.textContent = String(row && (row.text || row.thinking&&row.thinking.text || '') || ''); return node; }}; global._decorateTransparentEventRow = (node, opts) => {{ node.classList.add('transparent-event-row'); node.setAttribute('data-transparent-event-row','1'); if(opts && Object.prototype.hasOwnProperty.call(opts,'type')) node.setAttribute('data-event-type', opts.type); if(opts && Object.prototype.hasOwnProperty.call(opts,'text')) node.setAttribute('data-text', opts.text); if(opts && Object.prototype.hasOwnProperty.call(opts,'status')) node.setAttribute('data-event-status', opts.status); return node; }}; global._thinkingActivityNode = (text)=>{{ const node = new FakeElement('div'); node.classList.add('agent-activity-thinking'); node.textContent = text || ''; return node; }}; global._anchorSceneToolCallFromRow = (row) => ({{ name:(row.tool && row.tool.name) || row.tool_name || 'tool', done:true }}); global._autoCompressionWorklogNode = () => new FakeElement('div'); global._autoCompressionPreviewText = () => 'preview'; global._transparentToolStatus = () => 'done'; global.buildToolCard = () => {{ const node = new FakeElement('div'); node.classList.add('tool-card-row'); return node; }}; const emptyState = new FakeElement('div'); const msgInner = new FakeElement('div'); const messages = new FakeElement('div'); const turn = new FakeElement('div'); turn.id = 'liveAssistantTurn'; const liveRunStatus = new FakeElement('div'); liveRunStatus.id = 'liveRunStatus'; msgInner.appendChild(turn); turn.appendChild(liveRunStatus); global.document._findById = (id) => id === 'emptyState' ? emptyState : id === 'msgInner' ? msgInner : id === 'messages' ? messages : id === 'liveAssistantTurn' ? turn : null; global.$ = (id)=>global.document._findById(id); global._createAssistantTurn = () => turn; global._assistantTurnBlocks = () => turn; global._anchorSceneTransparentNodeForRow = (row) => null; eval(extractFunc('_anchorSceneLiveTokenFinalPrefix')); eval(extractFunc('_anchorSceneTransparentNodeForRow')); eval(extractFunc('_transparentLiveRowKey')); eval(extractFunc('_transparentLiveRowsCompatible')); eval(extractFunc('_transparentLiveRowAttributePairs')); eval(extractFunc('_transparentLiveRowInteractiveState')); eval(extractFunc('_rehydrateTransparentLiveRow')); eval(extractFunc('_refreshTransparentThinkingLiveRow')); eval(extractFunc('_bindTransparentFadeCleanup')); // `_appendTransparentFadeText` now delegates its insertion point to // `_transparentFadeAppendTarget`, so the extraction harness must provide it // (and the block-tag set it consults) or the extracted function throws. global._TRANSPARENT_FADE_BLOCK_TAGS = new Set([ 'P','DIV','H1','H2','H3','H4','H5','H6','BLOCKQUOTE','LI','UL','OL','PRE','TABLE', ]); eval(extractFunc('_transparentFadeAppendTarget')); eval(extractFunc('_appendTransparentFadeText')); eval(extractFunc('_refreshTransparentFadeProseRow')); eval(extractFunc('_refreshTransparentLiveRow')); eval(extractFunc('_restoreLiveAnchorScrollSnapshotAfterRebuild')); eval(extractFunc('_restoreLiveAnchorScrollSnapshotAfterRebuild')); eval(extractFunc('_renderLiveAnchorActivitySceneTransparent')); const firstScene = {{ version:'activity_scene_v1', activity_rows:[ {{ row_id:'row-kept', role:'prose', source_event_type:'process_prose', text:'first progress line' }}, {{ row_id:'row-stale', role:'prose', source_event_type:'process_prose', text:'will be removed' }}, ], }}; const secondScene = {{ version:'activity_scene_v1', activity_rows:[ {{ row_id:'row-kept', role:'prose', source_event_type:'process_prose', text:'updated progress line' }}, {{ row_id:'row-new', role:'prose', source_event_type:'process_prose', text:'new row appears' }}, ], }}; const thirdScene = {{ version:'activity_scene_v1', activity_rows:[ {{ role:'prose', source_event_type:'process_prose', text:'keyless row first' }}, ], }}; const fourthScene = {{ version:'activity_scene_v1', activity_rows:[ {{ role:'prose', source_event_type:'process_prose', text:'keyless row second' }}, ], }}; const firstRender = _renderLiveAnchorActivitySceneTransparent('stream-1', firstScene, {{ sessionId:'session-1' }}); const keptAfterFirst = turn.querySelector('.transparent-event-row[data-anchor-row-id=\"row-kept\"]'); const staleAfterFirst = turn.querySelector('.transparent-event-row[data-anchor-row-id=\"row-stale\"]'); const firstFooter = turn.querySelector('#liveRunStatus'); turn.insertBeforeCount = 0; turn.appendChildCount = 0; const secondRender = _renderLiveAnchorActivitySceneTransparent('stream-1', secondScene, {{ sessionId:'session-1' }}); const secondInsertBeforeCount = turn.insertBeforeCount || 0; const secondAppendChildCount = turn.appendChildCount || 0; const keptAfterSecond = turn.querySelector('.transparent-event-row[data-anchor-row-id=\"row-kept\"]'); const staleAfterSecond = turn.querySelector('.transparent-event-row[data-anchor-row-id=\"row-stale\"]'); const newAfterSecond = turn.querySelector('.transparent-event-row[data-anchor-row-id=\"row-new\"]'); const rows = turn.children.filter((child) => child.classList.contains('transparent-event-row')); const movesBeforeStableRender = FakeElement.moves; const stableOrderRender = _renderLiveAnchorActivitySceneTransparent('stream-1', secondScene, {{ sessionId:'session-1' }}); const movesAfterStableRender = FakeElement.moves; const idxs = {{ keptDirect: turn.children.indexOf(keptAfterSecond), freshDirect: turn.children.indexOf(newAfterSecond), footerDirect: turn.children.indexOf(firstFooter), staleInVisibleRows: rows.findIndex((child) => child.getAttribute('data-anchor-row-id') === 'row-stale'), rowKeeps: rows.indexOf(keptAfterSecond), rowNew: rows.indexOf(newAfterSecond), stale: rows.findIndex((child) => child.getAttribute('data-anchor-row-id') === 'row-stale'), }}; turn.insertBeforeCount = 0; turn.appendChildCount = 0; _renderLiveAnchorActivitySceneTransparent('stream-1', secondScene, {{ sessionId:'session-1' }}); const stableInsertBeforeCount = turn.insertBeforeCount || 0; const stableAppendChildCount = turn.appendChildCount || 0; const reorderedScene = {{ version:'activity_scene_v1', activity_rows:[ {{ row_id:'row-new', role:'prose', source_event_type:'process_prose', text:'new row appears' }}, {{ row_id:'row-kept', role:'prose', source_event_type:'process_prose', text:'updated progress line' }}, ], }}; turn.insertBeforeCount = 0; turn.appendChildCount = 0; _renderLiveAnchorActivitySceneTransparent('stream-1', reorderedScene, {{ sessionId:'session-1' }}); const reorderInsertBeforeCount = turn.insertBeforeCount || 0; const reorderedIds = turn.children .filter((child) => child.classList.contains('transparent-event-row')) .map((child) => child.getAttribute('data-anchor-row-id')); firstFooter.remove(); turn.insertBefore(firstFooter, turn.children[0] || null); turn.insertBeforeCount = 0; turn.appendChildCount = 0; _renderLiveAnchorActivitySceneTransparent('stream-1', reorderedScene, {{ sessionId:'session-1' }}); const footerRepairInsertBeforeCount = turn.insertBeforeCount || 0; const footerRepairIdxs = {{ newRow: turn.children.indexOf(turn.querySelector('.transparent-event-row[data-anchor-row-id=\"row-new\"]')), keptRow: turn.children.indexOf(turn.querySelector('.transparent-event-row[data-anchor-row-id=\"row-kept\"]')), footer: turn.children.indexOf(firstFooter), }}; _renderLiveAnchorActivitySceneTransparent('stream-1', thirdScene, {{ sessionId:'session-1' }}); const keylessRowsAfterThird = turn.querySelectorAll('.transparent-event-row[data-anchor-row-id=\"\"]'); _renderLiveAnchorActivitySceneTransparent('stream-1', fourthScene, {{ sessionId:'session-1' }}); const keylessRowsAfterFourth = turn.querySelectorAll('.transparent-event-row[data-anchor-row-id=\"\"]'); const fadeParent = new FakeElement('div'); const fadeExisting = new FakeElement('div'); fadeExisting.className = 'assistant-segment transparent-event-row'; fadeExisting.setAttribute('data-anchor-row-role', 'prose'); fadeExisting.setAttribute('data-anchor-row-id', 'fade-row'); fadeExisting.setAttribute('data-anchor-source-event-type', 'process_prose'); const staleBody = new FakeElement('div'); staleBody.className = 'msg-body stream-fade-active'; const oldSpan = new FakeElement('span'); oldSpan.className = 'stream-fade-word is-new'; oldSpan.textContent = 'old'; staleBody.appendChild(oldSpan); staleBody.appendChild((()=>{{ const n = new FakeElement('#text'); n.textContent = ' '; return n; }})()); fadeExisting.appendChild(staleBody); fadeExisting.setAttribute('data-stream-fade-text', 'old '); fadeParent.appendChild(fadeExisting); const fadeCandidate = new FakeElement('div'); fadeCandidate.className = 'assistant-segment transparent-event-row'; fadeCandidate.setAttribute('data-anchor-row-role', 'prose'); fadeCandidate.setAttribute('data-anchor-row-id', 'fade-row'); fadeCandidate.setAttribute('data-anchor-source-event-type', 'process_prose'); fadeCandidate.dataset.rawText = 'old new'; const fadeBody = new FakeElement('div'); fadeBody.className = 'msg-body stream-fade-active'; const fadeSpan = new FakeElement('span'); fadeSpan.className = 'stream-fade-word is-new'; fadeSpan.textContent = 'old'; fadeBody.appendChild(fadeSpan); fadeBody.appendChild((()=>{{ const n = new FakeElement('#text'); n.textContent = ' new'; return n; }})()); fadeCandidate.appendChild(fadeBody); const fadeRefresh = _refreshTransparentLiveRow(fadeExisting, fadeCandidate); const fadeSpans = fadeParent.querySelectorAll('.stream-fade-word.is-new'); process.stdout.write(JSON.stringify({{ firstRender, secondRender, stableOrderRender, sameNode: keptAfterFirst === keptAfterSecond, stableOrderMovedRows: movesAfterStableRender - movesBeforeStableRender, keptId: keptAfterSecond && keptAfterSecond.getAttribute('data-anchor-row-id'), keptSource: keptAfterSecond && keptAfterSecond.getAttribute('data-anchor-source-event-type'), keptText: keptAfterSecond && keptAfterSecond.textContent, staleGone: staleAfterSecond === null, staleAfterFirst: staleAfterFirst !== null, idxs, secondInsertBeforeCount, secondAppendChildCount, stableInsertBeforeCount, stableAppendChildCount, reorderInsertBeforeCount, reorderedIds, footerRepairInsertBeforeCount, footerRepairIdxs, hasNewRow: !!newAfterSecond, newRowSession: newAfterSecond && newAfterSecond.getAttribute('data-session-id'), keylessAfterThird: keylessRowsAfterThird.length, keylessAfterFourth: keylessRowsAfterFourth.length, keylessTextsAfterFourth: keylessRowsAfterFourth.map((child) => child.textContent), totalRowsAfterFourth: turn.children.filter((child) => child.classList.contains('transparent-event-row')).length, fadeKeptExisting: fadeRefresh === fadeExisting, fadeExistingStillParented: fadeExisting.parentNode === fadeParent, fadeCandidateDetached: fadeCandidate.parentNode === null, fadeOldSpanPreserved: fadeSpans[0] === oldSpan, fadeSpanTexts: fadeSpans.map(span=>span.textContent), fadeText: fadeExisting.getAttribute('data-stream-fade-text'), fadeSpanPreserved: !!fadeParent.querySelector('.stream-fade-word.is-new'), fadeBodyActive: !!fadeParent.querySelector('.msg-body.stream-fade-active'), }})); """ script = script.replace("{{", "{").replace("}}", "}") data = _run_node_script(script, str(ROOT / "static" / "ui.js")) assert data["firstRender"] is True assert data["secondRender"] is True assert data["stableOrderRender"] is True assert data["sameNode"] is True assert data["stableOrderMovedRows"] == 0 assert data["keptId"] == "row-kept" assert data["keptSource"] == "process_prose" assert data["keptText"] == "updated progress line" assert data["staleGone"] is True assert data["staleAfterFirst"] is True assert data["idxs"]["keptDirect"] == 0 assert data["idxs"]["freshDirect"] == 1 assert data["idxs"]["footerDirect"] > data["idxs"]["freshDirect"] assert data["idxs"]["freshDirect"] < data["idxs"]["footerDirect"] assert data["idxs"]["rowKeeps"] == 0 assert data["idxs"]["rowNew"] == 1 assert data["idxs"]["stale"] == -1 assert data["idxs"]["staleInVisibleRows"] == -1 assert data["secondInsertBeforeCount"] >= 1 assert data["secondAppendChildCount"] == 0 assert data["stableInsertBeforeCount"] == 0 assert data["stableAppendChildCount"] == 0 assert data["reorderInsertBeforeCount"] >= 1 assert data["reorderedIds"] == ["row-new", "row-kept"] assert data["footerRepairInsertBeforeCount"] >= 1 assert data["footerRepairIdxs"]["newRow"] == 0 assert data["footerRepairIdxs"]["keptRow"] == 1 assert data["footerRepairIdxs"]["footer"] > data["footerRepairIdxs"]["keptRow"] assert data["hasNewRow"] is True assert data["newRowSession"] == "session-1" assert data["keylessAfterThird"] == 1 assert data["keylessAfterFourth"] == 1 assert data["keylessTextsAfterFourth"] == ["keyless row second"] assert data["totalRowsAfterFourth"] == 1 assert data["fadeKeptExisting"] is True assert data["fadeExistingStillParented"] is True assert data["fadeCandidateDetached"] is True assert data["fadeOldSpanPreserved"] is True assert data["fadeSpanTexts"] == ["old", "new"] assert data["fadeText"] == "old new" assert data["fadeSpanPreserved"] is True assert data["fadeBodyActive"] is True @pytest.mark.skipif(NODE is None, reason="node not on PATH") def test_transparent_live_row_refresh_rebinds_controls_and_carries_tool_data(): script = r""" const fs = require('fs'); const src = fs.readFileSync(process.env.UI_JS_PATH, 'utf8'); function extractFunc(name){ const marker = new RegExp('function\\s+' + name + '\\s*\\('); const start = src.search(marker); if(start < 0) throw new Error(name + ' not found'); let i = src.indexOf('{', start) + 1; let depth = 1; while(depth > 0 && i < src.length){ if(src[i] === '{') depth += 1; else if(src[i] === '}') depth -= 1; i += 1; } return src.slice(start, i); } class FakeElement { constructor(tag='div'){ this.tagName = String(tag).toUpperCase(); this.children = []; this.parentNode = null; this.attributes = Object.create(null); this.dataset = Object.create(null); this.style = Object.create(null); this.hidden = false; this.id = ''; this.onclick = null; this.onkeydown = null; this.title = ''; this._textContent = ''; this._innerHTML = ''; this._classes = new Set(); const self = this; this.classList = { add(...names){ names.forEach(name=>self._classes.add(name)); }, remove(...names){ names.forEach(name=>self._classes.delete(name)); }, contains(name){ return self._classes.has(name); }, toggle(name, force){ const next = force === undefined ? !self._classes.has(name) : !!force; if(next) self._classes.add(name); else self._classes.delete(name); return next; }, }; } get parentElement(){ return this.parentNode; } get firstChild(){ return this.children[0]||null; } get className(){ return Array.from(this._classes).join(' '); } set className(value){ this._classes = new Set(String(value).trim().split(/\s+/).filter(Boolean)); } get textContent(){ if(this.children.length) return this.children.map(child=>child.textContent).join(''); return this._textContent; } set textContent(value){ this._textContent = String(value ?? ''); this._innerHTML = this._textContent; this.children = []; } get innerHTML(){ return this._innerHTML; } set innerHTML(value){ this._innerHTML = String(value ?? ''); this._textContent = this._innerHTML; this.children = []; if(this._innerHTML.indexOf('tool-template') >= 0) buildToolTemplate(this); } setAttribute(name, value){ const key = String(name); const val = String(value); this.attributes[key] = val; if(key === 'id') this.id = val; if(key.startsWith('data-')){ const dataKey = key.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase()); this.dataset[dataKey] = val; } if(key === 'class') this.className = val; } getAttribute(name){ return Object.prototype.hasOwnProperty.call(this.attributes, name) ? this.attributes[name] : null; } getAttributeNames(){ return Object.keys(this.attributes); } removeAttribute(name){ delete this.attributes[name]; if(name === 'id') this.id = ''; if(name.startsWith('data-')){ const dataKey = name.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase()); delete this.dataset[dataKey]; } if(name === 'class') this._classes = new Set(); } appendChild(child){ if(child && child.parentNode) child.remove(); if(!child) return null; child.parentNode = this; this.children.push(child); return child; } insertBefore(child, refNode){ if(child && child.parentNode) child.remove(); if(!child) return null; const idx = this.children.indexOf(refNode); child.parentNode = this; if(idx < 0) this.children.push(child); else this.children.splice(idx, 0, child); return child; } remove(){ if(!this.parentNode) return; const siblings = this.parentNode.children; const idx = siblings.indexOf(this); if(idx >= 0) siblings.splice(idx, 1); this.parentNode = null; } matches(selector){ return matchesSelector(this, selector); } querySelector(selector){ return this.querySelectorAll(selector)[0] || null; } querySelectorAll(selector){ const out = []; const walk = (node)=>{ for(const child of node.children){ if(matchesSelector(child, selector)) out.push(child); walk(child); } }; walk(this); return out; } closest(selector){ let node = this; while(node){ if(matchesSelector(node, selector)) return node; node = node.parentNode; } return null; } } function matchesSelector(el, selector){ if(!selector) return false; const options = selector.split(',').map(part=>part.trim()).filter(Boolean); return options.some(part=>matchesSimple(el, part)); } function matchesSimple(el, selector){ selector = selector.replace(/^:scope\s*>\s*/, '').trim(); if(!selector) return false; const idMatch = selector.match(/#([^\.\[#\s]+)/); if(idMatch && el.id !== idMatch[1]) return false; const clsMatches = selector.match(/\.([A-Za-z0-9_-]+)/g) || []; for(const cls of clsMatches){ if(!el.classList.contains(cls.slice(1))) return false; } const attrMatches = selector.match(/\[([^=\]]+)(?:=\"([^\"]*)\")?\]/g) || []; for(const attrMatch of attrMatches){ const [, name, expected] = attrMatch.match(/\[([^=\]]+)(?:=\"([^\"]*)\")?\]/); const value = el.getAttribute(name); if(value === null) return false; if(expected !== undefined && String(value) !== String(expected)) return false; } return !!(idMatch || clsMatches.length || attrMatches.length); } function el(tag, classes){ const node = new FakeElement(tag); String(classes||'').split(/\s+/).filter(Boolean).forEach(cls=>node.classList.add(cls)); return node; } function buildToolTemplate(row){ const card = el('div', 'tool-card transparent-event-card'); const header = el('div', 'tool-card-header'); const name = el('span', 'tool-card-name'); name.textContent = 'shell'; const copy = el('span', 'transparent-event-copy'); const toggle = el('span', 'tool-card-toggle'); const detail = el('div', 'tool-card-detail'); detail.setAttribute('data-transparent-detail-mode', 'full'); const tabs = el('div', 'transparent-detail-modes'); const full = el('span', 'transparent-detail-mode active'); full.setAttribute('data-mode', 'full'); const output = el('span', 'transparent-detail-mode'); output.setAttribute('data-mode', 'output'); const args = el('div', 'tool-card-args'); const result = el('div', 'tool-card-result'); const pre = el('pre', ''); pre.textContent = 'fresh output'; result.appendChild(pre); tabs.appendChild(full); tabs.appendChild(output); detail.appendChild(tabs); detail.appendChild(args); detail.appendChild(result); header.appendChild(name); header.appendChild(copy); header.appendChild(toggle); card.appendChild(header); card.appendChild(detail); row.appendChild(card); } const copied = []; Object.defineProperty(global, 'navigator', { configurable: true, value: { clipboard: { writeText: (text)=>{ copied.push(text); return { then(fn){ fn(); return { catch(){} }; } }; } } }, }); global.document = { createElement:(tag)=>new FakeElement(tag), body:new FakeElement('body'), execCommand:()=>true }; global.t = (key)=>key === 'copy' ? 'Copy' : key; global.showToast = ()=>{}; eval(extractFunc('_showTransparentCopiedFeedback')); eval(extractFunc('_copyEventToClipboard')); eval(extractFunc('_attachCopyButton')); eval(extractFunc('_setTransparentCardOpen')); eval(extractFunc('_wireTransparentHeaderToggle')); eval(extractFunc('_transparentLiveRowAttributePairs')); eval(extractFunc('_transparentLiveRowInteractiveState')); eval(extractFunc('_rehydrateTransparentLiveRow')); eval(extractFunc('_refreshTransparentThinkingLiveRow')); eval(extractFunc('_refreshTransparentLiveRow')); const existing = new FakeElement('div'); existing.classList.add('transparent-event-row'); existing.setAttribute('data-anchor-row-id', 'row-tool'); existing.setAttribute('data-expanded', '1'); existing.innerHTML = 'tool-template-old'; existing._tcData = { name:'old_tool', args:{ stale:true }, snippet:'old output' }; const oldCard = existing.querySelector('.tool-card'); oldCard.classList.add('open'); const oldDetail = existing.querySelector('.tool-card-detail'); oldDetail.setAttribute('data-transparent-detail-mode', 'output'); const candidate = new FakeElement('div'); candidate.classList.add('transparent-event-row'); candidate.setAttribute('data-anchor-row-id', 'row-tool'); candidate.setAttribute('data-event-type', 'tool'); candidate.innerHTML = 'tool-template-new'; candidate._tcData = { name:'fresh_tool', args:{ cmd:'ls' }, snippet:'fresh output' }; const refreshed = _refreshTransparentLiveRow(existing, candidate); const header = refreshed.querySelector('.tool-card-header'); const copy = refreshed.querySelector('.transparent-event-copy'); const card = refreshed.querySelector('.tool-card'); const detail = refreshed.querySelector('.tool-card-detail'); const outputTab = refreshed.querySelector('.transparent-detail-mode[data-mode="output"]'); const fullTab = refreshed.querySelector('.transparent-detail-mode[data-mode="full"]'); const initiallyOpenAfterRefresh = card.classList.contains('open'); const ariaExpandedAfterRefresh = header.getAttribute('aria-expanded'); const detailMode = detail.getAttribute('data-transparent-detail-mode'); const outputActive = outputTab.classList.contains('active'); const fullActive = fullTab.classList.contains('active'); copy.onclick({ stopPropagation(){}, preventDefault(){}, target: copy, }); header.onclick({ target: header, preventDefault(){}, }); const afterClickOpen = card.classList.contains('open'); const afterClickExpanded = refreshed.getAttribute('data-expanded'); header.onkeydown({ key: 'Enter', target: header, preventDefault(){}, }); process.stdout.write(JSON.stringify({ sameNode: refreshed === existing, copiedText: copied[0], existingToolName: refreshed._tcData && refreshed._tcData.name, candidateHasToolData: Object.prototype.hasOwnProperty.call(candidate, '_tcData'), copyBound: typeof copy.onclick === 'function' && typeof copy.onkeydown === 'function', headerBound: typeof header.onclick === 'function' && typeof header.onkeydown === 'function', initiallyOpenAfterRefresh, ariaExpandedAfterRefresh, detailMode, outputActive, fullActive, afterClickOpen, afterClickExpanded, afterKeyOpen: card.classList.contains('open'), afterKeyExpanded: refreshed.getAttribute('data-expanded'), })); """ data = _run_node_script(script, str(ROOT / "static" / "ui.js")) assert data["sameNode"] is True assert data["copyBound"] is True assert data["headerBound"] is True assert data["existingToolName"] == "fresh_tool" assert data["candidateHasToolData"] is False assert "tool: fresh_tool" in data["copiedText"] assert '"cmd": "ls"' in data["copiedText"] assert "fresh output" in data["copiedText"] assert data["initiallyOpenAfterRefresh"] is True assert data["ariaExpandedAfterRefresh"] == "true" assert data["detailMode"] == "output" assert data["outputActive"] is True assert data["fullActive"] is False assert data["afterClickOpen"] is False assert data["afterClickExpanded"] == "0" assert data["afterKeyOpen"] is True assert data["afterKeyExpanded"] == "1" @pytest.mark.skipif(NODE is None, reason="node not on PATH") def test_transparent_live_row_refresh_skips_unchanged_child_dom_and_carries_tool_data(): script = r""" const fs = require('fs'); const src = fs.readFileSync(process.env.UI_JS_PATH, 'utf8'); function extractFunc(name){ const marker = new RegExp('function\\s+' + name + '\\s*\\('); const start = src.search(marker); if(start < 0) throw new Error(name + ' not found'); let i = src.indexOf('{', start) + 1; let depth = 1; while(depth > 0 && i < src.length){ if(src[i] === '{') depth += 1; else if(src[i] === '}') depth -= 1; i += 1; } return src.slice(start, i); } class FakeElement { constructor(tag='div'){ this.tagName = String(tag).toUpperCase(); this.children = []; this.parentNode = null; this.attributes = Object.create(null); this.dataset = Object.create(null); this.style = Object.create(null); this.id = ''; this.onclick = null; this.onkeydown = null; this._textContent = ''; this._innerHTML = ''; this.innerHTMLSetCount = 0; this._classes = new Set(); const self = this; this.classList = { add(...names){ names.forEach(name=>self._classes.add(name)); }, remove(...names){ names.forEach(name=>self._classes.delete(name)); }, contains(name){ return self._classes.has(name); }, toggle(name, force){ const next = force === undefined ? !self._classes.has(name) : !!force; if(next) self._classes.add(name); else self._classes.delete(name); return next; }, }; } get parentElement(){ return this.parentNode; } get firstChild(){ return this.children[0]||null; } get className(){ return Array.from(this._classes).join(' '); } set className(value){ this._classes = new Set(String(value).trim().split(/\s+/).filter(Boolean)); } get textContent(){ return this.children.length ? this.children.map(child=>child.textContent).join('') : this._textContent; } set textContent(value){ this._textContent = String(value ?? ''); this._innerHTML = this._textContent; this.children = []; } get innerHTML(){ return this._innerHTML; } set innerHTML(value){ this.innerHTMLSetCount += 1; this._innerHTML = String(value ?? ''); this._textContent = this._innerHTML; this.children = []; if(this._innerHTML.indexOf('tool-template') >= 0) buildToolTemplate(this); } setAttribute(name, value){ const key = String(name); const val = String(value); this.attributes[key] = val; if(key === 'id') this.id = val; if(key.startsWith('data-')){ const dataKey = key.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase()); this.dataset[dataKey] = val; } if(key === 'class') this.className = val; } getAttribute(name){ return Object.prototype.hasOwnProperty.call(this.attributes, name) ? this.attributes[name] : null; } getAttributeNames(){ return Object.keys(this.attributes); } removeAttribute(name){ delete this.attributes[name]; if(name === 'id') this.id = ''; if(name.startsWith('data-')){ const dataKey = name.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase()); delete this.dataset[dataKey]; } } appendChild(child){ child.parentNode = this; this.children.push(child); return child; } querySelector(selector){ return this.querySelectorAll(selector)[0] || null; } querySelectorAll(selector){ const out = []; const walk = (node)=>{ for(const child of node.children){ if(matchesSelector(child, selector)) out.push(child); walk(child); } }; walk(this); return out; } closest(selector){ let node = this; while(node){ if(matchesSelector(node, selector)) return node; node = node.parentNode; } return null; } } function matchesSelector(el, selector){ return String(selector||'').split(',').map(part=>part.trim()).filter(Boolean).some(part=>matchesSimple(el, part)); } function matchesSimple(el, selector){ const clsMatches = selector.match(/\.([A-Za-z0-9_-]+)/g) || []; for(const cls of clsMatches){ if(!el.classList.contains(cls.slice(1))) return false; } const attrMatches = selector.match(/\[([^=\]]+)(?:=\"([^\"]*)\")?\]/g) || []; for(const attrMatch of attrMatches){ const [, name, expected] = attrMatch.match(/\[([^=\]]+)(?:=\"([^\"]*)\")?\]/); const value = el.getAttribute(name); if(value === null) return false; if(expected !== undefined && String(value) !== String(expected)) return false; } return !!(clsMatches.length || attrMatches.length); } function el(tag, classes){ const node = new FakeElement(tag); String(classes||'').split(/\s+/).filter(Boolean).forEach(cls=>node.classList.add(cls)); return node; } function buildToolTemplate(row){ const card = el('div', 'tool-card transparent-event-card'); const header = el('div', 'tool-card-header'); const copy = el('span', 'transparent-event-copy'); const detail = el('div', 'tool-card-detail'); detail.setAttribute('data-transparent-detail-mode', 'full'); const full = el('span', 'transparent-detail-mode active'); full.setAttribute('data-mode', 'full'); const output = el('span', 'transparent-detail-mode'); output.setAttribute('data-mode', 'output'); detail.appendChild(full); detail.appendChild(output); header.appendChild(copy); card.appendChild(header); card.appendChild(detail); row.appendChild(card); } global.document = { createElement:(tag)=>new FakeElement(tag) }; global.t = (key)=>key === 'copy' ? 'Copy' : key; eval(extractFunc('_setTransparentCardOpen')); eval(extractFunc('_attachCopyButton')); eval(extractFunc('_transparentLiveRowAttributePairs')); eval(extractFunc('_transparentLiveRowInteractiveState')); eval(extractFunc('_rehydrateTransparentLiveRow')); eval(extractFunc('_refreshTransparentThinkingLiveRow')); eval(extractFunc('_refreshTransparentLiveRow')); const existing = new FakeElement('div'); existing.classList.add('transparent-event-row'); existing.setAttribute('data-anchor-row-id', 'row-tool'); existing.setAttribute('data-expanded', '1'); existing.innerHTML = 'tool-template-same'; existing.innerHTMLSetCount = 0; existing._tcData = { name:'old_tool' }; const header = existing.querySelector('.tool-card-header'); const copy = existing.querySelector('.transparent-event-copy'); header.children = header.children.filter((child)=>child !== copy); copy.parentNode = null; const headerClick = function headerClick(){}; const headerKeydown = function headerKeydown(){}; const copyClick = function copyClick(){}; header.onclick = headerClick; header.onkeydown = headerKeydown; copy.onclick = copyClick; const card = existing.querySelector('.tool-card'); card.classList.add('open'); const detail = existing.querySelector('.tool-card-detail'); detail.setAttribute('data-transparent-detail-mode', 'output'); const outputTab = existing.querySelector('.transparent-detail-mode[data-mode="output"]'); const fullTab = existing.querySelector('.transparent-detail-mode[data-mode="full"]'); outputTab.classList.add('active'); fullTab.classList.remove('active'); const candidate = new FakeElement('div'); candidate.classList.add('transparent-event-row'); candidate.setAttribute('data-anchor-row-id', 'row-tool'); candidate.setAttribute('data-event-type', 'tool'); candidate.innerHTML = 'tool-template-same'; candidate._tcData = { name:'fresh_tool', args:{ cmd:'pwd' }, snippet:'fresh output' }; const refreshed = _refreshTransparentLiveRow(existing, candidate); const countAfterCarry = existing.innerHTMLSetCount; const candidateHasToolDataAfterCarry = Object.prototype.hasOwnProperty.call(candidate, '_tcData'); const ownToolNameAfterCarry = existing._tcData && existing._tcData.name; const repairedCopiesAfterCarry = existing.querySelectorAll('.transparent-event-copy'); const repairedCopy = repairedCopiesAfterCarry[0] || null; const candidateWithoutData = new FakeElement('div'); candidateWithoutData.classList.add('transparent-event-row'); candidateWithoutData.setAttribute('data-anchor-row-id', 'row-tool'); candidateWithoutData.setAttribute('data-event-type', 'tool'); candidateWithoutData.innerHTML = 'tool-template-same'; _refreshTransparentLiveRow(existing, candidateWithoutData); const repairedCopiesAfterSecondRefresh = existing.querySelectorAll('.transparent-event-copy'); process.stdout.write(JSON.stringify({ sameNode: refreshed === existing, innerHTMLSetCount: existing.innerHTMLSetCount, countAfterCarry, ownToolNameAfterCarry, candidateHasToolDataAfterCarry, hasToolDataAfterClear: Object.prototype.hasOwnProperty.call(existing, '_tcData'), cardStillOpen: card.classList.contains('open'), rowExpanded: existing.getAttribute('data-expanded'), headerAria: header.getAttribute('aria-expanded'), detailMode: detail.getAttribute('data-transparent-detail-mode'), outputActive: outputTab.classList.contains('active'), fullActive: fullTab.classList.contains('active'), headerClickSurvived: header.onclick === headerClick, headerKeydownSurvived: header.onkeydown === headerKeydown, repairedCopyCountAfterCarry: repairedCopiesAfterCarry.length, repairedCopyCountAfterSecondRefresh: repairedCopiesAfterSecondRefresh.length, repairedCopyBound: !!(repairedCopy && typeof repairedCopy.onclick === 'function' && typeof repairedCopy.onkeydown === 'function'), oldMissingCopyDetached: copy.parentNode === null, })); """ data = _run_node_script(script, str(ROOT / "static" / "ui.js")) assert data["sameNode"] is True assert data["innerHTMLSetCount"] == 0 assert data["countAfterCarry"] == 0 assert data["ownToolNameAfterCarry"] == "fresh_tool" assert data["candidateHasToolDataAfterCarry"] is False assert data["hasToolDataAfterClear"] is False assert data["cardStillOpen"] is True assert data["rowExpanded"] == "1" assert data["headerAria"] == "true" assert data["detailMode"] == "output" assert data["outputActive"] is True assert data["fullActive"] is False assert data["headerClickSurvived"] is True assert data["headerKeydownSurvived"] is True assert data["repairedCopyCountAfterCarry"] == 1 assert data["repairedCopyCountAfterSecondRefresh"] == 1 assert data["repairedCopyBound"] is True assert data["oldMissingCopyDetached"] is True @pytest.mark.skipif(NODE is None, reason="node not on PATH") def test_transparent_live_scene_inline_fallback_skips_unchanged_child_dom(): script = r""" const fs = require('fs'); const src = fs.readFileSync(process.env.UI_JS_PATH, 'utf8'); function extractFunc(name){ const marker = new RegExp('function\\s+' + name + '\\s*\\('); const start = src.search(marker); if(start < 0) throw new Error(name + ' not found'); let i = src.indexOf('{', start) + 1; let depth = 1; while(depth > 0 && i < src.length){ if(src[i] === '{') depth += 1; else if(src[i] === '}') depth -= 1; i += 1; } return src.slice(start, i); } class FakeElement { constructor(tag='div'){ this.tagName = String(tag).toUpperCase(); this.children = []; this.parentNode = null; this.attributes = Object.create(null); this.dataset = Object.create(null); this.style = Object.create(null); this.hidden = false; this.id = ''; this.onclick = null; this._textContent = ''; this._innerHTML = ''; this.innerHTMLSetCount = 0; this._classes = new Set(); const self = this; this.classList = { add(...names){ names.forEach(name=>self._classes.add(name)); }, remove(...names){ names.forEach(name=>self._classes.delete(name)); }, contains(name){ return self._classes.has(name); }, toggle(name, force){ const next = force === undefined ? !self._classes.has(name) : !!force; if(next) self._classes.add(name); else self._classes.delete(name); return next; }, }; } get parentElement(){ return this.parentNode; } get firstChild(){ return this.children[0]||null; } get className(){ return Array.from(this._classes).join(' '); } set className(value){ this._classes = new Set(String(value).trim().split(/\s+/).filter(Boolean)); } get textContent(){ return this.children.length ? this.children.map(child=>child.textContent).join('') : this._textContent; } set textContent(value){ this._textContent = String(value ?? ''); this._innerHTML = this._textContent; this.children = []; } get innerHTML(){ return this._innerHTML; } set innerHTML(value){ this.innerHTMLSetCount += 1; this._innerHTML = String(value ?? ''); this._textContent = this._innerHTML; this.children = []; if(this._innerHTML.indexOf('tool-template') >= 0) buildToolTemplate(this); } setAttribute(name, value){ const key = String(name); const val = String(value); this.attributes[key] = val; if(key === 'id') this.id = val; if(key.startsWith('data-')){ const dataKey = key.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase()); this.dataset[dataKey] = val; } } getAttribute(name){ return Object.prototype.hasOwnProperty.call(this.attributes, name) ? this.attributes[name] : null; } getAttributeNames(){ return Object.keys(this.attributes); } removeAttribute(name){ delete this.attributes[name]; } appendChild(child){ if(child.parentNode) child.remove(); child.parentNode = this; this.children.push(child); return child; } insertBefore(child, refNode){ if(child.parentNode) child.remove(); const idx = this.children.indexOf(refNode); child.parentNode = this; if(idx < 0) this.children.push(child); else this.children.splice(idx, 0, child); return child; } remove(){ if(!this.parentNode) return; const siblings = this.parentNode.children; const idx = siblings.indexOf(this); if(idx >= 0) siblings.splice(idx, 1); this.parentNode = null; } querySelector(selector){ return this.querySelectorAll(selector)[0] || null; } querySelectorAll(selector){ const out = []; const walk = (node)=>{ for(const child of node.children){ if(matchesSelector(child, selector)) out.push(child); walk(child); } }; walk(this); return out; } closest(selector){ let node = this; while(node){ if(matchesSelector(node, selector)) return node; node = node.parentNode; } return null; } } function matchesSelector(el, selector){ return String(selector||'').split(',').map(part=>part.trim()).filter(Boolean).some(part=>matchesSimple(el, part)); } function matchesSimple(el, selector){ selector = selector.replace(/^:scope\s*>\s*/, '').trim(); const idMatch = selector.match(/#([^\.\[#\s]+)/); if(idMatch && el.id !== idMatch[1]) return false; const clsMatches = selector.match(/\.([A-Za-z0-9_-]+)/g) || []; for(const cls of clsMatches){ if(!el.classList.contains(cls.slice(1))) return false; } const attrMatches = selector.match(/\[([^=\]]+)(?:=\"([^\"]*)\")?\]/g) || []; for(const attrMatch of attrMatches){ const [, name, expected] = attrMatch.match(/\[([^=\]]+)(?:=\"([^\"]*)\")?\]/); const value = el.getAttribute(name); if(value === null) return false; if(expected !== undefined && String(value) !== String(expected)) return false; } return !!(idMatch || clsMatches.length || attrMatches.length); } function el(tag, classes){ const node = new FakeElement(tag); String(classes||'').split(/\s+/).filter(Boolean).forEach(cls=>node.classList.add(cls)); return node; } function buildToolTemplate(row){ const card = el('div', 'tool-card transparent-event-card'); const header = el('div', 'tool-card-header'); const copy = el('span', 'transparent-event-copy'); const detail = el('div', 'tool-card-detail'); detail.setAttribute('data-transparent-detail-mode', 'full'); header.appendChild(copy); card.appendChild(header); card.appendChild(detail); row.appendChild(card); } const emptyState = new FakeElement('div'); const msgInner = new FakeElement('div'); const messages = new FakeElement('div'); const turn = new FakeElement('div'); turn.id = 'liveAssistantTurn'; const liveRunStatus = new FakeElement('div'); liveRunStatus.id = 'liveRunStatus'; turn.appendChild(liveRunStatus); msgInner.appendChild(turn); global.window = {}; global.document = { createElement:(tag)=>new FakeElement(tag) }; global.requestAnimationFrame = (fn)=>fn(); global.S = { session:{ session_id:'session-1' }, activeStreamId:'stream-1' }; global.$ = (id)=>id === 'emptyState' ? emptyState : id === 'msgInner' ? msgInner : id === 'messages' ? messages : id === 'liveAssistantTurn' ? turn : null; global._createAssistantTurn = () => turn; global._assistantTurnBlocks = () => turn; global._captureMessageScrollSnapshot = () => ({ scrollHeight:1000 }); global._prepareLiveAnchorScrollRebuildGuard = () => ({ readerAwayFromBottom:false, release:null }); global._restoreMessageScrollSnapshotSameFrame = () => {}; global.scrollIfPinned = () => {}; global._moveLiveRunStatusToTurnEnd = () => {}; global._messageUserUnpinned = false; global._anchorSceneRowsForRendering = (scene) => scene.activity_rows || []; global._syncTransparentEventControls = () => {}; global._setTransparentCardOpen = (card, open) => { card.classList.toggle('open', !!open); const row = card.closest('.transparent-event-row'); if(row) row.setAttribute('data-expanded', open ? '1' : '0'); const header = card.querySelector('.tool-card-header'); if(header) header.setAttribute('aria-expanded', open ? 'true' : 'false'); }; global._attachCopyButton = (header) => header && header.querySelector('.transparent-event-copy'); global._anchorSceneTransparentNodeForRow = (row, opts) => { const node = new FakeElement('div'); node.classList.add('transparent-event-row'); node.setAttribute('data-anchor-scene-row','1'); node.setAttribute('data-anchor-live-scene-row','1'); node.setAttribute('data-live-stream-owned','1'); node.setAttribute('data-anchor-row-id', row.row_id); node.setAttribute('data-anchor-row-role', row.role); node.setAttribute('data-anchor-source-event-type', row.source_event_type); node.setAttribute('data-anchor-stream-id', opts.streamId); node.setAttribute('data-session-id', opts.sessionId); node.innerHTML = 'tool-template-same'; node._tcData = { name: row.toolName }; return node; }; eval(extractFunc('_transparentLiveRowKey')); eval(extractFunc('_transparentLiveRowsCompatible')); eval(extractFunc('_transparentLiveRowAttributePairs')); eval(extractFunc('_transparentLiveRowInteractiveState')); eval(extractFunc('_rehydrateTransparentLiveRow')); eval(extractFunc('_refreshTransparentThinkingLiveRow')); eval(extractFunc('_refreshTransparentLiveRow')); eval(extractFunc('_restoreLiveAnchorScrollSnapshotAfterRebuild')); eval(extractFunc('_renderLiveAnchorActivitySceneTransparent')); const firstScene = { version:'activity_scene_v1', activity_rows:[{ row_id:'row-tool', role:'tool', source_event_type:'tool_delta', toolName:'old_tool' }] }; const secondScene = { version:'activity_scene_v1', activity_rows:[{ row_id:'row-tool', role:'tool', source_event_type:'tool_delta', toolName:'fresh_tool' }] }; _renderLiveAnchorActivitySceneTransparent('stream-1', firstScene, { sessionId:'session-1' }); const row = turn.querySelector('.transparent-event-row[data-anchor-row-id="row-tool"]'); row.innerHTMLSetCount = 0; row.setAttribute('data-expanded','1'); row.querySelector('.tool-card').classList.add('open'); const header = row.querySelector('.tool-card-header'); const copy = row.querySelector('.transparent-event-copy'); const headerClick = function headerClick(){}; const copyClick = function copyClick(){}; header.onclick = headerClick; copy.onclick = copyClick; _renderLiveAnchorActivitySceneTransparent('stream-1', secondScene, { sessionId:'session-1' }); process.stdout.write(JSON.stringify({ innerHTMLSetCount: row.innerHTMLSetCount, ownToolName: row._tcData && row._tcData.name, headerClickSurvived: header.onclick === headerClick, copyClickSurvived: copy.onclick === copyClick, cardStillOpen: row.querySelector('.tool-card').classList.contains('open'), rowExpanded: row.getAttribute('data-expanded'), headerAria: header.getAttribute('aria-expanded'), })); """ data = _run_node_script(script, str(ROOT / "static" / "ui.js")) assert data["innerHTMLSetCount"] == 0 assert data["ownToolName"] == "fresh_tool" assert data["headerClickSurvived"] is True assert data["copyClickSurvived"] is True assert data["cardStillOpen"] is True assert data["rowExpanded"] == "1" assert data["headerAria"] == "true" @pytest.mark.skipif(NODE is None, reason="node not on PATH") def test_transparent_fade_helper_clears_settled_rows_without_assigning_fade(): script = """ const fs = require('fs'); const src = fs.readFileSync(process.env.UI_JS_PATH, 'utf8'); function extractFunc(name){{ const marker = new RegExp('function\\\\s+' + name + '\\\\s*\\\\('); const start = src.search(marker); if(start < 0) throw new Error(name + ' not found'); let i = src.indexOf('{{', start) + 1; let depth = 1; while(depth > 0 && i < src.length){{ if(src[i] === '{{') depth += 1; else if(src[i] === '}}') depth -= 1; i += 1; }} return src.slice(start, i); }} const htmlRegistry = new Map(); class FakeElement {{ constructor(tag='div'){{ this.tagName = String(tag).toUpperCase(); this.children = []; this.parentNode = null; this.attributes = Object.create(null); this.dataset = Object.create(null); this.style = Object.create(null); this.hidden = false; this.id = ''; this._textContent = ''; this._innerHTML = ''; this._classes = new Set(); this.onclick = null; this.onkeydown = null; this.title = ''; const self = this; this.classList = {{ add(...names){{ names.forEach(name=>self._classes.add(name)); }}, remove(...names){{ names.forEach(name=>self._classes.delete(name)); }}, contains(name){{ return self._classes.has(name); }}, toggle(name, force){{ if(force === true){{ self._classes.add(name); return true; }} if(force === false){{ self._classes.delete(name); return false; }} if(self._classes.has(name)){{ self._classes.delete(name); return false; }} self._classes.add(name); return true; }}, }}; }} cloneNode(deep){{ const clone = new FakeElement(this.tagName); clone.className = this.className; clone._textContent = this._textContent; clone._innerHTML = this._innerHTML; clone.hidden = this.hidden; clone.id = this.id; clone.title = this.title; Object.entries(this.attributes).forEach(([name, value])=>clone.setAttribute(name, value)); if(Object.prototype.hasOwnProperty.call(this, '_tcData')) clone._tcData = this._tcData; if(deep){{ this.children.forEach(child=>clone.appendChild(child.cloneNode(true))); }} return clone; }} get parentElement(){{ return this.parentNode; }} get firstChild(){{ return this.children[0]||null; }} get className(){{ return Array.from(this._classes).join(' '); }} set className(value){{ this._classes = new Set(String(value).trim().split(/\\s+/).filter(Boolean)); }} get textContent(){{ if(this.children.length) return this.children.map(child=>child.textContent).join(''); return this._textContent; }} set textContent(value){{ this._textContent = String(value ?? ''); this._innerHTML = this._textContent; this.children = []; }} get innerHTML(){{ return this._innerHTML; }} set innerHTML(value){{ this._innerHTML = String(value ?? ''); this._textContent = ''; this.children = []; const factory = htmlRegistry.get(this._innerHTML); if(factory){{ factory().forEach(child=>this.appendChild(child.cloneNode(true))); return; }} this._textContent = this._innerHTML; }} setAttribute(name, value){{ const key = String(name); const val = String(value); this.attributes[key] = val; if(key === 'id') this.id = val; if(key.startsWith('data-')){{ const dataKey = key.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase()); this.dataset[dataKey] = val; }} if(key === 'class') this.className = val; }} getAttribute(name){{ return Object.prototype.hasOwnProperty.call(this.attributes, name) ? this.attributes[name] : null; }} getAttributeNames(){{ return Object.keys(this.attributes); }} removeAttribute(name){{ delete this.attributes[name]; if(name === 'id') this.id = ''; if(name.startsWith('data-')){{ const dataKey = name.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase()); delete this.dataset[dataKey]; }} if(name === 'class') this._classes = new Set(); }} appendChild(child){{ if(child && child.parentNode) child.remove(); if(!child) return null; child.parentNode = this; this.children.push(child); return child; }} insertBefore(child, refNode){{ if(child && child.parentNode) child.remove(); if(!child) return null; const idx = this.children.indexOf(refNode); child.parentNode = this; if(idx < 0) this.children.push(child); else this.children.splice(idx, 0, child); return child; }} remove(){{ if(!this.parentNode) return; const siblings = this.parentNode.children; const idx = siblings.indexOf(this); if(idx >= 0) siblings.splice(idx, 1); this.parentNode = null; }} matches(selector){{ return matchesSelector(this, selector); }} querySelector(selector){{ return this.querySelectorAll(selector)[0] || null; }} querySelectorAll(selector){{ const out = []; const walk = (node)=>{ for(const child of node.children){{ if(matchesSelector(child, selector)) out.push(child); walk(child); }} }; walk(this); return out; }} closest(selector){{ let node = this; while(node){{ if(matchesSelector(node, selector)) return node; node = node.parentNode; }} return null; }} }} function matchesSelector(el, selector){{ if(!selector) return false; const options = selector.split(',').map(part=>part.trim()).filter(Boolean); return options.some(part=>matchesSimple(el, part)); }} function matchesSimple(el, selector){{ selector = selector.replace(/^:scope\\s*>\\s*/, '').trim(); if(!selector) return false; const idMatch = selector.match(/#([^.\\[#]+)/); if(idMatch && el.id !== idMatch[1]) return false; const clsMatches = selector.match(/\\.([A-Za-z0-9_-]+)/g) || []; for(const cls of clsMatches){{ if(!el.classList.contains(cls.slice(1))) return false; }} const attrMatches = selector.match(/\\[([^=\\]]+)(?:=\\"([^\\"]*)\\")?\\]/g) || []; for(const attrMatch of attrMatches){{ const [, name, expected] = attrMatch.match(/\\[([^=\\]]+)(?:=\\"([^\\"]*)\\")?\\]/); const value = el.getAttribute(name); if(value === null) return false; if(expected !== undefined && String(value) !== String(expected)) return false; }} return !!(idMatch || clsMatches.length || attrMatches.length); }} function buildToolChildren(version){{ const card = new FakeElement('div'); card.className = 'tool-card open'; const header = new FakeElement('div'); header.className = 'tool-card-header'; const name = new FakeElement('span'); name.className = 'tool-card-name'; name.textContent = version === 'first' ? 'Shell old' : 'Shell new'; const toggle = new FakeElement('span'); toggle.className = 'tool-card-toggle'; toggle.textContent = '>'; header.appendChild(name); header.appendChild(toggle); const detail = new FakeElement('div'); detail.className = 'tool-card-detail'; detail.setAttribute('data-transparent-detail-mode', 'output'); const full = new FakeElement('span'); full.className = 'transparent-detail-mode'; full.setAttribute('data-mode', 'full'); full.textContent = 'Full'; const output = new FakeElement('span'); output.className = 'transparent-detail-mode active'; output.setAttribute('data-mode', 'output'); output.textContent = 'Output'; detail.appendChild(full); detail.appendChild(output); card.appendChild(header); card.appendChild(detail); return [card]; }} function toolPayload(version){{ return version === 'first' ? {{ name:'shell', args:{{ cmd:'echo old' }}, snippet:'old payload', done:false }} : {{ name:'shell', args:{{ cmd:'echo new' }}, snippet:'new payload', done:false }}; }} function makeToolRow(version){{ const token = version === 'first' ? '__tool_row_first__' : '__tool_row_second__'; htmlRegistry.set(token, ()=>buildToolChildren(version)); const row = new FakeElement('div'); row.className = 'transparent-event-row'; row.setAttribute('data-transparent-event-row', '1'); row.setAttribute('data-event-type', 'tool'); row.setAttribute('data-anchor-scene-row', '1'); row.setAttribute('data-anchor-live-scene-row', '1'); row.setAttribute('data-anchor-row-id', 'row-tool'); row.setAttribute('data-anchor-row-role', 'tool'); row.setAttribute('data-anchor-source-event-type', 'process_tool'); row.setAttribute('data-anchor-stream-id', 'stream-1'); row.setAttribute('data-session-id', 'session-1'); row.setAttribute('data-live-stream-owned', '1'); row.setAttribute('data-expanded', '1'); row.innerHTML = token; row._tcData = toolPayload(version); const header = row.querySelector('.tool-card-header'); const card = row.querySelector('.tool-card'); const detail = row.querySelector('.tool-card-detail'); _wireTransparentHeaderToggle(header); _attachCopyButton(header); _setTransparentCardOpen(card, true); detail.setAttribute('data-transparent-detail-mode', 'output'); detail.querySelectorAll('.transparent-detail-mode').forEach(el=>el.classList.toggle('active', el.getAttribute('data-mode') === 'output')); return row; }} global.window = {{}}; global.document = {{ body:new FakeElement('body'), createElement:(tag)=>new FakeElement(tag), execCommand:()=>true, }}; global.requestAnimationFrame = (fn)=>fn(); global.CSS = {{ escape:(value)=>String(value) }}; global.t = (key)=>key; global.showToast = () => {{}}; global.S = {{ session:{{ session_id:'session-1' }}, activeStreamId:'stream-1' }}; global._captureMessageScrollSnapshot = () => ({{ scrollHeight: 1000 }}); global._prepareLiveAnchorScrollRebuildGuard = () => ({{ readerAwayFromBottom:false, release:null }}); global._restoreMessageScrollSnapshotSameFrame = () => {{}}; global.scrollIfPinned = () => {{}}; global._moveLiveRunStatusToTurnEnd = () => {{}}; global._messageUserUnpinned = false; global._syncTransparentEventControls = () => {{}}; global._anchorSceneRowsForRendering = (scene) => scene && scene.activity_rows || []; const emptyState = new FakeElement('div'); const msgInner = new FakeElement('div'); const turn = new FakeElement('div'); turn.id = 'liveAssistantTurn'; const liveRunStatus = new FakeElement('div'); liveRunStatus.id = 'liveRunStatus'; msgInner.appendChild(turn); turn.appendChild(liveRunStatus); global.document._findById = (id) => id === 'emptyState' ? emptyState : id === 'msgInner' ? msgInner : id === 'liveAssistantTurn' ? turn : null; global.$ = (id)=>global.document._findById(id); global._createAssistantTurn = () => turn; global._assistantTurnBlocks = () => turn; global._anchorSceneTransparentNodeForRow = () => null; eval(extractFunc('_attachCopyButton')); eval(extractFunc('_setTransparentCardOpen')); eval(extractFunc('_wireTransparentHeaderToggle')); eval(extractFunc('_transparentLiveRowKey')); eval(extractFunc('_transparentLiveRowsCompatible')); eval(extractFunc('_transparentLiveRowAttributePairs')); eval(extractFunc('_transparentLiveRowInteractiveState')); eval(extractFunc('_rehydrateTransparentLiveRow')); eval(extractFunc('_refreshTransparentThinkingLiveRow')); eval(extractFunc('_refreshTransparentLiveRow')); eval(extractFunc('_restoreLiveAnchorScrollSnapshotAfterRebuild')); eval(extractFunc('_renderLiveAnchorActivitySceneTransparent')); global._copyEventToClipboard = (row) => {{ const tc = row && row._tcData || {{}}; global.__copied = JSON.stringify({{ name: tc.name || '', cmd: tc.args && tc.args.cmd || '', snippet: tc.snippet || '', }}); }}; global._anchorSceneTransparentNodeForRow = (row) => makeToolRow(row.version); (async () => {{ const firstScene = {{ version:'activity_scene_v1', activity_rows:[{{ row_id:'row-tool', role:'tool', source_event_type:'process_tool', version:'first' }}], }}; const secondScene = {{ version:'activity_scene_v1', activity_rows:[{{ row_id:'row-tool', role:'tool', source_event_type:'process_tool', version:'second' }}], }}; _renderLiveAnchorActivitySceneTransparent('stream-1', firstScene, {{ sessionId:'session-1' }}); const firstRow = turn.querySelector('.transparent-event-row[data-anchor-row-id="row-tool"]'); const firstCard = firstRow.querySelector('.tool-card'); const firstDetail = firstRow.querySelector('.tool-card-detail'); _setTransparentCardOpen(firstCard, true); firstDetail.setAttribute('data-transparent-detail-mode', 'output'); _renderLiveAnchorActivitySceneTransparent('stream-1', secondScene, {{ sessionId:'session-1' }}); const keptRow = turn.querySelector('.transparent-event-row[data-anchor-row-id="row-tool"]'); const header = keptRow.querySelector('.tool-card-header'); const copy = keptRow.querySelector('.transparent-event-copy'); const card = keptRow.querySelector('.tool-card'); const detail = keptRow.querySelector('.tool-card-detail'); copy.onclick({{ stopPropagation(){{}}, preventDefault(){{}} }}); await Promise.resolve(); process.stdout.write(JSON.stringify({{ sameNode: firstRow === keptRow, tcSnippet: keptRow._tcData && keptRow._tcData.snippet, tcCommand: keptRow._tcData && keptRow._tcData.args && keptRow._tcData.args.cmd, hasCopyHandler: !!(copy && typeof copy.onclick === 'function'), hasHeaderHandler: !!(header && typeof header.onclick === 'function'), copiedText: global.__copied || '', cardOpen: !!(card && card.classList.contains('open')), detailMode: detail && detail.getAttribute('data-transparent-detail-mode'), headerName: (header.querySelector('.tool-card-name') || {{ textContent:'' }}).textContent, }})); }})().catch(err => {{ console.error(err && err.stack || String(err)); process.exit(1); }}); """ script = script.replace("{{", "{").replace("}}", "}") data = _run_node_script(script, str(ROOT / "static" / "ui.js")) assert data["sameNode"] is True assert data["tcSnippet"] == "new payload" assert data["tcCommand"] == "echo new" assert data["hasCopyHandler"] is True assert data["hasHeaderHandler"] is True assert data["copiedText"] == '{"name":"shell","cmd":"echo new","snippet":"new payload"}' assert data["cardOpen"] is True assert data["detailMode"] == "output" assert data["headerName"] == "Shell new" @pytest.mark.skipif(NODE is None, reason="node not on PATH") def test_transparent_live_thinking_refresh_preserves_scroll_container(): script = """ const fs = require('fs'); const src = fs.readFileSync(process.env.UI_JS_PATH, 'utf8'); function extractFunc(name){ const marker = new RegExp('function\\\\s+' + name + '\\\\s*\\\\('); const start = src.search(marker); if(start < 0) throw new Error(name + ' not found'); let i = src.indexOf('{', start) + 1; let depth = 1; while(depth > 0 && i < src.length){ if(src[i] === '{') depth += 1; else if(src[i] === '}') depth -= 1; i += 1; } return src.slice(start, i); } class FakeElement { constructor(tag='div'){ this.tagName = String(tag).toUpperCase(); this.children = []; this.parentNode = null; this.attributes = Object.create(null); this._textContent = ''; this._innerHTML = ''; this.innerHTMLSetCount = 0; this._classes = new Set(); const self = this; this.classList = { add(...names){ names.forEach(name=>self._classes.add(name)); }, remove(...names){ names.forEach(name=>self._classes.delete(name)); }, contains(name){ return self._classes.has(name); }, toggle(name, force){ const enabled = force === undefined ? !self._classes.has(name) : !!force; if(enabled) self._classes.add(name); else self._classes.delete(name); return enabled; }, }; } get className(){ return Array.from(this._classes).join(' '); } set className(value){ this._classes = new Set(String(value).trim().split(/\\s+/).filter(Boolean)); } get textContent(){ return this.children.length ? this.children.map(child=>child.textContent).join('') : this._textContent; } set textContent(value){ this._textContent = String(value ?? ''); this.children = []; } get innerHTML(){ return this._innerHTML || this.children.map(child=>child.textContent).join(''); } set innerHTML(value){ this.innerHTMLSetCount += 1; this._innerHTML = String(value ?? ''); this._textContent = this._innerHTML; this.children = []; } setAttribute(name, value){ this.attributes[String(name)] = String(value); if(name === 'class') this.className = value; } getAttribute(name){ return Object.prototype.hasOwnProperty.call(this.attributes, name) ? this.attributes[name] : null; } getAttributeNames(){ return Object.keys(this.attributes); } removeAttribute(name){ delete this.attributes[name]; if(name === 'class') this._classes = new Set(); } appendChild(child){ if(child.parentNode) child.remove(); child.parentNode = this; this.children.push(child); return child; } remove(){ if(!this.parentNode) return; const idx = this.parentNode.children.indexOf(this); if(idx >= 0) this.parentNode.children.splice(idx, 1); this.parentNode = null; } querySelector(selector){ return this.querySelectorAll(selector)[0] || null; } querySelectorAll(selector){ const parts = String(selector).split(',').map(s=>s.trim()).filter(Boolean); const out = []; const walk = (node)=>{ for(const child of node.children){ if(parts.some(part=>matchesSelectorPath(child, part))) out.push(child); walk(child); } }; walk(this); return out; } } function matchesSelectorPath(el, selector){ const tokens = String(selector).split(/\\s+/).filter(Boolean); if(!tokens.length) return false; if(!matchesSimple(el, tokens[tokens.length - 1])) return false; let ancestor = el.parentNode; for(let i=tokens.length - 2;i>=0;i--){ while(ancestor && !matchesSimple(ancestor, tokens[i])) ancestor = ancestor.parentNode; if(!ancestor) return false; ancestor = ancestor.parentNode; } return true; } function matchesSimple(el, selector){ const tag = String(selector).match(/^[A-Za-z][A-Za-z0-9-]*/); if(tag && el.tagName !== tag[0].toUpperCase()) return false; const classes = selector.match(/\\.([A-Za-z0-9_-]+)/g) || []; if(!classes.length) return !!tag; return classes.every(cls=>el.classList.contains(cls.slice(1))); } function thinkingRow(text, preview){ const row = new FakeElement('div'); row.className = 'agent-activity-thinking transparent-event-row transparent-thinking-event'; row.setAttribute('data-event-type', 'thinking'); row.setAttribute('data-anchor-row-id', 'think-1'); row.setAttribute('data-anchor-row-role', 'thinking'); row.setAttribute('data-anchor-source-event-type', 'reasoning'); const card = new FakeElement('div'); card.className = 'thinking-card open'; const header = new FakeElement('div'); header.className = 'thinking-card-header'; const label = new FakeElement('span'); label.className = 'thinking-card-label'; label.textContent = 'Thinking'; const previewEl = new FakeElement('span'); previewEl.className = 'transparent-event-thinking-preview'; previewEl.textContent = preview; const body = new FakeElement('div'); body.className = 'thinking-card-body'; body.scrollTop = 37; const pre = new FakeElement('pre'); pre.textContent = text; header.appendChild(label); header.appendChild(previewEl); body.appendChild(pre); card.appendChild(header); card.appendChild(body); row.appendChild(card); return row; } global._decorateTransparentEventRow = (row, opts)=>{ const preview = row.querySelector('.transparent-event-thinking-preview'); if(preview) preview.textContent = opts.preview || opts.text || ''; row.setAttribute('data-event-type', opts.type || 'thinking'); return row; }; eval(extractFunc('_transparentLiveRowAttributePairs')); eval(extractFunc('_transparentLiveRowInteractiveState')); eval(extractFunc('_rehydrateTransparentLiveRow')); eval(extractFunc('_refreshTransparentThinkingLiveRow')); eval(extractFunc('_refreshTransparentLiveRow')); const existing = thinkingRow('short thought', 'short thought'); const originalBody = existing.querySelector('.thinking-card-body'); const originalPre = existing.querySelector('.thinking-card-body pre'); const candidate = thinkingRow('long thought\\n'.repeat(80), 'long thought'); const refreshed = _refreshTransparentLiveRow(existing, candidate); const refreshedBody = refreshed.querySelector('.thinking-card-body'); const refreshedPre = refreshed.querySelector('.thinking-card-body pre'); const refreshedPreview = refreshed.querySelector('.transparent-event-thinking-preview'); process.stdout.write(JSON.stringify({ sameRow: refreshed === existing, sameBody: refreshedBody === originalBody, samePre: refreshedPre === originalPre, textUpdated: refreshedPre.textContent === 'long thought\\n'.repeat(80), previewUpdated: refreshedPreview.textContent === 'long thought', scrollTopPreserved: refreshedBody.scrollTop === 37, innerHTMLSetCount: refreshed.innerHTMLSetCount, cardOpen: !!refreshed.querySelector('.thinking-card').classList.contains('open'), })); """ data = _run_node_script(script, str(ROOT / "static" / "ui.js")) assert data["sameRow"] is True assert data["sameBody"] is True assert data["samePre"] is True assert data["textUpdated"] is True assert data["previewUpdated"] is True assert data["scrollTopPreserved"] is True assert data["innerHTMLSetCount"] == 0 assert data["cardOpen"] is True @pytest.mark.skipif(NODE is None, reason="node not on PATH") def test_transparent_rebound_copy_controls_report_only_confirmed_success(): script = r""" const fs = require('fs'); const src = fs.readFileSync(process.env.UI_JS_PATH, 'utf8'); function extractFunc(name){ const marker = new RegExp('function\\s+' + name + '\\s*\\('); const start = src.search(marker); if(start < 0) throw new Error(name + ' not found'); let i = src.indexOf('{', start) + 1; let depth = 1; while(depth > 0 && i < src.length){ if(src[i] === '{') depth += 1; else if(src[i] === '}') depth -= 1; i += 1; } return src.slice(start, i); } const htmlRegistry = new Map(); const snapshotRegistry = new Map(); class FakeElement { constructor(tag='div'){ this.tagName = String(tag).toUpperCase(); this.children = []; this.parentNode = null; this.attributes = Object.create(null); this.dataset = Object.create(null); this._styleValues = Object.create(null); this.style = {}; const self = this; Object.defineProperty(this.style, 'color', { get(){ return self._styleValues.color || ''; }, set(value){ self._recordWrite('style'); self._styleValues.color=String(value ?? ''); }, }); Object.defineProperty(this.style, 'opacity', { get(){ return self._styleValues.opacity || ''; }, set(value){ self._recordWrite('style'); self._styleValues.opacity=String(value ?? ''); }, }); Object.defineProperty(this.style, 'cssText', { get(){ return Object.entries(self._styleValues).filter(([,value])=>value !== '').map(([name,value])=>`${name}: ${value};`).join(' '); }, set(value){ self._recordWrite('style'); self._styleValues = Object.create(null); String(value ?? '').split(';').forEach(part=>{ const index=part.indexOf(':'); if(index < 0) return; const name=part.slice(0,index).trim(); const item=part.slice(index+1).trim(); if(name) self._styleValues[name]=item; }); }, }); this._title = ''; this.onclick = null; this.onkeydown = null; this._innerHTML = ''; this._textContent = ''; this._classes = new Set(); this.id = ''; this.content = this.tagName === 'TEMPLATE' ? { firstElementChild:null } : null; this.classList = { add(...names){ names.forEach(name=>self._classes.add(name)); }, remove(...names){ names.forEach(name=>self._classes.delete(name)); }, contains(name){ return self._classes.has(name); }, toggle(name, force){ const next = force === undefined ? !self._classes.has(name) : !!force; if(next) self._classes.add(name); else self._classes.delete(name); return next; }, }; } get parentElement(){ return this.parentNode; } cloneNode(deep){ const clone=new FakeElement(this.tagName); clone.className=this.className; clone._innerHTML=this._innerHTML; clone._textContent=this._textContent; clone.style.cssText=this.style.cssText; Object.entries(this.attributes).forEach(([name,value])=>clone.setAttribute(name,value)); if(deep) this.children.forEach(child=>clone.appendChild(child.cloneNode(true))); return clone; } get isConnected(){ let node = this; while(node){ if(global.document&&node===global.document.body) return true; node = node.parentNode; } return false; } get title(){ return this._title; } set title(value){ this._recordWrite('title'); this._title=String(value ?? ''); } get className(){ return Array.from(this._classes).join(' '); } set className(value){ this._classes = new Set(String(value).split(/\s+/).filter(Boolean)); } get innerHTML(){ if(this.tagName === 'TEMPLATE') return this._templateHtml || ''; return this._innerHTML; } set innerHTML(value){ if(this.tagName === 'TEMPLATE'){ this._templateHtml=String(value ?? ''); const stored=snapshotRegistry.get(this._templateHtml); this.content.firstElementChild=stored ? stored.cloneNode(true) : null; return; } this._recordWrite('innerHTML'); this.children.forEach(child=>{ child.parentNode=null; }); this._innerHTML = String(value ?? ''); this._textContent = this._innerHTML; this.children = []; const factory = htmlRegistry.get(this._innerHTML); if(factory){ this._textContent = ''; factory().forEach(child=>this.appendChild(child)); } } get textContent(){ return this.children.length ? this.children.map(child=>child.textContent).join('') : this._textContent; } set textContent(value){ this._recordWrite('textContent'); this.children.forEach(child=>{ child.parentNode=null; }); this._textContent = String(value ?? ''); this._innerHTML = this._textContent; this.children = []; } _recordWrite(kind){ if(this._trackDetachedWrites&& !this.isConnected) this._trackDetachedWrites.push(kind); } setAttribute(name, value){ this._recordWrite(name === 'aria-label' ? 'aria-label' : `attribute:${name}`); this.attributes[String(name)] = String(value); if(name === 'title') this.title = String(value); if(name === 'id') this.id = String(value); if(String(name).startsWith('data-')){ const key=String(name).slice(5).replace(/-([a-z])/g,(_,char)=>char.toUpperCase()); this.dataset[key]=String(value); } } getAttribute(name){ return Object.prototype.hasOwnProperty.call(this.attributes, name) ? this.attributes[name] : null; } getAttributeNames(){ return Object.keys(this.attributes); } removeAttribute(name){ this._recordWrite(name === 'aria-label' ? 'aria-label' : `attribute:${name}`); delete this.attributes[name]; if(name === 'title') this.title = ''; if(name === 'id') this.id = ''; if(String(name).startsWith('data-')){ const key=String(name).slice(5).replace(/-([a-z])/g,(_,char)=>char.toUpperCase()); delete this.dataset[key]; } } appendChild(child){ if(child.parentNode) child.remove(); child.parentNode = this; this.children.push(child); return child; } insertBefore(child, ref){ if(child.parentNode) child.remove(); const index = this.children.indexOf(ref); child.parentNode = this; if(index < 0) this.children.push(child); else this.children.splice(index, 0, child); return child; } removeChild(child){ child.remove(); return child; } remove(){ if(!this.parentNode) return; const siblings = this.parentNode.children; const index = siblings.indexOf(this); if(index >= 0) siblings.splice(index, 1); this.parentNode = null; } replaceWith(replacement){ if(!this.parentNode) return; const siblings=this.parentNode.children; const index=siblings.indexOf(this); if(replacement.parentNode) replacement.remove(); if(index >= 0){ siblings[index]=replacement; replacement.parentNode=this.parentNode; this.parentNode=null; } } _serialize(){ const attrs=Object.entries(this.attributes); if(this.id&&!Object.prototype.hasOwnProperty.call(this.attributes,'id')) attrs.push(['id',this.id]); if(this.className&&!Object.prototype.hasOwnProperty.call(this.attributes,'class')) attrs.push(['class',this.className]); if(this.style.cssText) attrs.push(['style',this.style.cssText]); const attrText=attrs.map(([name,value])=>` ${name}="${String(value)}"`).join(''); const content=this.children.length ? this.children.map(child=>child._serialize()).join('') : this.innerHTML; return `<${this.tagName.toLowerCase()}${attrText}>${content}`; } get outerHTML(){ const html=this._serialize(); snapshotRegistry.set(html,this.cloneNode(true)); return html; } select(){} querySelector(selector){ return this.querySelectorAll(selector)[0] || null; } querySelectorAll(selector){ const found = []; const walk = node=>{ node.children.forEach(child=>{ if(matches(child, selector)) found.push(child); walk(child); }); }; walk(this); return found; } closest(selector){ let node = this; while(node){ if(matches(node, selector)) return node; node = node.parentNode; } return null; } } function matches(node, selector){ return String(selector||'').split(',').map(part=>part.trim()).filter(Boolean).some(part=>{ const classes = part.match(/\.([A-Za-z0-9_-]+)/g) || []; if(classes.some(cls=>!node.classList.contains(cls.slice(1)))) return false; const attrs = part.match(/\[([^=\]]+)(?:="([^"]*)")?\]/g) || []; if(attrs.some(raw=>{ const match = raw.match(/\[([^=\]]+)(?:="([^"]*)")?\]/); const value = node.getAttribute(match[1]); return value === null || (match[2] !== undefined && value !== match[2]); })) return false; return classes.length > 0 || attrs.length > 0; }); } function element(tag, classes){ const node = new FakeElement(tag); node.className = classes || ''; return node; } function toolRow(){ const row = element('div', 'transparent-event-row'); row.setAttribute('data-event-type', 'tool'); row.setAttribute('data-expanded', '0'); row._tcData = { name:'shell', args:{ command:'echo secret-token' }, snippet:'tool output' }; const card = element('div', 'tool-card'); const header = element('div', 'tool-card-header'); const copy = element('span', 'transparent-event-copy'); copy.innerHTML = ''; const toggle = element('span', 'tool-card-toggle'); header.appendChild(copy); header.appendChild(toggle); card.appendChild(header); row.appendChild(card); return { row, card, header, copy }; } let reconcileTemplateId = 0; function reconcileToolRow(version, original){ const token = `__reconcile_tool_${version}_${++reconcileTemplateId}__`; htmlRegistry.set(token, ()=>{ const card = element('div', 'tool-card'); const header = element('div', 'tool-card-header'); const copy = element('span', 'transparent-event-copy'); copy.innerHTML = original.html; copy.style.color = original.color; copy.style.opacity = original.opacity || ''; copy.setAttribute('title', original.title); copy.setAttribute('aria-label', original.aria); const toggle = element('span', 'tool-card-toggle'); header.appendChild(copy); header.appendChild(toggle); card.appendChild(header); return [card]; }); const row = element('div', 'transparent-event-row'); row.setAttribute('data-event-type', 'tool'); row.setAttribute('data-anchor-row-id', 'copy-race'); row.setAttribute('data-anchor-row-role', 'tool'); row.setAttribute('data-anchor-source-event-type', 'process_tool'); row.setAttribute('data-expanded', '0'); row._tcData = { name:'shell', args:{ command:`echo ${version}` }, snippet:`${version} output` }; row.innerHTML = token; return { row, card:row.querySelector('.tool-card'), header:row.querySelector('.tool-card-header'), copy:row.querySelector('.transparent-event-copy'), }; } function thinkingRow(text){ const row = element('div', 'transparent-event-row transparent-thinking-event'); row.setAttribute('data-event-type', 'thinking'); row.setAttribute('data-expanded', '0'); const card = element('div', 'thinking-card'); const header = element('div', 'thinking-card-header'); const copy = element('button', 'thinking-copy-btn'); copy.innerHTML = ''; const toggle = element('span', 'thinking-card-toggle'); const body = element('div', 'thinking-card-body'); const pre = element('pre', ''); pre.textContent = text; header.appendChild(copy); header.appendChild(toggle); body.appendChild(pre); card.appendChild(header); card.appendChild(body); row.appendChild(card); return { row, card, header, copy }; } let nextTimer = 1; const timers = new Map(); const clearedTimers = []; global.setTimeout = (fn, ms)=>{ const id=nextTimer++; timers.set(id,{ fn, ms }); return id; }; global.clearTimeout = (id)=>{ clearedTimers.push(id); timers.delete(id); }; function runTimer(id){ const timer=timers.get(id); timers.delete(id); timer.fn(); } let clipboardMode = 'success'; let deferredClipboardResolve = null; let fallbackResult = true; const writes = []; const toasts = []; Object.defineProperty(global, 'navigator', { configurable:true, value:{ clipboard:{ writeText:(text)=>{ writes.push(text); if(clipboardMode === 'deferred'){ return new Promise(resolve=>{ deferredClipboardResolve=resolve; }); } return clipboardMode === 'success' ? Promise.resolve() : Promise.reject(new Error('denied')); } } }, }); global.document = { body:new FakeElement('body'), createElement:(tag)=>new FakeElement(tag), execCommand:()=>fallbackResult, }; global.t = key=>({ copy:'Copy', copied:'Copied', copy_failed:'Copy failed' })[key] || key; global.li = (name, size)=>``; global.showToast = (message, duration, type)=>toasts.push({ message, duration, type:type||'' }); global._redactToolTargetLabel = value=>String(value).replace(/secret-token/g, '[REDACTED]'); global._wireTransparentHeaderToggle = ()=>{}; global._setTransparentCardOpen = (card, open)=>card.classList.toggle('open', !!open); global.INFLIGHT = Object.create(null); global.S = { session:{ session_id:'snapshot-session' } }; global.requestAnimationFrame = (fn)=>fn(); global._rehydrateTransparentStreamDom = ()=>{}; global.normalizeLiveActivityGroupPlacement = ()=>{}; global._dedupeLiveProcessedWorklogAnchors = ()=>{}; global.placeLiveToolCardsHost = ()=>{}; global._postProcessWithAnchorSuppression = ()=>{}; global.$ = (id)=>document.body.querySelector(`[id="${id}"]`); eval(extractFunc('snapshotLiveTurnHtmlForSession')); eval(extractFunc('_liveAssistantSegmentTextLength')); eval(extractFunc('_mergeRestoredLiveAssistantSegment')); eval(extractFunc('restoreLiveTurnHtmlForSession')); eval(extractFunc('_showTransparentCopiedFeedback')); eval(extractFunc('_copyEventToClipboard')); eval(extractFunc('_attachCopyButton')); eval(extractFunc('_transparentLiveRowAttributePairs')); eval(extractFunc('_transparentLiveRowInteractiveState')); eval(extractFunc('_rehydrateTransparentLiveRow')); eval(extractFunc('_refreshTransparentThinkingLiveRow')); eval(extractFunc('_refreshTransparentLiveRow')); function eventProbe(){ return { stopped:false, prevented:false, stopPropagation(){ this.stopped=true; }, preventDefault(){ this.prevented=true; }, }; } async function settle(){ await Promise.resolve(); await Promise.resolve(); } function replaceCopyControl(header, oldCopy, classes, original){ const replacement = element('button', classes); replacement.innerHTML = original.html; replacement.style.color = original.color; replacement.style.opacity = original.opacity || ''; replacement.setAttribute('title', original.title); replacement.setAttribute('aria-label', original.aria); oldCopy.remove(); const toggle = header.querySelector('.tool-card-toggle,.thinking-card-toggle'); if(toggle&&toggle.parentNode===header) header.insertBefore(replacement,toggle); else header.appendChild(replacement); _attachCopyButton(header); return replacement; } (async()=>{ const tool = toolRow(); document.body.appendChild(tool.row); _attachCopyButton(tool.header); tool.copy.innerHTML = ''; tool.copy.style.color = 'rgb(1, 2, 3)'; tool.copy.style.opacity = '0.28'; tool.copy.setAttribute('title', 'Original tool title'); tool.copy.setAttribute('aria-label', 'Original tool aria'); const original = { html:tool.copy.innerHTML, color:tool.copy.style.color, opacity:tool.copy.style.opacity, styleCssText:tool.copy.style.cssText, title:tool.copy.title, titleAttr:tool.copy.getAttribute('title'), aria:tool.copy.getAttribute('aria-label'), }; const firstEvent = eventProbe(); tool.copy.onclick(firstEvent); await settle(); const firstTimerId = Array.from(timers.keys())[0]; const firstTimer = timers.get(firstTimerId); const firstState = { check:tool.copy.innerHTML.includes('data-icon="check"'), title:tool.copy.title, aria:tool.copy.getAttribute('aria-label'), opacity:tool.copy.style.opacity, duration:firstTimer.ms, stopped:firstEvent.stopped, prevented:firstEvent.prevented, rowExpanded:tool.row.getAttribute('data-expanded'), cardOpen:tool.card.classList.contains('open'), }; const staleCallback = firstTimer.fn; tool.copy.onclick(eventProbe()); await settle(); const secondTimerId = Array.from(timers.keys())[0]; staleCallback(); const staleTimerIgnored = tool.copy.innerHTML.includes('data-icon="check"'); runTimer(secondTimerId); const restored = { html:tool.copy.innerHTML, color:tool.copy.style.color, opacity:tool.copy.style.opacity, styleCssText:tool.copy.style.cssText, title:tool.copy.title, titleAttr:tool.copy.getAttribute('title'), aria:tool.copy.getAttribute('aria-label'), }; const thinking = thinkingRow('transparent reasoning'); document.body.appendChild(thinking.row); const candidate = thinkingRow('candidate reasoning'); _rehydrateTransparentLiveRow(thinking.row, candidate.row, { expanded:false }); const thinkingControls = thinking.header.querySelectorAll('.transparent-event-copy,.thinking-copy-btn'); thinking.copy.style.opacity = '0.4'; const thinkingOriginalStyle = thinking.copy.style.cssText; thinking.copy.onclick(eventProbe()); await settle(); const thinkingState = { count:thinkingControls.length, bound:typeof thinking.copy.onclick === 'function' && typeof thinking.copy.onkeydown === 'function', check:thinking.copy.innerHTML.includes('data-icon="check"'), opacity:thinking.copy.style.opacity, copied:writes[writes.length - 1], }; runTimer(Array.from(timers.keys())[0]); const thinkingRestoredStyle = thinking.copy.style.cssText; clipboardMode = 'failure'; fallbackResult = true; tool.copy.onclick(eventProbe()); await settle(); const fallbackSuccess = { check:tool.copy.innerHTML.includes('data-icon="check"'), toast:toasts[toasts.length - 1].message, }; runTimer(Array.from(timers.keys())[0]); fallbackResult = false; const toastCountBeforeFailure = toasts.length; tool.copy.onclick(eventProbe()); await settle(); const failure = { check:tool.copy.innerHTML.includes('data-icon="check"'), feedbackState:!!tool.row._transparentCopiedFeedback, toasts:toasts.slice(toastCountBeforeFailure).map(item=>item.message), }; const empty = thinkingRow(''); document.body.appendChild(empty.row); _attachCopyButton(empty.header); const writesBeforeEmpty = writes.length; empty.copy.onclick(eventProbe()); await settle(); const emptyCopySkipped = writes.length === writesBeforeEmpty && !empty.row._transparentCopiedFeedback; // Copy success can settle after the real live-row reconciler has replaced the // activated control. The stable row owns the feedback lifetime, so the current // visible replacement receives the check. clipboardMode = 'deferred'; const race = reconcileToolRow('existing', { html:'', color:'rgb(3, 4, 5)', title:'Existing title', aria:'Existing aria', opacity:'0.28', }); document.body.appendChild(race.row); _attachCopyButton(race.header); let headerToggleCount = 0; race.header.onclick = ()=>{ headerToggleCount += 1; }; const beforeSuccess = eventProbe(); const activatedCopy = race.copy; race.copy.onclick(beforeSuccess); const candidateBeforeSuccess = reconcileToolRow('replacement-before-success', { html:'', color:'rgb(7, 8, 9)', title:'Replacement before success title', aria:'Replacement before success aria', opacity:'0.31', }); _refreshTransparentLiveRow(race.row, candidateBeforeSuccess.row); race.card = race.row.querySelector('.tool-card'); race.header = race.row.querySelector('.tool-card-header'); race.copy = race.row.querySelector('.transparent-event-copy'); // Binding supplies the normal Copy label. Set distinct presentation after // reconciliation so the feedback snapshot proves which control is current. race.copy.innerHTML = ''; race.copy.style.color = 'rgb(7, 8, 9)'; race.copy.setAttribute('title', 'Replacement before success title'); race.copy.setAttribute('aria-label', 'Replacement before success aria'); const activatedControlDetached = !activatedCopy.isConnected; const beforeResolutionCheck = race.copy.innerHTML.includes('data-icon="check"'); deferredClipboardResolve(); await settle(); const firstRaceTimerId = race.row._transparentCopiedFeedback.timer; const staleRaceTimer = timers.get(firstRaceTimerId).fn; const replacementAfterSuccess = { check:race.copy.innerHTML.includes('data-icon="check"'), title:race.copy.title, aria:race.copy.getAttribute('aria-label'), opacity:race.copy.style.opacity, }; const candidateDuringFeedback = reconcileToolRow('replacement-during-feedback', { html:'', color:'rgb(11, 12, 13)', title:'Replacement during feedback title', aria:'Replacement during feedback aria', opacity:'0.33', }); _refreshTransparentLiveRow(race.row, candidateDuringFeedback.row); race.card = race.row.querySelector('.tool-card'); race.header = race.row.querySelector('.tool-card-header'); race.copy = race.row.querySelector('.transparent-event-copy'); const inheritedDuringFeedback = { check:race.copy.innerHTML.includes('data-icon="check"'), title:race.copy.title, aria:race.copy.getAttribute('aria-label'), opacity:race.copy.style.opacity, }; clipboardMode = 'success'; race.copy.onclick(eventProbe()); await settle(); const latestRaceTimerId = race.row._transparentCopiedFeedback.timer; const latestRaceTimerDuration = timers.get(latestRaceTimerId).ms; staleRaceTimer(); const staleTimerDidNotClearLatest = race.copy.innerHTML.includes('data-icon="check"'); race.row.remove(); const detachedWrites = []; race.row._trackDetachedWrites = detachedWrites; race.copy._trackDetachedWrites = detachedWrites; let expiryThrew = false; try{ runTimer(latestRaceTimerId); }catch(_){ expiryThrew = true; } const detachedExpiry = { rowConnected:race.row.isConnected, controlConnected:race.copy.isConnected, feedbackState:!!race.row._transparentCopiedFeedback, writes:detachedWrites, threw:expiryThrew, html:race.copy.innerHTML, color:race.copy.style.color, title:race.copy.title, aria:race.copy.getAttribute('aria-label'), }; clipboardMode = 'deferred'; const thinkingRace = thinkingRow('rebound transparent reasoning'); document.body.appendChild(thinkingRace.row); _attachCopyButton(thinkingRace.header); thinkingRace.copy.onclick(eventProbe()); thinkingRace.copy = replaceCopyControl(thinkingRace.header, thinkingRace.copy, 'thinking-copy-btn', { html:'', color:'rgb(14, 15, 16)', title:'Thinking replacement title', aria:'Thinking replacement aria', opacity:'0.36', }); deferredClipboardResolve(); await settle(); const thinkingRaceCheck = { count:thinkingRace.header.querySelectorAll('.transparent-event-copy,.thinking-copy-btn').length, bound:typeof thinkingRace.copy.onclick === 'function' && typeof thinkingRace.copy.onkeydown === 'function', check:thinkingRace.copy.innerHTML.includes('data-icon="check"'), opacity:thinkingRace.copy.style.opacity, }; runTimer(thinkingRace.row._transparentCopiedFeedback.timer); // Real snapshot/restore lifecycle with two simultaneous feedback controls. const msgInner = element('div', 'msg-inner'); msgInner.setAttribute('id', 'msgInner'); const liveTurn = element('div', 'assistant-turn'); liveTurn.setAttribute('id', 'liveAssistantTurn'); liveTurn.setAttribute('data-session-id', 'snapshot-session'); const snapshotTool = toolRow(); const snapshotThinking = thinkingRow('snapshot reasoning'); liveTurn.appendChild(snapshotTool.row); liveTurn.appendChild(snapshotThinking.row); msgInner.appendChild(liveTurn); document.body.appendChild(msgInner); _attachCopyButton(snapshotTool.header); _attachCopyButton(snapshotThinking.header); snapshotTool.copy.innerHTML = ''; snapshotTool.copy.style.cssText = 'color: rgb(21, 22, 23); opacity: 0.28; border-width: 2px;'; snapshotTool.copy.setAttribute('title', 'Snapshot tool title'); snapshotTool.copy.setAttribute('aria-label', 'Snapshot tool aria'); snapshotThinking.copy.innerHTML = ''; snapshotThinking.copy.style.cssText = 'opacity: 0.42; padding-left: 3px;'; snapshotThinking.copy.removeAttribute('title'); snapshotThinking.copy.removeAttribute('aria-label'); const snapshotToolNormalStyle = snapshotTool.copy.style.cssText; const snapshotThinkingNormalStyle = snapshotThinking.copy.style.cssText; _showTransparentCopiedFeedback(snapshotTool.copy, snapshotTool.row); _showTransparentCopiedFeedback(snapshotThinking.copy, snapshotThinking.row); const snapshotToolTimer = snapshotTool.row._transparentCopiedFeedback.timer; const snapshotThinkingTimer = snapshotThinking.row._transparentCopiedFeedback.timer; INFLIGHT['snapshot-session'] = {}; snapshotLiveTurnHtmlForSession('snapshot-session'); const storedSnapshot = INFLIGHT['snapshot-session'].liveTurnHtml; const liveAfterSnapshot = { toolCheck:snapshotTool.copy.innerHTML.includes('data-icon="check"'), thinkingCheck:snapshotThinking.copy.innerHTML.includes('data-icon="check"'), toolTitle:snapshotTool.copy.getAttribute('title'), toolAria:snapshotTool.copy.getAttribute('aria-label'), toolOpacity:snapshotTool.copy.style.opacity, thinkingOpacity:snapshotThinking.copy.style.opacity, toolFeedback:!!snapshotTool.row._transparentCopiedFeedback, thinkingFeedback:!!snapshotThinking.row._transparentCopiedFeedback, toolNormalSaved:!!snapshotTool.copy._transparentCopiedFeedbackNormal, thinkingNormalSaved:!!snapshotThinking.copy._transparentCopiedFeedbackNormal, }; const restoreResult = restoreLiveTurnHtmlForSession('snapshot-session'); const restoredTurn = $('liveAssistantTurn'); const restoredControls = restoredTurn.querySelectorAll('.transparent-event-copy,.thinking-copy-btn'); const restoredSnapshotState = { count:restoredControls.length, toolHtml:restoredControls[0].innerHTML, toolStyle:restoredControls[0].style.cssText, toolTitle:restoredControls[0].getAttribute('title'), toolAria:restoredControls[0].getAttribute('aria-label'), thinkingHtml:restoredControls[1].innerHTML, thinkingStyle:restoredControls[1].style.cssText, thinkingTitle:restoredControls[1].getAttribute('title'), thinkingAria:restoredControls[1].getAttribute('aria-label'), }; const expiryDetachedWrites = []; snapshotTool.copy._trackDetachedWrites = expiryDetachedWrites; snapshotThinking.copy._trackDetachedWrites = expiryDetachedWrites; let snapshotExpiryThrew = false; try{ runTimer(snapshotToolTimer); runTimer(snapshotThinkingTimer); }catch(_){ snapshotExpiryThrew = true; } const afterSnapshotExpiry = { toolFeedback:!!snapshotTool.row._transparentCopiedFeedback, thinkingFeedback:!!snapshotThinking.row._transparentCopiedFeedback, detachedWrites:expiryDetachedWrites, threw:snapshotExpiryThrew, restoredStillNormal:restoredControls[0].innerHTML.includes('snapshot-tool-copy') && restoredControls[1].innerHTML.includes('snapshot-thinking-copy'), }; process.stdout.write(JSON.stringify({ firstState, original, restored, firstTimerReplaced:clearedTimers.includes(firstTimerId) && secondTimerId !== firstTimerId, staleTimerIgnored, payload:writes[0], thinkingState, thinkingOriginalStyle, thinkingRestoredStyle, fallbackSuccess, failure, emptyCopySkipped, replacementRace:{ activatedControlDetached, beforeResolutionCheck, replacementAfterSuccess, inheritedDuringFeedback, latestRaceTimerDuration, staleTimerDidNotClearLatest, detachedExpiry, oneFunctionalControl:race.header.querySelectorAll('.transparent-event-copy,.thinking-copy-btn').length === 1 && typeof race.copy.onclick === 'function', activationDidNotToggle:beforeSuccess.stopped && beforeSuccess.prevented && headerToggleCount === 0, }, thinkingReplacementRace:thinkingRaceCheck, snapshotLifecycle:{ storedSnapshot, snapshotToolNormalStyle, snapshotThinkingNormalStyle, liveAfterSnapshot, restoreResult, restoredSnapshotState, afterSnapshotExpiry, }, })); })().catch(error=>{ console.error(error); process.exit(1); }); """ data = _run_node_script(script, str(ROOT / "static" / "ui.js")) assert data["firstState"] == { "check": True, "title": "Copied", "aria": "Copied", "opacity": "1", "duration": 1500, "stopped": True, "prevented": True, "rowExpanded": "0", "cardOpen": False, } assert data["firstTimerReplaced"] is True assert data["staleTimerIgnored"] is True assert data["restored"] == data["original"] assert "tool: shell" in data["payload"] assert "[REDACTED]" in data["payload"] assert "secret-token" not in data["payload"] assert "tool output" in data["payload"] assert data["thinkingState"] == { "count": 1, "bound": True, "check": True, "opacity": "1", "copied": "transparent reasoning", } assert data["thinkingRestoredStyle"] == data["thinkingOriginalStyle"] assert data["fallbackSuccess"] == {"check": True, "toast": "Copied"} assert data["failure"] == { "check": False, "feedbackState": False, "toasts": ["Copy failed"], } assert data["emptyCopySkipped"] is True assert data["replacementRace"] == { "activatedControlDetached": True, "beforeResolutionCheck": False, "replacementAfterSuccess": { "check": True, "title": "Copied", "aria": "Copied", "opacity": "1", }, "inheritedDuringFeedback": { "check": True, "title": "Copied", "aria": "Copied", "opacity": "1", }, "latestRaceTimerDuration": 1500, "staleTimerDidNotClearLatest": True, "detachedExpiry": { "rowConnected": False, "controlConnected": False, "feedbackState": False, "writes": [], "threw": False, "html": '', "color": "var(--accent)", "title": "Copied", "aria": "Copied", }, "oneFunctionalControl": True, "activationDidNotToggle": True, } assert data["thinkingReplacementRace"] == { "count": 1, "bound": True, "check": True, "opacity": "1", } snapshot = data["snapshotLifecycle"] assert "snapshot-tool-copy" in snapshot["storedSnapshot"] assert "snapshot-thinking-copy" in snapshot["storedSnapshot"] assert 'title="Snapshot tool title"' in snapshot["storedSnapshot"] assert 'aria-label="Snapshot tool aria"' in snapshot["storedSnapshot"] assert "Copied" not in snapshot["storedSnapshot"] assert 'data-icon="check"' not in snapshot["storedSnapshot"] assert "var(--accent)" not in snapshot["storedSnapshot"] assert "opacity: 1" not in snapshot["storedSnapshot"] assert snapshot["liveAfterSnapshot"] == { "toolCheck": True, "thinkingCheck": True, "toolTitle": "Copied", "toolAria": "Copied", "toolOpacity": "1", "thinkingOpacity": "1", "toolFeedback": True, "thinkingFeedback": True, "toolNormalSaved": True, "thinkingNormalSaved": True, } assert snapshot["restoreResult"] is True assert snapshot["restoredSnapshotState"] == { "count": 2, "toolHtml": '', "toolStyle": snapshot["snapshotToolNormalStyle"], "toolTitle": "Snapshot tool title", "toolAria": "Snapshot tool aria", "thinkingHtml": '', "thinkingStyle": snapshot["snapshotThinkingNormalStyle"], "thinkingTitle": None, "thinkingAria": None, } assert snapshot["afterSnapshotExpiry"] == { "toolFeedback": False, "thinkingFeedback": False, "detachedWrites": [], "threw": False, "restoredStillNormal": True, }