feat: complete WorkBuddy parity and Windows release workflow

This commit is contained in:
xiaohan.yuan committed 2026-10-18 12:45:21 +08:00
1 parent 143516b3a8
commit d5512df549
434 files changed
+54977 -1854

No files matched your search

+28 -9
View File
@@ -14,6 +14,7 @@ const assert = require('node:assert/strict')
const EXPECTED_TABS = ['settings', 'account', 'subscriptionUsage', 'appearance', 'keyboardShortcuts', 'personalization',
'memory', 'extensions', 'models', 'securityCenter', 'helpFeedback', 'getHelp']
const REMOVED_TABS = ['claw', 'deviceConnections', 'dataManagement', 'buddyApps']
const HELP_FEEDBACK_ICON_PATH = 'M7.1 1.2Q9.1261 1.2 10.7207 2.4413L11.4578 1.4943Q9.5381 0 7.1 0Q4.1591 0 2.0796 2.0796Q0 4.1591 0 7.1Q0 10.0409 2.0796 12.1205Q4.1591 14.2 7.1 14.2Q10.0409 14.2 12.1205 12.1205Q14.2 10.0409 14.2 7.1Q14.2 4.8675 12.9272 3.0426L11.9429 3.7291Q13 5.2447 13 7.1Q13 9.5439 11.2719 11.2719Q9.5439 13 7.1 13Q4.6561 13 2.9281 11.2719Q1.2 9.5439 1.2 7.1Q1.2 4.6561 2.9281 2.9281Q4.6561 1.2 7.1 1.2ZM7.6986 3.1008L7.6986 4.3008L6.4986 4.3008L6.4986 3.1008L7.6986 3.1008ZM6.4986 5.6011L6.4986 11.1011L7.6986 11.1011L7.6986 5.6011L6.4986 5.6011Z'
if (!process.versions.electron) {
;(async () => {
@@ -86,7 +87,11 @@ if (!process.versions.electron) {
groups:[...document.querySelectorAll('.settings-navigation__group-title')].map(node=>node.textContent.trim()),
dividers:document.querySelectorAll('.settings-navigation__group-divider').length,
nav:[nav?Math.round(nav.getBoundingClientRect().width):0,nav?nav.clientWidth:0,nav?nav.scrollHeight-nav.clientHeight:0],
geometry:items.map(node=>{const r=node.getBoundingClientRect();return [Math.round(r.x),Math.round(r.width),Math.round(r.height)]})}})()`
geometry:items.map(node=>{const r=node.getBoundingClientRect();return [Math.round(r.x),Math.round(r.width),Math.round(r.height)]}),
icons:items.map(node=>{const svg=node.querySelector('.settings-navigation__icon svg'),path=svg?.querySelector('path');
const r=svg?.getBoundingClientRect();return {svg:!!svg,width:r?.width??0,height:r?.height??0,viewBox:svg?.getAttribute('viewBox')??'',
fill:svg?.getAttribute('fill')??'',d:path?.getAttribute('d')??'',pathFill:path?.getAttribute('fill')??'',
transform:path?.getAttribute('transform')??''}})}})()`
;(async () => {
// 导入前不做任何 await,避免 Electron 先进入 ready(主进程 bundle 顶层会调用 disableHardwareAcceleration)。
@@ -112,6 +117,12 @@ if (!process.versions.electron) {
const nav = await read(navSource, settings)
snapshots.nav = nav
check('settings nav keeps the 12 remaining entries in order', nav.tabs, EXPECTED_TABS)
const helpFeedbackIcon = nav.icons[nav.tabs.indexOf('helpFeedback')]
check('about navigation keeps the fixed 5.8.3 info-circle icon instead of an empty SVG',
[helpFeedbackIcon?.svg, helpFeedbackIcon?.width, helpFeedbackIcon?.height,
helpFeedbackIcon?.viewBox, helpFeedbackIcon?.fill, helpFeedbackIcon?.pathFill,
helpFeedbackIcon?.transform, helpFeedbackIcon?.d],
[true, 16, 16, '0 0 16 16', 'none', 'currentColor', 'matrix(1 0 0 1 0.899994 0.899994)', HELP_FEEDBACK_ICON_PATH])
check('removed entries are gone from the DOM', REMOVED_TABS.map(tab => nav.tabs.includes(tab)), [false, false, false, false])
check('removed entries are gone from labels and search metadata', [nav.labels.join('|').includes('助理'), nav.labels.join('|').includes('设备连接'),
nav.labels.join('|').includes('数据管理'), nav.labels.join('|').includes('应用管理')], [false, false, false, false])
@@ -125,10 +136,10 @@ if (!process.versions.electron) {
// 原版 4 组;about 组按组件约定渲染为分隔线而不是标题。
check('nav keeps the four recovered groups', [nav.groups.length, nav.dividers], [3, 1])
// 通用(settings) 在 TODO 10 接入后同样可用。
const expectedDisabled = EXPECTED_TABS.filter(tab => !['settings', 'models', 'appearance', 'keyboardShortcuts', 'personalization', 'extensions', 'helpFeedback', 'getHelp'].includes(tab))
const expectedDisabled = EXPECTED_TABS.filter(tab => !['settings', 'models', 'appearance', 'keyboardShortcuts', 'personalization', 'memory', 'extensions', 'helpFeedback', 'getHelp'].includes(tab))
check('unwired entries stay disabled', nav.tabs.filter((tab, index) => nav.disabled[index] === 'true'), expectedDisabled)
check('unwired entries keep the 暂未接入 hint', nav.titles.filter(title => (title ?? '').includes('暂未接入')).length, expectedDisabled.length)
check('settings, appearance, shortcuts, personalization, agents, models, about and help stay enabled', nav.tabs.filter((tab, index) => !nav.disabled[index]), ['settings', 'appearance', 'keyboardShortcuts', 'personalization', 'extensions', 'models', 'helpFeedback', 'getHelp'])
check('settings, appearance, shortcuts, personalization, memory, agents, models, about and help stay enabled', nav.tabs.filter((tab, index) => !nav.disabled[index]), ['settings', 'appearance', 'keyboardShortcuts', 'personalization', 'memory', 'extensions', 'models', 'helpFeedback', 'getHelp'])
await step('settings-search-excludes-removed')
await read(`(()=>{const input=document.querySelector('.settings-navigation input[type="search"]');
@@ -161,12 +172,12 @@ if (!process.versions.electron) {
check('stale navigation to 助理 falls back to the models panel', await read(`!!document.querySelector('.models-settings-panel')`, settings), true)
const previous = settings.id
await read(`document.querySelector('.settings-window-close').click()`, settings)
for (let index = 0; index < 200 && !settings.isDestroyed(); index++) await pause(20)
check('settings window closed before the deep-link relaunch', settings.isDestroyed(), true)
// 新窗口直接以已移除的 section 启动:同样不能是空白页。
for (let index = 0; index < 200 && settings.isVisible(); index++) await pause(20)
check('settings window hides before the deep-link relaunch', !settings.isDestroyed() && !settings.isVisible(), true)
// 复用窗口以已移除的 section 启动:同样不能是空白页。
await openSettings('dataManagement')
check('fresh window with a removed section lands on the models panel', await read(`!!document.querySelector('.models-settings-panel')`, settings), true)
check('fresh window reuses a new settings window', settings.id !== previous, true)
check('reopened window with a removed section lands on the models panel', await read(`!!document.querySelector('.models-settings-panel')`, settings), true)
check('reopening keeps the cached settings window', settings.id === previous, true)
const finalNav = await read(navSource, settings)
snapshots.finalNav = finalNav
check('nav still excludes removed entries after relaunch', finalNav.tabs, EXPECTED_TABS)
@@ -174,7 +185,15 @@ if (!process.versions.electron) {
const report = { result: checks.every(item => item.pass) ? 'PASS' : 'FAIL',
scope: 'Real isolated YCode App/preload/IPC/renderer + native settings window; navigation entries, geometry, search, disabled hints and stale-target fallback measured live.',
sourceEvidence: ['ev_2309de12a003fca2c863a223cbda5bccad36b3af73e29bc309fdaed94745cf9e'], snapshots, checks, rendererErrors }
sourceEvidence: ['ev_2309de12a003fca2c863a223cbda5bccad36b3af73e29bc309fdaed94745cf9e'],
visualEvidence: [{ type: 'ycode-before', path: '.tmp/agents-settings-YLCzhi/ycode-agents-light.png',
observation: 'about navigation label has no icon while getHelp has a question-mark icon' },
{ type: 'ycode-before-crop', path: '.tmp/n06-ycode-settings-nav-about-missing.png',
observation: 'cropped current YCode settings navigation shows the missing About icon' },
{ type: 'workbuddy-reference-crop', path: '.tmp/n06-workbuddy-settings-nav-about.png',
observation: 'WorkBuddy reference shows the info-circle icon beside About and the question icon beside Get Help' },
{ type: 'rea-fixed', evidenceId: 'ev_2309de12a003fca2c863a223cbda5bccad36b3af73e29bc309fdaed94745cf9e',
observation: 'helpFeedback SVG child path at 16x16 is the info-circle glyph' }], snapshots, checks, rendererErrors }
await fs.writeFile(path.join(directory, 'report.json'), JSON.stringify(report, null, 2))
console.log('SETTINGS_NAV_' + report.result + ' ' + directory)
for (const failure of checks.filter(item => !item.pass)) console.log('SETTINGS_NAV_FAILURE ' + JSON.stringify(failure))