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 => )}{!activity.length ?

{zh ? "当前时间线尚无已完成或返回的记录。" : "No completed work or returned results in this timeline."}

: null}
} ; - 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 ? : 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") ? : null}
- {!quietWork || railOpen ? compactRail ? {event.preventDefault(); onCloseRail();}}> + {event.preventDefault(); onCloseRail();}}> {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");