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 @@ -26,16 +26,14 @@
.research-report-tabs > button { min-height: 44px; border: 0; border-block-end: 2px solid transparent; padding: 10px 0; background: none; color: var(--pw-muted); font: inherit; cursor: pointer; }
.research-report-tabs > button[aria-selected="true"] { border-block-end-color: var(--pw-text); color: var(--pw-text); }
.research-report-tabs > button > span { margin-inline-start: 8px; color: var(--pw-muted); font-size: var(--text-body); }
.research-report-columns, .research-report-comparison { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }
.research-report-columns { color: var(--pw-muted); font-size: var(--text-body); padding-block: 0 12px; }
.research-report-comparison { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }
.research-report-finding, .research-report-experiment { border-block-start: 1px solid var(--pw-line); padding-block: 16px; }
.research-report-finding > h5, .research-report-experiment h5 { margin-block-end: 8px; }
.research-report-finding h5 > span, .research-report-experiment h5 > span { color: var(--pw-muted); font-weight: 400; }
.research-report-comparison > div, .research-report-comparison > dl { min-width: 0; }
.research-report-comparison h6 { color: var(--pw-muted); font-weight: 500; font-size: var(--text-body); }
.research-report-account h6 { color: var(--pw-muted); font-weight: 500; font-size: var(--text-body); }
.research-report-experiment > header > p { max-width: 90ch; }
@container (max-width: 700px) {
.research-report-columns { display: none; }
.research-report-comparison { grid-template-columns: 1fr; gap: 12px; }
}
@media (max-width: 640px) {
Expand All @@ -44,7 +42,6 @@
.research-analysis.has-synthesis > header h3 { grid-column: 1; grid-row: 1; align-self: center; }
.research-analysis.has-synthesis > header p { grid-column: 1 / -1; grid-row: 2; }
.research-analysis.has-synthesis > header .analysis-header-actions { grid-column: 2; grid-row: 1; }
.research-report-columns { display: none; }
.research-report-comparison { grid-template-columns: 1fr; gap: 12px; }
.research-synthesis dl { grid-template-columns: 1fr; gap: 4px; }
.research-synthesis dd { margin-block-end: 12px; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@ export function ResearchSynthesis({synthesis: s, zh, detail = false, onExpand, o
const episodeLink = (ids: string[]) => onReviewEpisodes && ids.length ? <button type="button" className="research-review-episodes"
onClick={event => onReviewEpisodes(ids, event.currentTarget)}>{zh ? "查看片段" : "View episodes"} · {ids.length}<ArrowDownRight size={14} aria-hidden/></button> : null;
const controls = (experiment: TrajectoryResearchSynthesis["next_experiments"][number]) => <dl>
<dt>{zh ? "最小干预" : "Smallest intervention"}</dt><dd>{experiment.intervention}</dd>
<dt>{zh ? "拟议干预" : "Proposed intervention"}</dt><dd>{experiment.intervention}</dd>
<dt>{zh ? "固定条件" : "Controls"}</dt><dd>{experiment.controls}</dd>
</dl>;
const predictions = (experiment: TrajectoryResearchSynthesis["next_experiments"][number]) => <dl>
<dt>{zh ? "预期区分" : "Distinguishing prediction"}</dt><dd>{experiment.prediction}</dd>
<dt>{zh ? "推翻解释的结果" : "Disconfirming result"}</dt><dd>{experiment.disconfirming_result}</dd>
</dl>;
const findingAccount = (finding: TrajectoryResearchSynthesis["findings"][number]) => <div className={`research-report-account${detail ? " research-report-comparison" : ""}`}>
<div><h6>{zh ? "观察与依据" : "Observation and evidence"}</h6>
<p>{finding.observation}</p>{citations(finding.citations)}</div>
<div><h6>{zh ? "解释" : "Interpretation"}</h6>
<p>{finding.interpretation}</p>{alternatives(finding.alternative_explanations)}</div>
</div>;
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>
Expand All @@ -35,8 +41,7 @@ export function ResearchSynthesis({synthesis: s, zh, detail = false, onExpand, o
if (!detail) {
body = <>
<ol className="research-report-findings">{s.findings.map((finding, i) => <li key={i}><details className="research-synthesis-finding">
<summary>{finding.title}</summary><p>{finding.observation}</p><p>{finding.interpretation}</p>
{alternatives(finding.alternative_explanations)}{citations(finding.citations)}
<summary>{finding.title}</summary>{findingAccount(finding)}
</details></li>)}</ol>
{s.next_experiments[0] ? <p className="research-report-next"><strong>{zh ? "优先研究" : "Investigate first"}</strong> · {s.next_experiments[0].question}</p> : null}
</>;
Expand All @@ -62,12 +67,10 @@ export function ResearchSynthesis({synthesis: s, zh, detail = false, onExpand, o
{episodeLink(experiment.episode_ids)}
</article>)}
</div> : <div className="research-report-matrix">
{!!s.findings.length && <div className="research-report-columns" aria-hidden><span>{zh ? "观察与依据" : "Observation and evidence"}</span><span>{zh ? "解释与竞争解释" : "Interpretation and alternatives"}</span></div>}
{!s.findings.length ? <p>{zh ? "现有证据不足以形成进一步结论。" : "The evidence does not justify further findings."}</p> : null}
{s.findings.map((finding, i) => <article key={i} className="research-report-finding">
<h5><span>{i + 1}.</span> {finding.title}</h5>
<div className="research-report-comparison"><div><p>{finding.observation}</p>{citations(finding.citations)}</div>
<div><p>{finding.interpretation}</p>{alternatives(finding.alternative_explanations)}</div></div>
{findingAccount(finding)}
{episodeLink(finding.episode_ids)}
</article>)}
</div>}
Expand Down
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Reading observations and interpretations

The same saved finding must distinguish its observation/evidence from its
interpretation and competing explanations in ordinary and deep reading.
Previously ordinary expansion had unlabeled paragraphs, and deep reading hid
its only column labels at narrow widths. Each finding now retains its own
labels, with citations beside the observation.

## Reproduce

Use `research-synthesis-readback.json` through the existing `research-history`
browser scenario and its disposable real SQLite/HTTP reader. Reading uses saved
synthetic provider output; it dispatches no model and starts no research work.
The complete packaged scenario passed, including citation return, exact related
episodes, language switching and read-only failure/recovery cases.

Before: `8a9284079aae2613acc1119b3f77e823abe75f91`; the touched component, stylesheet
and synthetic fixture are byte-identical to target main
`ce58c9141b4b805d4189f89959ade1be1d41ef40`.
After: code commit `3add7e2bc`; subsequent evidence-only changes do not alter it.
Both are packaged source builds, not installed App qualification.

| View | Before | After |
| --- | --- | --- |
| Deep report, 1280×900 | [Desktop](finding-account-before-desktop.png) | [Desktop](finding-account-after-desktop.png) |
| Deep report, 390×844 | [Phone](finding-account-before-phone.png) | [Phone](finding-account-after-phone.png) |
| Ordinary expanded finding | Unlabeled paragraphs | [Desktop](finding-account-ordinary-desktop.png), [phone](finding-account-ordinary-phone.png) |

## Visual judgment

The report remains the reading anchor; the desktop comparison stays in two
columns. On the phone, observation, citation, interpretation and alternatives
remain identifiable when stacked. Later findings keep their own labels, and
unknown retention remains visible. Ordinary findings still start collapsed.
The body remains 16px; neither viewport overflows horizontally. No new panel,
control, banner, color token or mandatory step was added. The original
whole-page results → report → history journey, experiment tab and source
reader remain available. This establishes the display distinction and author
visual inspection, not independent scientific validity or measured user
comprehension improvement.

## Proposed interventions

The saved field is an intervention proposal. Its previous “Smallest
intervention” label could suggest that minimality had already been established.
The reader now says “Proposed intervention” / “拟议干预”; all saved text, controls,
predictions and original citations remain unchanged. This presentation does not
validate a design, authorize execution or change any observer instructions.

The same synthetic saved report and real disposable SQLite/HTTP reader were used
at 1280×900 and 390×844. Before uses packaged UI at
`1f805941a297ca28d53ff093e566fa7df5263160`; after is the same component on the
updated target baseline with the two localized label strings changed. No body
text, layout or configuration changed. The compact label fits both viewports;
body text stays 16px and the full control/prediction detail remains available.
These are author source-build checks, not installed-product or comprehension
qualification.

| View | Before | After |
| --- | --- | --- |
| Proposed experiment, desktop | [Desktop](proposal-label-before-desktop.png) | [Desktop](proposal-label-after-desktop.png) |
| Proposed experiment, phone | [Phone](proposal-label-before-phone.png) | [Phone](proposal-label-after-phone.png) |
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.
24 changes: 24 additions & 0 deletions examples/personal-workspace-browser/research-history.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,20 @@ export const researchHistoryScenario = {
await reading.getByRole('button', {name: '指标', exact: true}).click();
assert(await synthesisPage.evaluate(() => document.activeElement?.contains(document.querySelector('.research-progress'))));
await synthesis.locator('.research-synthesis-finding > summary').first().click();
// A reader must be able to separate the observed result from its
// interpretation in both reading modes, including stacked narrow views.
const assertFindingAccount = async reader => {
const account = reader.locator('.research-report-account').first();
const observed = account.locator(':scope > div').nth(0);
const interpreted = account.locator(':scope > div').nth(1);
assert(await observed.getByRole('heading', {name: '观察与依据', exact: true}).isVisible());
assert.match(await observed.innerText(), /第三次候选误差为 3.*第二次候选无效/s);
assert.equal(await observed.getByRole('button', {name: /^证据 1: facts/}).count(), 1);
assert(await interpreted.getByRole('heading', {name: '解释', exact: true}).isVisible());
assert.match(await interpreted.innerText(), /尚不能定位机制.*随机波动/s);
assert.equal(await interpreted.getByRole('button', {name: /^证据/}).count(), 0, 'An observation citation must not label the interpretation as proven');
};
await assertFindingAccount(synthesis);
await synthesis.getByRole('button', {name: /^证据 1: facts/}).first().click();
const source = synthesisPage.getByRole('region', {name: '引用原文', exact: true});
await source.waitFor();
Expand All @@ -233,6 +247,7 @@ export const researchHistoryScenario = {
await source.press('Escape');
assert.equal(await synthesisPage.getByRole('region', {name: '轨迹深度分析', exact: true}).count(), 0);
await synthesisPage.setViewportSize({width: 390, height: 844});
await assertFindingAccount(synthesis);
await synthesis.locator('.research-synthesis-finding > summary').first().click();
await synthesisPage.evaluate(() => {for (const e of document.querySelectorAll('*')) if (e.scrollTop) e.scrollTop = 0;});
assert(await synthesisPage.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1));
Expand All @@ -244,6 +259,10 @@ 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);
await assertFindingAccount(analysis);
const unretained = analysis.locator('.research-report-account').nth(1);
assert.match(await unretained.innerText(), /原始记录没有保留信息.*保留结果未知/s);
assert.equal(await unretained.getByRole('heading', {name: '竞争解释', exact: true}).count(), 0, 'Missing alternatives are not filled with an invented explanation');
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');
Expand All @@ -261,6 +280,7 @@ export const researchHistoryScenario = {
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 assertFindingAccount(analysis);
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 @@ -331,6 +351,10 @@ export const researchHistoryScenario = {
await synthesisPage.setViewportSize({width: 390, height: 844});
await synthesisPage.getByRole('button', {name: 'Read deeper', exact: true}).click();
const english = synthesisPage.locator('.research-analysis');
const englishAccount = english.locator('.research-report-account').first();
assert(await englishAccount.getByRole('heading', {name: 'Observation and evidence', exact: true}).isVisible());
assert(await englishAccount.getByRole('heading', {name: 'Interpretation', exact: true}).isVisible());
assert.match(await englishAccount.locator(':scope > div').nth(1).innerText(), /mechanism remains unresolved.*Random variation/s);
const englishReview = english.locator('.analysis-method').first();
const englishOrigin = english.locator('.research-report-finding .research-review-episodes').first();
await englishOrigin.click();
Expand Down
Loading