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 @@ -102,11 +102,20 @@
.research-record-filters { display: flex; flex-wrap: wrap; gap: 8px; margin-block: 12px; }
.research-record-filters input { flex: 1 1 220px; min-width: 0; }
.research-record-filters input, .research-record-filters select, .research-record-search button { min-height: 44px; padding: 8px 12px; border: 1px solid var(--pw-line); border-radius: 6px; color: var(--pw-text); background: var(--pw-bg); font: inherit; }
.research-record-search > p { color: var(--pw-muted); }
.research-record-count, .research-comparison-controls { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; margin-block: 12px; color: var(--pw-muted); font-size: var(--text-body); }
.research-comparison-controls > label { margin-inline-start: auto; color: var(--pw-text); }
.research-record-search .context-help-trigger { min-height: 28px; padding: 4px; border: 0; border-radius: 4px; }
.research-record-search .research-history-records { list-style: none; padding: 0; }
.research-record-search li { border-bottom: 1px solid var(--pw-line); }
.research-record-search summary { min-height: 44px; overflow-wrap: anywhere; }
.research-record-search summary span { display: block; color: var(--pw-muted); font-size: var(--text-caption); }
.research-record-entry:not([open]) > summary > strong { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
.research-record-entry[open] > summary > strong { display: block; line-height: var(--leading-prose); }
.research-record-entry > summary { position: relative; padding-inline-start: 18px; list-style: none; }
.research-record-entry > summary::-webkit-details-marker { display: none; }
.research-record-entry > summary::before { content: "▸"; position: absolute; inset-inline-start: 0; }
.research-record-entry[open] > summary::before { content: "▾"; }
.research-record-sources { margin-block: 12px; }
.research-record-search p { white-space: pre-wrap; line-height: var(--leading-prose); overflow-wrap: anywhere; }
.research-record-search code { display: block; overflow-wrap: anywhere; font-size: var(--text-body); }
.research-record-search dd { margin: 0 0 8px; }
Expand Down Expand Up @@ -292,10 +301,12 @@
.research-comparison-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }
.research-comparison-pair > div { min-width: 0; overflow-wrap: anywhere; white-space: pre-wrap; }
.research-comparison-pair pre { white-space: pre-wrap; }
.research-comparison-identities p { margin-block: 4px 12px; }
.research-comparison-field { border-block-start: 1px solid var(--pw-line); padding-block: 12px; }
.research-comparison-side, .research-comparison-missing { color: var(--pw-muted); }
.research-comparison-side { display: none; }
@media (max-width: 640px) {
.research-comparison-pair { grid-template-columns: 1fr; gap: 16px; }
.research-comparison-identities { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.research-comparison-side { display: block; margin-block-end: 4px; }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ResearchRecordComparison} from "./research-record-comparison";
import {CircleHelp} from "lucide-react";
import {CircleHelp, TriangleAlert} from "lucide-react";
import {ContextHelp} from "./context-help";
import {useMemo, useState, type ReactNode} from "react";
import type {ExploreHistoryEvent} from "../../../../../../loopx/control_plane/presentation/explore_history.ts";
Expand All @@ -21,6 +21,8 @@ export function ResearchRecordSearch({goalId, events, displayEvents, renderBody,
const matches = useMemo(() => searchResearchRecords(events, query, status), [events, query, status]);
const displayed = useMemo(() => new Map(displayEvents?.map(event => [event.event_id, event])), [displayEvents]);
const statuses = [...new Set(events.map(event => event.status).filter(Boolean))].sort();
const countLabel = zh ? `匹配 ${matches.length} / ${events.length} 条已读记录` : `${matches.length} matches in ${events.length} loaded records`;
const readState = busy ? (zh ? "读取中" : "Reading") : stale ? (zh ? "刷新失败" : "Refresh failed") : "";
const reset = () => {setQuery(""); setStatus(""); setLimit(50);};
return <div className="research-record-search">
<div className="research-record-filters">
Expand All @@ -42,22 +44,32 @@ export function ResearchRecordSearch({goalId, events, displayEvents, renderBody,
}}>{zh ? "下载匹配记录(JSON)" : "Download matches (JSON)"}</button>
</div>
{downloadFailed ? <p role="alert">{zh ? "无法开始下载,记录未改变。请重试下载。" : "Could not start the download. Records are unchanged; try downloading again."}</p> : null}
<p role="status">{zh ? `匹配 ${matches.length} / ${events.length} 条已读记录` : `${matches.length} matches in ${events.length} loaded records`}
{!complete ? (zh ? " · 历史不完整" : " · History incomplete") : ""}
{busy ? (zh ? " · 正在读取历史" : " · Reading history") : stale ? (zh ? " · 刷新失败,显示上次读取的记录" : " · Refresh failed; showing previously read records")
: ""}</p>
{!matches.length ? <ContextHelp label={zh ? "已读范围无匹配记录" : "No matches in loaded records"} icon={CircleHelp}><p>{complete && !stale && !busy
? (zh ? "没有匹配的记录。尝试其他文字或清除筛选。" : "No matching records. Try another term or clear the filters.")
: (zh ? "已读部分没有匹配记录;其余历史尚未核验。" : "No matches in the loaded records; the remaining history is not verified.")}</p></ContextHelp> : null}
<div className="research-record-count">
<span role="status" aria-label={`${countLabel}${readState ? ` · ${readState}` : ""}`}>
{matches.length} / {events.length} {zh ? "条记录" : "records"}{readState ? ` · ${readState}` : ""}
</span>
<ContextHelp label={!matches.length ? (zh ? "已读范围无匹配记录" : "No matches in loaded records")
: stale ? (zh ? "刷新失败;搜索范围" : "Refresh failed; search scope")
: !complete ? (zh ? "历史不完整;搜索范围" : "Incomplete history; search scope") : (zh ? "搜索范围" : "Search scope")}
icon={!complete || stale ? TriangleAlert : CircleHelp}>
<p>{countLabel}{zh ? "。" : "."}</p>
<p>{complete ? (zh ? "已搜索完整载入的历史。" : "Search covers the complete loaded history.") : (zh ? "历史不完整;其余记录尚未核验。" : "History is incomplete; remaining records have not been verified.")}</p>
{stale ? <p>{zh ? "刷新失败,显示上次读取的记录。" : "Refresh failed; showing previously read records."}</p> : null}
{!matches.length ? <p>{complete && !stale && !busy
? (zh ? "没有匹配的记录。尝试其他文字或清除筛选。" : "No matching records. Try another term or clear the filters.")
: (zh ? "已读部分没有匹配记录;其余历史尚未核验。" : "No matches in the loaded records; the remaining history is not verified.")}</p> : null}
</ContextHelp>
</div>
<ol className="research-history-records">{matches.slice(0, limit).map(event => {
const shown = displayed.get(event.event_id) ?? event;
return <li key={event.event_id}>
<details><summary><strong>{shown.title}</strong><span>{timeLabel(event.recorded_at)} · {stateLabel(event.status)}</span></summary>
<details className="research-record-entry"><summary><strong>{shown.title}</strong><span>{timeLabel(event.recorded_at)} · {stateLabel(event.status)}</span></summary>
{renderBody ? renderBody(shown) : event.summary ? <p>{event.summary}</p> : null}
{shown.blocked_reason ? <p>{shown.blocked_reason}</p> : null}
{renderBody ? <details><summary>{zh ? "完整原文" : "Full original text"}</summary>
<h4>{event.title}</h4><p>{event.summary}</p>{event.blocked_reason ? <p>{event.blocked_reason}</p> : null}
</details> : null}
<details className="research-record-sources"><summary>{zh ? "来源与标识" : "Sources and identity"}</summary>
<dl><dt>{zh ? "记录 ID" : "Record ID"}</dt><dd><code>{event.event_id}</code></dd>
<dt>{zh ? "结果 ID" : "Result ID"}</dt><dd><code>{event.result_id}</code></dd>
{event.run_id ? <><dt>{zh ? "运行 ID" : "Run ID"}</dt><dd><code>{event.run_id}</code></dd></> : null}
Expand All @@ -67,8 +79,9 @@ export function ResearchRecordSearch({goalId, events, displayEvents, renderBody,
{event.from_node || event.to_node ? <><dt>{zh ? "显式关联" : "Recorded link"}</dt><dd><code>{event.from_node} → {event.to_node}</code></dd></> : null}
{event.agent_id ? <><dt>Agent ID</dt><dd><code>{event.agent_id}</code></dd></> : null}
</dl>
<ResearchRecordComparison events={events} selected={event} zh={zh} stateLabel={stateLabel} timeLabel={timeLabel}/>
{event.evidence_refs.map(ref => <code key={ref}>{ref}</code>)}
</details>
<ResearchRecordComparison events={events} selected={event} zh={zh} stateLabel={stateLabel} timeLabel={timeLabel}/>
</details>
</li>;})}</ol>
{limit < matches.length ? <button type="button" onClick={() => setLimit(value => value + 50)}>{zh ? "继续展开记录" : "Show more records"} · {Math.min(limit, matches.length)} / {matches.length}</button> : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {useState} from "react";
import type {ExploreHistoryEvent} from "../../../../../../loopx/control_plane/presentation/explore_history.ts";
import {changedResearchFields, researchComparisonFields, researchRecordVersions, type ResearchComparisonField} from "./research-record-versions.ts";
import {ExploreMeasurements} from "./explore-measurements";
import {ContextHelp} from "./context-help";

const labels: Record<ResearchComparisonField, [string, string]> = {
title: ["Title", "标题"], summary: ["Original text", "原文"], status: ["Recorded status", "记录状态"],
Expand Down Expand Up @@ -43,12 +44,18 @@ export function ResearchRecordComparison({events, selected, zh, stateLabel, time
<label>{zh ? "比较对象" : "Compare with"}<select value={comparison.event_id} onChange={event => setChosen(event.target.value)}>
{alternatives.map(event => <option key={event.event_id} value={event.event_id}>{timeLabel(event.recorded_at)} · {event.title} · {event.event_id}</option>)}
</select></label>
<p>{zh ? "比较原始记录,不代表同条件评测或研究质量变化。" : "Compares original records, not matched evaluations or changes in research quality."}</p>
<div className="research-comparison-pair">
{[selected, comparison].map((event, i) => <div key={event.event_id}><strong>{i === 0 ? (zh ? "所选记录" : "Selected record") : (zh ? "对照记录" : "Comparison record")}</strong><p>{timeLabel(event.recorded_at)}</p><code>{event.event_id}</code></div>)}
<div className="research-comparison-controls">
<span role="status">{zh ? `${changed.length} 个字段不同` : `${changed.length} fields differ`}</span>
<ContextHelp label={zh ? "比较范围与记录标识" : "Comparison scope and record identities"}>
<p>{zh ? "比较原始记录,不代表同条件评测或研究质量变化。" : "Compares original records, not matched evaluations or changes in research quality."}</p>
<p>{zh ? "所选记录" : "Selected record"}<code>{selected.event_id}</code></p>
<p>{zh ? "对照记录" : "Comparison record"}<code>{comparison.event_id}</code></p>
</ContextHelp>
<label><input type="checkbox" checked={showAll} onChange={event => setShowAll(event.target.checked)}/>{zh ? "包括相同字段" : "Include unchanged fields"}</label>
</div>
<div className="research-comparison-pair research-comparison-identities">
{[selected, comparison].map((event, i) => <div key={event.event_id}><strong>{i === 0 ? (zh ? "所选记录" : "Selected record") : (zh ? "对照记录" : "Comparison record")}</strong><p>{timeLabel(event.recorded_at)}</p></div>)}
</div>
<p role="status">{zh ? `${changed.length} 个字段不同` : `${changed.length} fields differ`}</p>
<label><input type="checkbox" checked={showAll} onChange={event => setShowAll(event.target.checked)}/>{zh ? "包括相同字段" : "Include unchanged fields"}</label>
{(showAll ? researchComparisonFields : changed).map(field => <section key={field} className="research-comparison-field">
<h4>{labels[field][zh ? 1 : 0]}</h4>
<div className="research-comparison-pair">{[selected, comparison].map((event, i) => <div key={event.event_id}>
Expand Down
10 changes: 10 additions & 0 deletions docs/development/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,16 @@ action consequences or relying on fixed alignment offsets.
Reading a citation opens only the requested original, preserving the explicit
open or closed state of the full analysis and returning to the exact citation.

Search results use the same reading hierarchy: compact title, recorded time and
status in the list; opening a row restores its complete title and body. Keep
protocol identities and source references in one disclosure, with version
comparison beside it. Counts stay compact; coverage and comparison-method
explanations use contextual help. Reading failures retain a visible state and
the existing recovery action. Do not clamp the opened document or its evidence.

搜索列表以标题、时间和记录状态供浏览,展开后完整呈现正文。来源与标识合并按需查看,
版本比较保留原位入口;范围、口径用共享帮助,真实读取失败和恢复动作仍可见。

When an explicit analysis supplies a whole-history translation, use it throughout
the matching research snapshot, including routes, decisions and expanded history.
Offer one in-place original/translation switch; keep coverage and source/version
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Research record reading

Public-safe synthetic data: 79 recorded events and one separate single-record
Goal, served by the packaged Chat frontend and disposable production history
backend. These are presentation and recovery checks, not scientific results or
installed-App adoption.

- Before: `cd819fd206cd9de46a789df0669e13bb2ed763e5`.
- List views use the same long, mixed-language records before and after.
- Record views retain the same negative observation and its three revisions.
- Source identities remain readable on demand; the Chinese view retains original
evidence text in its original language.
- Comparison views preserve unmeasured versus measured zero and both originals.
- `first-desktop.png` shows the complete first viewport before opening search.

The PR description records the tested candidate revision and validation. The
existing `research-record-search` scenario recreates the captures and checks
paging, filtered downloads, keyboard expansion, help, partial/failed reads,
recovery and Goal isolation without research execution or source mutations.
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
Loading