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
@@ -1,6 +1,6 @@
.goal-research-workbench { width: min(1280px, 100%); margin: 0 auto; }
.research-saved-evidence { margin: 24px 0; border-top: 1px solid var(--pw-line); }
.research-saved-evidence > summary { padding: 16px 0; min-height: 44px; cursor: pointer; }
.research-saved-evidence { margin: 20px 0; padding-inline: 16px; border: 1px solid var(--pw-line); border-radius: 10px; background: var(--pw-bg); }
.research-saved-evidence > summary { min-height: 52px; align-content: center; padding: 12px 0; color: var(--pw-text); font-size: var(--text-body); font-weight: 500; cursor: pointer; }
.goal-explore-results { width: 100%; padding: 24px 0; border: 0; border-radius: 0; }
.goal-explore-results > p { font-size: var(--text-lead); line-height: var(--leading-prose); }
.explore-reader-heading { display: flex; align-items: center; gap: 8px; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
.analysis-episode > summary { min-height: 48px; padding-block: 12px; cursor: pointer; list-style: none; }
.analysis-episode > summary::-webkit-details-marker { display: none; }
.analysis-episode-name { min-width: 0; max-width: 76ch; font-weight: 500; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
.analysis-episode[open] > summary .analysis-episode-name { display: block; }
.analysis-episode[open] > summary .analysis-episode-name { display: block; -webkit-line-clamp: unset; overflow: visible; }
.analysis-verdict-label { display: none; }
.analysis-episode-exceptions { display: flex; flex-direction: column; color: var(--pw-muted); }
.analysis-episode > summary > svg { flex-shrink: 0; }
Expand Down Expand Up @@ -81,7 +81,9 @@

/* Readback has one status surface, shared by the summary and its detail. */
.analysis-read-error,.analysis-read-empty{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-block:12px;color:var(--pw-muted)}
.analysis-read-error>button,.analysis-read-empty>button{display:inline-flex;align-items:center;gap:4px;min-height:44px;border:1px solid var(--pw-line);border-radius:6px;padding:8px 12px;color:var(--pw-text);background:var(--pw-bg);font:inherit;cursor:pointer}
.analysis-read-empty{margin-block:0 16px;padding:10px 12px;border:1px solid var(--pw-line);border-radius:8px;background:var(--pw-hover);font-size:var(--text-body)}
.analysis-read-empty>button{margin-inline-start:auto}
.analysis-read-error>button,.analysis-read-empty>button{display:inline-flex;align-items:center;justify-content:center;gap:4px;min-height:44px;border:1px solid var(--pw-line);border-radius:6px;padding:8px 12px;color:var(--pw-text);background:var(--pw-bg);font:inherit;cursor:pointer}
.analysis-read-error>button:disabled,.analysis-read-empty>button:disabled{opacity:.5;cursor:default}
.analysis-read-loading{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;padding-block:16px}
.analysis-read-loading>span{height:5rem;border-radius:6px;background:var(--pw-line)}
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Fragment, useEffect, useId, useLayoutEffect, useMemo, useRef, useState} from "react";
import {defaultParseSearch} from "@tanstack/react-router";
import {ArrowLeft, ArrowRight, Circle, FlaskConical, GitBranch, RefreshCw, Search} from "lucide-react";
import {AlertCircle, ArrowLeft, ArrowRight, Circle, FlaskConical, GitBranch, RefreshCw, Search} from "lucide-react";
import {ResearchRecordSearch} from "./research-record-browser";
import {ResearchAnalysisButton, ResearchAnalysisPanel, ResearchAnalysisStatus, ResearchCitationPanel, ResearchProgressSummary, ResearchReportSummary, useResearchAnalysis} from "./goal-research-analysis";
import {buildExploreTimeline, parseExploreFindingSections, type TimelineFinding} from "../../../../../../loopx/control_plane/presentation/explore_timeline.js";
Expand Down Expand Up @@ -368,6 +368,16 @@ export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0, re
const progressiveOverview = progressive && !readingSelection;
const jump = (target: HTMLElement | null) => {target?.focus({preventScroll: true}); target?.scrollIntoView({block: "start"});};
const showHistory = () => {analysis.close(); requestAnimationFrame(() => jump(detail.current ?? historyAnchor.current));};
const refresh = () => {
setRevision(n => n + 1);
void analysis.read().catch(() => undefined);
};
const focusResearchMode = () => {
const control = document.querySelector<HTMLElement>("[data-testid='goal-research-mode']");
control?.scrollIntoView({block: "nearest"});
control?.querySelector<HTMLElement>("button:not(:disabled)")?.focus({preventScroll: true});
};
const readFailure = error || history.error;
return <section className="research-history" aria-label={copy.title} aria-busy={busy}>
{progressiveOverview ? <>
<nav className="research-reading-order" aria-label={zh ? "研究阅读" : "Research reading"}>
Expand Down Expand Up @@ -398,23 +408,35 @@ export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0, re
<div className="research-analysis-scoped-action">{!progressiveOverview ? <ResearchAnalysisButton analysis={analysis}/> : null}
{readingSelection && analysis.pack ? <span className="research-analysis-scope">{zh ? "分析范围" : "Analysis scope"} · {analysis.pack.title}</span> : null}
</div>
<button type="button" aria-label={copy.refresh} disabled={busy} onClick={() => {
setRevision(n => n + 1);
void analysis.read().catch(() => undefined);
}}><RefreshCw size={15} aria-hidden/></button>
<button type="button" aria-label={copy.refresh} disabled={busy} onClick={refresh}><RefreshCw size={15} aria-hidden/></button>
</div></header>
<div className="research-history-meta">
<p className="research-history-coverage" role="status">{copy.time}{value?.startAt ? <> · <time>{timeLabel(value.startAt)}</time> — <time>{timeLabel(value.endAt)}</time></> : null} · {busy ? `${copy.reading} ${loaded}${read.total ? ` / ${read.total}` : ""}`
: read.total === null ? (zh ? "来源尚未读到" : "Source has not been read")
: `${read.events.length} / ${read.total} ${copy.records}${read.complete ? "" : ` · ${copy.partial}`}`}</p>
{!readingSelection ? displayControl : null}
</div>
{error || history.error ? <p role="alert" className="research-history-error">{copy.failure} <small>{error || history.error}</small></p> : null}
{!busy && !error && read.total === 0 ? <p className="research-history-empty" role="status">{zh ? "这个 Goal 尚无已记录的研究历程。" : "This Goal has no recorded research history yet."}</p> : null}
{readFailure ? <div className="research-history-read-error" role="alert">
<AlertCircle size={18} aria-hidden/>
<div><strong>{read.events.length ? (zh ? "刷新失败,当前记录已保留" : "Refresh failed; current records are preserved") : copy.failure}</strong>
<p>{read.events.length ? (zh ? "可继续阅读,或重试获取最新记录。" : "Keep reading, or retry for the latest records.") : (zh ? "请检查本地服务后重试;技术详情默认收起。" : "Check the local service and retry. Technical details stay collapsed.")}</p>
<details><summary>{zh ? "技术详情" : "Technical details"}</summary><code>{readFailure}</code></details>
</div>
<button type="button" disabled={busy} onClick={refresh}><RefreshCw size={14} aria-hidden/>{zh ? "重试" : "Retry"}</button>
</div> : null}
{!busy && !readFailure && read.total === 0 ? <div className="research-history-empty">
<FlaskConical size={22} aria-hidden/>
<div role="status"><strong>{zh ? "这个 Goal 尚无研究历程" : "No research history for this Goal yet"}</strong><p>{zh ? "开启研究模式后,后续研究问题、尝试与证据会在这里形成可回读的历程。" : "Enable research mode to build a readable history of questions, attempts and evidence from later work."}</p></div>
<button type="button" onClick={focusResearchMode}>{zh ? "查看研究模式" : "Review research mode"}</button>
</div> : null}
{!progressiveOverview ? <><ResearchAnalysisStatus analysis={analysis}/>
{!readingSelection ? <ResearchProgressSummary analysis={analysis}/> : null}
<ResearchAnalysisPanel analysis={analysis} onHistory={showHistory}/><ResearchCitationPanel analysis={analysis}/></> : null}
{busy && !read.events.length ? <div className="research-history-loading" role="status">{copy.loading}…</div> : null}
{busy && !read.events.length ? <div className="research-history-loading" role="status" aria-label={copy.loading}>
<span className="research-history-loading-label">{copy.loading}…</span>
<div className="research-history-loading-phases" aria-hidden>{Array.from({length: 3}, (_, index) => <span key={index}/>)}</div>
<div className="research-history-loading-chart" aria-hidden>{Array.from({length: 18}, (_, index) => <span key={index}/>)}</div>
</div> : null}
{value?.nodes.length ? <div className={`research-history-layout${selected || selectedMembers?.length ? " has-selection" : ""}`}>

{selected ? <div className="research-history-detail" ref={detail} role="region" aria-label={selected.latest.title} tabIndex={-1} onKeyDown={event => {if (event.key === "Escape") {event.stopPropagation(); closeReader();}}}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
.research-assessment > summary { display: grid; grid-template-columns: minmax(0, 1fr) auto 14px; align-items: center; gap: 16px; padding-block: 14px; list-style: none; font-size: var(--text-lead); line-height: var(--leading-body); }
.research-assessment > summary::-webkit-details-marker { display: none; }
.research-assessment-name { min-width: 0; font-weight: 500; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
.research-assessment[open] > summary .research-assessment-name { display: block; }
.research-assessment[open] > summary .research-assessment-name { display: block; -webkit-line-clamp: unset; overflow: visible; }
.research-assessment-verdict { color: var(--pw-muted); font-size: var(--text-body); }
.research-assessment[open] > summary > svg { transform: rotate(180deg); }
.research-assessment-body { max-width: 80ch; padding-block: 0 16px; }
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.
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 14 additions & 3 deletions examples/personal-workspace-browser/research-history.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,12 @@ export const researchHistoryScenario = {
await history.getByRole('button',{name:'刷新记录',exact:true}).click();
await history.getByRole('alert').waitFor();
assert.match(await history.textContent(),/76 \/ 76/,'A failed refresh retains the complete real capture');
const readError = history.locator('.research-history-read-error');
assert.match(await readError.innerText(), /刷新失败,当前记录已保留/);
assert.equal(await readError.locator('details').evaluate(element => element.open), false, 'Technical failure detail stays secondary');
await readError.screenshot({path:resolve(outputDir,'research-history-refresh-error.png'),animations:'disabled'});
unavailable=false;
await history.getByRole('button',{name:'刷新记录',exact:true}).click();
await readError.getByRole('button',{name:'重试',exact:true}).click();
await history.getByRole('alert').waitFor({state:'hidden'});
await page.setViewportSize({width:390,height:844});
assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth+1));
Expand Down Expand Up @@ -1084,6 +1088,11 @@ print(json.dumps([
const previousOwner = owner; owner = emptyOwner; await previousOwner.close();
await page.reload({waitUntil: "networkidle"});
await history.getByRole("status").filter({hasText: "0 / 0"}).waitFor();
const emptyHistory = history.locator('.research-history-empty');
await emptyHistory.screenshot({path: resolve(outputDir, 'research-history-empty.png'), animations: 'disabled'});
await emptyHistory.getByRole('button', {name: '查看研究模式', exact: true}).click();
assert(await page.locator("[data-testid='goal-research-mode']").evaluate(element => element.contains(document.activeElement)),
'The empty-state action focuses the existing research-mode control');
await progress.locator(`.research-progress-scope[data-pack-id="${"d".repeat(64)}"]`).waitFor();

comparisonFixture = true;
Expand Down Expand Up @@ -1145,8 +1154,10 @@ print(json.dumps([
await page.screenshot({path: resolve(outputDir, 'research-assessment-comparison-mobile.png'), animations: 'disabled'});
await rows.last().locator(':scope > summary').click();
await rows.last().getByText(/Synthetic evidence connects/).waitFor();
assert(await rows.last().locator('.research-assessment-name').evaluate(element => element.clientHeight >= element.scrollHeight),
'Expanding the row restores the complete question without a second copy');
assert(await rows.last().locator('.research-assessment-name').evaluate(element => {
const style = getComputedStyle(element);
return style.display === 'block' && style.webkitLineClamp === 'none' && style.overflow === 'visible';
}), 'Expanding the row restores the complete question without a second copy');
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1));
await page.screenshot({path: resolve(outputDir, 'research-assessment-expanded-mobile.png'), animations: 'disabled'});
await metricDetail.getByRole('button', {name: '收起指标详情', exact: true}).click();
Expand Down
Loading