/** Own-component synthetic fixture. run() supports jsdom; probe() records real rAF geometry * only when run in a real browser. Never reads history, host files, or a provider. */ import React, { useLayoutEffect, type ReactNode } from 'react' import { createRoot } from 'react-dom/client' import { flushSync } from 'react-dom' import { MarkdownImage, type MarkdownImageProps } from '../src/renderer/src/workbuddy/images/MarkdownImage' import MessageBubble from '../src/renderer/src/components/MessageBubble' import '../src/renderer/src/workbuddy/conversation/conversation.css' // Valid synthetic 320×180 PNG (all chunk CRCs checked); padding remains after IEND. const pixel = 'iVBORw0KGgoAAAANSUhEUgAAAUAAAAC0CAIAAABqhmJGAAACwUlEQVR4nO3OQQkAMQADsEqflEk5aeehnzIIREBy7gc8KvMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1zAdALfMBUMt8ANQyHwC1H3jo+KxCh14RAAAAAElFTkSuQmCC' const source = 'data:image/png;base64,' + btoa(atob(pixel) + '\0'.repeat(18000)) const nextSource = 'data:image/png;base64,' + btoa(atob(pixel) + '\0'.repeat(18001)) const container = document.getElementById('root')! const root = createRoot(container) const checks: string[] = [] const samples: object[] = [] const errors: string[] = [] const observer = new MutationObserver(records => { for (const record of records) for (const node of record.addedNodes) { if (node.textContent?.includes('base64,')) errors.push('URI text inserted into DOM') } }) observer.observe(container,{subtree:true,childList:true,characterData:true}) window.addEventListener('error', event => errors.push(event.message)) window.addEventListener('unhandledrejection', event => errors.push(String(event.reason))) const check = (value: unknown, label: string) => { if (!value) throw Error(label); checks.push(label) } const pause = () => new Promise(resolve => setTimeout(resolve, 0)) const settle = async () => { await pause(); await pause() } function sample(label: string) { const text = container.textContent || '' const images = [...container.querySelectorAll('img.markdown-content__image')] const region = container.querySelector('.cr-collapse__content') || container check(!text.includes('data:') && !text.includes('base64,') && !text.includes('AAAA'.repeat(16)), label + ': no URI text') const value = { label, textLength: text.length, imageCount: images.length, height: region.getBoundingClientRect().height, imageHeight: images[0]?.getBoundingClientRect().height ?? 0 } samples.push(value) return value } function CommitProbe({ children, label }: { children: ReactNode; label: string }) { useLayoutEffect(() => { sample(label + ': layout-before-passive-effects') }) return <>{children} } function mount(node: ReactNode, label: string) { flushSync(() => root.render({node})) sample(label + ': sync-commit') } const img = () => container.querySelector('img.markdown-content__image') const concise = (label: string) => { check(!img(), label + ': no image'); check((container.textContent || '').length < 160, label + ': concise'); sample(label) } const renderImage = (props: Partial & {src: string}, label: string) => mount(, label) function deferred() { let resolve!: (s: string | undefined) => void; let reject!: (e: Error) => void; const promise = new Promise((a,b) => {resolve=a;reject=b}); return {promise,resolve,reject} } function bubble(session: string) { return } async function frames(label: string, count = 8) { sample(label + ': before-rAF') for (let i = 0; i < count; i++) await new Promise(resolve => requestAnimationFrame(() => {sample(label + ': frame-' + i);resolve()})) } async function folds(withFrames: boolean) { for (const session of ['synthetic-A','synthetic-B','synthetic-A']) { mount(bubble(session),session); await settle() for (let cycle=0;cycle<3;cycle++) { const before = img() const button = container.querySelector('.cr-collapse--completed > .cr-collapse__header') check(button, 'actual CompletedFold header exists') sample(session + ': before-toggle-' + cycle) flushSync(() => button!.click()) sample(session + ': toggle-' + cycle) await settle() check(img()?.getAttribute('src') === source, 'actual MessageBubble preserves image') check(img() !== before, 'actual CompletedFold remounts image across toggle') if (withFrames) await frames(session + ': toggle-' + cycle) } } } async function run() { renderImage({src:source},'cold'); check(img()?.getAttribute('src') === source,'safe first commit image'); check((samples[0] as {imageCount:number}).imageCount===1,'layout probe sees image before passive effects'); await settle(); sample('stable') img()!.dispatchEvent(new Event('error')); await settle(); concise('native image error') renderImage({src:nextSource},'error recovery new src'); check(img()?.getAttribute('src') === nextSource,'new safe src resets prior error synchronously') for (const src of ['javascript:alert(1)','data:text/html,'+'A'.repeat(24000),'/synthetic/no-host.png']) { renderImage({src},'invalid/no-host'); concise('invalid/no-host initial'); await settle(); concise('invalid/no-host settled') } const sameSrc='/synthetic/same.png', firstResolver=deferred(), secondResolver=deferred() renderImage({src:sameSrc,imageUrlResolver:()=>firstResolver.promise},'same source first resolver');await settle() renderImage({src:sameSrc,imageUrlResolver:()=>secondResolver.promise},'same source second resolver');await settle() firstResolver.resolve(source);await settle();concise('old resolver identity ignored') secondResolver.resolve(nextSource);await settle();check(img()?.getAttribute('src')===nextSource,'new resolver identity wins') const local='file:///synthetic/only.png', host=deferred(), hostResolver=()=>host.promise renderImage({src:local,imageUrlResolver:hostResolver},'local direct');check(img()?.getAttribute('src')===local,'existing unpreferred local semantics') renderImage({src:local,imageUrlResolver:hostResolver,preferHostResolution:true},'prefer host enabled');concise('host preference removes direct file before effects');await settle() host.resolve(source);await settle();check(img()?.getAttribute('src')===source,'preferred host result') renderImage({src:source,imageUrlResolver:async()=>undefined},'same direct first adapter');img()!.dispatchEvent(new Event('error'));await settle();concise('direct failed before new adapter') renderImage({src:source,imageUrlResolver:async()=>undefined},'same direct new adapter');img()!.dispatchEvent(new Event('error'));await settle();concise('immediate error after adapter reset retained') renderImage({src:nextSource},'A to B');renderImage({src:source},'B to A retry');check(img()?.getAttribute('src')===source,'A to B to A resets old failure') const old = deferred(), latest = deferred(); const resolved: string[] = [] const resolver = (src: string) => src.endsWith('old.png') ? old.promise : latest.promise const onResolve = (src: string) => {resolved.push(src)} renderImage({src:'/synthetic/old.png',imageUrlResolver:resolver,onResolve},'pending old');await settle() renderImage({src:'/synthetic/new.png',imageUrlResolver:resolver,onResolve},'pending new');concise('no old during new');await settle() old.resolve(source);await settle();concise('stale success ignored');check(!resolved.length,'stale success has no onResolve') latest.resolve(nextSource);await settle();check(img()?.getAttribute('src')===nextSource,'latest result wins');check(resolved.length===1&&resolved[0].endsWith('new.png'),'latest onResolve only') const failure=deferred();const failResolver=()=>failure.promise renderImage({src:'/synthetic/fail.png',imageUrlResolver:failResolver},'pending failure clears previous');concise('old resolved image removed synchronously');await settle() renderImage({src:source},'safe switch while rejection pending');failure.reject(Error('synthetic failure'));await settle();check(img()?.getAttribute('src')===source,'stale rejection cannot fail new safe image') renderImage({src:'/synthetic/reject.png',imageUrlResolver:async()=>{throw Error('synthetic')}},'active rejection');await settle();concise('active rejection');check(container.querySelector('[aria-busy="false"]'),'failure not busy') renderImage({src:'/synthetic/unsafe.png',imageUrlResolver:async()=> 'javascript:alert(1)'},'unsafe resolver result');await settle();concise('unsafe resolver rejected') const abandoned=deferred();let abandonedResolve=0 renderImage({src:'/synthetic/unmount.png',imageUrlResolver:()=>abandoned.promise,onResolve:()=>abandonedResolve++},'unmount pending');await settle();mount(empty,'unmounted');abandoned.resolve(source);await settle();check(abandonedResolve===0,'unmounted completion ignored') let preview: unknown; renderImage({src:source,urlList:[source],onPreviewImage:r=>{preview=r}},'preview');img()!.click();check(JSON.stringify(preview)===JSON.stringify({urlList:[source],initialIndex:0}),'safe image preview click retained') await folds(false);check(!errors.length,'no renderer errors') return {checks,samples,errors,limits:'DOM lifecycle only; jsdom heights are not layout acceptance. Synthetic data only.'} } async function probe() { await folds(true);check(img()?.complete && img()?.naturalWidth === 320,'synthetic PNG decoded in real browser');check(samples.some(s => (s as {imageHeight:number}).imageHeight > 0),'native frames include nonzero image geometry');check(samples.every(s => (s as {height:number}).height < 2000),'native region has no thousands-pixel address spike');check(!errors.length,'no renderer errors');return {checks,samples,errors,limits:'Real geometry only if executed in a real browser with fixture CSS. No user data.'} } ;(window as any).__b04 = {run,probe}