{t('supervisor.listEmpty')}
+ )} + {listTab === 'event' && layout.events.map((event, index) => ( ))} -{t('supervisor.legend')}
diff --git a/src/renderer/src/i18n/locales/en-US/heartbeat.ts b/src/renderer/src/i18n/locales/en-US/heartbeat.ts index 48a39e2c..3e5a92a8 100644 --- a/src/renderer/src/i18n/locales/en-US/heartbeat.ts +++ b/src/renderer/src/i18n/locales/en-US/heartbeat.ts @@ -85,6 +85,8 @@ export const heartbeat = { newReview: 'New review', summary: 'Work summary', changes: 'Changes', runningHint: 'A new review is running. The existing result remains available to read.', empty: 'No successful review yet', emptyHint: 'You can review current progress manually even without an automatic supervision plan.', run: 'Review current progress', running: 'Reviewing…', retryRun: 'Retry review', dismiss: 'Dismiss', graphScope: 'Graph scope', graphEmpty: 'No story events in this scope', legend: 'Solid lines show event impact on entities; dashed lines show entity relations. Time runs counter-clockwise with a visible gap.', start: 'Start', end: 'End', + listTabs: { event: 'Events', entity: 'Entities', relation: 'Relations' }, + listEmpty: 'No records of this type in this review.', events: 'Time events', entities: 'Knowledge entities', relations: 'Entity relations', eventSources: 'Event sources', sources: 'Related sources', noSources: 'No related sources are available.', selectHint: 'Select an event, entity, or relation to inspect it.', sourceSnapshot: 'Source details', sourceMissing: 'Source not found', confirm: 'Confirm', revise: 'Revise', remove: 'Remove relation', label: 'Entity name', save: 'Save revision', cancel: 'Cancel', removeHint: 'This changes graph organization only; the original source remains available.', relationTypes: { supports: 'Supports', 'depends-on': 'Depends on', contrasts: 'Contrasts', related: 'Related' }, diff --git a/src/renderer/src/i18n/locales/zh-CN/heartbeat.ts b/src/renderer/src/i18n/locales/zh-CN/heartbeat.ts index ea7f9666..490ea79f 100644 --- a/src/renderer/src/i18n/locales/zh-CN/heartbeat.ts +++ b/src/renderer/src/i18n/locales/zh-CN/heartbeat.ts @@ -82,6 +82,8 @@ export const heartbeat = { newReview: '新回顾', summary: '工作总结', changes: '本次变化', runningHint: '新回顾正在整理,已有结果仍可阅读。', empty: '还没有成功回顾', emptyHint: '即使没有自动监督计划,也可以手动回顾当前进展。', run: '回顾当前进展', running: '回顾整理中…', retryRun: '重试回顾', dismiss: '关闭提示', graphScope: '图谱范围', graphEmpty: '当前范围没有故事线事件', legend: '实线表示事件影响实体,虚线表示实体关系。时间轴逆时针排列,起止之间保留缺口。', start: '起点', end: '终点', + listTabs: { event: '事件', entity: '实体', relation: '关系' }, + listEmpty: '本次回顾暂无此类记录。', events: '时间事件', entities: '知识实体', relations: '实体关系', eventSources: '事件来源', sources: '关联来源', noSources: '没有可用的关联来源。', selectHint: '选择事件、实体或关系查看详情。', sourceSnapshot: '来源详情', sourceMissing: '来源不存在', confirm: '确认', revise: '修订', remove: '移除关系', label: '实体名称', save: '保存修订', cancel: '取消', removeHint: '这只改变图谱中的关系组织,原始来源仍然保留。', relationTypes: { supports: '支持', 'depends-on': '依赖', contrasts: '对比', related: '相关' }, diff --git a/src/renderer/src/supervisor-workspace.css b/src/renderer/src/supervisor-workspace.css index b19c3fb0..6dfe4b63 100644 --- a/src/renderer/src/supervisor-workspace.css +++ b/src/renderer/src/supervisor-workspace.css @@ -413,27 +413,27 @@ padding: var(--space-4); background: var(--surface-subtle); border-radius: var(--radius-card) 0 0 var(--radius-card); + overflow: hidden; +} +.supervisor-workspace__graph-list > .page-tabs, +.supervisor-workspace__graph-list > p { + flex: 0 0 auto; +} +.supervisor-workspace__graph-list > .supervisor-workspace__list-panel { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; + gap: var(--space-1); overflow: auto; scrollbar-width: thin; } -.supervisor-workspace__graph-list > * { +.supervisor-workspace__graph-list .page-tabs__tab { + gap: var(--space-1); + padding-inline: var(--space-1); +} +.supervisor-workspace__list-panel > button { flex: 0 0 auto; -} -.supervisor-workspace__graph-list h3 { - display: flex; - justify-content: space-between; - margin: var(--space-4) 0 var(--space-2); - font-size: var(--font-body); -} -.supervisor-workspace__graph-list h3:first-child { - margin-top: var(--space-1); -} -.supervisor-workspace__graph-list h3 span { - color: var(--text-muted); - font-weight: 400; - font-variant-numeric: tabular-nums; -} -.supervisor-workspace__graph-list button { display: flex; align-items: baseline; gap: var(--space-2); @@ -468,10 +468,10 @@ background: currentColor; border-radius: 50%; } -.supervisor-workspace__graph-list button:hover { +.supervisor-workspace__list-panel > button:hover { background: var(--surface-raised); } -.supervisor-workspace__graph-list button[aria-pressed='true'] { +.supervisor-workspace__list-panel > button[aria-pressed='true'] { border-color: var(--accent); background: var(--accent-subtle); } diff --git a/tests/support/supervisor-layout-driver.mjs b/tests/support/supervisor-layout-driver.mjs index 8af443cd..ccd89152 100644 --- a/tests/support/supervisor-layout-driver.mjs +++ b/tests/support/supervisor-layout-driver.mjs @@ -669,7 +669,8 @@ app win.setContentSize(width, 1100) await settle() const scrolling = await js(`(() => { - const list = document.querySelector('.supervisor-workspace__graph-list'); + const sidebar = document.querySelector('.supervisor-workspace__graph-list'); + const list = sidebar.querySelector('[role=tabpanel]'); const canvas = document.querySelector('.supervisor-workspace__graph-canvas'); const shell = document.querySelector('.page-shell'); list.scrollIntoView({block:'end'}); @@ -678,7 +679,7 @@ app const last = list.lastElementChild.getBoundingClientRect(); const button = list.querySelector('button:last-of-type'); const b = button.getBoundingClientRect(), r = list.getBoundingClientRect(); - return { width: innerWidth, height: r.height, canvasHeight: canvas.getBoundingClientRect().height, + return { width: innerWidth, height: sidebar.getBoundingClientRect().height, canvasHeight: canvas.getBoundingClientRect().height, clientHeight: list.clientHeight, scrollHeight: list.scrollHeight, scrollTop: list.scrollTop, bottomGap: r.bottom - last.bottom, paddingBottom: parseFloat(getComputedStyle(list).paddingBottom), @@ -703,15 +704,19 @@ app } win.setContentSize(1440, 1100) await js( - 'document.querySelector(".supervisor-workspace__graph-list button:last-of-type").click()' - ) + 'document.querySelectorAll(".supervisor-workspace__graph-list [role=tab]")[2].click()' + ) + await settle() + await js('document.querySelector(".supervisor-workspace__list-panel button:last-of-type").click()') await settle() assert( await js('document.querySelectorAll("[data-relation]").length === 1'), 'Selected relation endpoints must remain visible' ) + await js('document.querySelectorAll(".supervisor-workspace__graph-list [role=tab]")[0].click()') + await settle() const target = await js( - `(() => { const e = document.querySelectorAll('.supervisor-workspace__graph-list button')[79]; e.scrollIntoView({block:'center'}); const r = e.getBoundingClientRect(); return { x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2), hit: e.contains(document.elementFromPoint(r.x + r.width / 2, r.y + r.height / 2)) }; })()` + `(() => { const e = document.querySelectorAll('.supervisor-workspace__list-panel button')[79]; e.scrollIntoView({block:'center'}); const r = e.getBoundingClientRect(); return { x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2), hit: e.contains(document.elementFromPoint(r.x + r.width / 2, r.y + r.height / 2)) }; })()` ) assert(target.hit, 'Last event is not reachable') win.webContents.sendInputEvent({ @@ -736,9 +741,9 @@ app ) assert.equal( await js( - 'document.querySelectorAll(".supervisor-workspace__graph-list button").length' + 'document.querySelectorAll(".supervisor-workspace__list-panel button").length' ), - 121 + 80 ) const positions = await js( '[...document.querySelectorAll(".supervisor-workspace__node:not(.supervisor-workspace__entity) circle")].map(e => [e.getAttribute("cx"), e.getAttribute("cy")].join())' @@ -946,7 +951,7 @@ app const panel = document.querySelector('#supervisor-panel-${tab}'); return { width: innerWidth, pageWidth: document.documentElement.scrollWidth, panelWidth: panel.clientWidth, scrollWidth: panel.scrollWidth, - tabs: [...document.querySelectorAll('[role=tab]')].map(e => e.textContent), + tabs: [...document.querySelectorAll('[role=tab][id^="supervisor-tab-"]')].map(e => e.textContent), nestedMenus: panel.querySelectorAll('[role=tablist]').length, reports: !!panel.querySelector('#heartbeat-reports-title'), reportWidth: panel.querySelector('#heartbeat-panel-history > section')?.getBoundingClientRect().width, @@ -958,7 +963,7 @@ app refresh: [...panel.querySelectorAll('button')].some(e => e.textContent.includes('刷新')) }; })()`) assert.deepEqual(menuLayout.tabs, ['工作回顾', '故事线图谱', '自动监督', '活动记录', '设置']) - assert.equal(menuLayout.nestedMenus, 0) + assert.equal(menuLayout.nestedMenus, tab === 'graph' ? 1 : 0) assert(menuLayout.pageWidth <= width && menuLayout.scrollWidth <= menuLayout.panelWidth, 'Menu panel overflow') assert.equal(menuLayout.reports, tab === 'plans') if (tab === 'plans') assert(Math.abs(menuLayout.reportWidth - menuLayout.panelWidth) <= 1, 'Reports must use full reading width')