diff --git a/apps/presentation/dashboard/docs/assets/research-native-results/after-desktop.png b/apps/presentation/dashboard/docs/assets/research-native-results/after-desktop.png new file mode 100644 index 0000000000..d29c8d2bbc Binary files /dev/null and b/apps/presentation/dashboard/docs/assets/research-native-results/after-desktop.png differ diff --git a/apps/presentation/dashboard/docs/assets/research-native-results/after-phone.png b/apps/presentation/dashboard/docs/assets/research-native-results/after-phone.png new file mode 100644 index 0000000000..0095cef143 Binary files /dev/null and b/apps/presentation/dashboard/docs/assets/research-native-results/after-phone.png differ diff --git a/apps/presentation/dashboard/docs/assets/research-native-results/before-desktop.png b/apps/presentation/dashboard/docs/assets/research-native-results/before-desktop.png new file mode 100644 index 0000000000..e840105eea Binary files /dev/null and b/apps/presentation/dashboard/docs/assets/research-native-results/before-desktop.png differ diff --git a/apps/presentation/dashboard/docs/assets/research-native-results/before-phone.png b/apps/presentation/dashboard/docs/assets/research-native-results/before-phone.png new file mode 100644 index 0000000000..54c3766183 Binary files /dev/null and b/apps/presentation/dashboard/docs/assets/research-native-results/before-phone.png differ diff --git a/apps/presentation/dashboard/docs/assets/research-native-results/first-desktop.png b/apps/presentation/dashboard/docs/assets/research-native-results/first-desktop.png new file mode 100644 index 0000000000..730fab65e9 Binary files /dev/null and b/apps/presentation/dashboard/docs/assets/research-native-results/first-desktop.png differ diff --git a/apps/presentation/dashboard/docs/assets/research-native-results/first-phone.png b/apps/presentation/dashboard/docs/assets/research-native-results/first-phone.png new file mode 100644 index 0000000000..0a99a322a3 Binary files /dev/null and b/apps/presentation/dashboard/docs/assets/research-native-results/first-phone.png differ diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.css b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.css index 11f4b4e827..45c0b81786 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.css +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.css @@ -21,6 +21,20 @@ .research-effect-evidence article > span { font-size: var(--text-caption); } .research-effect-citations { display: flex; flex-wrap: wrap; gap: 4px; } .research-effect-citations button { display: inline-flex; align-items: center; gap: 4px; min-height: 44px; font: inherit; font-size: var(--text-caption); background: transparent; border: 1px solid var(--pw-line); border-radius: 6px; padding: 4px 8px; color: var(--pw-text); } +.research-native-results { border-block-start: 1px solid var(--pw-line); margin-block-start: 16px; padding-block-start: 16px; } +.research-native-results h4 { margin: 0; font-size: var(--text-caption); font-weight: 600; } +.research-native-results > p { margin-block: 8px; } +.research-native-values { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px 16px; margin-block: 12px; } +.research-native-values > div { min-width: 0; } +.research-native-values dt, .research-native-values dd > span { font-size: var(--text-caption); color: var(--pw-muted); } +.research-native-values dd { margin: 0; } +.research-native-values dd button { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 0; font: inherit; background: none; border: 0; color: var(--pw-text); cursor: pointer; } +.research-native-values dd button:hover { text-decoration: underline; } +.research-native-values dd button:focus-visible { outline: 2px solid var(--pw-blue); outline-offset: 2px; } +.research-native-basis dd { margin: 4px 0 12px; } +.research-native-values strong { font-size: var(--text-body); font-weight: 600; font-variant-numeric: tabular-nums; } +.research-native-values small { display: block; } +.research-native-basis { font-size: var(--text-caption); } .research-progress-basis > dl, .research-usage-basis { display: grid; grid-template-columns: 120px minmax(0, 1fr); gap: 8px 16px; font-size: var(--text-caption); } .research-progress-basis dt, .research-usage-basis dt { color: var(--pw-muted); } .research-progress-basis dd, .research-usage-basis dd { margin: 0; } @@ -35,6 +49,7 @@ .research-progress label { flex-direction: column; align-items: stretch; width: 100%; } .research-progress select { max-width: 100%; } .research-effect-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 16px; } + .research-native-values { grid-template-columns: repeat(2, minmax(0, 1fr)); } .research-progress-values { grid-template-columns: 1fr; } .research-progress-basis > dl, .research-usage-basis { grid-template-columns: 1fr; gap: 4px; } .research-progress-basis dd, .research-usage-basis dd { margin-bottom: 8px; } diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.tsx index 855b558a93..9d6d529444 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.tsx @@ -27,9 +27,39 @@ export function verdictLabel(metric: TrajectoryMetric, verdict: AssessmentVerdic return verdict === "yes" ? (zh ? "有依据" : "Supported") : (zh ? "未建立" : "Not established"); } +// Format the native owner's existing projection, without deriving a gain, +// deciding adoption, or borrowing values from another pack. +function NativeResults({native, zh, onCitation}: {native: ResearchProgressPack["native_outcomes"]; zh: boolean; onCitation: (citation: AssessmentCitation, origin?: HTMLButtonElement) => void}) { + const outcomes = native?.projection; + if (!native || !outcomes) return

