Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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; } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? <p className="analysis-warning">{zh ? "基础分析暂不可读;本版仍可独立分析完整范围。" : "The base analysis cannot be read; this version can still assess its complete scope independently."} <button type="button" onClick={() => void a.read().catch(() => undefined)}>{zh ? "重试读取" : "Retry reading"}</button></p> : 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 <section className="analysis-continuation" aria-label={zh ? "分析版本变化" : "Analysis version changes"}>
<div className="analysis-continuation-summary"><strong>{c.can_reuse ? (zh ? "证据内容未变" : "Evidence content unchanged") : (zh ? "接续上一版" : "Continue from previous version")}</strong>
<span>{changes}</span>
<div className="analysis-continuation-controls">
<button type="button" onClick={() => {a.select(c.base_pack_id); requestAnimationFrame(() => a.panel.current?.focus({preventScroll: true}));}}>{zh ? "查看上一版" : "Previous version"}</button>
</div>
{!c.base_available ? <p className="analysis-warning">{zh ? "上一版尚无适用分析,本次将独立分析完整范围。" : "No applicable previous analysis is available. This version will assess the full scope independently."}</p> : null}
{c.configuration_changed || c.objective_changed ? <p className="analysis-warning">{zh ? "分析设置或目标已变更,两版判断不作为同条件比较。" : "The analysis configuration or objective changed. Judgments are not a matched comparison."}</p> : null}
<details className="analysis-method"><summary>{p.result && c.base_available ? `${c.verdict_changes.length} ${zh ? "项判断变化" : "changed judgments"}` : (zh ? "查看变化" : "Inspect changes")}</summary>
<p>{c.base_title} · {c.base_scope.revision} → {c.current_scope.revision}</p>
<p>{zh ? "上次覆盖" : "Previous coverage"}: {c.base_episode_count} · {c.base_scope.window}<br/>{zh ? "本次覆盖" : "Current coverage"}: {p.episode_count} · {c.current_scope.window}</p>
<p>{zh ? "来源(新增 / 更正 / 删除)" : "Sources (added / revised / removed)"}: {c.sources.added.length} / {c.sources.revised.length} / {c.sources.removed.length}<br/>{zh ? "片段(新增 / 更正 / 删除)" : "Episodes (added / revised / removed)"}: {c.episodes.added.length} / {c.episodes.revised.length} / {c.episodes.removed.length}<br/>{zh ? "适用判断项(新增 / 移除)" : "Eligible judgments (added / removed)"}: {c.new_pair_count} / {c.removed_pair_count}</p>
<details className="analysis-method"><summary>{zh ? "版本与变化" : "Version and changes"}</summary>
<div className="analysis-continuation-summary"><strong>{c.can_reuse ? (zh ? "证据内容未变" : "Evidence content unchanged") : (zh ? "接续上一版" : "Continue from previous version")}</strong><span>{reassessment}</span></div>
<p>{p.result && c.base_available ? `${c.verdict_changes.length} ${zh ? "项判断变化" : "changed judgments"}` : (zh ? "查看变化" : "Inspect changes")}</p>
<dl><dt>{zh ? "上一版" : "Previous"}</dt><dd>{c.base_title} · {c.base_scope.revision}<br/>{c.base_episode_count} · {c.base_scope.window}</dd>
<dt>{zh ? "本版" : "Current"}</dt><dd>{c.current_scope.revision}<br/>{p.episode_count} · {c.current_scope.window}</dd>
<dt>{zh ? "来源变动" : "Source changes"}</dt><dd>{zh ? "新增 / 更正 / 删除" : "Added / revised / removed"}: {c.sources.added.length} / {c.sources.revised.length} / {c.sources.removed.length}</dd>
<dt>{zh ? "片段变动" : "Episode changes"}</dt><dd>{zh ? "新增 / 更正 / 删除" : "Added / revised / removed"}: {c.episodes.added.length} / {c.episodes.revised.length} / {c.episodes.removed.length}</dd>
<dt>{zh ? "适用判断项" : "Eligible judgments"}</dt><dd>{zh ? "新增 / 移除" : "Added / removed"}: {c.new_pair_count} / {c.removed_pair_count}</dd></dl>
{c.episodes.added.length ? <p>{zh ? "新增片段" : "New episodes"}: {c.episodes.added.join(" · ")}</p> : null}
{c.episodes.revised.length ? <p>{zh ? "更正片段" : "Revised episodes"}: {c.episodes.revised.join(" · ")}</p> : null}
{c.episodes.removed.length ? <p>{zh ? "移除片段" : "Removed episodes"}: {c.episodes.removed.join(" · ")}</p> : null}
<p>{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."}</p>
{c.verdict_changes.length ? <ul>{c.verdict_changes.map(row => <li key={`${row.episode_id}:${row.metric}`}>{row.episode_id} · {metricCopy[row.metric][zh ? 0 : 1]}: {verdictLabel(row.metric, row.before, zh)} → {verdictLabel(row.metric, row.after, zh)}</li>)}</ul> : null}
</details>
</details>
</div>
{!c.base_available ? <p className="analysis-warning">{zh ? "上一版尚无适用分析,本次将独立分析完整范围。" : "No applicable previous analysis is available. This version will assess the full scope independently."}</p> : null}
{c.configuration_changed || c.objective_changed ? <p className="analysis-warning">{zh ? "分析设置或目标已变更,两版判断不作为同条件比较。" : "The analysis configuration or objective changed. Judgments are not a matched comparison."}</p> : null}
</section>;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export function ResearchSynthesis({synthesis: s, zh, detail = false, onExpand, o
<dt>{zh ? "预期区分" : "Distinguishing prediction"}</dt><dd>{experiment.prediction}</dd>
<dt>{zh ? "推翻解释的结果" : "Disconfirming result"}</dt><dd>{experiment.disconfirming_result}</dd>
</dl>;
const overview = <>
<p className="research-synthesis-summary">{s.summary.text}</p>
<details className="research-report-summary-sources"><summary>{zh ? "概览来源" : "Summary sources"} · {s.summary.citations.length}</summary>{citations(s.summary.citations)}</details>
</>;
let body: ReactNode;
if (!detail) {
body = <>
Expand All @@ -48,6 +52,7 @@ export function ResearchSynthesis({synthesis: s, zh, detail = false, onExpand, o
}}>{next ? (zh ? "下一步实验" : "Next experiments") : (zh ? "结论与解释" : "Findings and explanations")} <span>{next ? s.next_experiments.length : s.findings.length}</span></button>)}
</div>
<div id={`${id}-body`} role="tabpanel" aria-labelledby={`${id}-${experiments}-tab`}>
{!experiments ? overview : null}
{experiments ? <div className="research-report-experiments">
{!s.next_experiments.length ? <p>{zh ? "现有证据不足以提出有区分力的下一步实验。" : "The evidence does not justify a discriminating next experiment."}</p> : null}
{s.next_experiments.map((experiment, i) => <article key={i} className="research-report-experiment">
Expand All @@ -73,8 +78,7 @@ export function ResearchSynthesis({synthesis: s, zh, detail = false, onExpand, o
<header>{!detail ? <h3>{zh ? "研究汇报" : "Research report"}</h3> : null}<span>{zh ? "Agent 分析" : "Agent analysis"}</span>
{!detail && onExpand ? <button type="button" className="research-report-expand" onClick={event => onExpand(event.currentTarget)}>{zh ? "深入解读" : "Read deeper"}<ArrowUpRight size={14} aria-hidden/></button> : null}
</header>
<p className="research-synthesis-summary">{s.summary.text}</p>
<details className="research-report-summary-sources"><summary>{zh ? "概览来源" : "Summary sources"} · {s.summary.citations.length}</summary>{citations(s.summary.citations)}</details>
{!detail ? overview : null}
{body}
</section>;
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 16 additions & 7 deletions examples/personal-workspace-browser/research-history.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -218,16 +218,23 @@ 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);
if (await synthesisPage.locator('.context-help-popover:popover-open').count()) await synthesisPage.keyboard.press('Escape');
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
Expand Down Expand Up @@ -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});
Expand All @@ -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));
Expand Down
Loading