diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-analysis.css b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-analysis.css
index 5851785b3a..8fa9dae101 100644
--- a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-analysis.css
+++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-analysis.css
@@ -69,13 +69,16 @@
.analysis-method > summary { min-height: 44px; display: flex; align-items: center; cursor: pointer; color: var(--pw-text); }
.analysis-method dl { display: grid; grid-template-columns: 100px minmax(0, 1fr); gap: 8px 16px; }
.analysis-method dd { margin: 0; color: var(--pw-text); }
-.analysis-continuation { margin-block: 16px; border-block: 1px solid var(--pw-line); padding-block: 12px; }
+.analysis-continuation { margin-block: 8px; }
+.analysis-continuation-controls { display: flow-root; }
+.analysis-continuation-controls > details { min-width: 0; }
+.analysis-continuation-controls > button { float: inline-end; margin-inline-start: 16px; }
+.analysis-continuation-controls > details[open] { padding-block-end: 12px; }
.analysis-continuation-summary { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 16px; }
.analysis-continuation-summary strong { font-weight: 500; }
.analysis-continuation-summary > span { color: var(--pw-muted); }
-.analysis-continuation-summary button { margin-inline-start: auto; }
.analysis-continuation .analysis-method { border: 0; margin: 0; padding: 0; }
-.analysis-continuation .analysis-method > summary { display: list-item; list-style-position: inside; min-height: 36px; }
+.analysis-continuation .analysis-method > summary { display: list-item; list-style-position: inside; min-height: 44px; }
.analysis-spin { animation: analysis-request-spin 1s linear infinite; }
@keyframes analysis-request-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .analysis-spin { animation: none; } }
diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-analysis.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-analysis.tsx
index 5c6e34c7c8..f05a3026db 100644
--- a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-analysis.tsx
+++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-analysis.tsx
@@ -229,32 +229,28 @@ const analysisActionLabel = (p: Pack | null, zh: boolean) => p?.continuation?.ca
function AnalysisContinuation({pack: p, analysis: a}: {pack: Pack; analysis: Controller}) {
const c = p.continuation, zh = a.zh;
if (!c) return p.continuation_unavailable ?
{zh ? "基础分析暂不可读;本版仍可独立分析完整范围。" : "The base analysis cannot be read; this version can still assess its complete scope independently."} void a.read().catch(() => undefined)}>{zh ? "重试读取" : "Retry reading"}
: null;
- const changes = [
- c.sources.added.length ? `${zh ? "新增来源" : "New sources"} ${c.sources.added.length}` : "",
- c.sources.revised.length ? `${zh ? "更新来源" : "Updated sources"} ${c.sources.revised.length}` : "",
- c.sources.removed.length ? `${zh ? "移除来源" : "Removed sources"} ${c.sources.removed.length}` : "",
- c.episodes.added.length ? `${zh ? "新增片段" : "New episodes"} ${c.episodes.added.length}` : "",
- c.episodes.revised.length ? `${zh ? "更新片段" : "Updated episodes"} ${c.episodes.revised.length}` : "",
- c.episodes.removed.length ? `${zh ? "移除片段" : "Removed episodes"} ${c.episodes.removed.length}` : "",
- c.can_reuse ? (zh ? "复用原判读" : "Reuse judgments") : `${zh ? (p.result ? "已复核全部" : "完整复核") : "Reassess all"} ${p.episode_count} ${zh ? "片段" : "episodes"}`,
- ].filter(Boolean).join(" · ");
+ const reassessment = c.can_reuse ? (zh ? "复用原判读" : "Reuse judgments")
+ : `${zh ? (p.result ? "已复核全部" : "完整复核") : "Reassess all"} ${p.episode_count} ${zh ? "片段" : "episodes"}`;
return
- {c.can_reuse ? (zh ? "证据内容未变" : "Evidence content unchanged") : (zh ? "接续上一版" : "Continue from previous version")}
-
{changes}
+
{a.select(c.base_pack_id); requestAnimationFrame(() => a.panel.current?.focus({preventScroll: true}));}}>{zh ? "查看上一版" : "Previous version"}
-
- {!c.base_available ?
{zh ? "上一版尚无适用分析,本次将独立分析完整范围。" : "No applicable previous analysis is available. This version will assess the full scope independently."}
: null}
- {c.configuration_changed || c.objective_changed ?
{zh ? "分析设置或目标已变更,两版判断不作为同条件比较。" : "The analysis configuration or objective changed. Judgments are not a matched comparison."}
: null}
-
{p.result && c.base_available ? `${c.verdict_changes.length} ${zh ? "项判断变化" : "changed judgments"}` : (zh ? "查看变化" : "Inspect changes")}
- {c.base_title} · {c.base_scope.revision} → {c.current_scope.revision}
- {zh ? "上次覆盖" : "Previous coverage"}: {c.base_episode_count} · {c.base_scope.window} {zh ? "本次覆盖" : "Current coverage"}: {p.episode_count} · {c.current_scope.window}
- {zh ? "来源(新增 / 更正 / 删除)" : "Sources (added / revised / removed)"}: {c.sources.added.length} / {c.sources.revised.length} / {c.sources.removed.length} {zh ? "片段(新增 / 更正 / 删除)" : "Episodes (added / revised / removed)"}: {c.episodes.added.length} / {c.episodes.revised.length} / {c.episodes.removed.length} {zh ? "适用判断项(新增 / 移除)" : "Eligible judgments (added / removed)"}: {c.new_pair_count} / {c.removed_pair_count}
+ {zh ? "版本与变化" : "Version and changes"}
+ {c.can_reuse ? (zh ? "证据内容未变" : "Evidence content unchanged") : (zh ? "接续上一版" : "Continue from previous version")} {reassessment}
+ {p.result && c.base_available ? `${c.verdict_changes.length} ${zh ? "项判断变化" : "changed judgments"}` : (zh ? "查看变化" : "Inspect changes")}
+ {zh ? "上一版" : "Previous"} {c.base_title} · {c.base_scope.revision} {c.base_episode_count} · {c.base_scope.window}
+ {zh ? "本版" : "Current"} {c.current_scope.revision} {p.episode_count} · {c.current_scope.window}
+ {zh ? "来源变动" : "Source changes"} {zh ? "新增 / 更正 / 删除" : "Added / revised / removed"}: {c.sources.added.length} / {c.sources.revised.length} / {c.sources.removed.length}
+ {zh ? "片段变动" : "Episode changes"} {zh ? "新增 / 更正 / 删除" : "Added / revised / removed"}: {c.episodes.added.length} / {c.episodes.revised.length} / {c.episodes.removed.length}
+ {zh ? "适用判断项" : "Eligible judgments"} {zh ? "新增 / 移除" : "Added / removed"}: {c.new_pair_count} / {c.removed_pair_count}
{c.episodes.added.length ? {zh ? "新增片段" : "New episodes"}: {c.episodes.added.join(" · ")}
: null}
{c.episodes.revised.length ? {zh ? "更正片段" : "Revised episodes"}: {c.episodes.revised.join(" · ")}
: null}
{c.episodes.removed.length ? {zh ? "移除片段" : "Removed episodes"}: {c.episodes.removed.join(" · ")}
: null}
{zh ? "无法仅凭局部变化证明旧结论不受影响,因此实质变化均复核完整新版证据;指标按本版适用项重新计算,旧版不覆盖、不相加。" : "Local changes cannot prove earlier conclusions unaffected. Substantive changes therefore reassess the complete new evidence. Metrics are recalculated from this version’s eligible items; previous results are neither overwritten nor added."}
{c.verdict_changes.length ? {c.verdict_changes.map(row => {row.episode_id} · {metricCopy[row.metric][zh ? 0 : 1]}: {verdictLabel(row.metric, row.before, zh)} → {verdictLabel(row.metric, row.after, zh)} )} : null}
-
+
+
+ {!c.base_available ? {zh ? "上一版尚无适用分析,本次将独立分析完整范围。" : "No applicable previous analysis is available. This version will assess the full scope independently."}
: null}
+ {c.configuration_changed || c.objective_changed ? {zh ? "分析设置或目标已变更,两版判断不作为同条件比较。" : "The analysis configuration or objective changed. Judgments are not a matched comparison."}
: null}
;
}
diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-synthesis.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-synthesis.tsx
index be2a43e580..18cb2d9c56 100644
--- a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-synthesis.tsx
+++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-synthesis.tsx
@@ -27,6 +27,10 @@ export function ResearchSynthesis({synthesis: s, zh, detail = false, onExpand, o
+ {!experiments ? overview : null}
{experiments ?
{!s.next_experiments.length ?
{zh ? "现有证据不足以提出有区分力的下一步实验。" : "The evidence does not justify a discriminating next experiment."}
: null}
{s.next_experiments.map((experiment, i) =>
@@ -73,8 +78,7 @@ export function ResearchSynthesis({synthesis: s, zh, detail = false, onExpand, o
{!detail ? {zh ? "研究汇报" : "Research report"} : null}{zh ? "Agent 分析" : "Agent analysis"}
{!detail && onExpand ? onExpand(event.currentTarget)}>{zh ? "深入解读" : "Read deeper"} : null}
- {s.summary.text}
- {zh ? "概览来源" : "Summary sources"} · {s.summary.citations.length} {citations(s.summary.citations)}
+ {!detail ? overview : null}
{body}
;
}
diff --git a/examples/personal-workspace-browser/evidence/research-finding-reader/report-next-experiment-mobile.png b/examples/personal-workspace-browser/evidence/research-finding-reader/report-next-experiment-mobile.png
new file mode 100644
index 0000000000..e7aa8b9b2d
Binary files /dev/null and b/examples/personal-workspace-browser/evidence/research-finding-reader/report-next-experiment-mobile.png differ
diff --git a/examples/personal-workspace-browser/evidence/research-finding-reader/report-reading-after-desktop.png b/examples/personal-workspace-browser/evidence/research-finding-reader/report-reading-after-desktop.png
new file mode 100644
index 0000000000..79746e2435
Binary files /dev/null and b/examples/personal-workspace-browser/evidence/research-finding-reader/report-reading-after-desktop.png differ
diff --git a/examples/personal-workspace-browser/evidence/research-finding-reader/report-reading-after-mobile.png b/examples/personal-workspace-browser/evidence/research-finding-reader/report-reading-after-mobile.png
new file mode 100644
index 0000000000..876ce8a659
Binary files /dev/null and b/examples/personal-workspace-browser/evidence/research-finding-reader/report-reading-after-mobile.png differ
diff --git a/examples/personal-workspace-browser/evidence/research-finding-reader/report-reading-before-desktop.png b/examples/personal-workspace-browser/evidence/research-finding-reader/report-reading-before-desktop.png
new file mode 100644
index 0000000000..55fa6da53a
Binary files /dev/null and b/examples/personal-workspace-browser/evidence/research-finding-reader/report-reading-before-desktop.png differ
diff --git a/examples/personal-workspace-browser/evidence/research-finding-reader/report-reading-before-mobile.png b/examples/personal-workspace-browser/evidence/research-finding-reader/report-reading-before-mobile.png
new file mode 100644
index 0000000000..7a55819183
Binary files /dev/null and b/examples/personal-workspace-browser/evidence/research-finding-reader/report-reading-before-mobile.png differ
diff --git a/examples/personal-workspace-browser/research-history.mjs b/examples/personal-workspace-browser/research-history.mjs
index 55fe13ac43..d68696d797 100644
--- a/examples/personal-workspace-browser/research-history.mjs
+++ b/examples/personal-workspace-browser/research-history.mjs
@@ -218,9 +218,15 @@ export const researchHistoryScenario = {
await analysis.waitFor();
assert.equal(await synthesisPage.getByRole('region', {name: '研究汇报', exact: true}).count(), 1, 'One reading focus when expanded');
assert.match(await analysis.innerText(), /竞争解释.*随机波动/s);
+ const savedOverview = await analysis.locator('.research-synthesis-summary').innerText();
+ assert(await analysis.locator('.research-report-tabs').evaluate(tabs => {
+ const overview = tabs.parentElement.querySelector('.research-synthesis-summary');
+ return Boolean(overview && (tabs.compareDocumentPosition(overview) & Node.DOCUMENT_POSITION_FOLLOWING));
+ }), 'Choose findings or experiments before reading a potentially long overview');
await analysis.getByRole('tab', {name: /结论与解释/}).focus();
await synthesisPage.keyboard.press('ArrowRight');
assert.equal(await analysis.getByRole('tab', {name: /下一步实验/}).getAttribute('aria-selected'), 'true');
+ assert.equal(await analysis.locator('.research-synthesis-summary').count(), 0, 'Experiment reading starts with the experiment instead of repeating the report overview');
assert.match(await analysis.innerText(), /固定条件.*随机序列.*推翻解释的结果/s);
assert.equal(await analysis.locator('.analysis-method').first().evaluate(e => e.open), false, 'Episode judgments remain available as secondary verification');
await synthesisPage.mouse.move(1, 1);
@@ -228,6 +234,7 @@ export const researchHistoryScenario = {
await synthesisPage.screenshot({path: resolve(outputDir, 'research-synthesis-deep-mobile.png'), animations: 'disabled'});
await synthesisPage.setViewportSize({width: 1280, height: 900});
await analysis.getByRole('tab', {name: /结论与解释/}).click();
+ assert.equal(await analysis.locator('.research-synthesis-summary').innerText(), savedOverview, 'Returning to findings restores the complete saved overview');
await analysis.evaluate(e => e.scrollIntoView({block: 'start'}));
await synthesisPage.screenshot({path: resolve(outputDir, 'research-synthesis-deep-desktop.png'), animations: 'disabled'});
// The report already declares its episode lineage. Follow those exact
@@ -1371,13 +1378,15 @@ print(json.dumps([
assert.equal(await chineseAnalysis.locator('.analysis-episode[open]').count(), 0);
continuationFixture = true;
await page.reload({waitUntil: 'networkidle'});
- // The inline version summary is a semantic section, not another page.
+ // Version bookkeeping is optional detail; the saved analysis leads.
const version = chineseAnalysis.locator('.analysis-continuation');
- await version.getByText('接续上一版', {exact: true}).waitFor();
- await version.getByText(/已复核全部 4 片段/).waitFor();
+ await version.getByText('版本与变化', {exact: true}).waitFor();
assert.equal(await version.locator('details').getAttribute('open'), null);
- await version.locator('summary').click();
- assert.match(await version.innerText(), /original-capture → later-evidence/);
+ assert.doesNotMatch(await version.innerText(), /新增来源|已复核全部|original-capture/);
+ await version.locator('summary').focus();
+ await page.keyboard.press('Enter');
+ await version.getByText(/已复核全部 4 片段/).waitFor();
+ assert.match(await version.innerText(), /original-capture.*later-evidence/s);
assert.match(await version.innerText(), /variant-alpha.*已做收益检验.*证据不足/);
await version.locator('summary').click();
await page.setViewportSize({width: 1440, height: 1000});
@@ -1388,9 +1397,9 @@ print(json.dumps([
await chineseAnalysis.getByText('Synthetic original analysis', {exact: true}).waitFor();
assert.equal(await chineseAnalysis.locator('.analysis-continuation').count(), 0);
await page.goBack({waitUntil: 'networkidle'});
- await version.getByText('接续上一版', {exact: true}).waitFor();
+ await version.getByText('版本与变化', {exact: true}).waitFor();
await page.reload({waitUntil: 'networkidle'});
- await version.getByText('接续上一版', {exact: true}).waitFor();
+ await version.getByText('版本与变化', {exact: true}).waitFor();
await page.setViewportSize({width: 390, height: 844});
await chineseAnalysis.evaluate(element => element.scrollIntoView({block: 'start'}));
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1));