fix(B01): retain native card state and restore mode and confirmation styles

This commit is contained in:
Codex committed 2026-10-21 13:41:18 +09:00
1 parent ddd677bae3
commit 1ed71b63b5
14 files changed
+407 -37

No files matched your search

+25 -2
View File
@@ -66,6 +66,27 @@ if (!process.versions.electron) {
const capture = await main.webContents.debugger.sendCommand('Page.captureScreenshot', { format: 'png', captureBeyondViewport: false })
await fs.writeFile(path.join(directory, name + '.png'), Buffer.from(capture.data, 'base64'))
}
// Assert styles in the real body portal, not just variant class names.
async function checkConfirmationStyles(kind) {
const previous = await read('document.documentElement.dataset.theme || "light"')
const setTheme = async theme => {
await read(`(()=>{localStorage.setItem('ycode:theme',${JSON.stringify(theme)});window.dispatchEvent(new StorageEvent('storage',{key:'ycode:theme',newValue:${JSON.stringify(theme)}}))})()`)
await wait(`document.documentElement.dataset.theme === ${JSON.stringify(theme)}`, 'application theme synchronization')
await pause(400)
}
for (const theme of ['light', 'dark']) {
await setTheme(theme)
const values = await read(`(()=>{const style=name=>{const s=getComputedStyle(document.querySelector('.task-popconfirm__'+name));return {background:s.backgroundColor,color:s.color,border:[s.borderTopWidth,s.borderTopStyle,s.borderTopColor],geometry:[s.minWidth,s.paddingLeft,s.paddingRight,s.height,s.borderRadius,s.fontSize]}};return {cancel:style('cancel'),confirm:style('confirm'),titleWeight:getComputedStyle(document.querySelector('.task-popconfirm__title')).fontWeight}})()`)
const dark = theme === 'dark'
check(kind+' '+theme+' secondary background/border', [values.cancel.background, values.cancel.border], [dark ? 'rgb(38, 38, 38)' : 'rgb(255, 255, 255)', ['1px', 'solid', dark ? 'rgb(61, 61, 61)' : 'rgb(222, 222, 222)']])
check(kind+' '+theme+' primary/danger background', values.confirm.background, kind === 'delete' ? (dark ? 'rgb(175, 25, 26)' : 'rgb(232, 92, 92)') : (dark ? 'rgba(255, 255, 255, 0.9)' : 'rgba(0, 0, 0, 0.9)'))
check(kind+' '+theme+' confirmation foreground', values.confirm.color, kind === 'archive' && dark ? 'rgba(0, 0, 0, 0.9)' : 'rgb(255, 255, 255)')
for (const action of ['confirm', 'cancel']) check(kind+' '+theme+' '+action+' geometry', values[action].geometry, ['66px', '19px', '19px', '28px', '6px', '12px'])
check(kind+' '+theme+' title weight', values.titleWeight, '600')
await shot(kind+'-confirm-'+theme)
}
await setTheme(previous)
}
// 真实用户路径:先悬停行让操作按钮显形,再点击按钮。
const openMenu = `(()=>{const row=document.querySelector('[data-session-id]');if(!row)return null;row.classList.add('sidebar__item--menu-open');const button=row.querySelector('.agent-card-more-button');if(!button)return null;button.click();return true})()`
const menuSource = `(()=>{const menu=document.querySelector('.conversation-context-menu');if(!menu)return null;const style=getComputedStyle(menu);const rect=menu.getBoundingClientRect();return {rect:[rect.x,rect.y,rect.width,rect.height],background:style.backgroundColor,borderRadius:style.borderRadius,padding:style.padding,items:[...menu.querySelectorAll('.cb-context-menu-item')].map(button=>{const box=button.getBoundingClientRect();const icon=button.querySelector('.cb-context-menu-item-icon svg');const label=button.querySelector('.cb-context-menu-item-label');const itemStyle=getComputedStyle(button);return {label:label?label.textContent:'',width:box.width,height:box.height,fontSize:itemStyle.fontSize,lineHeight:itemStyle.lineHeight,borderRadius:itemStyle.borderRadius,color:itemStyle.color,icon:icon?[icon.getBoundingClientRect().width,icon.getBoundingClientRect().height]:null}}),dividers:[...menu.querySelectorAll('.cb-context-menu-divider')].map(divider=>{const box=divider.getBoundingClientRect();const dividerStyle=getComputedStyle(divider);return {width:box.width,height:box.height,margin:dividerStyle.margin}})}})()`
@@ -167,9 +188,10 @@ if (!process.versions.electron) {
const archiveConfirm = await read(`(()=>{const dialog=document.querySelector('.task-popconfirm');const box=node=>{const r=node.getBoundingClientRect();return [r.x,r.y,r.width,r.height]};const style=getComputedStyle(dialog);return {box:box(dialog),title:dialog.querySelector('.task-popconfirm__title')?.textContent,desc:dialog.querySelector('.task-popconfirm__description')?.textContent,cancel:dialog.querySelector('.task-popconfirm__cancel')?.textContent,confirm:dialog.querySelector('.task-popconfirm__confirm')?.textContent,padding:style.padding,borderRadius:style.borderRadius}})()`)
check('archive opens a confirmation before changing the task', archiveConfirm.title, '归档任务')
check('archive restores the documented original destination', archiveConfirm.desc, '确认将该任务归档吗?归档后可在“系统设置-数据管理”中查看。')
check('archive explains the unavailable destination and working fallback', await read(`document.querySelector('.task-popconfirm__compatibility').textContent.includes('侧栏“已归档”')`), true)
check('archive does not append unrequested compatibility copy', await read(`!!document.querySelector('.task-popconfirm__compatibility')`), false)
check('archive confirmation actions use the recovered labels', [archiveConfirm.cancel, archiveConfirm.confirm], ['取消', '确认归档'])
check('archive confirmation uses the plan-defined 240px popconfirm', [Math.round(archiveConfirm.box[2]), archiveConfirm.padding, archiveConfirm.borderRadius], [240, '16px', '12px'])
check('archive confirmation uses the plan-defined 240px popconfirm', [Math.round(archiveConfirm.box[2]), archiveConfirm.padding, archiveConfirm.borderRadius], [240, '16px', '16px'])
await checkConfirmationStyles('archive')
await shot('archive-confirm')
await read(`document.querySelector('.task-popconfirm__cancel').click()`)
await wait(`!document.querySelector('.task-popconfirm')`, 'archive cancellation closes')
@@ -195,6 +217,7 @@ if (!process.versions.electron) {
check('delete uses the idle confirmation description', deleteConfirm.desc, '删除后,这些任务将从你的所有设备中移除,且无法恢复。')
check('delete confirmation actions use the recovered idle labels', [deleteConfirm.cancel, deleteConfirm.confirm], ['取消', '确认'])
check('idle delete is not marked as running', deleteConfirm.running, null)
await checkConfirmationStyles('delete')
await shot('delete-confirm')
await read(`document.querySelector('.task-popconfirm__confirm').click()`)
await wait(`window.ycode.session.listArchived().then(list=>!list.some(item=>item.id===${JSON.stringify(remainingId)}))`, 'delete completes after confirmation')