chore: establish YCode replication baseline and TODO

This commit is contained in:
xiaohan.yuan committed 2026-10-17 02:43:49 +08:00
commit 6dc59f89ba
1118 files changed
+196002

No files matched your search

+166
View File
@@ -0,0 +1,166 @@
// Production isolated native Electron windows, real preload/IPC, no messages or upstream calls.
// WorkBuddy reference is a fixed REA PNG; debugger operations below target YCode only.
const fs = require('node:fs/promises')
const path = require('node:path')
const assert = require('node:assert/strict')
const { createHash } = require('node:crypto')
const hash = bytes => createHash('sha256').update(bytes).digest('hex')
const referencePath = path.resolve('.tmp/rea-home-static-20261016/workbuddy.png')
const referenceSha256 = '360b076811ef19381f0670c314b24b06252310bf24fdc245f529f4894cfd7241'
const assetPath = path.resolve('src/renderer/src/components/source-home-assets/workbuddy-buddy-thumbnail-2713ed5b.png')
const assetSha256 = '2713ed5bbf1288d56ebeaca49bfb0d622c967914ab9c98955cc995932049e7e8'
if (!process.versions.electron) {
;(async () => {
assert.equal(hash(await fs.readFile(referencePath)), referenceSha256, 'REA reference PNG changed')
assert.equal(hash(await fs.readFile(assetPath)), assetSha256, 'REA robot asset changed')
const directory = await fs.mkdtemp(path.resolve('.tmp/desktop-appearance-'))
await Promise.all(['data', 'home'].map(name => fs.mkdir(path.join(directory, name))))
const env = { ...process.env, YCODE_QA_DIRECTORY: directory, YCODE_USER_DATA_DIR: path.join(directory, 'data'), YCODE_OMP_HOME: path.join(directory, 'home'), YCODE_QA_OFFSCREEN: '0' }
delete env.ELECTRON_RUN_AS_NODE; delete env.ELECTRON_RENDERER_URL; delete env.YCODE_DISABLE_GPU; delete env.YCODE_DISABLE_GPU_SANDBOX
const child = require('node:child_process').spawn(require('electron'), ['--no-sandbox', '--disable-gpu-sandbox', '--use-angle=swiftshader', '--disable-gpu-compositing', __filename],
{ env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] })
let output = ''
child.stdout.on('data', bytes => { output += bytes; if (bytes.toString().includes('APPEARANCE_')) process.stdout.write(bytes) })
child.stderr.on('data', bytes => { output += bytes })
const timer = setTimeout(() => child.kill(), 90000)
child.once('exit', async code => {
clearTimeout(timer); await fs.writeFile(path.join(directory, 'process.log'), output)
console.log(JSON.stringify({ directory, exitCode: code })); if (code) console.error(output.slice(-6000)); process.exitCode = code ?? 1
})
})().catch(error => { console.error(error); process.exitCode = 1 })
} else {
const { app, BrowserWindow, nativeImage } = require('electron')
// Exactly the established visible-swiftshader capture mode; no --disable-gpu request.
app.disableHardwareAcceleration()
app.commandLine.appendSwitch('use-angle', 'swiftshader'); app.commandLine.appendSwitch('disable-gpu-compositing')
const { pathToFileURL } = require('node:url')
const directory = process.env.YCODE_QA_DIRECTORY, data = path.join(directory, 'data'), home = path.join(directory, 'home')
const relative = path.relative(path.resolve('.tmp'), directory)
assert.ok(relative.startsWith('desktop-appearance-') && !relative.includes('..') && !path.isAbsolute(relative))
app.setPath('userData', data); app.setPath('sessionData', data); app.setPath('home', home)
let main, settings, phase = 'boot'
const checks = [], themes = [], rendererErrors = [], screenshots = []
const pause = ms => new Promise(resolve => setTimeout(resolve, ms))
async function read(source, win = main) { let timer; try { return await Promise.race([win.webContents.executeJavaScript(source), new Promise((_, reject) => { timer = setTimeout(() => reject(Error('Renderer timeout')), 12000) })]) } catch(error) { throw Error(error.message + ' — ' + source.slice(0, 180)) } finally { clearTimeout(timer) } }
async function wait(source, label, win = main) { for (let i = 0; i < 250; i++) { const value = await read(source, win); if (value) return value; await pause(25) } throw Error('Timeout: ' + label) }
async function step(label) { phase = label; await fs.writeFile(path.join(directory, 'phase.txt'), label) }
function check(label, actual, expected) { const pass = typeof expected === 'function' ? expected(actual) : JSON.stringify(actual) === JSON.stringify(expected); checks.push({ label, pass, actual, expected: typeof expected === 'function' ? expected.toString() : expected }) }
async function click(selector, win = main) { await read(`(()=>{const n=document.querySelector(${JSON.stringify(selector)});if(!n)throw Error('Missing clickable');n.click()})()`, win); await pause(60) }
async function clickText(selector, text, win = main) { await read(`(()=>{const n=[...document.querySelectorAll(${JSON.stringify(selector)})].find(n=>n.textContent.trim()===${JSON.stringify(text)});if(!n)throw Error('Missing text button');n.click()})()`, win); await pause(60) }
function observe(win) { win.webContents.on('console-message', event => { if (event.level === 'error' || event.level >= 3) rendererErrors.push({ windowId: win.id, message: event.message }) }) }
async function shot(name, win = main) {
if (!win.webContents.debugger.isAttached()) win.webContents.debugger.attach('1.3')
win.showInactive(); win.webContents.setBackgroundThrottling(false)
await read('document.fonts.ready.then(()=>true)', win); await pause(150)
const image = await win.webContents.debugger.sendCommand('Page.captureScreenshot', { format: 'png', fromSurface: true, captureBeyondViewport: false })
const file = path.join(directory, name + '.png'), bytes = Buffer.from(image.data, 'base64')
await fs.writeFile(file, bytes); screenshots.push({ file, sha256: hash(bytes), visible: win.isVisible(), method: 'Page.captureScreenshot({fromSurface:true})' }); return bytes
}
async function openSettings(section) {
await read(`window.ycode.app.openSettings(${JSON.stringify(section)})`)
for (let i = 0; i < 150; i++) { settings = BrowserWindow.getAllWindows().find(win => win !== main); if (settings && !settings.webContents.isLoading()) break; await pause(25) }
assert.ok(settings); await wait('!!document.querySelector(".settings-window-root")', 'real native settings window', settings); return settings
}
async function themeSnapshot(label) {
const value = { label, main: await read('[document.documentElement.dataset.theme,localStorage.getItem("ycode:theme")]'),
settings: await read('[document.documentElement.dataset.theme,localStorage.getItem("ycode:theme")]', settings), settingsWindowId: settings.id }
themes.push(value); return value
}
function compareRobot(sourceBytes, ownBytes) {
const roi = { x: 1085, y: 286, width: 120, height: 78 }
const source = nativeImage.createFromBuffer(sourceBytes), actual = nativeImage.createFromBuffer(ownBytes)
assert.deepEqual(source.getSize(), { width: 1336, height: 901 }); assert.deepEqual(actual.getSize(), source.getSize())
const a = source.crop(roi).toBitmap({ scaleFactor: 1 }), b = actual.crop(roi).toBitmap({ scaleFactor: 1 }), delta = Buffer.alloc(a.length)
let changedPixels = 0, maxChannelDifference = 0, totalChannelDifference = 0
const histogram = {}
for (let offset = 0; offset < a.length; offset += 4) {
let pixelDelta = 0
for (let channel = 0; channel < 4; channel++) { const difference = Math.abs(a[offset + channel] - b[offset + channel]); pixelDelta = Math.max(pixelDelta, difference); maxChannelDifference = Math.max(maxChannelDifference, difference); totalChannelDifference += difference; if (channel < 3) delta[offset + channel] = Math.min(255, difference * 12) }
delta[offset + 3] = 255
if (pixelDelta) changedPixels++
histogram[pixelDelta] = (histogram[pixelDelta] || 0) + 1
}
return { report: { roi: [1085, 286, 1205, 364], totalPixels: 9360, changedPixels, maxChannelDifference, totalChannelDifference,
pixelMaximumDifferenceHistogram: histogram, exact: changedPixels === 0, tolerance: 0, excludedPixels: 0 }, sourceCrop: source.crop(roi).toPNG(), actualCrop: actual.crop(roi).toPNG(), diff: nativeImage.createFromBitmap(delta, { width: 120, height: 78 }).toPNG() }
}
;(async () => {
await import(pathToFileURL(path.resolve('out/main/index.mjs')).href); await app.whenReady()
for (let i = 0; i < 200; i++) { main = BrowserWindow.getAllWindows().find(win => win.getTitle() === 'YCode'); if (main && !main.webContents.isLoading()) break; await pause(25) }
assert.ok(main); observe(main); await wait('!!window.ycode&&!!document.querySelector(".source-home")', 'production homepage')
assert.equal(path.resolve((await read('window.ycode.app.info()')).configDir), data)
check('fresh isolated App has no messages/sessions', await read('window.ycode.session.list().then(s=>s.length)'), 0)
await step('ui-dark-theme-and-reused-settings-navigation')
await openSettings('appearance'); observe(settings)
await click('[data-settings-tab="appearance"]', settings)
await clickText('.segmented__item', '深色', settings)
await wait('document.documentElement.dataset.theme==="dark"', 'main receives dark appearance')
let theme = await themeSnapshot('dark-selected-in-native-settings')
check('native UI dark changes both renderers and persistent shared storage', [theme.main, theme.settings], [['dark', 'dark'], ['dark', 'dark']])
await shot('main-dark'); await shot('settings-dark', settings)
const previous = settings.id
await openSettings('model'); await wait('!!document.querySelector(".models-settings-panel")', 'reused settings navigates to actual models panel', settings)
check('openSettings(model) reuses existing native window', settings.id, previous)
check('reused settings actually navigated away from appearance', await read('!!document.querySelector(".models-settings-panel")&&!document.querySelector(".segmented")', settings), true)
await click('.settings-window-close', settings)
for (let i = 0; i < 150 && !settings.isDestroyed(); i++) await pause(20)
check('settings close destroys actual native window', settings.isDestroyed(), true)
await openSettings('appearance'); observe(settings)
check('reopened settings uses a fresh native window', settings.id !== previous, true)
theme = await themeSnapshot('reopened-settings-persists-dark')
check('native settings reopen preserves shared dark appearance', [theme.main, theme.settings], [['dark', 'dark'], ['dark', 'dark']])
await clickText('.segmented__item', '浅色', settings)
await wait('document.documentElement.dataset.theme==="light"', 'main receives light appearance')
theme = await themeSnapshot('light-restored-in-native-settings')
check('native UI light restores both renderers and storage', [theme.main, theme.settings], [['light', 'light'], ['light', 'light']])
await click('.settings-window-close', settings)
await step('source-software-gpu-home-calibration')
await read('localStorage.setItem("ycode:sidebar-width","284.25");localStorage.setItem("ycode:sidebar-collapsed","0")')
main.webContents.reload(); await wait('!!document.querySelector(".source-home")', 'home after source sidebar state reload')
if (!main.webContents.debugger.isAttached()) main.webContents.debugger.attach('1.3')
await main.webContents.debugger.sendCommand('Emulation.setDeviceMetricsOverride', { width: 1336, height: 901, deviceScaleFactor: 1, mobile: false })
main.webContents.setBackgroundThrottling(false); main.showInactive()
await wait('innerWidth===1336&&innerHeight===901', 'source-sized viewport'); await pause(300)
await click('.sidebar__tab[title="新建任务"]')
await read('document.querySelector(".wb-composer__input").focus()')
await read('Promise.all([...document.images].map(img=>img.decode().catch(()=>undefined))).then(()=>true)'); await pause(250)
const layout = await read(`(()=>{const selectors=['.sidebar','.source-home','.wb-home-header__title','.wb-home-composer','.quick-actions','.wb-home-route__growth-buddy','.source-home-robot img','.cr-input-container'];return {viewport:[innerWidth,innerHeight],devicePixelRatio,theme:document.documentElement.dataset.theme,focused:document.activeElement?.className,nodes:selectors.map(selector=>{const n=document.querySelector(selector);if(!n)return {selector,missing:true};const r=n.getBoundingClientRect(),s=getComputedStyle(n);return {selector,bounds:[r.x,r.y,r.width,r.height],fontFamily:s.fontFamily,fontSize:s.fontSize,transform:s.transform,background:s.backgroundColor,objectFit:s.objectFit,border:s.border,shadow:s.boxShadow,image:n instanceof HTMLImageElement?{src:n.currentSrc,naturalWidth:n.naturalWidth,naturalHeight:n.naturalHeight}:undefined}})}})()`)
const composerStructure = await read(`(()=>{const surface=document.querySelector('.wb-home-route__input-box'),main=surface?.querySelector(':scope > .cr-input-box__main'),footer=surface?.querySelector(':scope > .wb-composer__workspace-footer'),input=main?.querySelector('.cr-input-container'),mainBox=main?.getBoundingClientRect(),footerBox=footer?.getBoundingClientRect(),inputBox=input?.getBoundingClientRect();return {footerIsDirectChild:footer?.parentElement===surface,surfaceGap:surface?getComputedStyle(surface).gap:undefined,surfaceBackground:surface?getComputedStyle(surface).backgroundImage:undefined,mainBackground:main?getComputedStyle(main).backgroundColor:undefined,footerBackground:footer?getComputedStyle(footer).backgroundColor:undefined,inputRadius:input?getComputedStyle(input).borderRadius:undefined,inputBounds:inputBox?{x:inputBox.x,right:inputBox.right,bottom:inputBox.bottom}:undefined,footerGap:mainBox&&footerBox?footerBox.top-mainBox.bottom:undefined,mainWidth:mainBox?.width,footerWidth:footerBox?.width}})()`)
check('calibrated viewport and source sidebar width', [layout.viewport, layout.nodes.find(node => node.selector === '.sidebar')?.bounds[2]], [[1336, 901], 284.25])
check('focused real home composer', layout.focused?.includes('wb-composer__input'), true)
check('home footer is nested in the input card with no flex gap', [composerStructure.footerIsDirectChild, composerStructure.surfaceGap], [true, '0px'])
check('home input editor touches the footer across the full width', [composerStructure.footerGap, composerStructure.mainWidth, composerStructure.footerWidth], [0, 800, 800])
check('home white editor retains all four rounded corners', composerStructure.inputRadius, '24px')
check('home main and footer share the outer gradient backdrop', [composerStructure.surfaceBackground?.startsWith('linear-gradient('), composerStructure.mainBackground, composerStructure.footerBackground], [true, 'rgba(0, 0, 0, 0)', 'rgba(0, 0, 0, 0)'])
check('calibrated robot source wrapper geometry', layout.nodes.find(node => node.selector === '.wb-home-route__growth-buddy')?.bounds, [1085.125, 286.5, 120, 120])
const gpu = { mode: 'visible-swiftshader', electronVersion: process.versions.electron, chromiumVersion: process.versions.chrome,
hardwareAccelerationDisabledEarly: true, offscreen: false, windowVisible: main.isVisible(), useAngle: app.commandLine.getSwitchValue('use-angle'),
disableGpuCompositing: app.commandLine.hasSwitch('disable-gpu-compositing'), disableGpuSwitchPresent: app.commandLine.hasSwitch('disable-gpu'),
captureMethod: 'Page.captureScreenshot({format:png,fromSurface:true,captureBeyondViewport:false})', featureStatus: app.getGPUFeatureStatus(),
info: await Promise.race([app.getGPUInfo('complete'), pause(4000).then(() => ({ unavailable: 'timeout' }))]) }
const home = await shot('home-focused-swiftshader')
const homeImage = nativeImage.createFromBuffer(home), homeBitmap = homeImage.toBitmap(), homeWidth = homeImage.getSize().width
const cornerBounds = composerStructure.inputBounds
const cornerSamples = [Math.ceil(cornerBounds.x) + 2, Math.floor(cornerBounds.right) - 3].map(x => {
const y = Math.floor(cornerBounds.bottom) - 4, offset = (y * homeWidth + x) * 4
return { x, y, rgb: [homeBitmap[offset + 2], homeBitmap[offset + 1], homeBitmap[offset]] }
})
check('both lower editor corner cutouts show the gray backdrop instead of the white page', cornerSamples, samples => samples.every(sample => sample.rgb.every(channel => channel >= 200 && channel < 253)))
const sourceBytes = await fs.readFile(referencePath); assert.equal(hash(sourceBytes), referenceSha256)
const comparison = compareRobot(sourceBytes, home)
await Promise.all([fs.writeFile(path.join(directory, 'robot-source.png'), comparison.sourceCrop), fs.writeFile(path.join(directory, 'robot-ycode.png'), comparison.actualCrop), fs.writeFile(path.join(directory, 'robot-diff-amplified12.png'), comparison.diff)])
check('robot static ROI exact pixels at zero tolerance', comparison.report.changedPixels, 0)
check('desktop UI generated no sessions/messages', await read('window.ycode.session.list().then(s=>s.length)'), 0)
check('no renderer errors', rendererErrors, [])
const report = { result: checks.every(item => item.pass) ? 'PASS' : 'FAIL', scope: 'Native production App appearance synchronization/navigation and one focused static robot region. No message sends, fake IPC, hover/video/full-UI parity claim.',
evidence: { reference: { path: referencePath, evidenceId: 'ev_4fd56b4f30ca87b4d7012c80195becea384ffa95582b059558a16b8d871d57a8', sha256: referenceSha256 },
sourceAsset: { path: assetPath, evidenceId: 'ev_474129797ce3ab73eaaab2cd41247653e7f128c3b89b598158b57b74e1f17e88', sha256: assetSha256 } }, checks, themes, gpu, layout, screenshots, robotComparison: comparison.report, rendererErrors }
await fs.writeFile(path.join(directory, 'report.json'), JSON.stringify(report, null, 2))
console.log('APPEARANCE_INTEGRATION_' + report.result + ' ' + directory)
console.log('APPEARANCE_ROBOT ' + JSON.stringify(comparison.report))
for (const item of checks.filter(check => !check.pass)) console.log('APPEARANCE_FAILURE ' + JSON.stringify(item))
app.exit(report.result === 'PASS' ? 0 : 1)
})().catch(async error => {
await fs.writeFile(path.join(directory, 'failure.json'), JSON.stringify({ phase, error: String(error), stack: error.stack, checks, themes, rendererErrors }, null, 2))
console.error(error); if (main && !main.isDestroyed()) await shot('failure').catch(() => undefined); app.exit(1)
})
}