diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-acceptance-observation-card.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-acceptance-observation-card.tsx
index 7a20307b4d..92a7845374 100644
--- a/apps/presentation/dashboard/src/features/personal-workspace/goal-acceptance-observation-card.tsx
+++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-acceptance-observation-card.tsx
@@ -1,7 +1,7 @@
import type { WorkspaceGoal } from "./personal-workspace-model";
import { useWorkspaceI18n } from "./i18n";
-export function GoalAcceptanceObservationCard({ goal }: { goal: WorkspaceGoal }) {
+export function GoalAcceptanceObservationCard({ goal, overview = false }: { goal: WorkspaceGoal; overview?: boolean }) {
const { t, locale } = useWorkspaceI18n();
const checkLabels = locale === "zh-CN" ? {
checkpoint_satisfied: "检查点满足", checkpoint_fresh: "检查点有效",
@@ -22,8 +22,8 @@ export function GoalAcceptanceObservationCard({ goal }: { goal: WorkspaceGoal })
const label = (key: string) => labels[key as keyof typeof labels] ?? t("acceptance.unknown");
const projection = goal.acceptanceObservation;
const unavailable = goal.loadState || !projection || projection.goal_id !== goal.goalId || projection.coverage === "unavailable";
- return
-
{t("acceptance.title")} {t("common.readOnly")}
+ const content =
+ {!overview ?
{t("acceptance.title")} {t("common.readOnly")} : null}
{t(unavailable ? "acceptance.unavailable" : "acceptance.partial")}
{!unavailable && projection ? <>
{t("acceptance.gaps")}
@@ -55,4 +55,11 @@ export function GoalAcceptanceObservationCard({ goal }: { goal: WorkspaceGoal })
{projection.truncated ? {t("acceptance.truncated")}
: null}
> : null}
;
+ if (!overview) return content;
+ const zh = locale === "zh-CN";
+ return
+ {t("acceptance.title")} {unavailable ? t("acceptance.unavailable")
+ : `${projection!.acceptance_gaps.length} ${zh ? "项缺口" : "gaps"} · ${projection!.guards.length} ${zh ? "项限制" : "guards"} · ${zh ? "仅部分观测" : "Partial observations"}`}
+ {content}
+ ;
}
diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.tsx
index 3c7ef29ca0..de1deff525 100644
--- a/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.tsx
+++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.tsx
@@ -111,7 +111,7 @@ export function GoalOverview({ active, goal, items, userTodos, readOnly, onOpenD
{copy.remote} : }/>
- {readOnly ? <>{copy.remote}
>
+ {readOnly ?
:
}
diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.css b/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.css
index 1f125ab856..a88468709c 100644
--- a/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.css
+++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.css
@@ -1,4 +1,4 @@
-.goal-workspace-composition { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: 32px; align-items: start; }
+.goal-workspace-composition { display: grid; grid-template-columns: minmax(0, 1fr); }
.goal-workspace-document { display: grid; gap: 32px; min-width: 0; }
.goal-now, .goal-workspace-results { min-width: 0; display: grid; gap: 16px; }
.goal-now h2, .goal-workspace-results > h2 { font-size: var(--text-heading); line-height: var(--leading-heading); font-weight: 600; }
@@ -11,10 +11,12 @@
.goal-owner-group > header { display: flex; flex-wrap: wrap; gap: 12px; align-items: baseline; padding: 8px 0; border-bottom: 1px solid var(--pw-line-strong); }
.goal-owner-group h3 { font-size: var(--text-body); line-height: var(--leading-body); font-weight: 600; }
.goal-owner-group > header > span { color: var(--pw-muted); font-size: var(--text-caption); }
+.goal-work-more > summary { width: fit-content; padding: 12px 0; color: var(--pw-muted); font-size: var(--text-body); cursor: pointer; }
.goal-overview .goal-work-row { width: 100%; display: grid; grid-template-columns: 16px minmax(0, 1fr) auto 36px; gap: 10px; border: 0; border-bottom: 1px solid var(--pw-line); border-radius: 0; background: transparent; padding: 12px 0; }
.goal-overview .goal-work-row[aria-pressed="true"] { background: var(--pw-hover); box-shadow: inset 2px 0 var(--pw-blue); }
.goal-work-row-text { display: grid; gap: 4px; min-width: 0; }
.goal-work-row strong { font-weight: 500; font-size: var(--text-body); line-height: var(--leading-body); overflow-wrap: anywhere; }
+.goal-owner-group > .goal-work-row strong { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.goal-work-row small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; color: var(--pw-muted); font-size: var(--text-caption); }
.goal-work-row time { color: var(--pw-faint); font-size: var(--text-caption); text-align: right; }
.goal-work-badge { font-size: var(--text-caption); color: var(--pw-muted); white-space: nowrap; }
@@ -62,16 +64,14 @@
.goal-all-work { border-left: 0; border-top: 1px solid var(--pw-line); padding: 20px 0 0; }
.goal-overview-summary.has-attention { grid-template-columns: minmax(0, 1fr); }
}
-.goal-overview .goal-direction-toggle, .personal-channel-header .personal-channel-actions > .goal-all-work-trigger { display: none; }
+.goal-overview .goal-direction-toggle { display: none; }
+.personal-channel-header .personal-channel-actions > .goal-all-work-trigger { display: inline-flex; }
.goal-direction-content { display: grid; gap: 12px; }
.goal-direction-content[hidden] { display: none; }
-.goal-work-rail-dialog { margin: 12px 12px 12px auto; width: min(400px, calc(100vw - 24px)); max-height: calc(100dvh - 24px); border: 1px solid var(--pw-line); border-radius: 12px; background: var(--pw-bg); color: var(--pw-text); padding: 20px; }
+.goal-work-rail-dialog { margin: 12px 12px 12px auto; width: min(480px, calc(100vw - 24px)); max-height: calc(100dvh - 24px); border: 1px solid var(--pw-line); border-radius: 12px; background: var(--pw-bg); color: var(--pw-text); padding: 20px; }
.goal-work-rail-dialog::backdrop { background: rgb(0 0 0 / 30%); }
.goal-work-rail-dialog .goal-all-work { border: 0; padding: 0; }
.goal-work-rail-close { margin-bottom: 16px; }
-@media (max-width: 1199px) {
- .personal-channel-header .personal-channel-actions > .goal-all-work-trigger { display: inline-flex; }
-}
@media (max-width: 640px) {
.goal-overview .goal-direction-toggle { display: inline-flex; justify-self: start; min-height: 32px; border: 0; padding: 4px 0; background: transparent; color: var(--pw-muted); font-size: var(--text-caption); }
}
@@ -79,7 +79,6 @@
.goal-owner-group > header .goal-owner-conversation { margin-left: auto; padding: 6px 8px; min-height: 44px; border: 0; background: transparent; color: var(--pw-text); font: inherit; font-size: var(--text-caption); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
/* Known empty regions yield space; loading, partial coverage and errors still mount. */
-.goal-workspace-composition.is-quiet { grid-template-columns: minmax(0, 1fr); }
.goal-workspace-results:has(> h2:only-child) { display: none; }
.goal-workspace-results .personal-files-list.is-empty > header { display: none; }
.goal-workspace-results .personal-files-list.is-empty:has(> header:only-child) { display: none; }
diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.tsx
index 8d1efb9d99..a3ec253285 100644
--- a/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.tsx
+++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.tsx
@@ -72,21 +72,14 @@ export function GoalWorkspaceWork({goal, digest, items, userTodos, selectedTodoI
}) {
const {locale, t} = useWorkspaceI18n();
const zh = locale === "zh-CN";
- const [compactRail, setCompactRail] = useState(() => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(max-width: 1199px)").matches);
const dialog = useRef(null);
- useEffect(() => {
- const media = window.matchMedia("(max-width: 1199px)");
- const update = () => setCompactRail(media.matches);
- update(); media.addEventListener("change", update);
- return () => media.removeEventListener("change", update);
- }, []);
useEffect(() => {
const node = dialog.current;
- if (compactRail && railOpen && active) node?.showModal(); else node?.close();
+ if (railOpen && active) node?.showModal(); else node?.close();
return () => node?.close();
- }, [compactRail, railOpen, active]);
+ }, [railOpen, active]);
const select = (selection: WorkspaceDrawerSelection) => {
- if (compactRail && railOpen) {dialog.current?.close(); onCloseRail();}
+ if (railOpen) {dialog.current?.close(); onCloseRail();}
onSelect(selection);
};
const [lens, setLens] = useState<"owner" | "board" | "map">("owner");
@@ -141,13 +134,13 @@ export function GoalWorkspaceWork({goal, digest, items, userTodos, selectedTodoI
{sectionLabels[section]} {digest.work.rail[section].filter(matches).map(id => task(id, true))}
)}
{filter === "all" || filter === "done" ? todos.get(id) ?? [])}
- enabled={!readOnly && active && (!compactRail || railOpen)} refreshRevision={historyRefreshRevision} listView onSelect={select} selectedTodoId={selectedTodoId} query={query}/> : null}
+ enabled={!readOnly && active && railOpen} refreshRevision={historyRefreshRevision} listView onSelect={select} selectedTodoId={selectedTodoId} query={query}/> : null}
> : {activity.map(item =>
item.kind === "run" ? select({kind: "run", item: item.run}) : item.kind === "output" ? select({kind: "output", item: item.output}) : item.kind === "proposal" ? select({kind: "proposal", item: item.proposal}) : undefined}>
{item.kind === "run" ? <>{item.run.status === "completed" ? : }{item.run.title}{localizedSessionStatus(item.run.status, t)} > : item.kind === "output" ? <>{item.output.title}{zh ? "成果返回" : "Result returned"} > : item.kind === "proposal" ? <>{item.proposal.title}{zh ? "决定已记录" : "Decision recorded"} > : null}
)}{!activity.length ?
{zh ? "当前时间线尚无已完成或返回的记录。" : "No completed work or returned results in this timeline."}
: null}
}
{zh ? "统计仅覆盖此 Goal;任务与历史可能分批载入。" : "Counts cover this Goal; tasks and history may be loaded in bounded pages."}{new Date(digest.observedAt).toLocaleTimeString(locale)}
;
- return <>
+ return <>
{zh ? "当前工作" : "Now"}
@@ -163,7 +156,11 @@ export function GoalWorkspaceWork({goal, digest, items, userTodos, selectedTodoI
{ownerName(group.ownerId)} {group.todoIds.length} {zh ? "项任务" : "tasks"} · {group.todoIds.filter(id => rows.get(id)?.displayClass === group.displayClass).length} {goalWorkClassLabel(group.displayClass, zh)}
{!readOnly && group.ownerId && onOpenAgentConversation ? onOpenAgentConversation(group.ownerId!)}>{zh ? "工作记录" : "Work conversation"} : null}
- {group.todoIds.map(id => task(id))}
+ {group.todoIds.slice(0, 3).map(id => task(id))}
+ {group.todoIds.length > 3 ?
+ {group.todoIds.slice(3).map(id => task(id))}
+ : null}
)}
{!digest.work.groups.length ?
{digest.work.coverage === "complete" ? (zh ? "当前没有可列出的进行中任务。延后与完成记录在全部工作中。" : "No current work to list. Deferred and completed work remain in All work.") : (zh ? "当前快照没有可列出的任务。" : "No work is listed in this snapshot.")}
: null}
@@ -171,10 +168,16 @@ export function GoalWorkspaceWork({goal, digest, items, userTodos, selectedTodoI
{visited.has("map") ?
{map}
: null}
- {!quietWork || railOpen ? compactRail ?
{event.preventDefault(); onCloseRail();}}>
+ {event.preventDefault(); onCloseRail();}}>
{zh ? "关闭全部工作" : "Close all work"}
{rail}
- : rail : null}
+
>;
}
+
+function RemainingWork({selectedTodoId, label, children}: {selectedTodoId: string | null; label: string; children: ReactNode}) {
+ const disclosure = useRef(null);
+ useEffect(() => {if (selectedTodoId && disclosure.current) disclosure.current.open = true;}, [selectedTodoId]);
+ return {label} {children} ;
+}
diff --git a/docs/development/design.md b/docs/development/design.md
index a323ba4383..c8b1fec8a1 100644
--- a/docs/development/design.md
+++ b/docs/development/design.md
@@ -595,6 +595,18 @@ From top to bottom, one reading column with an optional right rail:
4. **Owner rail.** What needs the owner sits beside the hero as a hairline
list: glyph, the decision in owner language, relative age, an arrow that
opens it in place. A quiet Goal shows no empty rail.
+ Do not repeat the same work in a permanently visible task rail. Overview
+ shows up to three compact tasks per responsible owner; the group summary
+ retains its task count and significant state, including blocked work.
+ Expand the remaining tasks in place or open All work for search, filters,
+ completed history and activity. Direct task links reveal their selected row.
+ Read-only acceptance observations begin as one disclosure with gap/guard
+ counts and an explicit partial or unknown state; full reasons, requirements
+ and evidence remain inside. Zero listed gaps never means Goal acceptance.
+ 不要在常驻任务侧栏重复展示主栏的工作。概览每位负责人默认最多展示三条紧凑任务,
+ 组摘要保留任务数和受阻等重要状态;其余任务就地展开,搜索、筛选、已完成历史与活动
+ 从“全部工作”打开。任务直链自动展开选中行。只读验收观察默认折叠,摘要显示缺口、
+ 限制数量及“部分观测”或“未知”;完整原因、要求与证据保留在正文,零缺口不等于验收通过。
5. **Folded composer.** Reading views fold the composer to one line that
expands on request; conversation views keep it open.
diff --git a/examples/personal-workspace-browser/evidence/overview-reading/after-desktop.png b/examples/personal-workspace-browser/evidence/overview-reading/after-desktop.png
new file mode 100644
index 0000000000..76e5c4eee4
Binary files /dev/null and b/examples/personal-workspace-browser/evidence/overview-reading/after-desktop.png differ
diff --git a/examples/personal-workspace-browser/evidence/overview-reading/after-phone.png b/examples/personal-workspace-browser/evidence/overview-reading/after-phone.png
new file mode 100644
index 0000000000..0122af3106
Binary files /dev/null and b/examples/personal-workspace-browser/evidence/overview-reading/after-phone.png differ
diff --git a/examples/personal-workspace-browser/evidence/overview-reading/before-desktop.png b/examples/personal-workspace-browser/evidence/overview-reading/before-desktop.png
new file mode 100644
index 0000000000..14cc2c9c73
Binary files /dev/null and b/examples/personal-workspace-browser/evidence/overview-reading/before-desktop.png differ
diff --git a/examples/personal-workspace-browser/evidence/overview-reading/populated-desktop.png b/examples/personal-workspace-browser/evidence/overview-reading/populated-desktop.png
new file mode 100644
index 0000000000..5c68168642
Binary files /dev/null and b/examples/personal-workspace-browser/evidence/overview-reading/populated-desktop.png differ
diff --git a/examples/personal-workspace-browser/goal-workspace-m2.mjs b/examples/personal-workspace-browser/goal-workspace-m2.mjs
index 71118e9c33..75679d28d8 100644
--- a/examples/personal-workspace-browser/goal-workspace-m2.mjs
+++ b/examples/personal-workspace-browser/goal-workspace-m2.mjs
@@ -12,6 +12,9 @@ export const goalWorkspaceM2Scenario = {
let scale = false, omitSelected = false;
const many = Array.from({length: 200}, (_, i) => ({todo_id: `scale-${String(i).padStart(3,"0")}`, text: `Synthetic work item ${i + 1}`, title: `Synthetic work item ${i + 1}`,
role: "agent", done: false, status: i === 0 ? "blocked" : "open", task_class: "advancement_task", claimed_by: i % 5 === 0 ? null : `worker-${i % 4}`}));
+ for (const [i, todo] of many.entries()) {todo.text += i % 2
+ ? ": Reconcile the complete source records, preserve unresolved differences, and verify the result against the original acceptance criteria before returning evidence to the responsible owner."
+ : ":逐项核对原始记录与完整验收要求,保留未解决的差异,检查来源、执行结果与实际采用情况,再向负责人返回可复核的证据。"; todo.title = todo.text;}
const session = await openWorkspacePage(browser, url, {collectCoverage, viewport: {width: 1440, height: 1000}, beforeGoto: async (api,page) => {
await installShowcaseWorkspace(api,page, {done: 23, cost: 24.8, deliveryFails: false});
const transform = api.transformStatus;
@@ -50,6 +53,7 @@ export const goalWorkspaceM2Scenario = {
const now = page.getByRole("region", {name: "当前工作", exact: true});
await now.waitFor();
await page.locator('.goal-owner-group[data-owner-id="ledger-migrator"]').waitFor();
+ assert.equal(await page.getByRole("complementary", {name:"全部工作",exact:true}).isVisible(),false,"Overview must not duplicate its work in a permanent rail");
assert.equal(await page.locator('.goal-owner-group [data-todo-id="showcase-retire-csv"]').count(),0);
const row = now.locator('[data-todo-id="showcase-map-columns"]');
await row.click();
@@ -91,6 +95,7 @@ export const goalWorkspaceM2Scenario = {
await page.waitForURL(value => !value.searchParams.has("todo"));
await page.locator('.personal-context-drawer').waitFor({state:"hidden"});
assert.equal(writes.length,0,`Read-only navigation made an effect request: ${writes}`);
+ await now.locator('[data-todo-id="showcase-backfill"][data-work-class="waiting_dependency"]').waitFor();
await page.screenshot({path:resolve(outputDir,'goal-workspace-m2-desktop.png'),animations:'disabled'});
await page.setViewportSize({width:390,height:844});
assert.equal(await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth+1),false);
@@ -134,10 +139,22 @@ export const goalWorkspaceM2Scenario = {
await page.getByRole("button", {name:"刷新状态",exact:true}).click();
await statusResponse;
const renderStart = performance.now();
- await now.locator('.goal-owner-group [data-todo-id="scale-199"]').waitFor();
+ await now.locator('.goal-owner-group [data-todo-id="scale-199"]').waitFor({state:"attached"});
const initialMs = performance.now() - renderStart;
assert.ok(initialMs < 2000, `200-item Now render exceeded frozen 2s budget: ${initialMs}ms`);
assert.equal(await now.locator('.goal-owner-group [data-todo-id]').count(),200);
+ assert.equal(await now.locator('.goal-owner-group [data-todo-id]:visible').count(),15,"Five owners show three tasks each before expansion");
+ await now.locator('.goal-owner-group[data-owner-id=""] > header').getByText(/1 受阻/).waitFor();
+ await page.screenshot({path:resolve(outputDir,'overview-populated-desktop.png'),animations:'disabled',fullPage:true});
+ await page.setViewportSize({width:390,height:844});
+ assert.equal(await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth+1),false);
+ await page.screenshot({path:resolve(outputDir,'overview-populated-mobile.png'),animations:'disabled'});
+ await page.setViewportSize({width:1440,height:1000});
+ const expandStart = performance.now();
+ for (const summary of await now.locator('.goal-work-more > summary').all()) {await summary.focus();await page.keyboard.press('Enter');}
+ assert.equal(await now.locator('.goal-owner-group [data-todo-id]:visible').count(),200,"All work remains reachable in the original owner order");
+ const expandMs = performance.now() - expandStart;
+ assert.ok(expandMs < 2000, `Full 200-item expansion exceeded 2s budget: ${expandMs}ms`);
await now.getByRole('button',{name:'关系图',exact:true}).click();
await now.locator('.work-map-node').filter({hasText:'Synthetic work item 200'}).waitFor();
await now.getByRole('button',{name:'按负责人',exact:true}).click();
@@ -146,9 +163,26 @@ export const goalWorkspaceM2Scenario = {
assert.equal(await now.locator('.goal-owner-group [data-work-class="blocked"]').count(),1);
assert.equal(await now.locator('.goal-owner-group [data-work-class="open"]').count(),99);
const rail = page.getByRole('complementary',{name:'全部工作',exact:true});
+ await railTrigger.click();
+ await railDialog.waitFor();
await rail.getByRole('button',{name:'全部 · 200',exact:true}).waitFor();
assert.equal(await rail.getByRole('button',{name:'未完成 · 199',exact:true}).count(),1);
assert.equal(await rail.getByRole('button',{name:'受阻与延后 · 1',exact:true}).count(),1);
+ await page.keyboard.press('Escape');await railDialog.waitFor({state:'hidden'});
+ assert.equal(await railTrigger.evaluate(node=>node===document.activeElement),true,"Desktop rail Escape restores focus too");
+ const remaining = now.locator('.goal-owner-group[data-owner-id="worker-3"] .goal-work-more');
+ await remaining.locator(':scope > summary').click();
+ await railTrigger.click();
+ await rail.locator('[data-todo-id="scale-199"]').click();
+ await railDialog.waitFor({state:'hidden'});
+ await now.locator('[data-todo-id="scale-199"][aria-pressed="true"]').waitFor();
+ assert.equal(await remaining.evaluate(node=>node.open),true,"Selecting later work reveals its owner row");
+ await remaining.locator(':scope > summary').click();
+ await railTrigger.click();
+ await rail.locator('[data-todo-id="scale-191"]').click();
+ await now.locator('[data-todo-id="scale-191"][aria-pressed="true"]').waitFor();
+ assert.equal(await remaining.evaluate(node=>node.open),true,"A different selection in the same closed group also reveals its row");
+ await page.locator('.personal-drawer-close').click();
await now.getByRole('button',{name:'看板',exact:true}).click();
assert.equal(await now.locator('.personal-task-card[data-work-class]').count(),200);
assert.equal(await now.locator('.personal-task-card[data-work-class="waiting_dependency"]').count(),100);
@@ -164,7 +198,7 @@ export const goalWorkspaceM2Scenario = {
assert.ok(switchMs.every(ms=>ms<1000), `Loaded lens switches exceeded frozen 1s budget: ${switchMs}`);
assert.equal(writes.length,0,`Rendering created effects: ${writes}`);
assert.equal(session.errors.length,0,session.errors.join('\n'));
- console.log(JSON.stringify({m2RenderBudget:{initialMs,switchMs,nodeCount:200}}));
+ console.log(JSON.stringify({m2RenderBudget:{initialMs,expandMs,switchMs,nodeCount:200}}));
await verifyRemoteSelection(browser, url);
return {coverageEntries:session.coverageEntries,note:'Responsibility groups, shared task selection/reload, Board/Map switching, no effect requests, desktop/mobile composition. Synthetic status/map; creation is separately qualified against real backend.'};
} catch(error) {await page.screenshot({path:resolve(outputDir,'goal-workspace-m2-failed.png')});throw error;}
@@ -176,6 +210,14 @@ export const goalWorkspaceM2Scenario = {
async function verifyRemoteSelection(browser, url) {
const snapshot = JSON.parse(await readFile(resolve(repoRoot,"examples/status.example.json"),"utf8"));
const goalId = snapshot.run_history.goals[0].id;
+ snapshot.run_history.goals[0].acceptance_observation = {
+ schema_version:"goal_acceptance_observation_projection_v0",goal_id:goalId,read_only:true,acceptance_assessed:false,
+ coverage:"partial",missing_sources:["current_run"],truncated:true,historical_progress:[],
+ acceptance_gaps:[{kind:"outcome_gap",owner:"reviewer",reason:"Archived totals have not been independently reconciled.",
+ evidence_required:"Compare each archived total with the source ledger and retain unresolved differences.",observed_at:null,source:"agent_vision"}],
+ guards:[{kind:"owner_decision",todo_id:null,blocks_agent:null,owner:"reviewer",reason:"Cut-over remains held pending the owner's decision.",evidence_required:"Recorded owner decision",decision_scope:"cut-over"}],
+ next_action:"Reconcile the archived totals before requesting cut-over.",next_action_source:"agent_vision",
+ };
const target = new URL(url);
target.searchParams.set("statusUrl","http://127.0.0.1:8978/status.json");
target.searchParams.set("goalId",goalId);
@@ -196,6 +238,18 @@ async function verifyRemoteSelection(browser, url) {
await page.getByRole("status").filter({hasText:"链接中的任务未出现在此来源的当前快照中"}).waitFor();
assert.equal(await page.getByRole("button",{name:"添加任务",exact:true}).count(),0);
assert.equal(await page.locator('.goal-owner-conversation').count(),0,"A remote snapshot cannot open a local Agent conversation");
+ const observation = page.locator('.goal-observation-details');
+ await observation.locator(':scope > summary').getByText('1 项缺口 · 1 项限制 · 仅部分观测',{exact:true}).waitFor();
+ assert.equal(await observation.evaluate(node=>node.open),false);
+ await page.screenshot({path:resolve(outputDir,'overview-readonly-desktop.png'),animations:'disabled'});
+ await observation.locator(':scope > summary').focus();await page.keyboard.press('Enter');
+ await observation.getByText('Archived totals have not been independently reconciled.',{exact:true}).waitFor();
+ await observation.getByText("Cut-over remains held pending the owner's decision.",{exact:true}).waitFor();
+ await page.screenshot({path:resolve(outputDir,'overview-readonly-expanded.png'),animations:'disabled'});
+ snapshot.run_history.goals[0].acceptance_observation.goal_id = 'another-goal';
+ await page.getByRole('button',{name:'刷新状态',exact:true}).click();
+ await observation.locator(':scope > summary').getByText('验收观测不可用,Goal 是否达成仍未知。',{exact:true}).waitFor();
+ assert.equal(await observation.getByText('Archived totals have not been independently reconciled.',{exact:true}).count(),0,"Wrong-Goal observations never survive a refresh");
await page.getByRole("region",{name:"当前工作",exact:true}).getByRole("button",{name:"关系图",exact:true}).click();
assert.equal(await page.locator(".work-map").count(),0,"Remote selection cannot reuse a local map");
target.searchParams.set("view", "conversation");