fix: simplify continuing work from supervisor reviews
Let users continue discussions without opening sources first, edit a question, and send source-linked review context to the target conversation. Add explicit conversation navigation and remove redundant context disclosure and entity-writeback controls. Hide empty automatic supervision metrics until activity exists. Release note: Continue work directly from supervision feedback or graph sources with relevant context, fewer controls, and clearer automatic supervision empty states.
This commit is contained in:
@@ -229,8 +229,8 @@ app
|
||||
})()`)
|
||||
assert(report.pageWidth <= width && report.scrollWidth <= report.clientWidth, 'Recap/automatic overflow')
|
||||
assert.equal(report.refresh, 1, 'One refresh owner per panel')
|
||||
assert.equal(report.reports, tab === 'plans')
|
||||
assert.equal(report.suggestions, tab === 'plans')
|
||||
assert.equal(report.reports, tab === 'plans' && scenario === 'populated')
|
||||
assert.equal(report.suggestions, tab === 'plans' && scenario === 'populated')
|
||||
assert.equal(report.recap, tab === 'overview' && scenario !== 'empty')
|
||||
if (tab === 'overview') {
|
||||
assert(!report.text.includes('暂无自动监督报告') && !report.text.includes('待确认记忆'))
|
||||
@@ -240,13 +240,13 @@ app
|
||||
assert(report.clientWidth - report.proseWidth <= 50, 'Prose fills the panel within card padding')
|
||||
} else assert(report.text.includes('还没有成功回顾'))
|
||||
} else {
|
||||
assert.equal(report.text.includes('暂无自动监督报告'), scenario !== 'populated')
|
||||
assert(report.text.includes('自动监督报告'))
|
||||
assert(!report.text.includes('暂无自动监督报告'))
|
||||
assert.equal(report.text.includes('自动监督报告'), scenario === 'populated')
|
||||
}
|
||||
reports.push({ scenario, tab, ...report })
|
||||
const prefix = `recap-${scenario}-${tab}-${width}`
|
||||
await writeFile(join(artifacts, `${prefix}.png`), (await win.webContents.capturePage()).toPNG())
|
||||
if (tab === 'plans') {
|
||||
if (tab === 'plans' && scenario === 'populated') {
|
||||
for (const anchor of ['heartbeat-memory-title', 'heartbeat-reports-title', 'heartbeat-runs-title']) {
|
||||
await js(`document.getElementById('${anchor}').scrollIntoView({block:'start'})`)
|
||||
if (anchor === 'heartbeat-reports-title' && scenario === 'populated') await js(`document.querySelector('#heartbeat-panel-history button[aria-expanded=false]')?.click()`)
|
||||
@@ -277,8 +277,9 @@ app
|
||||
return
|
||||
}
|
||||
if (process.env.GOODBUDDY_SUPERVISOR_AUTOMATIC_OVERVIEW) {
|
||||
for (const empty of [false, true]) {
|
||||
await win.loadURL(process.env.GOODBUDDY_SUPERVISOR_URL + (empty ? '' : '?menu=1'))
|
||||
for (const scenario of ['populated', 'empty', 'plan-only']) {
|
||||
const empty = scenario !== 'populated'
|
||||
await win.loadURL(process.env.GOODBUDDY_SUPERVISOR_URL + (scenario === 'populated' ? '?menu=1' : scenario === 'plan-only' ? '?plan-only=1' : ''))
|
||||
await wait('!!document.querySelector("#supervisor-tab-plans")')
|
||||
await js('document.fonts.ready')
|
||||
await js('document.documentElement.dataset.theme = "light"')
|
||||
@@ -295,22 +296,27 @@ app
|
||||
overview: !!panel.querySelector('#heartbeat-panel-overview'),
|
||||
metrics: !!panel.querySelector('.heartbeat-center__metrics'),
|
||||
trend: !!panel.querySelector('#heartbeat-trend-title'),
|
||||
audit: !!panel.querySelector('#heartbeat-runs-title'),
|
||||
audit: !!panel.querySelector('#heartbeat-runs-title'),
|
||||
reports: !!panel.querySelector('#heartbeat-reports-title'),
|
||||
suggestions: !!panel.querySelector('#heartbeat-panel-suggestions'),
|
||||
emptyReports: panel.textContent.includes('暂无自动监督报告'),
|
||||
plans: !!panel.querySelector('#heartbeat-panel-plans'),
|
||||
create: [...panel.querySelectorAll('button')].filter(b => b.textContent === '创建自动监督计划').length,
|
||||
emptyStates: panel.querySelectorAll('#heartbeat-panel-overview .empty-state').length,
|
||||
activity: panel.querySelectorAll('.supervisor-activity__item').length };
|
||||
})()`)
|
||||
assert(report.pageWidth <= width && report.scrollWidth <= report.panelWidth, 'Automatic panel overflow')
|
||||
for (const key of ['overview', 'metrics', 'trend', 'audit', 'plans']) assert.equal(report[key], tab === 'plans', key)
|
||||
for (const key of ['overview', 'plans']) assert.equal(report[key], tab === 'plans', key)
|
||||
for (const key of ['metrics', 'trend', 'audit', 'reports', 'suggestions']) assert.equal(report[key], tab === 'plans' && !empty, key)
|
||||
assert.equal(report.emptyReports, false)
|
||||
assert.equal(report.create, tab === 'plans' ? 1 : 0)
|
||||
assert.equal(report.emptyStates, 0)
|
||||
if (tab === 'activity') assert.equal(report.activity, 3)
|
||||
reports.push({ empty, tab, ...report })
|
||||
const prefix = `automatic-${empty ? 'empty' : 'populated'}-${tab}-light-${width}`
|
||||
reports.push({ scenario, empty, tab, ...report })
|
||||
const prefix = `automatic-${scenario}-${tab}-light-${width}`
|
||||
await writeFile(join(artifacts, `${prefix}.png`), (await win.webContents.capturePage()).toPNG())
|
||||
if (tab === 'plans') {
|
||||
for (const anchor of ['heartbeat-trend-title', 'heartbeat-runs-title']) {
|
||||
for (const anchor of empty ? [] : ['heartbeat-trend-title', 'heartbeat-runs-title']) {
|
||||
await js(`document.getElementById('${anchor}').scrollIntoView({block:'start'})`)
|
||||
await settle()
|
||||
await writeFile(join(artifacts, `${prefix}-${anchor}.png`), (await win.webContents.capturePage()).toPNG())
|
||||
@@ -335,6 +341,45 @@ app
|
||||
}
|
||||
assert.deepEqual(errors, [])
|
||||
await writeFile(join(artifacts, 'automatic-overview-measurements.json'), JSON.stringify(reports, null, 2))
|
||||
await win.loadURL(process.env.GOODBUDDY_SUPERVISOR_URL + '?preview=1')
|
||||
await wait('document.querySelector(".supervisor-workspace")?.getAttribute("aria-busy") === "false"')
|
||||
await js('document.querySelector("#supervisor-tab-graph").click()')
|
||||
await wait('!!document.querySelector(".supervisor-workspace__map")')
|
||||
await js(`document.querySelector('.supervisor-workspace__map [aria-label="观察工作过程"]').dispatchEvent(new MouseEvent('click', {bubbles:true}))`)
|
||||
await settle()
|
||||
await js('document.querySelector(".supervisor-workspace__detail > .link-button").click()')
|
||||
await wait('!!document.querySelector(".supervisor-discussion > button")')
|
||||
await js('document.querySelector(".supervisor-discussion > button").click()')
|
||||
await wait('!!document.querySelector(".supervisor-discussion textarea")')
|
||||
await js('document.fonts.ready')
|
||||
const previews = []
|
||||
for (const theme of ['light', 'dark']) {
|
||||
await js(`document.documentElement.dataset.theme = '${theme}'`)
|
||||
for (const width of [1440, 390]) {
|
||||
win.setContentSize(width, 1100)
|
||||
await js(`document.querySelector('.supervisor-discussion').scrollIntoView({block:'center'})`)
|
||||
await settle()
|
||||
const report = await js(`(() => {
|
||||
const preview = document.querySelector('.supervisor-discussion');
|
||||
const box = e => { const r = e.getBoundingClientRect(); return {left:r.left, right:r.right, top:r.top, bottom:r.bottom}; };
|
||||
return { width:innerWidth, pageWidth:document.documentElement.scrollWidth,
|
||||
clientWidth:preview.clientWidth, scrollWidth:preview.scrollWidth,
|
||||
question:preview.querySelector('textarea').value, text:preview.textContent,
|
||||
controls:[...preview.querySelectorAll('textarea, button')].map(box) };
|
||||
})()`)
|
||||
assert(report.pageWidth <= width && report.scrollWidth <= report.clientWidth, 'Discussion preview overflow')
|
||||
assert(report.question.trim() && report.text.includes('模拟会议记录'), 'Discussion question and destination visible')
|
||||
assert(report.controls.every(box => box.left >= 0 && box.right <= width && box.top >= 0 && box.bottom <= 1100), 'Preview controls fit viewport')
|
||||
assert(await js('!document.querySelector(".supervisor-discussion [role=alert]")'), 'Preview loads without error')
|
||||
assert(await js('!document.querySelector(".supervisor-discussion details")'), 'No redundant context disclosure')
|
||||
previews.push({ theme, ...report })
|
||||
await writeFile(join(artifacts, `discussion-preview-${theme}-${width}.png`), (await win.webContents.capturePage()).toPNG())
|
||||
}
|
||||
}
|
||||
await js('document.querySelector(".supervisor-discussion .secondary-button").click()')
|
||||
await wait('!document.querySelector(".supervisor-discussion textarea")')
|
||||
assert.deepEqual(errors, [])
|
||||
await writeFile(join(artifacts, 'discussion-preview-measurements.json'), JSON.stringify(previews, null, 2))
|
||||
console.log(JSON.stringify({ artifacts, cases: reports.length, errors }))
|
||||
win.destroy()
|
||||
app.quit()
|
||||
|
||||
@@ -146,6 +146,8 @@ Object.defineProperty(window, 'goodbuddy', {
|
||||
? {}
|
||||
: {
|
||||
supervision: {
|
||||
continueContext: async () => ({ prompt: '模拟讨论上下文:本周已核对交付清单,负责人已确认。\n\n原始依据:模拟会议记录。外部评审时间仍待确认,下一步需要核对验收条件。' }),
|
||||
continue: async () => { throw new Error('Preview fixture must not send a message') },
|
||||
execution: async () => ({ active: params.has('activity'), ...(params.has('activity') ? { runId: 'activity-1' } : {}) }),
|
||||
batches: async ({ runId, offset, limit }: { runId: string; offset: number; limit: number }) => Array.from({ length: runId === 'activity-0' ? 6 : 12 }, (_, index) => ({
|
||||
id: `leaf-${index}`, projectId: index < 3 ? 'Atlas' : 'Beacon', conversationId: `conversation-${Math.floor(index / 2)}`,
|
||||
@@ -204,6 +206,7 @@ Object.defineProperty(window, 'goodbuddy', {
|
||||
entityAction: async () => {},
|
||||
relationAction: async () => {},
|
||||
source: async () => ({
|
||||
...(params.has('preview') ? { sourceType: 'conversation', sourceId: 'simulated-conversation-uuid' } : {}),
|
||||
title: '模拟会议记录',
|
||||
content: '视觉 fixture,不是真实用户数据。',
|
||||
occurredAt: '2026-09-21T08:00:00.000Z'
|
||||
@@ -252,7 +255,7 @@ createRoot(document.getElementById('root')!).render(
|
||||
<HeartbeatCenter
|
||||
applicationSettings={applicationSettingsSchema.parse({ checkUpdatesOnStartup: true, updateSource: 'github', modelDownloadSource: 'modelscope', localToolEnvironment: defaultLocalToolEnvironmentSettings, conversationHtmlRenderingEnabled: true, remoteProjectsEnabled: false })}
|
||||
onUpdateApplicationSettings={async () => true}
|
||||
configs={menu ? [{ id: 'plan', name: '模拟每日回顾', scope: { kind: 'global' }, timezone: 'Asia/Shanghai', recurrence: { type: 'daily', localTime: '09:00' }, enabled: true, lookbackHours: 48, retentionDays: 90, nextRunAt: '2026-09-24T01:00:00.000Z', createdAt, updatedAt: createdAt }] : []}
|
||||
configs={menu || params.has('plan-only') ? [{ id: 'plan', name: '模拟每日回顾', scope: { kind: 'global' }, timezone: 'Asia/Shanghai', recurrence: { type: 'daily', localTime: '09:00' }, enabled: true, lookbackHours: 48, retentionDays: 90, nextRunAt: '2026-09-24T01:00:00.000Z', createdAt, updatedAt: createdAt }] : []}
|
||||
runs={menu ? [{ id: 'run', configId: 'plan', trigger: 'scheduled', scheduledFor: createdAt, status: 'completed', attemptCount: 1, createdAt, updatedAt: createdAt }] : []}
|
||||
entries={menu ? [{ id: 'report', configId: 'plan', runId: 'run', scheduledFor: createdAt, summary: '模拟自动监督报告:交付计划已更新,待核对负责人和验收日期。', highlights: ['保留原始依据,核对交付时间。'], proposedMemoryIds: ['memory'], followUpTaskIds: ['task'], createdAt }] : []}
|
||||
memories={menu ? [{ id: 'memory', scope: 'global', type: 'preference', content: '模拟建议:周会总结保留负责人和下一次检查日期。', confidence: 0.9, salience: 0.8, status: 'proposed', createdAt, updatedAt: createdAt }] : []}
|
||||
@@ -267,7 +270,8 @@ createRoot(document.getElementById('root')!).render(
|
||||
onSetMemoryStatus={noop}
|
||||
onSetTaskStatus={noop}
|
||||
onUseFollowUpTask={() => {}}
|
||||
onRetryLoad={noop}
|
||||
onRetryLoad={noop}
|
||||
onOpenConversation={params.has('preview') ? () => { throw new Error('Preview fixture must not navigate') } : undefined}
|
||||
/>
|
||||
</PageShell>}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user