{zh ? "原生测量:尚无同范围捕获;分析器判读不能补成实际收益。" : "Native measurements: no capture for this scope. Analyzer judgments cannot supply realized utility."}

; + const historical = native.applicability === "historical"; + const points = [["initial", "首次提交", "Initial submission"], ["best_valid", "窗口内最佳有效值", "Best valid in capture"], ["retained", "原生保留结果", "Native retained result"], ["final_valid", "最后有效提交", "Final valid submission"]] as const; + return
+

{historical ? (zh ? "历史原生测量" : "Historical native measurements") : (zh ? "原生测量" : "Native measurements")}

+ {historical ?

{zh ? "来源已变化或不可读;以下仅为历史捕获,当前结果未核验。" : "The source changed or is unavailable. These are historical captures; current results are unverified."}

: null} +

{outcomes.metric.name} · {outcomes.metric.unit} · {outcomes.metric.higher_is_better ? (zh ? "越高越好" : "higher is better") : (zh ? "越低越好" : "lower is better")} · {outcomes.valid_count} / {outcomes.sample_count} {zh ? "项有效结果" : "valid results"}

+

{outcomes.complete ? (zh ? "声明为完整前缀" : "Declared complete prefix") : (zh ? "部分窗口,起点未知" : "Partial window; initial unknown")} · —

+
{points.map(([key, cn, en]) => { + const point = outcomes[key]; + return
{zh ? cn : en}
{point ? <> + + {zh ? "提交" : "Submission"} {point.submission_index} + : {zh ? "— 未知,不记零" : "— Unknown, not zero"}}
; + })}
+

{zh ? "最佳只限上述窗口;原生保留与最后提交可能不同。检验过收益、采用了证据及原目标达成各需各自证据,不能由这些数值推断。" : "Best is limited to this capture; retained and final submissions can differ. Utility testing, evidence adoption and original-objective success each need their own evidence."}

+
{zh ? "测量来源与限制" : "Measurement sources and limits"} +

{native.artifact}
{native.artifact_sha256}

