diff --git a/apps/presentation/dashboard/smoke/explore-finding-diagnostics-smoke.ts b/apps/presentation/dashboard/smoke/explore-finding-diagnostics-smoke.ts new file mode 100644 index 0000000000..f9ea7bbca2 --- /dev/null +++ b/apps/presentation/dashboard/smoke/explore-finding-diagnostics-smoke.ts @@ -0,0 +1,31 @@ +import assert from "node:assert/strict"; +import {fetchExploreResults} from "../src/data/chat"; + +// The saved-finding transport used to strip the backend's quarantine reason. +// A measured, quarantined and unmeasured finding must remain distinguishable. +const row = {finding_id: "legacy", finding: "Recorded finding", summary: "Original observation", + status: "confirmed", node_id: "question", question: "Compare candidates", scope: "Fixed input", + agent_id: "recorder", evidence_refs: ["result:original"], last_updated_at: "2026-01-01T00:00:00Z", + linked_todos: []}; +const measurement = {metric: "quality", value: 8, unit: "points", higher_is_better: true, + comparison_scope: "validation:fixed", input_revision: "input:v1", evaluation_ref: "method:v1", + candidate_ref: "candidate:a", evidence_ref: "result:original"}; +const reason = "measurements must cite the executed candidate's result reference"; +const previousFetch = globalThis.fetch; +try { + globalThis.fetch = async () => new Response(JSON.stringify({ok: true, goal_id: "research", total: 3, + next_cursor: null, items: [{...row, measurement_error: reason}, + {...row, finding_id: "measured", measurements: [measurement]}, {...row, finding_id: "unmeasured"}]}), + {headers: {"Content-Type": "application/json"}}); + const page = await fetchExploreResults("research"); + assert.equal(page.items[0]!.measurement_error, reason, "Quarantine reason reaches the existing reader"); + assert.equal(page.items[0]!.measurements, undefined, "Quarantined quantities stay absent"); + assert.equal(page.items[0]!.status, "confirmed", "The transport does not rewrite the recorded finding status"); + assert.equal(page.items[1]!.measurements?.[0]?.value, 8, "Valid quantities retain their value"); + assert.equal(page.items[1]!.measurement_error, undefined); + assert.equal(page.items[2]!.measurements, undefined); + assert.equal(page.items[2]!.measurement_error, undefined, "Unmeasured is not a quarantine error"); +} finally { + globalThis.fetch = previousFetch; +} +console.log("PASS saved finding measurement diagnostics, valid values and unmeasured parity"); diff --git a/apps/presentation/dashboard/src/data/chat.ts b/apps/presentation/dashboard/src/data/chat.ts index 0ecc419867..d13a1c0c44 100644 --- a/apps/presentation/dashboard/src/data/chat.ts +++ b/apps/presentation/dashboard/src/data/chat.ts @@ -2914,7 +2914,8 @@ const exploreResultPageSchema = z.object({ finding_id: z.string(), finding: z.string(), summary: z.string(), status: z.string(), node_id: z.string(), question: z.string(), scope: z.string(), agent_id: z.string(), evidence_refs: z.array(z.string()), last_updated_at: z.string(), - measurements: z.unknown().transform(normalizeExploreMeasurements).optional(), + measurements: z.unknown().transform(value => normalizeExploreMeasurements(value)).optional(), + measurement_error: z.string().optional(), linked_todos_available: z.boolean().optional(), linked_todos: z.array(z.object({ todo_id: z.string(), text: z.string(), status: z.string(), claimed_by: z.string(), diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-explore-results.css b/apps/presentation/dashboard/src/features/personal-workspace/goal-explore-results.css index 1bb3ad8753..4adcd3e56c 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-explore-results.css +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-explore-results.css @@ -17,6 +17,7 @@ .goal-explore-results .goal-team-result-reader { overflow-wrap: anywhere; } .goal-explore-results .goal-team-result-reader > h4 { font-size: var(--text-lead); line-height: var(--leading-lead); font-weight: 600; margin: 0 0 12px; } .explore-reader-status { color: var(--pw-muted); font-size: var(--text-caption); } +.explore-measurement-warning { margin-block: 16px; font-size: var(--text-body); line-height: var(--leading-body); } .goal-explore-results .goal-team-result-reader p, .goal-explore-results .goal-team-result-reader li { font-size: var(--text-lead); line-height: var(--leading-prose); color: var(--pw-text); } .explore-finding-prose, .explore-finding-raw { max-width: 72ch; } .explore-finding-prose section { margin: 24px 0; } diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-explore-results.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-explore-results.tsx index 33691654da..a69fbebc54 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-explore-results.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-explore-results.tsx @@ -1,6 +1,6 @@ import {exploreFindingCopy} from "./explore-finding-copy"; import {useEffect, useRef, useState} from "react"; -import {ChevronRight, RefreshCw} from "lucide-react"; +import {AlertTriangle, ChevronRight, RefreshCw} from "lucide-react"; import {fetchExploreResults, readTodoRequest, type ExploreResultPage} from "../../data/chat"; import {parseExploreFindingSections} from "../../../../../../loopx/control_plane/presentation/explore_timeline.js"; import {ExploreMeasurements} from "./explore-measurements"; @@ -111,7 +111,13 @@ export function GoalExploreResults({goalId, zh, active = true, readSourceUrl}: { {selected ?
{selected.measurement_error}
+{zh ? "请记录者追加执行与测量归属一致的更正结论;原记录仍保留在历史中。" : "Ask the recorder to append a corrected finding with matching execution and measurement references. The original stays in history."}
+{sections.observation}
{sections.interpretation}