+
{points.map(([key, cn, en]) => { + const point = outcomes[key]; + return point ?
{zh ? cn : en}
{point.candidate_ref}
{zh ? "提交" : "Submitted"}: {point.submitted_at}
{zh ? "收到结果" : "Result received"}: {point.observed_at}
: null; + })}
+ +
+
; +} + export function ResearchProgress({pack: p, packs, selectedId, error, zh, onSelect, onCitation}: {pack: ResearchProgressPack | null; packs: Pick[]; selectedId: string; error: string; zh: boolean; onSelect: (id: string) => void; onCitation: (citation: AssessmentCitation, origin?: HTMLButtonElement) => void}) { const measured = p?.solver_usage, usage = measured?.projection, result = p?.result; - const native = p?.native_outcomes, outcomes = native?.projection; const number = (n: number) => new Intl.NumberFormat(zh ? "zh-CN" : "en", {maximumFractionDigits: 1}).format(n); const percent = (n: number) => new Intl.NumberFormat(zh ? "zh-CN" : "en", {style: "percent", maximumFractionDigits: 0}).format(n); const duration = (s: number) => `${number(s / 60)} ${zh ? "分钟" : "min"}`; @@ -70,21 +100,12 @@ export function ResearchProgress({pack: p, packs, selectedId, error, zh, onSelec

{zh ? "六项分别判断,不合成总分、成功率或原生收益。" : "Six separate judgments, not a total score, success rate or native utility."}

:

{zh ? "此范围尚无分析器判读。已有用量不能替代效果;按需深度分析后在此展示。" : "No analyzer judgments exist for this scope. Usage is not effectiveness; requested deep analysis will appear here."}

} +
{zh ? "来源、方法与覆盖" : "Sources, method and coverage"}
{zh ? "题目 / 运行" : "Case / run"}
{p.scope.case_id}
{p.scope.run_id}
{zh ? "证据窗口" : "Evidence window"}
{p.scope.window}
{zh ? "取样规则" : "Selection rule"}
{p.selection.rule}
{zh ? "总体研究片段数" : "Population of episodes"}
{p.selection.population_episodes == null ? (zh ? "未知" : "Unknown") : number(p.selection.population_episodes)}
{result ? <>
{zh ? "分析方法" : "Analysis method"}
{result.method_version} · {zh ? "Agent 判读未校准" : "Uncalibrated Agent judgment"}
{zh ? "结果标识" : "Result key"}
{result.analysis_key}
{zh ? "分析限制" : "Limitations"}
{result.limitations.map((line, i) =>

{line}

)}
: null}
{zh ? "来源版本" : "Source revision"}
{p.scope.revision}
-
{zh ? "原生结果" : "Native outcomes"}
{outcomes ? <> -

{outcomes.metric.name} · {outcomes.metric.unit} · {outcomes.metric.higher_is_better ? (zh ? "越高越好" : "higher is better") : (zh ? "越低越好" : "lower is better")}

-

{outcomes.start} — {outcomes.end} · {outcomes.complete ? (zh ? "声明为完整前缀" : "Declared complete prefix") : (zh ? "部分窗口,起点未知" : "Partial window; initial unknown")} · {outcomes.valid_count} / {outcomes.sample_count} {zh ? "项有效结果" : "valid results"}

-
{([ ["initial", "首次提交", "Initial submission"], ["best_valid", "窗口内最佳有效值", "Best valid in capture"], ["retained", "原生保留结果", "Native retained result"], ["final_valid", "最后有效提交", "Final valid submission"] ] as const).map(([key, cn, en]) => { - const point = outcomes[key]; - return
{zh ? cn : en}
{point ? <>{String(point.value)} · {point.candidate_ref} : (zh ? "— 未知,不记零" : "— Unknown, not zero")}
; - })}
- {native?.applicability === "historical" ?

{zh ? "原生来源已变化或不可读;仅保留历史捕获,当前结果未核验。" : "Native source changed or is unavailable; retained historical capture, current results unverified."}

: null} -

{zh ? "原生测量与分析器判断分开。有效性和保留选择来自提供方,不认证研究因果收益;用量窗口独立,不补算达标成本或全程账单。" : "Native measurements are separate from analyzer judgments. Validity and retention are provider-recorded, not causal benefit. Usage has its own window; threshold and total-study costs are not inferred."}

- : (zh ? "— 尚无同范围的原生结果捕获;不能从分析器判断或用量补算。" : "— No native capture for this scope; analyzer judgments and usage cannot supply it.")}
{zh ? "用量与成本" : "Usage and cost"}{usage ? ` · ${zh ? "捕获跨度" : "Captured elapsed"} ${duration(usage.elapsed_seconds)}` : ` · ${zh ? "尚无完整用量捕获" : "No complete usage capture"}`}{measured?.applicability === "historical" ? (zh ? " · 历史捕获" : " · Historical capture") : ""}

{zh ? "只读捕获,窗口可能与以上判读片段及下方 Explore 历史不同。" : "Read-only capture; its window can differ from the judgments above and Explore history below."}

diff --git a/examples/personal-workspace-browser/research-history.mjs b/examples/personal-workspace-browser/research-history.mjs index ce112863a1..c0a84a9da9 100644 --- a/examples/personal-workspace-browser/research-history.mjs +++ b/examples/personal-workspace-browser/research-history.mjs @@ -27,7 +27,7 @@ function analysisReadback(selected = "a") { ...(["a", "b", "d"].includes(selected) ? {native_outcomes: {artifact: "native-outcomes.json", artifact_sha256: "9".repeat(64), applicability: selected === "b" ? "historical" : "current", reason_code: selected === "b" ? "source_changed" : null, projection: {method: "source_bound_native_outcomes_v1", scope, start: "2026-01-01T00:00:00Z", end: "2026-01-01T00:10:00Z", - complete: true, metric: {name: "error", unit: "points", higher_is_better: false}, sample_count: 3, valid_count: 2, + complete: selected !== "d", metric: {name: "error", unit: "points", higher_is_better: false}, sample_count: 3, valid_count: 2, ...Object.fromEntries(["initial", "best_valid", "retained", "final_valid"].map(key => [key, {submission_index: key === "initial" ? 1 : 3, result_id: key === "initial" ? "native-initial" : "native-final", candidate_ref: key === "initial" ? "candidate-first" : "candidate-retained", submitted_at: "2026-01-01T00:01:00Z", observed_at: "2026-01-01T00:02:00Z", value: key === "initial" ? 7 : 3, @@ -46,7 +46,13 @@ function analysisReadback(selected = "a") { {metric: 'redundancy', verdict: 'no', reason: 'Synthetic evidence records a new question rather than repeating a settled result.'} ].map(item => ({...item, episode_id: 'episode', citations: [{source_id: 'source', first_line: 1, last_line: 2}]}))]}, }}; - if (selected === "d") {snapshot.pack.result = null; snapshot.pack.job = null;} + if (selected === "b") { + snapshot.pack.native_outcomes.projection.sample_count = 4; + snapshot.pack.native_outcomes.projection.valid_count = 3; + snapshot.pack.native_outcomes.projection.final_valid = {...snapshot.pack.native_outcomes.projection.final_valid, + submission_index: 4, result_id: "native-later", candidate_ref: "candidate-later", value: 5}; + } + if (selected === "d") {snapshot.pack.result = null; snapshot.pack.job = null; snapshot.pack.native_outcomes.projection.initial = null;} return snapshot; } @@ -333,20 +339,21 @@ Path(sys.argv[1]).write_text(json.dumps({'goal_id':goal,'display_name':'Syntheti assert.match(await progress.innerText(), /更早失败.*恢复.*评测/s); await progress.locator('.research-usage > summary').click(); const basis = progress.locator('.research-progress-basis'); - await basis.locator('summary').click(); - assert.match(await basis.innerText(), /首次提交.*7.*candidate-first/s); - assert.match(await basis.innerText(), /原生保留结果.*3.*candidate-retained/s); - await basis.getByRole('button', {name: '查看原始证据', exact: true}).first().scrollIntoViewIfNeeded(); + assert.equal(await basis.evaluate(e => e.open), false); + const nativeResults = progress.getByRole('region', {name: '原生测量', exact: true}); + assert.match(await nativeResults.innerText(), /首次提交.*7.*提交 1/s, 'Native measurements need no disclosure before reading'); + assert.match(await nativeResults.innerText(), /原生保留结果.*3.*提交 3/s); + assert.match(await nativeResults.innerText(), /越低越好/); + await nativeResults.getByRole('button', {name: /首次提交.*查看原始证据/}).scrollIntoViewIfNeeded(); assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1), 'Native details remain readable on a narrow screen'); await page.screenshot({path: resolve(outputDir, "research-native-outcomes-mobile.png"), animations: "disabled"}); await page.setViewportSize({width:1280,height:900}); await page.screenshot({path: resolve(outputDir, "research-native-outcomes-desktop.png"), animations: "disabled"}); - await basis.getByRole('button', {name: '查看原始证据', exact: true}).first().click(); + await nativeResults.getByRole('button', {name: /首次提交.*查看原始证据/}).click(); await citedOriginal.waitFor(); assert.match(await citedOriginal.innerText(), /Synthetic original evidence/); await citedOriginal.press('Escape'); await history.getByRole('button', {name: '关闭分析', exact: true}).click(); - await basis.locator('summary').click(); await benefitMetric.locator('summary').click(); assert.match(await progress.innerText(), /0 \/ 1/); assert.match(await progress.innerText(), /1 \/ 2/); @@ -373,6 +380,9 @@ Path(sys.argv[1]).write_text(json.dumps({'goal_id':goal,'display_name':'Syntheti await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))); assert.equal(await analysis.locator("header p").first().textContent(), "Synthetic analysis B", "A delayed same-Goal read cannot undo a pack selection"); assert.match(await progress.innerText(), /历史捕获|historical/i); + assert.match(await nativeResults.innerText(), /历史原生测量.*当前结果未核验/s, 'Stale measurements never appear current'); + assert.match(await nativeResults.locator('[data-native-point="retained"]').innerText(), /3.*提交 3/s); + assert.match(await nativeResults.locator('[data-native-point="final_valid"]').innerText(), /5.*提交 4/s, 'The last submission is not silently presented as retained or best'); await page.reload({waitUntil: "networkidle"}); await analysis.locator("header p").first().filter({hasText: /^Synthetic analysis B$/}).waitFor(); await page.setViewportSize({width: 390, height: 844}); @@ -422,6 +432,8 @@ Path(sys.argv[1]).write_text(json.dumps({'goal_id':goal,'display_name':'Syntheti assert.equal(await analysis.count(), 0, "Changing a factual scope preserves the closed analysis panel"); assert.match(await progress.innerText(), /不适用|N\/A/); assert.doesNotMatch(await progress.innerText(), /0%/); + assert.equal(await nativeResults.count(), 0, 'A missing native capture cannot borrow the previous scope'); + assert.match(await progress.innerText(), /尚无同范围捕获/); await history.getByRole("button", {name: "查看分析", exact: true}).click(); await analysis.locator("header p").first().filter({hasText: /^Synthetic analysis C$/}).waitFor(); await analysis.press('Escape'); @@ -430,14 +442,14 @@ Path(sys.argv[1]).write_text(json.dumps({'goal_id':goal,'display_name':'Syntheti assert.match(await progress.innerText(), /此范围尚无分析器判读/); assert.equal(await progress.locator('.research-effect-metrics').count(), 0, 'An unanalysed cost pack never borrows another pack’s judgments'); assert.equal(await analysis.count(), 0, 'Choosing an unanalysed source never requests analysis'); - await basis.locator('summary').click(); - assert.match(await basis.innerText(), /原生保留结果.*candidate-retained/s, 'Native facts do not require an analyzer result'); - const nativeCitation = basis.getByRole('button', {name: '查看原始证据', exact: true}).first(); + assert.match(await nativeResults.innerText(), /原生保留结果.*3.*提交 3/s, 'Native facts do not require an analyzer result or disclosure'); + assert.match(await nativeResults.innerText(), /部分窗口,起点未知/); + assert.match(await nativeResults.locator('[data-native-point="initial"]').innerText(), /未知,不记零/); + const nativeCitation = nativeResults.getByRole('button', {name: /原生保留结果.*查看原始证据/}); await nativeCitation.focus(); await nativeCitation.press('Enter'); await citedOriginal.waitFor(); await citedOriginal.press('Escape'); await history.getByRole('button', {name: '关闭分析', exact: true}).click(); - await basis.locator('summary').click(); // Analysis sources do not require an Explore log. The real read-only // backend must keep their measurements discoverable with zero events. const emptyFixture = resolve(temporary, "empty.json");