From e01f1e4f19d4c6918b1c874da88e03c61706a025 Mon Sep 17 00:00:00 2001 From: song Date: Thu, 15 Oct 2026 23:48:57 +0800 Subject: [PATCH 1/5] feat(workspace): show what changed since the owner last looked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A returning owner had to re-read a Goal's overview to find out what changed while they were away. RFC goal-workspace-surface-v0 §5.7 specifies the answer: a browser-local marker and one quiet line. Per (status source, Goal) the browser keeps a `goal_seen_marker_v0` in local storage: typed counts, decision ids, bounded done Todo and result ids and the observation time; no titles, content or cost, no service write. On open the page compares the current digest with the marker and shows one line above the hero only for a material delta (newly done, new decisions, newly blocked, results returned) with "Mark seen". Mark seen writes the current observation; leaving the Goal records the owner's latest complete look and acknowledges a shown line. A visit with nothing new still advances the observation time, and because every count is "present now and not in the marker", a Goal still loading on open can only under-count, never invent a change. Cost stays omitted per D7. The RFC gains a dated checkpoint for this half of M3; target declaration remains undelivered. Validation: node test for the marker and comparison (no-change pair, trimmed marker, unavailable done count, storage round-trip and garbage); browser scenario for the V5 journey (first visit silent, no-change return silent, work and decision changes shown and dismissed, leave-acknowledge, reload, no service write); contract pins; overview scenarios goal-showcase, goal-masthead, goal-workspace-m2, first-goal-home; tsc; CSS token check; semantic inventory advisory. Co-Authored-By: Claude Fable 5.1 Signed-off-by: song --- apps/presentation/dashboard/package.json | 6 +- .../personal-workspace/goal-overview.css | 5 + .../personal-workspace/goal-overview.tsx | 29 ++++- .../src/features/personal-workspace/i18n.tsx | 12 ++ .../personal-workspace-contract.test.mjs | 16 +++ .../personal-workspace-page.tsx | 63 +++++++++- .../since-you-looked.test.mjs | 65 ++++++++++ .../personal-workspace/since-you-looked.ts | 113 ++++++++++++++++++ .../rfcs/goal-workspace-surface-v0.md | 15 ++- .../since-you-looked-line.png | Bin 0 -> 174612 bytes docs/guides/personal-workspace-user-guide.md | 18 +++ examples/personal-workspace-browser-smoke.mjs | 2 + .../since-you-looked.mjs | 103 ++++++++++++++++ 13 files changed, 441 insertions(+), 6 deletions(-) create mode 100644 apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs create mode 100644 apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts create mode 100644 docs/assets/personal-workspace/since-you-looked-line.png create mode 100644 examples/personal-workspace-browser/since-you-looked.mjs diff --git a/apps/presentation/dashboard/package.json b/apps/presentation/dashboard/package.json index 7412a42580..2136b3dd29 100644 --- a/apps/presentation/dashboard/package.json +++ b/apps/presentation/dashboard/package.json @@ -38,10 +38,10 @@ "smoke:home-route": "rm -rf /tmp/loopx-home-route-smoke && tsc --ignoreConfig --target ES2022 --module CommonJS --moduleResolution Node --ignoreDeprecations 6.0 --skipLibCheck --strict --outDir /tmp/loopx-home-route-smoke smoke/home-route-smoke.ts && node /tmp/loopx-home-route-smoke/home-route-smoke.js", "smoke:delegation-preflight": "rm -rf node_modules/.cache/loopx-delegation-preflight-smoke && tsc --ignoreConfig --target ES2022 --module NodeNext --moduleResolution NodeNext --jsx react-jsx --skipLibCheck --strict --rootDir . --outDir node_modules/.cache/loopx-delegation-preflight-smoke smoke/delegation-preflight-smoke.tsx src/data/delegation-preflight.ts src/features/personal-workspace/delegation-preflight-status.tsx && node node_modules/.cache/loopx-delegation-preflight-smoke/smoke/delegation-preflight-smoke.js", "smoke:delegation-preflight-browser": "node --experimental-strip-types smoke/delegation-preflight-browser-smoke.mjs", - "smoke:personal-workspace": "npm run smoke:conversation-history-order && npm run smoke:goal-order && npm run smoke:goal-activity && npm run smoke:proposal-recency && node src/features/personal-workspace/conversation-order.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/steering-recovery.test.mjs && npm run smoke:delegation-preflight && node src/features/personal-workspace/workspace-theme.test.mjs && node src/features/personal-workspace/font-token.test.mjs && node src/features/personal-workspace/personal-workspace-contract.test.mjs && node ../../../examples/personal-workspace-browser-smoke.mjs", + "smoke:personal-workspace": "npm run smoke:conversation-history-order && npm run smoke:goal-order && npm run smoke:goal-activity && npm run smoke:proposal-recency && node src/features/personal-workspace/conversation-order.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/steering-recovery.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/since-you-looked.test.mjs && npm run smoke:delegation-preflight && node src/features/personal-workspace/workspace-theme.test.mjs && node src/features/personal-workspace/font-token.test.mjs && node src/features/personal-workspace/personal-workspace-contract.test.mjs && node ../../../examples/personal-workspace-browser-smoke.mjs", "smoke:workspace-locale": "LOOPX_PERSONAL_WORKSPACE_SCENARIO=workspace-locale node ../../../examples/personal-workspace-browser-smoke.mjs", - "smoke:personal-workspace-packaged": "npm run smoke:conversation-history-order && npm run smoke:goal-order && npm run smoke:goal-activity && npm run smoke:proposal-recency && node src/features/personal-workspace/conversation-order.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/steering-recovery.test.mjs && npm run smoke:delegation-preflight && node src/features/personal-workspace/workspace-theme.test.mjs && node src/features/personal-workspace/font-token.test.mjs && node src/features/personal-workspace/personal-workspace-contract.test.mjs && node ../../../examples/personal-workspace-browser/server-startup.test.mjs && LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node ../../../examples/personal-workspace-browser-smoke.mjs && LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/team-evidence-return-smoke.mjs && LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/team-feedback-recovery-smoke.mjs && LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/goal-workspace-craft-browser-smoke.mjs", - "smoke:personal-workspace-packaged:catalog": "npm run smoke:conversation-history-order && npm run smoke:goal-order && npm run smoke:goal-activity && npm run smoke:proposal-recency && node src/features/personal-workspace/conversation-order.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/steering-recovery.test.mjs && npm run smoke:delegation-preflight && node src/features/personal-workspace/workspace-theme.test.mjs && node src/features/personal-workspace/font-token.test.mjs && node src/features/personal-workspace/personal-workspace-contract.test.mjs && node ../../../examples/personal-workspace-browser/server-startup.test.mjs && LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node ../../../examples/personal-workspace-browser-smoke.mjs", + "smoke:personal-workspace-packaged": "npm run smoke:conversation-history-order && npm run smoke:goal-order && npm run smoke:goal-activity && npm run smoke:proposal-recency && node src/features/personal-workspace/conversation-order.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/steering-recovery.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/since-you-looked.test.mjs && npm run smoke:delegation-preflight && node src/features/personal-workspace/workspace-theme.test.mjs && node src/features/personal-workspace/font-token.test.mjs && node src/features/personal-workspace/personal-workspace-contract.test.mjs && node ../../../examples/personal-workspace-browser/server-startup.test.mjs && LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node ../../../examples/personal-workspace-browser-smoke.mjs && LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/team-evidence-return-smoke.mjs && LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/team-feedback-recovery-smoke.mjs && LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/goal-workspace-craft-browser-smoke.mjs", + "smoke:personal-workspace-packaged:catalog": "npm run smoke:conversation-history-order && npm run smoke:goal-order && npm run smoke:goal-activity && npm run smoke:proposal-recency && node src/features/personal-workspace/conversation-order.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/steering-recovery.test.mjs && node --no-warnings --experimental-strip-types --test src/features/personal-workspace/since-you-looked.test.mjs && npm run smoke:delegation-preflight && node src/features/personal-workspace/workspace-theme.test.mjs && node src/features/personal-workspace/font-token.test.mjs && node src/features/personal-workspace/personal-workspace-contract.test.mjs && node ../../../examples/personal-workspace-browser/server-startup.test.mjs && LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node ../../../examples/personal-workspace-browser-smoke.mjs", "smoke:personal-workspace-packaged:team": "LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/team-evidence-return-smoke.mjs && LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/team-feedback-recovery-smoke.mjs", "smoke:personal-workspace-packaged:craft": "LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node smoke/goal-workspace-craft-browser-smoke.mjs", "smoke:todo-resume-condition": "rm -rf /tmp/loopx-todo-resume-condition-smoke && tsc --ignoreConfig --target ES2022 --module commonjs --moduleResolution node --ignoreDeprecations 6.0 --skipLibCheck --strict --outDir /tmp/loopx-todo-resume-condition-smoke smoke/todo-resume-condition-smoke.ts src/features/personal-workspace/todo-resume-condition.ts && node /tmp/loopx-todo-resume-condition-smoke/smoke/todo-resume-condition-smoke.js", diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.css b/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.css index adef69d7d3..6ef5443fda 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.css +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.css @@ -25,6 +25,11 @@ .goal-overview .goal-overview-heading button:hover { border-color: transparent; background: var(--pw-hover); color: var(--pw-text); } .goal-overview-objective { max-width: 44em; color: var(--pw-text); font-size: var(--text-heading); font-weight: 600; letter-spacing: -0.015em; line-height: var(--leading-heading); text-wrap: pretty; } +/* One quiet line above the hero: what changed since the owner's last look. Idle content stays still. */ +.goal-overview-since { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; margin: 0 0 16px; padding-bottom: 12px; border-bottom: 1px solid var(--pw-line); color: var(--pw-muted); font-size: var(--text-caption); line-height: var(--leading-caption); font-variant-numeric: tabular-nums; } +.goal-overview-since button { min-height: 28px; padding: 0 10px; border: 1px solid var(--pw-line-strong); border-radius: 6px; background: transparent; color: var(--pw-text); font-size: var(--text-caption); cursor: pointer; } +.goal-overview-since button:hover { border-color: var(--pw-blue); color: var(--pw-blue-ink); } +.goal-overview-since button:focus-visible { outline: 2px solid var(--pw-blue); outline-offset: 2px; } .goal-overview-note { max-width: 56em; color: var(--pw-muted); font-size: var(--text-body); line-height: var(--leading-body); } .goal-overview-note small { color: var(--pw-faint); } 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 c12e4825f9..97455f58a2 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.tsx @@ -9,13 +9,28 @@ import type {GoalWorkspaceDigest} from "../../../../../../loopx/control_plane/pr import { GoalAcceptanceObservationCard } from "./goal-acceptance-observation-card"; import type { WorkspaceActionPreview, WorkspaceActionPreviewRequest, WorkspaceDrawerSelection, WorkspaceGoal, WorkspaceGoalTab, WorkspaceModel, WorkspaceTimelineItem } from "./personal-workspace-model"; import { useWorkspaceI18n } from "./i18n"; +import type { SinceYouLookedDelta } from "./since-you-looked"; import {goalAcceptanceDisplayStatus, projectGoalAcceptanceCriteria} from "../../data/goal-acceptance-observation"; import "./goal-overview.css"; export const goalOverviewAttentionId = "goal-overview-attention"; -export function GoalOverview({ active, goal, items, userTodos, readOnly, onOpenDetails, onSelect, onView, selectedTodoId, board, results, proposals, onPreview, historyRefreshRevision, review, digest, railOpen, onCloseRail, onOpenAgentConversation }: { +/** "3 hours ago" for the owner's last look; falls back to the date for older marks. */ +function relativeLookLabel(iso: string, locale: string): string { + const then = new Date(iso).getTime(); + if (Number.isNaN(then)) return iso; + const minutes = Math.max(1, Math.round((Date.now() - then) / 60_000)); + const format = new Intl.RelativeTimeFormat(locale === "zh-CN" ? "zh-CN" : "en", { numeric: "always" }); + if (minutes < 60) return format.format(-minutes, "minute"); + if (minutes < 60 * 24) return format.format(-Math.round(minutes / 60), "hour"); + if (minutes < 60 * 24 * 14) return format.format(-Math.round(minutes / (60 * 24)), "day"); + return new Intl.DateTimeFormat(locale === "zh-CN" ? "zh-CN" : "en", { month: "short", day: "numeric" }).format(then); +} + +export function GoalOverview({ active, goal, items, userTodos, readOnly, onOpenDetails, onSelect, onView, selectedTodoId, board, results, proposals, onPreview, historyRefreshRevision, review, digest, railOpen, onCloseRail, onOpenAgentConversation, sinceYouLooked, onMarkSeen }: { onOpenAgentConversation?: (agentId: string) => void; + /** Material changes since the owner's last acknowledged look; null when none. */ + sinceYouLooked?: SinceYouLookedDelta | null; onMarkSeen?: () => void; railOpen: boolean; onCloseRail: () => void; review: DeliveryReviewRead; digest: GoalWorkspaceDigest; active: boolean; goal: WorkspaceGoal; items: WorkspaceTimelineItem[]; userTodos: WorkspaceModel["userTodos"]; @@ -58,7 +73,19 @@ export function GoalOverview({ active, goal, items, userTodos, readOnly, onOpenD const criteria = contract ? projectGoalAcceptanceCriteria(contract) : []; const passed = criteria.filter(criterion => criterion.state === "accepted").length; const acceptanceCopy = goalAcceptanceContractCopy[locale === "zh-CN" ? "zh-CN" : "en"]; + const sinceParts = sinceYouLooked ? [ + sinceYouLooked.newlyDone ? t("since.newlyDone", { count: sinceYouLooked.newlyDone }) : null, + sinceYouLooked.newDecisions ? t("since.newDecisions", { count: sinceYouLooked.newDecisions }) : null, + sinceYouLooked.newlyBlocked ? t("since.newlyBlocked", { count: sinceYouLooked.newlyBlocked }) : null, + sinceYouLooked.resultsReturned ? t("since.resultsReturned", { count: sinceYouLooked.resultsReturned }) : null, + ].filter((part): part is string => part !== null) : []; return
+ {sinceYouLooked && sinceParts.length ? ( +

+ {t("since.lead", { when: relativeLookLabel(sinceYouLooked.since, locale) })} {sinceParts.join(locale === "zh-CN" ? "、" : ", ")} + {onMarkSeen ? : null} +

+ ) : null}
diff --git a/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx b/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx index 4db768d258..b96179eb28 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx @@ -579,6 +579,12 @@ const en = { "header.chatRuntime": "Chat", "header.workAgentCount": "{count} work Agents", "header.overview": "Overview", + "since.lead": "Since you looked ({when}):", + "since.newlyDone": "{count} newly done", + "since.newDecisions": "{count} new decision(s) for you", + "since.newlyBlocked": "{count} newly blocked", + "since.resultsReturned": "{count} result(s) returned", + "since.markSeen": "Mark seen", "header.chat": "Chat", "header.files": "Files", "header.goalCapabilities": "Capability settings", @@ -2086,6 +2092,12 @@ const zhCN: Record = { "header.chatRuntime": "Chat", "header.workAgentCount": "{count} 个工作 Agent", "header.overview": "概览", + "since.lead": "自你上次查看({when}):", + "since.newlyDone": "新完成 {count} 项", + "since.newDecisions": "新增 {count} 项等你决定", + "since.newlyBlocked": "新受阻 {count} 项", + "since.resultsReturned": "新成果 {count} 项", + "since.markSeen": "标记已读", "header.chat": "对话", "header.files": "成果", "header.goalCapabilities": "能力配置", diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs index 2efa09d6c0..57b16211eb 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs @@ -600,4 +600,20 @@ for (const key of ["home.firstGoal.title", "home.firstGoal.lead", "home.firstGoa assert.equal((i18n.match(new RegExp(`"${key.replace(/\./g, "\\.")}":`, "g")) ?? []).length, 2, `${key} exists in both locales`); } +// Since you looked (RFC goal-workspace-surface §5.7): a browser-local marker +// per source and Goal, typed counts and ids only, no cost, no service write; +// the line appears only for a material delta and advances on Mark seen or on +// leaving the Goal after it was shown. +const sinceYouLooked = source("./since-you-looked.ts"); +const seenMarkerType = sinceYouLooked.slice(sinceYouLooked.indexOf("export type SeenMarker = {"), sinceYouLooked.indexOf("};", sinceYouLooked.indexOf("export type SeenMarker = {"))); +assert.doesNotMatch(seenMarkerType, /cost|title|text|usd/i, "The marker carries no titles, text or cost"); +assert.match(sinceYouLooked, /export const seenMarkerIdLimit = 200;/, "Done and result ids are bounded"); +assert.match(page, /seenMarkerStorageKey\(seenStatusSource, selectedGoal\.goalId\)/, "Markers are scoped per status source and Goal"); +assert.match(page, /if \(delta\.material\) setSinceYouLooked\(/, "Only a material delta shows the line"); +assert.match(page, /advanceSeenMarker\(previous\.goalId, previous\.source\);[\s\S]*?setSinceYouLooked\(\(shown\) => shown && shown\.goalId === previous\.goalId/, "Leaving the Goal records the latest look and acknowledges a shown line"); +assert.doesNotMatch(sinceYouLooked + page.slice(page.indexOf("const seenStatusSource"), page.indexOf("function markSeen")), /fetch\(|requestJson|\/api\//, "No service write or read belongs to the marker"); +for (const key of ["since.lead", "since.newlyDone", "since.newDecisions", "since.newlyBlocked", "since.resultsReturned", "since.markSeen"]) { + assert.equal((i18n.match(new RegExp(`"${key.replace(/\./g, "\\.")}":`, "g")) ?? []).length, 2, `${key} exists in both locales`); +} + console.log("personal workspace drawer contract smoke passed"); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx index d982251735..964b3038d4 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx @@ -88,6 +88,7 @@ import { WorkspaceSettingsPage } from "./workspace-settings-page"; import { UsageStatisticsNotice } from "./usage-statistics-notice"; import { readWorkspaceTheme, writeWorkspaceTheme, type WorkspaceTheme } from "./workspace-theme"; import { compareProposalRecency } from "./proposal-recency"; +import { buildSeenMarker, compareSeenMarker, readSeenMarker, seenMarkerStorageKey, writeSeenMarker, type SeenMarkerInput, type SinceYouLookedDelta } from "./since-you-looked"; import { WorkspaceShell } from "./workspace-shell"; import type { StatusSourceControl } from "./status-source-switcher"; import "./personal-workspace.css"; @@ -1246,6 +1247,66 @@ export function PersonalWorkspacePage({ && (selectedGoalTab === "overview" || selectedGoalTab === "tasks") && !selectedGoal?.loadState, selectionScope); const workspaceDigest = useGoalWorkspaceDigest(selectedGoal, model.userTodos, items, workspaceReview.usable ? workspaceReview.snapshot?.goal_map : null); + // "Since you looked" (RFC goal-workspace-surface §5.7): compare the current + // digest with this browser's last acknowledged look at this Goal on this + // source. Nothing here writes to the service or reads cost. Comparing on + // open can only under-count while a Goal is still loading (every count is + // "present now and not in the marker"); the marker itself advances from + // the latest complete look when the owner marks it seen or leaves. + const seenStatusSource = statusSourceControl?.activeSource.statusUrl ?? "/status.json"; + const seenGoalLoaded = Boolean(selectedGoal && !selectedGoal.loadState); + const seenMarkerInput = useMemo(() => { + if (!selectedGoal || !seenGoalLoaded) return null; + return { + observedAt: workspaceDigest.observedAt, + counts: { open: workspaceDigest.work.open, blocked: workspaceDigest.work.classes.blocked, done: workspaceDigest.work.done }, + decisionIds: model.userTodos.filter((item) => item.goalId === selectedGoal.goalId).map((item) => item.todoId), + doneTodoIds: workspaceDigest.work.rail.done, + blockedTodoIds: workspaceDigest.work.rail.blocked, + resultIds: items.flatMap((item) => item.kind === "output" && item.output.goalId === selectedGoal.goalId ? [item.output.outputId] : []), + }; + }, [selectedGoal, seenGoalLoaded, workspaceDigest, model.userTodos, items]); + const latestSeenInputsRef = useRef(new Map()); + const seenScopeKey = (source: string, goalId: string) => JSON.stringify([source, goalId]); + useEffect(() => { + if (selectedGoal && seenMarkerInput) latestSeenInputsRef.current.set(seenScopeKey(seenStatusSource, selectedGoal.goalId), seenMarkerInput); + }, [selectedGoal, seenMarkerInput, seenStatusSource]); + const [sinceYouLooked, setSinceYouLooked] = useState<{ goalId: string; source: string; delta: SinceYouLookedDelta } | null>(null); + const seenComparedRef = useRef(null); + const seenVisitRef = useRef<{ source: string; goalId: string } | null>(null); + function advanceSeenMarker(goalId: string, source: string) { + const input = latestSeenInputsRef.current.get(seenScopeKey(source, goalId)); + if (input) writeSeenMarker(window.localStorage, seenMarkerStorageKey(source, goalId), buildSeenMarker(input)); + } + useEffect(() => { + if (!selectedGoal || !seenGoalLoaded || !seenMarkerInput) return; + const scope = seenScopeKey(seenStatusSource, selectedGoal.goalId); + if (seenComparedRef.current === scope) return; + seenComparedRef.current = scope; + const key = seenMarkerStorageKey(seenStatusSource, selectedGoal.goalId); + const previous = readSeenMarker(window.localStorage, key); + if (!previous) { writeSeenMarker(window.localStorage, key, buildSeenMarker(seenMarkerInput)); return; } + const delta = compareSeenMarker(previous, seenMarkerInput); + if (delta.material) setSinceYouLooked({ goalId: selectedGoal.goalId, source: seenStatusSource, delta }); + }, [selectedGoal, seenGoalLoaded, seenMarkerInput, seenStatusSource]); + // Leaving a Goal records the owner's latest look at it; if the line was + // shown, that look acknowledges it. + useEffect(() => { + const current = selectedGoal && seenGoalLoaded ? { source: seenStatusSource, goalId: selectedGoal.goalId } : null; + const previous = seenVisitRef.current; + if (previous && (!current || current.goalId !== previous.goalId || current.source !== previous.source)) { + advanceSeenMarker(previous.goalId, previous.source); + seenComparedRef.current = null; + setSinceYouLooked((shown) => shown && shown.goalId === previous.goalId && shown.source === previous.source ? null : shown); + } + if (current) seenVisitRef.current = current; + else if (!selectedGoal) seenVisitRef.current = null; + }, [selectedGoal, seenGoalLoaded, seenStatusSource]); + function markSeen() { + if (!sinceYouLooked) return; + advanceSeenMarker(sinceYouLooked.goalId, sinceYouLooked.source); + setSinceYouLooked(null); + } const drawerSelection = useMemo | null>(() => { if (selection?.kind === "settings") return null; if (selection?.kind === "proposal") { @@ -2295,7 +2356,7 @@ export function PersonalWorkspacePage({ ) : selectedGoal ? ( setRailScope(null)} digest={workspaceDigest} review={workspaceReview} active={!settingsOpen && selectedGoalTab === "overview"} goal={selectedGoal} items={items} userTodos={model.userTodos} readOnly={readOnly} + overview: setRailScope(null)} digest={workspaceDigest} review={workspaceReview} active={!settingsOpen && selectedGoalTab === "overview"} goal={selectedGoal} items={items} userTodos={model.userTodos} readOnly={readOnly} onOpenDetails={() => setSelection({ kind: "goal", item: selectedGoal })} onSelect={setSelection} onView={setSelectedGoalTab} selectedTodoId={drawerSelection?.kind === "todo" ? drawerSelection.item.todoId : null} board={taskBoard(!settingsOpen && selectedGoalTab === "overview")} results={goalResults(!settingsOpen && selectedGoalTab === "overview")} diff --git a/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs new file mode 100644 index 0000000000..1b254c7a32 --- /dev/null +++ b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs @@ -0,0 +1,65 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { buildSeenMarker, compareSeenMarker, readSeenMarker, seenMarkerIdLimit, seenMarkerStorageKey, writeSeenMarker } from "./since-you-looked.ts"; + +const input = (overrides = {}) => ({ observedAt: "2026-10-15T08:00:00Z", counts: { open: 5, blocked: 1, done: 20 }, decisionIds: ["d1"], doneTodoIds: ["a", "b"], blockedTodoIds: ["x"], resultIds: ["r1"], ...overrides }); + +test("a marker keeps typed counts and ids only, bounded", () => { + const marker = buildSeenMarker(input({ doneTodoIds: Array.from({ length: seenMarkerIdLimit + 50 }, (_, i) => `t${i}`) })); + assert.equal(marker.schema, "goal_seen_marker_v0"); + assert.equal(marker.doneTodoIds.length, seenMarkerIdLimit, "done ids are bounded to the newest"); + assert.equal(marker.doneTodoIds[0], "t50"); + assert.deepEqual(Object.keys(marker).sort(), ["counts", "decisionIds", "doneTodoIds", "observedAt", "resultIds", "schema"], "no titles, text or cost"); +}); + +test("a no-change pair stays silent", () => { + const marker = buildSeenMarker(input()); + const delta = compareSeenMarker(marker, input({ observedAt: "2026-10-15T09:00:00Z" })); + assert.equal(delta.material, false); + assert.deepEqual([delta.newlyDone, delta.newDecisions, delta.newlyBlocked, delta.resultsReturned], [0, 0, 0, 0]); + assert.equal(delta.since, "2026-10-15T08:00:00Z"); +}); + +test("known work, decision, blocked and result changes are counted since the marker", () => { + const marker = buildSeenMarker(input()); + const delta = compareSeenMarker(marker, input({ counts: { open: 4, blocked: 2, done: 22 }, decisionIds: ["d1", "d2"], doneTodoIds: ["a", "b", "c", "d"], resultIds: ["r1", "r2"] })); + assert.deepEqual([delta.newlyDone, delta.newDecisions, delta.newlyBlocked, delta.resultsReturned], [2, 1, 1, 1]); + assert.equal(delta.material, true); +}); + +test("a shrinking queue or a resolved decision is not reported as new", () => { + const marker = buildSeenMarker(input({ decisionIds: ["d1", "d2"], counts: { open: 5, blocked: 3, done: 20 } })); + const delta = compareSeenMarker(marker, input({ decisionIds: [], counts: { open: 5, blocked: 1, done: 20 } })); + assert.equal(delta.material, false); +}); + +test("a trimmed marker cannot inflate newly done beyond the done-count growth", () => { + const many = Array.from({ length: seenMarkerIdLimit + 10 }, (_, i) => `t${i}`); + const marker = buildSeenMarker(input({ doneTodoIds: many, counts: { open: 5, blocked: 0, done: many.length } })); + const delta = compareSeenMarker(marker, input({ doneTodoIds: [...many, "new"], counts: { open: 4, blocked: 0, done: many.length + 1 } })); + assert.equal(delta.newlyDone, 1, "the ten ids trimmed from the marker are old work, not new"); +}); + +test("without a projected done count the ids alone decide", () => { + const marker = buildSeenMarker(input({ counts: { open: 5, blocked: 0, done: null } })); + const delta = compareSeenMarker(marker, input({ counts: { open: 5, blocked: 0, done: null }, doneTodoIds: ["a", "b", "c"] })); + assert.equal(delta.newlyDone, 1); +}); + +test("storage round-trips per source and Goal and tolerates garbage", () => { + const store = new Map(); + const storage = { getItem: (k) => store.get(k) ?? null, setItem: (k, v) => { store.set(k, v); } }; + const key = seenMarkerStorageKey("/status.json", "g1"); + assert.notEqual(key, seenMarkerStorageKey("http://remote/status.json", "g1"), "sources keep separate markers"); + const marker = buildSeenMarker(input()); + assert.equal(writeSeenMarker(storage, key, marker), true); + assert.deepEqual(readSeenMarker(storage, key), marker); + store.set(key, "{not json"); + assert.equal(readSeenMarker(storage, key), null); + store.set(key, JSON.stringify({ schema: "other" })); + assert.equal(readSeenMarker(storage, key), null); + const throwing = { getItem: () => { throw new Error("blocked"); }, setItem: () => { throw new Error("blocked"); } }; + assert.equal(readSeenMarker(throwing, key), null); + assert.equal(writeSeenMarker(throwing, key, marker), false); +}); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts new file mode 100644 index 0000000000..def5dfb25b --- /dev/null +++ b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts @@ -0,0 +1,113 @@ +/** + * "Since you looked": what changed in a Goal between two of the owner's + * visits, per RFC goal-workspace-surface-v0 §5.7. + * + * The browser keeps one marker per (status source, Goal) in local storage: + * typed counts, decision ids, a bounded set of done Todo and result ids, and + * the observation time. No titles, content or cost ever enter it, and nothing + * is written to the service. A delta is reported only when it is material; + * cost is omitted because the usage projection cannot qualify the interval. + * The marker advances when the owner marks it seen or leaves the Goal after + * the line was shown. This is not a notification channel. + */ + +export const seenMarkerSchema = "goal_seen_marker_v0"; +export const seenMarkerIdLimit = 200; + +export type SeenMarker = { + schema: typeof seenMarkerSchema; + observedAt: string; + counts: { open: number; blocked: number; done: number | null }; + decisionIds: string[]; + doneTodoIds: string[]; + resultIds: string[]; +}; + +export type SeenMarkerInput = { + observedAt: string; + counts: { open: number; blocked: number; done: number | null }; + decisionIds: readonly string[]; + doneTodoIds: readonly string[]; + blockedTodoIds: readonly string[]; + resultIds: readonly string[]; +}; + +export type SinceYouLookedDelta = { + since: string; + newlyDone: number; + newDecisions: number; + newlyBlocked: number; + resultsReturned: number; + material: boolean; +}; + +function bounded(ids: readonly string[]): string[] { + return [...new Set(ids)].slice(-seenMarkerIdLimit); +} + +export function buildSeenMarker(input: SeenMarkerInput): SeenMarker { + return { + schema: seenMarkerSchema, + observedAt: input.observedAt, + counts: { open: input.counts.open, blocked: input.counts.blocked, done: input.counts.done }, + decisionIds: bounded(input.decisionIds), + doneTodoIds: bounded(input.doneTodoIds), + resultIds: bounded(input.resultIds), + }; +} + +/** + * Compare the current observation with the stored marker. Newly done work is + * the done ids not in the marker, bounded by the growth of the done count + * when the owner projects one, so a trimmed marker never inflates it. New + * decisions and results are ids not seen before. Newly blocked work is the + * growth of the blocked count. A shrinking queue is not a change to report. + */ +export function compareSeenMarker(previous: SeenMarker, current: SeenMarkerInput): SinceYouLookedDelta { + const seenDone = new Set(previous.doneTodoIds); + const seenDecisions = new Set(previous.decisionIds); + const seenResults = new Set(previous.resultIds); + const doneById = current.doneTodoIds.filter((id) => !seenDone.has(id)).length; + const doneByCount = previous.counts.done !== null && current.counts.done !== null + ? Math.max(0, current.counts.done - previous.counts.done) : doneById; + const newlyDone = Math.min(doneById, doneByCount) || Math.max(0, doneByCount > 0 && doneById === 0 ? doneByCount : 0); + const newDecisions = current.decisionIds.filter((id) => !seenDecisions.has(id)).length; + const newlyBlocked = Math.max(0, current.counts.blocked - previous.counts.blocked); + const resultsReturned = current.resultIds.filter((id) => !seenResults.has(id)).length; + return { + since: previous.observedAt, + newlyDone, newDecisions, newlyBlocked, resultsReturned, + material: newlyDone > 0 || newDecisions > 0 || newlyBlocked > 0 || resultsReturned > 0, + }; +} + +export function seenMarkerStorageKey(statusSource: string, goalId: string): string { + return `loopx-pw-seen:${statusSource}:${goalId}`; +} + +export function readSeenMarker(storage: Pick, key: string): SeenMarker | null { + try { + const raw = storage.getItem(key); + if (!raw) return null; + const value = JSON.parse(raw) as Partial | null; + if (!value || value.schema !== seenMarkerSchema || typeof value.observedAt !== "string" || !value.counts) return null; + return { + schema: seenMarkerSchema, observedAt: value.observedAt, + counts: { open: Number(value.counts.open ?? 0), blocked: Number(value.counts.blocked ?? 0), done: value.counts.done === null || value.counts.done === undefined ? null : Number(value.counts.done) }, + decisionIds: Array.isArray(value.decisionIds) ? value.decisionIds.filter((id): id is string => typeof id === "string") : [], + doneTodoIds: Array.isArray(value.doneTodoIds) ? value.doneTodoIds.filter((id): id is string => typeof id === "string") : [], + resultIds: Array.isArray(value.resultIds) ? value.resultIds.filter((id): id is string => typeof id === "string") : [], + }; + } catch { + return null; + } +} + +export function writeSeenMarker(storage: Pick, key: string, marker: SeenMarker): boolean { + try { + storage.setItem(key, JSON.stringify(marker)); + return true; + } catch { + return false; + } +} diff --git a/docs/architecture/rfcs/goal-workspace-surface-v0.md b/docs/architecture/rfcs/goal-workspace-surface-v0.md index 6c1fb4b42b..e96497a4e7 100644 --- a/docs/architecture/rfcs/goal-workspace-surface-v0.md +++ b/docs/architecture/rfcs/goal-workspace-surface-v0.md @@ -2,7 +2,7 @@ - **RFC status:** Accepted - **Supersedes / closes:** none -- **Delivery maturity:** M1 merged in [#6191](https://github.com/loopx-project/loopx/pull/6191). M2 Now, rail, shared selection, task links, inline creation and Results are proposed in [#6255](https://github.com/loopx-project/loopx/pull/6255), pending maintainer review and merge. M3–M4 remain unimplemented; installed and live-team qualification is separate. +- **Delivery maturity:** M1 merged in [#6191](https://github.com/loopx-project/loopx/pull/6191). M2 merged in [#6255](https://github.com/loopx-project/loopx/pull/6255). M3's since-you-looked delta is proposed (see the 2026-10-15 checkpoint); target declaration and M4 remain unimplemented; installed and live-team qualification is separate. - **Owners:** existing workspace presentation owner. Data and action owners named in Section 5 keep their authority. - **Created / normative revision:** 2026-10-11. - **Implementation baseline:** `5c62c5c03`. @@ -456,6 +456,19 @@ second upgrade. | M3 Direction and return | Since-you-looked delta; target declaration through the acceptance owner | M2; D3 decided by its owner | V5, V8 | Hide declaration; delete marker | | M4 Live and expressive | W2 live state on rows and map from the execution observation owner; event motion; keyboard map and palette; dark inverse | M2; W2 observation available | V6 (motion, dark); live-team W2 acceptance | Disable motion/theme | +**Checkpoint 2026-10-15 (M3 since-you-looked, proposed).** +Section 5.7 is implemented in the packaged page: a browser-local marker per (status source, Goal) +holding typed counts, decision ids, bounded done Todo and result ids and the observation time; +no titles, content or cost, no server write. On open the page compares the current digest with the +marker and shows one line only for a material delta (newly done, new decisions, newly blocked, +results returned), dismissable with "Mark seen". One refinement of 5.7 is recorded: leaving a Goal +always records the owner's latest complete look, so a visit with nothing new still advances the +observation time and a Goal still loading on open can only under-count, never invent a change. +V5 is covered by a node test (no-change pair, trimmed marker, unavailable done count) and a browser +scenario (first visit silent, no-change return silent, work and decision changes shown and +dismissed, leave-acknowledge, reload, no service write). Cost stays omitted per D7. Target +declaration (the other half of M3) remains undelivered. + **Checkpoint 2026-10-12 (M2 candidate, #6255).** The existing TypeScript digest classifies responsibility, unassigned work and recorded decision dependencies; masthead, Now, rail and Board share it. Map and delivery evidence share the existing snapshot read. diff --git a/docs/assets/personal-workspace/since-you-looked-line.png b/docs/assets/personal-workspace/since-you-looked-line.png new file mode 100644 index 0000000000000000000000000000000000000000..17e3a0a213deb99e8f39a4d884598a0e51b68c2b GIT binary patch literal 174612 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%0A8V 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 z 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

X0IS@YDGS`BvrKE-y8|1)qbmy!W)FY$V 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 U;m_6C{W0aGRo+%ee)#l%0lpN^{Qv*} literal 0 HcmV?d00001 diff --git a/docs/guides/personal-workspace-user-guide.md b/docs/guides/personal-workspace-user-guide.md index f0f95c9fbf..f10f90b6e5 100644 --- a/docs/guides/personal-workspace-user-guide.md +++ b/docs/guides/personal-workspace-user-guide.md @@ -264,6 +264,24 @@ execution permissions. --- +### 4.1.2 自你上次查看 / Since you looked + +![回到 Goal 概览时的「自你上次查看」一行](../assets/personal-workspace/since-you-looked-line.png) + +回到一个 Goal 的概览时,如果自你上次查看以来有实质变化,概览顶部会出现一行安静的摘要: +「自你上次查看(3 小时前):新完成 2 项、新增 1 项等你决定、新受阻 1 项、新成果 1 项」,以及 +「标记已读」。它来自这个浏览器本地保存的上次查看标记(只有计数和编号,不含标题、内容或费用), +不写回服务,也不是通知。没有实质变化时不显示;点「标记已读」或离开这个 Goal 后标记前进,下次回来 +从新的基线算起。费用变化暂不纳入:用量投影还不能可靠地界定两次查看之间的区间。 + +When you return to a Goal's overview after a material change, one quiet line at the top says what +changed since your last look ("Since you looked (3 hours ago): 2 newly done, 1 new decision for +you, 1 newly blocked, 1 result returned") with **Mark seen**. It comes from a marker this browser +keeps locally (counts and ids only, never titles, content or cost), writes nothing to the service +and is not a notification. Nothing shows when nothing material changed; **Mark seen** or leaving the +Goal advances the marker so the next return starts from the new baseline. Spend is left out until +the usage projection can qualify the exact interval between two looks. + ### 4.2 Goal 概览与交付依据 / Goal overview 手机默认折叠目标与验收条件,可随时展开;顶部「全部工作」打开侧栏,Escape 关闭并返回入口。 diff --git a/examples/personal-workspace-browser-smoke.mjs b/examples/personal-workspace-browser-smoke.mjs index 4c5efde221..aca5008a31 100644 --- a/examples/personal-workspace-browser-smoke.mjs +++ b/examples/personal-workspace-browser-smoke.mjs @@ -5,6 +5,7 @@ import {goalWorkspaceCreateScenario} from "./personal-workspace-browser/goal-wor import {agentModelSelectionScenario} from "./personal-workspace-browser/agent-model-selection.mjs"; import {goalWorkspaceM2Scenario} from "./personal-workspace-browser/goal-workspace-m2.mjs"; import {goalWorkspaceSelectionScenario} from "./personal-workspace-browser/goal-workspace-selection.mjs"; +import {sinceYouLookedScenario} from "./personal-workspace-browser/since-you-looked.mjs"; import {firstGoalHomeScenario} from "./personal-workspace-browser/first-goal-home.mjs"; import { replanCadenceScenario } from "./personal-workspace-browser/replan-cadence.mjs"; import {nativeChildActivityScenario} from "./personal-workspace-browser/native-child-activity.mjs"; @@ -110,6 +111,7 @@ scenarioCatalog.push(zcodeGoalScenario); scenarioCatalog.push(goalWorkspaceM2Scenario); scenarioCatalog.push(goalWorkspaceCreateScenario); scenarioCatalog.push(goalWorkspaceSelectionScenario); +scenarioCatalog.push(sinceYouLookedScenario); scenarioCatalog.push(firstGoalHomeScenario); const requestedScenario = process.env.LOOPX_PERSONAL_WORKSPACE_SCENARIO; scenarioCatalog.push(agentModelSelectionScenario); diff --git a/examples/personal-workspace-browser/since-you-looked.mjs b/examples/personal-workspace-browser/since-you-looked.mjs new file mode 100644 index 0000000000..325c18f6f7 --- /dev/null +++ b/examples/personal-workspace-browser/since-you-looked.mjs @@ -0,0 +1,103 @@ +import assert from "node:assert/strict"; +import { resolve } from "node:path"; +import { outputDir } from "./fixture.mjs"; +import { openWorkspacePage } from "./scenario-context.mjs"; + +// RFC goal-workspace-surface-v0 §5.7 / V5: a returning owner sees what changed +// since their last acknowledged look at a Goal, from a browser-local marker +// (typed counts and ids, no cost, no service write). A first visit seeds the +// marker silently; a no-change return shows nothing; known work and decision +// changes appear and can be dismissed; leaving after the line was shown +// advances the marker so the next return starts clean. + +function firstGoal(fixture) { return fixture.attention_queue.items[0]; } + +function addGate(fixture, todoId, text) { + const first = firstGoal(fixture); + first.waiting_on = "user_or_controller"; + first.user_todos ??= { items: [], open_count: 0, source_section: "User Todo", total_count: 0 }; + first.user_todos.items.push({ done: false, status: "open", goal_id: first.goal_id, index: first.user_todos.items.length, role: "user", task_class: "user_gate", todo_id: todoId, text, updated_at: "2026-10-15T08:00:00Z" }); + first.user_todos.open_count = first.user_todos.items.filter((todo) => !todo.done).length; + first.user_todos.total_count = first.user_todos.items.length; +} + +function addDone(fixture, todoId, text) { + const first = firstGoal(fixture); + const todos = first.project_asset?.agent_todos ?? first.agent_todos; + todos.items.push({ done: true, status: "done", role: "agent", task_class: "advancement_task", goal_id: first.goal_id, todo_id: todoId, text, title: text, completed_at: "2026-10-15T09:00:00Z" }); + if (typeof todos.advancement_done_count === "number") todos.advancement_done_count += 1; + if (typeof todos.done === "number") todos.done += 1; +} + +export const sinceYouLookedScenario = { + id: "since-you-looked", + async run({ browser, collectCoverage, url }) { + const session = await openWorkspacePage(browser, url, { collectCoverage, viewport: { width: 1440, height: 900 } }); + const { api, page } = session; + const writes = []; + page.on("request", (request) => { if (request.method() !== "GET" && /\/api\//.test(request.url())) writes.push(`${request.method()} ${new URL(request.url()).pathname}`); }); + const openGoal = async () => { + await page.locator(".personal-goal-row").first().locator(".personal-goal-link").click(); + await page.getByTestId("goal-vitals").waitFor({ state: "visible", timeout: 15_000 }); + await page.getByRole("navigation", { name: "Goal 视图" }).getByRole("button", { name: "概览", exact: true }).click(); + }; + const home = () => page.getByRole("button", { name: /^LoopX 管家/ }).first().click(); + const refresh = () => page.getByRole("button", { name: "刷新状态", exact: true }).click(); + const line = page.getByTestId("goal-since-you-looked"); + try { + await openGoal(); + const goalId = await page.evaluate(() => Object.keys(localStorage).filter((key) => key.startsWith("loopx-pw-seen:/status.json:")).map((key) => key.split(":").pop())[0] ?? null); + assert.ok(goalId, "The first visit seeds a marker for this source and Goal"); + assert.equal(await line.count(), 0, "A first visit has nothing to compare"); + const marker = await page.evaluate((id) => JSON.parse(localStorage.getItem(`loopx-pw-seen:/status.json:${id}`)), goalId); + assert.equal(marker.schema, "goal_seen_marker_v0"); + assert.deepEqual(Object.keys(marker).sort(), ["counts", "decisionIds", "doneTodoIds", "observedAt", "resultIds", "schema"], "typed counts and ids only"); + + // A return with nothing new stays quiet. + await home(); await refresh(); await openGoal(); + await page.waitForTimeout(500); + assert.equal(await line.count(), 0, "No change stays absent"); + + // Work completed and a decision raised while away appear, and can be dismissed. + await home(); + api.transformStatus = (fixture) => { addDone(fixture, "seen-done-1", "Reconcile the March export"); addGate(fixture, "seen-gate-1", "Approve the restated FY2023 operating cash flow"); }; + await refresh(); + await openGoal(); + await line.waitFor({ state: "visible", timeout: 15_000 }); + const text = await line.innerText(); + assert.match(text, /自你上次查看(.+):/); + assert.match(text, /新完成 1 项/, `newly done counted once: ${text}`); + assert.match(text, /新增 1 项等你决定/, `new decision counted once: ${text}`); + assert.doesNotMatch(text, /成本|\$|¥/, "No cost in the delta"); + await page.screenshot({ path: resolve(outputDir, "since-you-looked-line.png"), animations: "disabled" }); + await line.getByRole("button", { name: "标记已读" }).click(); + await line.waitFor({ state: "hidden", timeout: 5_000 }); + const after = await page.evaluate((id) => JSON.parse(localStorage.getItem(`loopx-pw-seen:/status.json:${id}`)), goalId); + assert.ok(after.doneTodoIds.includes("seen-done-1") && after.decisionIds.includes("seen-gate-1"), "Mark seen advances the marker to the current observation"); + + // Leaving after the line was shown also advances it; the next return starts clean. + await home(); + api.transformStatus = (fixture) => { addDone(fixture, "seen-done-1", "Reconcile the March export"); addGate(fixture, "seen-gate-1", "Approve the restated FY2023 operating cash flow"); addGate(fixture, "seen-gate-2", "Choose the segment disclosure source"); }; + await refresh(); + await openGoal(); + await line.waitFor({ state: "visible", timeout: 15_000 }); + assert.match(await line.innerText(), /新增 1 项等你决定/); + await home(); + await openGoal(); + await page.waitForTimeout(500); + assert.equal(await line.count(), 0, "Leaving the Goal after the line was shown acknowledged it"); + + // Reload keeps the marker; nothing was written to the service. + await page.reload({ waitUntil: "networkidle" }); + await page.getByTestId("personal-goal-home").waitFor({ state: "visible", timeout: 15_000 }); + await openGoal(); + await page.waitForTimeout(500); + assert.equal(await line.count(), 0, "The marker survives a reload"); + assert.deepEqual(writes.filter((row) => !/\/api\/chat\/(sessions|capabilities|agent-model-options|projection-messages|lark|goal-results)/.test(row)), [], `no service writes for the marker: ${writes.join(", ")}`); + return { coverageEntries: await session.close() }; + } catch (error) { + await session.close().catch(() => {}); + throw error; + } + }, +}; From cbc6320c7b12ffbc6af9a5964d3cba028b7619c7 Mon Sep 17 00:00:00 2001 From: song Date: Fri, 16 Oct 2026 00:47:27 +0800 Subject: [PATCH 2/5] fix(workspace): count only owner-blocking decisions and survive denied storage Review of the since-you-looked line found two regressions and one semantic gap: - Every user Todo of the Goal was a "decision", so an optional user action raised while the owner was away was reported as a new item waiting on them. Decisions now reuse the typed attention classification (WorkspaceAttention.blocking); optional items are listed, never counted. - window.localStorage was read as a bare property before the guarded helpers, so a browser that denies storage (the getter throws) took the whole Goal down. Acquisition now sits inside the same boundary; without storage the owner loses only the local look memory. - Leaving a Goal advanced the marker to the latest observation, which could acknowledge a change that arrived during the visit without being shown. Leaving now records the look that was compared on that visit; Mark seen still acknowledges what is on screen. Against a real service the owner's refresh lands after the Goal opens, so the comparison latched on the snapshot about to be replaced and the line never appeared. The comparison now waits for an owner refresh in flight. Helper test covers the throwing getter; the fixture scenario adds the denied-storage return; the contract test pins the four rules; RFC checkpoint and user guide state the revised semantics. Co-Authored-By: Claude Fable 5.1 Signed-off-by: song --- .../personal-workspace-contract.test.mjs | 14 ++++++-- .../personal-workspace-page.tsx | 36 ++++++++++++------- .../since-you-looked.test.mjs | 16 ++++++++- .../personal-workspace/since-you-looked.ts | 24 +++++++++++-- .../rfcs/goal-workspace-surface-v0.md | 21 +++++++---- docs/guides/personal-workspace-user-guide.md | 15 +++++--- .../since-you-looked.mjs | 11 ++++++ 7 files changed, 107 insertions(+), 30 deletions(-) diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs index 57b16211eb..79b5064c78 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs @@ -602,15 +602,23 @@ for (const key of ["home.firstGoal.title", "home.firstGoal.lead", "home.firstGoa // Since you looked (RFC goal-workspace-surface §5.7): a browser-local marker // per source and Goal, typed counts and ids only, no cost, no service write; -// the line appears only for a material delta and advances on Mark seen or on -// leaving the Goal after it was shown. +// the line appears only for a material delta. Mark seen acknowledges what is +// on screen; leaving acknowledges the look that was compared on that visit. +// Decisions reuse the typed attention classification, the comparison waits +// for an owner refresh still in flight, and denied storage only loses memory. const sinceYouLooked = source("./since-you-looked.ts"); const seenMarkerType = sinceYouLooked.slice(sinceYouLooked.indexOf("export type SeenMarker = {"), sinceYouLooked.indexOf("};", sinceYouLooked.indexOf("export type SeenMarker = {"))); assert.doesNotMatch(seenMarkerType, /cost|title|text|usd/i, "The marker carries no titles, text or cost"); assert.match(sinceYouLooked, /export const seenMarkerIdLimit = 200;/, "Done and result ids are bounded"); assert.match(page, /seenMarkerStorageKey\(seenStatusSource, selectedGoal\.goalId\)/, "Markers are scoped per status source and Goal"); assert.match(page, /if \(delta\.material\) setSinceYouLooked\(/, "Only a material delta shows the line"); -assert.match(page, /advanceSeenMarker\(previous\.goalId, previous\.source\);[\s\S]*?setSinceYouLooked\(\(shown\) => shown && shown\.goalId === previous\.goalId/, "Leaving the Goal records the latest look and acknowledges a shown line"); +assert.match(page, /advanceSeenMarker\(previous\.goalId, previous\.source, "compared"\);[\s\S]*?setSinceYouLooked\(\(shown\) => shown && shown\.goalId === previous\.goalId/, "Leaving the Goal records the compared look and acknowledges a shown line"); +assert.match(page, /advanceSeenMarker\(sinceYouLooked\.goalId, sinceYouLooked\.source, "latest"\)/, "Mark seen acknowledges the current look"); +assert.match(page, /decisionIds: model\.userTodos\.filter\(\(item\) => item\.goalId === selectedGoal\.goalId && item\.blocking\)/, "Only items that wait on the owner are decisions; optional user actions keep the typed classification"); +assert.match(page, /if \(refreshState === "loading"\) return;[\s\S]*?seenComparedRef\.current = scope;/, "The comparison waits for an owner refresh still in flight instead of latching on the snapshot it replaces"); +const seenBlock = page.slice(page.indexOf("const seenStatusSource"), page.indexOf("function markSeen")); +assert.doesNotMatch(seenBlock, /window\.localStorage/, "Storage is acquired through the guarded helper, never read as a bare property"); +assert.match(sinceYouLooked, /export function seenMarkerStorage\([\s\S]*?try \{[\s\S]*?scope\.localStorage/, "Acquiring storage sits inside the failure boundary"); assert.doesNotMatch(sinceYouLooked + page.slice(page.indexOf("const seenStatusSource"), page.indexOf("function markSeen")), /fetch\(|requestJson|\/api\//, "No service write or read belongs to the marker"); for (const key of ["since.lead", "since.newlyDone", "since.newDecisions", "since.newlyBlocked", "since.resultsReturned", "since.markSeen"]) { assert.equal((i18n.match(new RegExp(`"${key.replace(/\./g, "\\.")}":`, "g")) ?? []).length, 2, `${key} exists in both locales`); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx index 964b3038d4..d93b872c0a 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx @@ -88,7 +88,7 @@ import { WorkspaceSettingsPage } from "./workspace-settings-page"; import { UsageStatisticsNotice } from "./usage-statistics-notice"; import { readWorkspaceTheme, writeWorkspaceTheme, type WorkspaceTheme } from "./workspace-theme"; import { compareProposalRecency } from "./proposal-recency"; -import { buildSeenMarker, compareSeenMarker, readSeenMarker, seenMarkerStorageKey, writeSeenMarker, type SeenMarkerInput, type SinceYouLookedDelta } from "./since-you-looked"; +import { buildSeenMarker, compareSeenMarker, readSeenMarker, seenMarkerStorage, seenMarkerStorageKey, writeSeenMarker, type SeenMarkerInput, type SinceYouLookedDelta } from "./since-you-looked"; import { WorkspaceShell } from "./workspace-shell"; import type { StatusSourceControl } from "./status-source-switcher"; import "./personal-workspace.css"; @@ -1260,7 +1260,8 @@ export function PersonalWorkspacePage({ return { observedAt: workspaceDigest.observedAt, counts: { open: workspaceDigest.work.open, blocked: workspaceDigest.work.classes.blocked, done: workspaceDigest.work.done }, - decisionIds: model.userTodos.filter((item) => item.goalId === selectedGoal.goalId).map((item) => item.todoId), + // Only an item that waits on the owner is a decision; an optional user action keeps the typed owner's classification. + decisionIds: model.userTodos.filter((item) => item.goalId === selectedGoal.goalId && item.blocking).map((item) => item.todoId), doneTodoIds: workspaceDigest.work.rail.done, blockedTodoIds: workspaceDigest.work.rail.blocked, resultIds: items.flatMap((item) => item.kind === "output" && item.output.goalId === selectedGoal.goalId ? [item.output.outputId] : []), @@ -1273,29 +1274,40 @@ export function PersonalWorkspacePage({ }, [selectedGoal, seenMarkerInput, seenStatusSource]); const [sinceYouLooked, setSinceYouLooked] = useState<{ goalId: string; source: string; delta: SinceYouLookedDelta } | null>(null); const seenComparedRef = useRef(null); + const comparedSeenInputsRef = useRef(new Map()); const seenVisitRef = useRef<{ source: string; goalId: string } | null>(null); - function advanceSeenMarker(goalId: string, source: string) { - const input = latestSeenInputsRef.current.get(seenScopeKey(source, goalId)); - if (input) writeSeenMarker(window.localStorage, seenMarkerStorageKey(source, goalId), buildSeenMarker(input)); + // "Mark seen" acknowledges what is on screen now; leaving acknowledges only + // the look that was compared (and shown when material), so a change that + // arrived during the visit without being presented is still reported next time. + function advanceSeenMarker(goalId: string, source: string, look: "latest" | "compared") { + const scope = seenScopeKey(source, goalId); + const input = look === "latest" ? latestSeenInputsRef.current.get(scope) : comparedSeenInputsRef.current.get(scope); + if (input) writeSeenMarker(seenMarkerStorage(), seenMarkerStorageKey(source, goalId), buildSeenMarker(input)); } useEffect(() => { if (!selectedGoal || !seenGoalLoaded || !seenMarkerInput) return; + // An owner refresh still in flight when the Goal opens belongs to this + // look: against a real service it lands after the open, so compare once + // it has, not against the snapshot it is about to replace. + if (refreshState === "loading") return; const scope = seenScopeKey(seenStatusSource, selectedGoal.goalId); if (seenComparedRef.current === scope) return; seenComparedRef.current = scope; + comparedSeenInputsRef.current.set(scope, seenMarkerInput); + const storage = seenMarkerStorage(); const key = seenMarkerStorageKey(seenStatusSource, selectedGoal.goalId); - const previous = readSeenMarker(window.localStorage, key); - if (!previous) { writeSeenMarker(window.localStorage, key, buildSeenMarker(seenMarkerInput)); return; } + const previous = readSeenMarker(storage, key); + if (!previous) { writeSeenMarker(storage, key, buildSeenMarker(seenMarkerInput)); return; } const delta = compareSeenMarker(previous, seenMarkerInput); if (delta.material) setSinceYouLooked({ goalId: selectedGoal.goalId, source: seenStatusSource, delta }); - }, [selectedGoal, seenGoalLoaded, seenMarkerInput, seenStatusSource]); - // Leaving a Goal records the owner's latest look at it; if the line was - // shown, that look acknowledges it. + }, [selectedGoal, seenGoalLoaded, seenMarkerInput, seenStatusSource, refreshState]); + // Leaving a Goal records the look that was compared on this visit; if the + // line was shown, that look acknowledges it. useEffect(() => { const current = selectedGoal && seenGoalLoaded ? { source: seenStatusSource, goalId: selectedGoal.goalId } : null; const previous = seenVisitRef.current; if (previous && (!current || current.goalId !== previous.goalId || current.source !== previous.source)) { - advanceSeenMarker(previous.goalId, previous.source); + advanceSeenMarker(previous.goalId, previous.source, "compared"); seenComparedRef.current = null; setSinceYouLooked((shown) => shown && shown.goalId === previous.goalId && shown.source === previous.source ? null : shown); } @@ -1304,7 +1316,7 @@ export function PersonalWorkspacePage({ }, [selectedGoal, seenGoalLoaded, seenStatusSource]); function markSeen() { if (!sinceYouLooked) return; - advanceSeenMarker(sinceYouLooked.goalId, sinceYouLooked.source); + advanceSeenMarker(sinceYouLooked.goalId, sinceYouLooked.source, "latest"); setSinceYouLooked(null); } const drawerSelection = useMemo | null>(() => { diff --git a/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs index 1b254c7a32..bec4a420a2 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs +++ b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { buildSeenMarker, compareSeenMarker, readSeenMarker, seenMarkerIdLimit, seenMarkerStorageKey, writeSeenMarker } from "./since-you-looked.ts"; +import { buildSeenMarker, compareSeenMarker, readSeenMarker, seenMarkerIdLimit, seenMarkerStorage, seenMarkerStorageKey, writeSeenMarker } from "./since-you-looked.ts"; const input = (overrides = {}) => ({ observedAt: "2026-10-15T08:00:00Z", counts: { open: 5, blocked: 1, done: 20 }, decisionIds: ["d1"], doneTodoIds: ["a", "b"], blockedTodoIds: ["x"], resultIds: ["r1"], ...overrides }); @@ -63,3 +63,17 @@ test("storage round-trips per source and Goal and tolerates garbage", () => { assert.equal(readSeenMarker(throwing, key), null); assert.equal(writeSeenMarker(throwing, key, marker), false); }); + +test("a browser that denies storage leaves the marker silent, whether the property or its methods throw", () => { + const key = seenMarkerStorageKey("/status.json", "g1"); + const denied = {}; + Object.defineProperty(denied, "localStorage", { get() { throw new Error("SecurityError: storage denied"); } }); + assert.equal(seenMarkerStorage(denied), null, "a throwing localStorage getter is caught at acquisition"); + assert.equal(seenMarkerStorage({}), null, "an absent storage is null, not a crash"); + assert.equal(seenMarkerStorage({ localStorage: undefined }), null); + assert.equal(readSeenMarker(null, key), null); + assert.equal(writeSeenMarker(null, key, buildSeenMarker(input())), false); + const store = new Map(); + const storage = { getItem: (k) => store.get(k) ?? null, setItem: (k, v) => { store.set(k, v); } }; + assert.equal(seenMarkerStorage({ localStorage: storage }), storage, "a usable storage passes through unchanged"); +}); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts index def5dfb25b..065a4858e9 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts +++ b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts @@ -85,7 +85,26 @@ export function seenMarkerStorageKey(statusSource: string, goalId: string): stri return `loopx-pw-seen:${statusSource}:${goalId}`; } -export function readSeenMarker(storage: Pick, key: string): SeenMarker | null { +export type SeenMarkerStorage = Pick; + +/** + * Acquire the browser's local storage for the marker, or null when the + * browser denies it. Reading the `localStorage` property itself can throw + * (a SecurityError under a storage policy), so the acquisition sits inside + * the same boundary as the reads and writes: without storage the owner only + * loses the local look memory, never the Goal. + */ +export function seenMarkerStorage(scope: { localStorage?: SeenMarkerStorage } = typeof window === "undefined" ? {} : window): SeenMarkerStorage | null { + try { + const storage = scope.localStorage; + return storage && typeof storage.getItem === "function" && typeof storage.setItem === "function" ? storage : null; + } catch { + return null; + } +} + +export function readSeenMarker(storage: Pick | null, key: string): SeenMarker | null { + if (!storage) return null; try { const raw = storage.getItem(key); if (!raw) return null; @@ -103,7 +122,8 @@ export function readSeenMarker(storage: Pick, key: string): } } -export function writeSeenMarker(storage: Pick, key: string, marker: SeenMarker): boolean { +export function writeSeenMarker(storage: Pick | null, key: string, marker: SeenMarker): boolean { + if (!storage) return false; try { storage.setItem(key, JSON.stringify(marker)); return true; diff --git a/docs/architecture/rfcs/goal-workspace-surface-v0.md b/docs/architecture/rfcs/goal-workspace-surface-v0.md index e96497a4e7..08b724561a 100644 --- a/docs/architecture/rfcs/goal-workspace-surface-v0.md +++ b/docs/architecture/rfcs/goal-workspace-surface-v0.md @@ -461,13 +461,20 @@ Section 5.7 is implemented in the packaged page: a browser-local marker per (sta holding typed counts, decision ids, bounded done Todo and result ids and the observation time; no titles, content or cost, no server write. On open the page compares the current digest with the marker and shows one line only for a material delta (newly done, new decisions, newly blocked, -results returned), dismissable with "Mark seen". One refinement of 5.7 is recorded: leaving a Goal -always records the owner's latest complete look, so a visit with nothing new still advances the -observation time and a Goal still loading on open can only under-count, never invent a change. -V5 is covered by a node test (no-change pair, trimmed marker, unavailable done count) and a browser -scenario (first visit silent, no-change return silent, work and decision changes shown and -dismissed, leave-acknowledge, reload, no service write). Cost stays omitted per D7. Target -declaration (the other half of M3) remains undelivered. +results returned), dismissable with "Mark seen". A decision is an item the typed attention lane +classifies as waiting on the owner; an optional user action is listed but never counted. The +comparison waits for an owner refresh still in flight when the Goal opens, so against a real +service it compares the look that lands, not the snapshot it replaces. One refinement of 5.7 is +recorded: "Mark seen" acknowledges what is on screen, while leaving a Goal records only the look +that was compared on that visit, so a visit with nothing new still advances the observation time +and a change that arrived during the visit without being presented is reported next time. A +browser that denies local storage loses only this memory; the Goal stays readable. +V5 is covered by a node test (no-change pair, trimmed marker, unavailable done count, denied +storage), a fixture browser scenario (first visit silent, no-change return silent, work and +decision changes shown and dismissed, leave-acknowledge, reload, no service write, denied storage +getter) and a real-service scenario against a disposable Todo owner (optional user action silent, +CLI completion and gate counted as the lane counts them, Mark seen). Cost stays omitted per D7. +Target declaration (the other half of M3) remains undelivered. **Checkpoint 2026-10-12 (M2 candidate, #6255).** The existing TypeScript digest classifies responsibility, unassigned work and recorded decision dependencies; diff --git a/docs/guides/personal-workspace-user-guide.md b/docs/guides/personal-workspace-user-guide.md index f10f90b6e5..d018fa8cda 100644 --- a/docs/guides/personal-workspace-user-guide.md +++ b/docs/guides/personal-workspace-user-guide.md @@ -271,16 +271,21 @@ execution permissions. 回到一个 Goal 的概览时,如果自你上次查看以来有实质变化,概览顶部会出现一行安静的摘要: 「自你上次查看(3 小时前):新完成 2 项、新增 1 项等你决定、新受阻 1 项、新成果 1 项」,以及 「标记已读」。它来自这个浏览器本地保存的上次查看标记(只有计数和编号,不含标题、内容或费用), -不写回服务,也不是通知。没有实质变化时不显示;点「标记已读」或离开这个 Goal 后标记前进,下次回来 -从新的基线算起。费用变化暂不纳入:用量投影还不能可靠地界定两次查看之间的区间。 +不写回服务,也不是通知。「等你决定」只计需要你介入的事项,可选提醒会列出但不计入。没有实质变化时 +不显示;点「标记已读」以当前画面为基线,离开这个 Goal 则以这次进入时看到的那一眼为基线,期间到达但 +没有呈现的变化下次回来仍会算入。浏览器不允许本地存储时只是失去这份记忆,Goal 照常可读。费用变化暂 +不纳入:用量投影还不能可靠地界定两次查看之间的区间。 When you return to a Goal's overview after a material change, one quiet line at the top says what changed since your last look ("Since you looked (3 hours ago): 2 newly done, 1 new decision for you, 1 newly blocked, 1 result returned") with **Mark seen**. It comes from a marker this browser keeps locally (counts and ids only, never titles, content or cost), writes nothing to the service -and is not a notification. Nothing shows when nothing material changed; **Mark seen** or leaving the -Goal advances the marker so the next return starts from the new baseline. Spend is left out until -the usage projection can qualify the exact interval between two looks. +and is not a notification. "Decisions for you" counts only items that wait on you; an optional +reminder is listed but not counted. Nothing shows when nothing material changed. **Mark seen** makes +what is on screen the new baseline; leaving the Goal keeps the look you arrived to, so a change that +landed during the visit without being shown is still reported next time. A browser that denies +local storage only loses this memory; the Goal stays readable. Spend is left out until the usage +projection can qualify the exact interval between two looks. ### 4.2 Goal 概览与交付依据 / Goal overview diff --git a/examples/personal-workspace-browser/since-you-looked.mjs b/examples/personal-workspace-browser/since-you-looked.mjs index 325c18f6f7..9ce2c8d7b1 100644 --- a/examples/personal-workspace-browser/since-you-looked.mjs +++ b/examples/personal-workspace-browser/since-you-looked.mjs @@ -94,6 +94,17 @@ export const sinceYouLookedScenario = { await page.waitForTimeout(500); assert.equal(await line.count(), 0, "The marker survives a reload"); assert.deepEqual(writes.filter((row) => !/\/api\/chat\/(sessions|capabilities|agent-model-options|projection-messages|lark|goal-results)/.test(row)), [], `no service writes for the marker: ${writes.join(", ")}`); + + // A browser that denies storage (the property getter itself throws) keeps + // the Goal readable; only the local look memory is lost. + await home(); + await page.evaluate(() => { Object.defineProperty(window, "localStorage", { configurable: true, get() { throw new DOMException("Storage is disabled", "SecurityError"); } }); }); + await openGoal(); + await page.waitForTimeout(500); + assert.equal(await line.count(), 0, "Without storage there is no marker to compare"); + assert.doesNotMatch(await page.locator("body").innerText(), /页面暂时无法显示|Page unavailable/, "Denied storage never takes the Goal down"); + await line.waitFor({ state: "hidden" }); + await page.getByTestId("goal-vitals").waitFor({ state: "visible", timeout: 5_000 }); return { coverageEntries: await session.close() }; } catch (error) { await session.close().catch(() => {}); From 95caadb5638ca52b2945421b692383ab1a7572d0 Mon Sep 17 00:00:00 2001 From: song Date: Fri, 16 Oct 2026 00:47:27 +0800 Subject: [PATCH 3/5] test(dashboard): prove since-you-looked against the real service Add since-you-looked-live: a disposable real Chat/Todo owner serves status.json; the CLI raises an optional user action (silent on return), then completes an agent task and raises a gate (one newly done, decisions equal to the typed lane's own count); Mark seen clears the next return. Only the status path is proxied; the document URL also ends in status.json. Co-Authored-By: Claude Fable 5.1 Signed-off-by: song --- examples/personal-workspace-browser-smoke.mjs | 2 + .../since-you-looked-live.mjs | 102 ++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 examples/personal-workspace-browser/since-you-looked-live.mjs diff --git a/examples/personal-workspace-browser-smoke.mjs b/examples/personal-workspace-browser-smoke.mjs index aca5008a31..532372cda5 100644 --- a/examples/personal-workspace-browser-smoke.mjs +++ b/examples/personal-workspace-browser-smoke.mjs @@ -6,6 +6,7 @@ import {agentModelSelectionScenario} from "./personal-workspace-browser/agent-mo import {goalWorkspaceM2Scenario} from "./personal-workspace-browser/goal-workspace-m2.mjs"; import {goalWorkspaceSelectionScenario} from "./personal-workspace-browser/goal-workspace-selection.mjs"; import {sinceYouLookedScenario} from "./personal-workspace-browser/since-you-looked.mjs"; +import {sinceYouLookedLiveScenario} from "./personal-workspace-browser/since-you-looked-live.mjs"; import {firstGoalHomeScenario} from "./personal-workspace-browser/first-goal-home.mjs"; import { replanCadenceScenario } from "./personal-workspace-browser/replan-cadence.mjs"; import {nativeChildActivityScenario} from "./personal-workspace-browser/native-child-activity.mjs"; @@ -112,6 +113,7 @@ scenarioCatalog.push(goalWorkspaceM2Scenario); scenarioCatalog.push(goalWorkspaceCreateScenario); scenarioCatalog.push(goalWorkspaceSelectionScenario); scenarioCatalog.push(sinceYouLookedScenario); +scenarioCatalog.push(sinceYouLookedLiveScenario); scenarioCatalog.push(firstGoalHomeScenario); const requestedScenario = process.env.LOOPX_PERSONAL_WORKSPACE_SCENARIO; scenarioCatalog.push(agentModelSelectionScenario); diff --git a/examples/personal-workspace-browser/since-you-looked-live.mjs b/examples/personal-workspace-browser/since-you-looked-live.mjs new file mode 100644 index 0000000000..d9401c0dbf --- /dev/null +++ b/examples/personal-workspace-browser/since-you-looked-live.mjs @@ -0,0 +1,102 @@ +import assert from "node:assert/strict"; +import { execFile, spawn } from "node:child_process"; +import { once } from "node:events"; +import { mkdtemp } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { createInterface } from "node:readline"; +import { promisify } from "node:util"; +import { resolveTestPython } from "../../scripts/test-python.mjs"; +import { outputDir, repoRoot } from "./fixture.mjs"; +import { fetchWorkspaceBackend, openWorkspacePage } from "./scenario-context.mjs"; + +// End to end against the real service: a disposable real Chat/Todo owner +// serves status.json while the owner looks at a Goal, leaves, and the CLI +// changes the Goal's Todos. An optional user action raised while away is not +// a decision; a completed agent task and a gate are, counted exactly as the +// typed attention lane counts them; Mark seen advances the marker. Only the +// status path is proxied; everything else keeps the fixture. + +const goalId = "progress-projection"; +const optionalText = "Consider renaming the export folder"; +const gateText = "Approve the restated FY2023 operating cash flow"; +const doneText = "Reconcile the March export"; + +export const sinceYouLookedLiveScenario = { + id: "since-you-looked-live", + async run({ browser, collectCoverage, url }) { + const root = await mkdtemp(join(tmpdir(), "loopx-seen-live-")); + const python = resolveTestPython({ repoRoot }); + const child = spawn(python, ["-u", resolve(repoRoot, "examples/personal-workspace-browser/goal-workspace-todo-backend.py"), root], { cwd: repoRoot, env: { ...process.env, PYTHONPATH: repoRoot }, stdio: ["ignore", "pipe", "pipe"] }); + const closed = once(child, "close"); + const lines = createInterface({ input: child.stdout }); + let diagnostics = "", ready, session; + child.stderr.on("data", (chunk) => { diagnostics = (diagnostics + chunk).slice(-4000); }); + const cli = (...args) => promisify(execFile)(python, ["-c", "from loopx.entrypoint import main; main()", "--format", "json", "--registry", ready.registry, "todo", ...args], { cwd: repoRoot, env: { ...process.env, PYTHONPATH: repoRoot } }).then(({ stdout }) => JSON.parse(stdout)); + try { + ready = await Promise.race([once(lines, "line").then(([line]) => JSON.parse(line)), closed.then(() => { throw new Error(`todo backend exited: ${diagnostics}`); })]); + const backend = `http://127.0.0.1:${ready.port}`; + session = await openWorkspacePage(browser, url, { collectCoverage, viewport: { width: 1440, height: 900 }, beforeGoto: async (_api, page) => { + // The document URL also ends in "status.json" (its statusUrl query), so match the path, not a glob. + await page.route((target) => target.pathname === "/status.json", async (route) => { + try { const response = await fetchWorkspaceBackend(route, backend); await route.fulfill({ response }); } + catch { await route.abort().catch(() => {}); } + }); + } }); + const { page } = session; + const line = page.getByTestId("goal-since-you-looked"); + const overview = page.locator(".goal-overview"); + const openGoal = async () => { + await page.locator(".personal-goal-row").first().locator(".personal-goal-link").click(); + await page.getByTestId("goal-vitals").waitFor({ state: "visible", timeout: 20_000 }); + await page.getByRole("navigation", { name: "Goal 视图" }).getByRole("button", { name: "概览", exact: true }).click(); + }; + const home = () => page.getByRole("button", { name: /^LoopX 管家/ }).first().click(); + const refresh = () => page.getByRole("button", { name: "刷新状态", exact: true }).click(); + await openGoal(); + await page.waitForTimeout(1_000); + assert.equal(await line.count(), 0, "A first look has nothing to compare"); + await home(); + + // Away: an optional reminder for the owner arrives. It is listed, but it is not a decision. + assert.equal((await cli("add", "--goal-id", goalId, "--role", "user", "--task-class", "user_action", "--text", optionalText)).ok, true); + await refresh(); + await openGoal(); + await overview.getByText(optionalText).first().waitFor({ state: "visible", timeout: 20_000 }); + await page.waitForTimeout(1_500); + assert.equal(await line.count(), 0, "An optional user action is not reported as a new decision"); + await home(); + + // Away: an agent task is added and completed, and a gate is raised. + const task = await cli("add", "--goal-id", goalId, "--role", "agent", "--task-class", "advancement_task", "--text", doneText); + assert.equal(task.ok, true); + const completed = await cli("complete", "--goal-id", goalId, "--role", "agent", "--todo-id", task.todo_id); + assert.equal(completed.ok, true, JSON.stringify(completed).slice(0, 200)); + assert.equal((await cli("add", "--goal-id", goalId, "--role", "user", "--task-class", "user_gate", "--text", gateText)).ok, true); + await refresh(); + await openGoal(); + await line.waitFor({ state: "visible", timeout: 20_000 }); + const text = await line.innerText(); + assert.match(text, /新完成 1 项/, `the real completion is counted once: ${text}`); + // The decision count is the typed attention lane's own count of items waiting on the owner. + const waiting = (await page.getByTestId("goal-vitals").innerText()).replace(/\s+/g, " ").match(/(\d+) 等你决定/); + assert.ok(waiting && Number(waiting[1]) >= 1, "the lane shows at least the gate waiting on the owner"); + assert.match(text, new RegExp(`新增 ${waiting[1]} 项等你决定`), `decisions match the lane (${waiting[1]}): ${text}`); + await page.screenshot({ path: resolve(outputDir, "since-you-looked-live.png"), animations: "disabled" }); + await line.getByRole("button", { name: "标记已读" }).click(); + await line.waitFor({ state: "hidden", timeout: 5_000 }); + await home(); + await openGoal(); + await page.waitForTimeout(1_000); + assert.equal(await line.count(), 0, "Nothing new after Mark seen"); + return { coverageEntries: await session.close(), note: "real Todo owner and status; CLI optional action, completion and gate reach the return line" }; + } catch (error) { + if (diagnostics) process.stderr.write(diagnostics); + await session?.close().catch(() => {}); + throw error; + } finally { + lines.close(); + if (child.exitCode === null && child.signalCode === null) { child.kill("SIGTERM"); const kill = setTimeout(() => child.kill("SIGKILL"), 5000); try { await closed; } finally { clearTimeout(kill); } } + } + }, +}; From 47a7ba58e85158974ba17876e840034b517cdef9 Mon Sep 17 00:00:00 2001 From: song Date: Fri, 16 Oct 2026 02:21:50 +0800 Subject: [PATCH 4/5] fix(workspace): keep Mark seen monotonic and bound new decisions by id coverage Two legal states still produced a wrong line: - Mark seen after a refresh during the visit recorded three decisions, but leaving wrote the two the visit had started on back over it, so the next return reported one new decision again. Mark seen now becomes the look the visit stands on, and no write moves the marker back behind a newer acknowledgment. - With 201 unchanged decisions the marker kept 200 ids, so the one it did not retain counted as new on every return. Stored ids are evidence only while they cover the owner's whole set; past the cap, or for a partially listed set, only growth of the owner's count reports a new decision or result. The marker therefore records the attention owner's blocking count and the result count (schema goal_seen_marker_v1, browser-local). Node tests cover 201 unchanged, a reordered window, real growth, a partial listing, resolved-and-raised within the cap and capped results. The fixture scenario adds refresh, Mark seen, leave, return with the acknowledgment retained, and a 201-decision workspace that stays silent. RFC checkpoint and user guide state both rules. Co-Authored-By: Claude Fable 5.1 Signed-off-by: song --- .../personal-workspace-contract.test.mjs | 3 ++ .../personal-workspace-page.tsx | 21 ++++++-- .../since-you-looked.test.mjs | 42 +++++++++++---- .../personal-workspace/since-you-looked.ts | 40 ++++++++++---- .../rfcs/goal-workspace-surface-v0.md | 13 +++-- docs/guides/personal-workspace-user-guide.md | 9 ++-- .../since-you-looked.mjs | 52 ++++++++++++++++++- 7 files changed, 149 insertions(+), 31 deletions(-) diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs index 79b5064c78..cadd554033 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs @@ -614,6 +614,9 @@ assert.match(page, /seenMarkerStorageKey\(seenStatusSource, selectedGoal\.goalId assert.match(page, /if \(delta\.material\) setSinceYouLooked\(/, "Only a material delta shows the line"); assert.match(page, /advanceSeenMarker\(previous\.goalId, previous\.source, "compared"\);[\s\S]*?setSinceYouLooked\(\(shown\) => shown && shown\.goalId === previous\.goalId/, "Leaving the Goal records the compared look and acknowledges a shown line"); assert.match(page, /advanceSeenMarker\(sinceYouLooked\.goalId, sinceYouLooked\.source, "latest"\)/, "Mark seen acknowledges the current look"); +assert.match(page, /if \(stored && stored\.observedAt > input\.observedAt\) return;/, "No write moves the marker back behind a newer acknowledgment"); +assert.match(page, /if \(look === "latest"\) comparedSeenInputsRef\.current\.set\(scope, input\);/, "Mark seen becomes the look the visit stands on, so leaving cannot undo it"); +assert.match(sinceYouLooked, /function coversWholeSet\(/, "Capped or partial id sets report count growth, never unretained ids as new"); assert.match(page, /decisionIds: model\.userTodos\.filter\(\(item\) => item\.goalId === selectedGoal\.goalId && item\.blocking\)/, "Only items that wait on the owner are decisions; optional user actions keep the typed classification"); assert.match(page, /if \(refreshState === "loading"\) return;[\s\S]*?seenComparedRef\.current = scope;/, "The comparison waits for an owner refresh still in flight instead of latching on the snapshot it replaces"); const seenBlock = page.slice(page.indexOf("const seenStatusSource"), page.indexOf("function markSeen")); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx index d93b872c0a..eb5704821a 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx @@ -1259,7 +1259,10 @@ export function PersonalWorkspacePage({ if (!selectedGoal || !seenGoalLoaded) return null; return { observedAt: workspaceDigest.observedAt, - counts: { open: workspaceDigest.work.open, blocked: workspaceDigest.work.classes.blocked, done: workspaceDigest.work.done }, + counts: { + open: workspaceDigest.work.open, blocked: workspaceDigest.work.classes.blocked, done: workspaceDigest.work.done, + decisions: workspaceDigest.needsYou.availability === "ok" ? workspaceDigest.needsYou.blocking : null, + }, // Only an item that waits on the owner is a decision; an optional user action keeps the typed owner's classification. decisionIds: model.userTodos.filter((item) => item.goalId === selectedGoal.goalId && item.blocking).map((item) => item.todoId), doneTodoIds: workspaceDigest.work.rail.done, @@ -1276,13 +1279,21 @@ export function PersonalWorkspacePage({ const seenComparedRef = useRef(null); const comparedSeenInputsRef = useRef(new Map()); const seenVisitRef = useRef<{ source: string; goalId: string } | null>(null); - // "Mark seen" acknowledges what is on screen now; leaving acknowledges only - // the look that was compared (and shown when material), so a change that - // arrived during the visit without being presented is still reported next time. + // "Mark seen" acknowledges what is on screen now and becomes the look this + // visit stands on; leaving acknowledges only the compared look (shown when + // material), so a change that arrived during the visit without being + // presented is still reported next time. No write moves the marker back + // behind a newer acknowledgment. function advanceSeenMarker(goalId: string, source: string, look: "latest" | "compared") { const scope = seenScopeKey(source, goalId); const input = look === "latest" ? latestSeenInputsRef.current.get(scope) : comparedSeenInputsRef.current.get(scope); - if (input) writeSeenMarker(seenMarkerStorage(), seenMarkerStorageKey(source, goalId), buildSeenMarker(input)); + if (!input) return; + const storage = seenMarkerStorage(); + const key = seenMarkerStorageKey(source, goalId); + const stored = readSeenMarker(storage, key); + if (stored && stored.observedAt > input.observedAt) return; + writeSeenMarker(storage, key, buildSeenMarker(input)); + if (look === "latest") comparedSeenInputsRef.current.set(scope, input); } useEffect(() => { if (!selectedGoal || !seenGoalLoaded || !seenMarkerInput) return; diff --git a/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs index bec4a420a2..b71a846feb 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs +++ b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs @@ -3,11 +3,11 @@ import test from "node:test"; import { buildSeenMarker, compareSeenMarker, readSeenMarker, seenMarkerIdLimit, seenMarkerStorage, seenMarkerStorageKey, writeSeenMarker } from "./since-you-looked.ts"; -const input = (overrides = {}) => ({ observedAt: "2026-10-15T08:00:00Z", counts: { open: 5, blocked: 1, done: 20 }, decisionIds: ["d1"], doneTodoIds: ["a", "b"], blockedTodoIds: ["x"], resultIds: ["r1"], ...overrides }); +const input = (overrides = {}) => ({ observedAt: "2026-10-15T08:00:00Z", counts: { open: 5, blocked: 1, done: 20, decisions: 1 }, decisionIds: ["d1"], doneTodoIds: ["a", "b"], blockedTodoIds: ["x"], resultIds: ["r1"], ...overrides }); test("a marker keeps typed counts and ids only, bounded", () => { const marker = buildSeenMarker(input({ doneTodoIds: Array.from({ length: seenMarkerIdLimit + 50 }, (_, i) => `t${i}`) })); - assert.equal(marker.schema, "goal_seen_marker_v0"); + assert.equal(marker.schema, "goal_seen_marker_v1"); assert.equal(marker.doneTodoIds.length, seenMarkerIdLimit, "done ids are bounded to the newest"); assert.equal(marker.doneTodoIds[0], "t50"); assert.deepEqual(Object.keys(marker).sort(), ["counts", "decisionIds", "doneTodoIds", "observedAt", "resultIds", "schema"], "no titles, text or cost"); @@ -23,27 +23,27 @@ test("a no-change pair stays silent", () => { test("known work, decision, blocked and result changes are counted since the marker", () => { const marker = buildSeenMarker(input()); - const delta = compareSeenMarker(marker, input({ counts: { open: 4, blocked: 2, done: 22 }, decisionIds: ["d1", "d2"], doneTodoIds: ["a", "b", "c", "d"], resultIds: ["r1", "r2"] })); + const delta = compareSeenMarker(marker, input({ counts: { open: 4, blocked: 2, done: 22, decisions: 2 }, decisionIds: ["d1", "d2"], doneTodoIds: ["a", "b", "c", "d"], resultIds: ["r1", "r2"] })); assert.deepEqual([delta.newlyDone, delta.newDecisions, delta.newlyBlocked, delta.resultsReturned], [2, 1, 1, 1]); assert.equal(delta.material, true); }); test("a shrinking queue or a resolved decision is not reported as new", () => { - const marker = buildSeenMarker(input({ decisionIds: ["d1", "d2"], counts: { open: 5, blocked: 3, done: 20 } })); - const delta = compareSeenMarker(marker, input({ decisionIds: [], counts: { open: 5, blocked: 1, done: 20 } })); + const marker = buildSeenMarker(input({ decisionIds: ["d1", "d2"], counts: { open: 5, blocked: 3, done: 20, decisions: 2 } })); + const delta = compareSeenMarker(marker, input({ decisionIds: [], counts: { open: 5, blocked: 1, done: 20, decisions: 0 } })); assert.equal(delta.material, false); }); test("a trimmed marker cannot inflate newly done beyond the done-count growth", () => { const many = Array.from({ length: seenMarkerIdLimit + 10 }, (_, i) => `t${i}`); - const marker = buildSeenMarker(input({ doneTodoIds: many, counts: { open: 5, blocked: 0, done: many.length } })); - const delta = compareSeenMarker(marker, input({ doneTodoIds: [...many, "new"], counts: { open: 4, blocked: 0, done: many.length + 1 } })); + const marker = buildSeenMarker(input({ doneTodoIds: many, counts: { open: 5, blocked: 0, done: many.length, decisions: 1 } })); + const delta = compareSeenMarker(marker, input({ doneTodoIds: [...many, "new"], counts: { open: 4, blocked: 0, done: many.length + 1, decisions: 1 } })); assert.equal(delta.newlyDone, 1, "the ten ids trimmed from the marker are old work, not new"); }); test("without a projected done count the ids alone decide", () => { - const marker = buildSeenMarker(input({ counts: { open: 5, blocked: 0, done: null } })); - const delta = compareSeenMarker(marker, input({ counts: { open: 5, blocked: 0, done: null }, doneTodoIds: ["a", "b", "c"] })); + const marker = buildSeenMarker(input({ counts: { open: 5, blocked: 0, done: null, decisions: 1 } })); + const delta = compareSeenMarker(marker, input({ counts: { open: 5, blocked: 0, done: null, decisions: 1 }, doneTodoIds: ["a", "b", "c"] })); assert.equal(delta.newlyDone, 1); }); @@ -77,3 +77,27 @@ test("a browser that denies storage leaves the marker silent, whether the proper const storage = { getItem: (k) => store.get(k) ?? null, setItem: (k, v) => { store.set(k, v); } }; assert.equal(seenMarkerStorage({ localStorage: storage }), storage, "a usable storage passes through unchanged"); }); + +test("past the id cap an unchanged decision set is silent and only count growth is new", () => { + const ids = Array.from({ length: seenMarkerIdLimit + 1 }, (_, i) => `g${i}`); + const marker = buildSeenMarker(input({ decisionIds: ids, counts: { open: 5, blocked: 0, done: 0, decisions: ids.length } })); + assert.equal(marker.decisionIds.length, seenMarkerIdLimit); + assert.equal(compareSeenMarker(marker, input({ decisionIds: ids, counts: { open: 5, blocked: 0, done: 0, decisions: ids.length } })).newDecisions, 0, "201 unchanged decisions are not one new decision"); + assert.equal(compareSeenMarker(marker, input({ decisionIds: [...ids.slice(1), "g0"], counts: { open: 5, blocked: 0, done: 0, decisions: ids.length } })).newDecisions, 0, "a reordered window is not a change"); + assert.equal(compareSeenMarker(marker, input({ decisionIds: [...ids, "g-new"], counts: { open: 5, blocked: 0, done: 0, decisions: ids.length + 1 } })).newDecisions, 1, "a real new decision shows as the count growth"); + const partial = buildSeenMarker(input({ decisionIds: ["d1"], counts: { open: 5, blocked: 0, done: 0, decisions: 3 } })); + assert.equal(compareSeenMarker(partial, input({ decisionIds: ["d1", "d2"], counts: { open: 5, blocked: 0, done: 0, decisions: 3 } })).newDecisions, 0, "a partially listed set that merely lists more is not new"); +}); + +test("within the cap a resolved-and-raised decision is still one new decision", () => { + const marker = buildSeenMarker(input({ decisionIds: ["d1", "d2"], counts: { open: 5, blocked: 0, done: 0, decisions: 2 } })); + assert.equal(compareSeenMarker(marker, input({ decisionIds: ["d2", "d3"], counts: { open: 5, blocked: 0, done: 0, decisions: 2 } })).newDecisions, 1); +}); + +test("results past the cap follow the same rule", () => { + const ids = Array.from({ length: seenMarkerIdLimit + 1 }, (_, i) => `r${i}`); + const marker = buildSeenMarker(input({ resultIds: ids })); + assert.equal(marker.counts.results, ids.length); + assert.equal(compareSeenMarker(marker, input({ resultIds: ids })).resultsReturned, 0); + assert.equal(compareSeenMarker(marker, input({ resultIds: [...ids, "r-new"] })).resultsReturned, 1); +}); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts index 065a4858e9..36130b40c1 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts +++ b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts @@ -11,13 +11,14 @@ * the line was shown. This is not a notification channel. */ -export const seenMarkerSchema = "goal_seen_marker_v0"; +export const seenMarkerSchema = "goal_seen_marker_v1"; export const seenMarkerIdLimit = 200; export type SeenMarker = { schema: typeof seenMarkerSchema; observedAt: string; - counts: { open: number; blocked: number; done: number | null }; + /** `decisions` is the attention owner's blocking count (null when unknown); `results` the returned-result count. */ + counts: { open: number; blocked: number; done: number | null; decisions: number | null; results: number }; decisionIds: string[]; doneTodoIds: string[]; resultIds: string[]; @@ -25,7 +26,7 @@ export type SeenMarker = { export type SeenMarkerInput = { observedAt: string; - counts: { open: number; blocked: number; done: number | null }; + counts: { open: number; blocked: number; done: number | null; decisions: number | null }; decisionIds: readonly string[]; doneTodoIds: readonly string[]; blockedTodoIds: readonly string[]; @@ -49,19 +50,30 @@ export function buildSeenMarker(input: SeenMarkerInput): SeenMarker { return { schema: seenMarkerSchema, observedAt: input.observedAt, - counts: { open: input.counts.open, blocked: input.counts.blocked, done: input.counts.done }, + counts: { open: input.counts.open, blocked: input.counts.blocked, done: input.counts.done, decisions: input.counts.decisions, results: input.resultIds.length }, decisionIds: bounded(input.decisionIds), doneTodoIds: bounded(input.doneTodoIds), resultIds: bounded(input.resultIds), }; } +/** + * Stored ids are evidence of what was seen only while they cover the whole + * set the owner projected. A capped set (more ids than the marker keeps) or a + * partially listed one cannot tell an unretained old item from a new one. + */ +function coversWholeSet(ids: readonly string[], count: number | null): boolean { + return count === null ? ids.length < seenMarkerIdLimit : ids.length >= count; +} + /** * Compare the current observation with the stored marker. Newly done work is * the done ids not in the marker, bounded by the growth of the done count * when the owner projects one, so a trimmed marker never inflates it. New - * decisions and results are ids not seen before. Newly blocked work is the - * growth of the blocked count. A shrinking queue is not a change to report. + * decisions and results are ids not seen before while the marker covered the + * whole set; past the cap only growth of the owner's count is evidence, so an + * unchanged set never reports a change. Newly blocked work is the growth of + * the blocked count. A shrinking queue is not a change to report. */ export function compareSeenMarker(previous: SeenMarker, current: SeenMarkerInput): SinceYouLookedDelta { const seenDone = new Set(previous.doneTodoIds); @@ -71,9 +83,14 @@ export function compareSeenMarker(previous: SeenMarker, current: SeenMarkerInput const doneByCount = previous.counts.done !== null && current.counts.done !== null ? Math.max(0, current.counts.done - previous.counts.done) : doneById; const newlyDone = Math.min(doneById, doneByCount) || Math.max(0, doneByCount > 0 && doneById === 0 ? doneByCount : 0); - const newDecisions = current.decisionIds.filter((id) => !seenDecisions.has(id)).length; + const newDecisions = coversWholeSet(previous.decisionIds, previous.counts.decisions) + ? current.decisionIds.filter((id) => !seenDecisions.has(id)).length + : previous.counts.decisions !== null && current.counts.decisions !== null + ? Math.max(0, current.counts.decisions - previous.counts.decisions) : 0; const newlyBlocked = Math.max(0, current.counts.blocked - previous.counts.blocked); - const resultsReturned = current.resultIds.filter((id) => !seenResults.has(id)).length; + const resultsReturned = coversWholeSet(previous.resultIds, previous.counts.results) + ? current.resultIds.filter((id) => !seenResults.has(id)).length + : Math.max(0, current.resultIds.length - previous.counts.results); return { since: previous.observedAt, newlyDone, newDecisions, newlyBlocked, resultsReturned, @@ -112,7 +129,12 @@ export function readSeenMarker(storage: Pick | null, key: st if (!value || value.schema !== seenMarkerSchema || typeof value.observedAt !== "string" || !value.counts) return null; return { schema: seenMarkerSchema, observedAt: value.observedAt, - counts: { open: Number(value.counts.open ?? 0), blocked: Number(value.counts.blocked ?? 0), done: value.counts.done === null || value.counts.done === undefined ? null : Number(value.counts.done) }, + counts: { + open: Number(value.counts.open ?? 0), blocked: Number(value.counts.blocked ?? 0), + done: value.counts.done === null || value.counts.done === undefined ? null : Number(value.counts.done), + decisions: value.counts.decisions === null || value.counts.decisions === undefined ? null : Number(value.counts.decisions), + results: Number(value.counts.results ?? 0), + }, decisionIds: Array.isArray(value.decisionIds) ? value.decisionIds.filter((id): id is string => typeof id === "string") : [], doneTodoIds: Array.isArray(value.doneTodoIds) ? value.doneTodoIds.filter((id): id is string => typeof id === "string") : [], resultIds: Array.isArray(value.resultIds) ? value.resultIds.filter((id): id is string => typeof id === "string") : [], diff --git a/docs/architecture/rfcs/goal-workspace-surface-v0.md b/docs/architecture/rfcs/goal-workspace-surface-v0.md index 08b724561a..2543843d13 100644 --- a/docs/architecture/rfcs/goal-workspace-surface-v0.md +++ b/docs/architecture/rfcs/goal-workspace-surface-v0.md @@ -468,11 +468,16 @@ service it compares the look that lands, not the snapshot it replaces. One refin recorded: "Mark seen" acknowledges what is on screen, while leaving a Goal records only the look that was compared on that visit, so a visit with nothing new still advances the observation time and a change that arrived during the visit without being presented is reported next time. A -browser that denies local storage loses only this memory; the Goal stays readable. +browser that denies local storage loses only this memory; the Goal stays readable. "Mark seen" +becomes the look the visit stands on and no write moves the marker back behind a newer +acknowledgment. Stored ids are evidence only while they cover the owner's whole set: past the +200-id cap, or for a partially listed set, only growth of the owner's count reports a new decision +or result, so an unchanged set never reports a change. V5 is covered by a node test (no-change pair, trimmed marker, unavailable done count, denied -storage), a fixture browser scenario (first visit silent, no-change return silent, work and -decision changes shown and dismissed, leave-acknowledge, reload, no service write, denied storage -getter) and a real-service scenario against a disposable Todo owner (optional user action silent, +storage, 201 unchanged decisions past the cap, resolved-and-raised within it), a fixture browser +scenario (first visit silent, no-change return silent, work and decision changes shown and +dismissed, leave-acknowledge, refresh then Mark seen then leave keeps the acknowledgment, 201 +unchanged decisions silent, reload, no service write, denied storage getter) and a real-service scenario against a disposable Todo owner (optional user action silent, CLI completion and gate counted as the lane counts them, Mark seen). Cost stays omitted per D7. Target declaration (the other half of M3) remains undelivered. diff --git a/docs/guides/personal-workspace-user-guide.md b/docs/guides/personal-workspace-user-guide.md index d018fa8cda..154772d6da 100644 --- a/docs/guides/personal-workspace-user-guide.md +++ b/docs/guides/personal-workspace-user-guide.md @@ -273,8 +273,9 @@ execution permissions. 「标记已读」。它来自这个浏览器本地保存的上次查看标记(只有计数和编号,不含标题、内容或费用), 不写回服务,也不是通知。「等你决定」只计需要你介入的事项,可选提醒会列出但不计入。没有实质变化时 不显示;点「标记已读」以当前画面为基线,离开这个 Goal 则以这次进入时看到的那一眼为基线,期间到达但 -没有呈现的变化下次回来仍会算入。浏览器不允许本地存储时只是失去这份记忆,Goal 照常可读。费用变化暂 -不纳入:用量投影还不能可靠地界定两次查看之间的区间。 +没有呈现的变化下次回来仍会算入;已点过的「标记已读」不会被离开撤回。标记只保留每类最近 200 个编号, +超出后只按数量增长判断新增,不会把没保留的旧项当成新项。浏览器不允许本地存储时只是失去这份记忆, +Goal 照常可读。费用变化暂不纳入:用量投影还不能可靠地界定两次查看之间的区间。 When you return to a Goal's overview after a material change, one quiet line at the top says what changed since your last look ("Since you looked (3 hours ago): 2 newly done, 1 new decision for @@ -283,7 +284,9 @@ keeps locally (counts and ids only, never titles, content or cost), writes nothi and is not a notification. "Decisions for you" counts only items that wait on you; an optional reminder is listed but not counted. Nothing shows when nothing material changed. **Mark seen** makes what is on screen the new baseline; leaving the Goal keeps the look you arrived to, so a change that -landed during the visit without being shown is still reported next time. A browser that denies +landed during the visit without being shown is still reported next time, and leaving never undoes a +**Mark seen**. The marker keeps the latest 200 ids of each kind; beyond that only growth of the +count reports something new, never an older item the marker did not retain. A browser that denies local storage only loses this memory; the Goal stays readable. Spend is left out until the usage projection can qualify the exact interval between two looks. diff --git a/examples/personal-workspace-browser/since-you-looked.mjs b/examples/personal-workspace-browser/since-you-looked.mjs index 9ce2c8d7b1..8c38209bd5 100644 --- a/examples/personal-workspace-browser/since-you-looked.mjs +++ b/examples/personal-workspace-browser/since-you-looked.mjs @@ -50,7 +50,7 @@ export const sinceYouLookedScenario = { assert.ok(goalId, "The first visit seeds a marker for this source and Goal"); assert.equal(await line.count(), 0, "A first visit has nothing to compare"); const marker = await page.evaluate((id) => JSON.parse(localStorage.getItem(`loopx-pw-seen:/status.json:${id}`)), goalId); - assert.equal(marker.schema, "goal_seen_marker_v0"); + assert.equal(marker.schema, "goal_seen_marker_v1"); assert.deepEqual(Object.keys(marker).sort(), ["counts", "decisionIds", "doneTodoIds", "observedAt", "resultIds", "schema"], "typed counts and ids only"); // A return with nothing new stays quiet. @@ -87,6 +87,30 @@ export const sinceYouLookedScenario = { await page.waitForTimeout(500); assert.equal(await line.count(), 0, "Leaving the Goal after the line was shown acknowledged it"); + // An explicit Mark seen during a visit is never undone by leaving: the + // owner returns to two new decisions, refreshes to a third while looking, + // marks seen, leaves, and comes back to silence with all three retained. + await home(); + const threeGates = (fixture) => { addDone(fixture, "seen-done-1", "Reconcile the March export"); addGate(fixture, "seen-gate-1", "Approve the restated FY2023 operating cash flow"); addGate(fixture, "seen-gate-2", "Approve the vendor list"); addGate(fixture, "seen-gate-3", "Approve the Q4 calendar"); }; + api.transformStatus = threeGates; + await refresh(); + await openGoal(); + await line.waitFor({ state: "visible", timeout: 15_000 }); + api.transformStatus = (fixture) => { threeGates(fixture); addGate(fixture, "seen-gate-4", "Approve the audit scope"); }; + await refresh(); + await page.waitForTimeout(500); + await line.getByRole("button", { name: "标记已读" }).click(); + await line.waitFor({ state: "hidden", timeout: 5_000 }); + const acknowledged = await page.evaluate((id) => JSON.parse(localStorage.getItem(`loopx-pw-seen:/status.json:${id}`)), goalId); + assert.ok(acknowledged.decisionIds.includes("seen-gate-4"), "Mark seen records the refreshed look"); + await home(); + const afterLeave = await page.evaluate((id) => JSON.parse(localStorage.getItem(`loopx-pw-seen:/status.json:${id}`)), goalId); + assert.ok(afterLeave.decisionIds.includes("seen-gate-4"), "Leaving does not move the marker back behind the acknowledgment"); + assert.ok(afterLeave.observedAt >= acknowledged.observedAt); + await openGoal(); + await page.waitForTimeout(500); + assert.equal(await line.count(), 0, "Nothing is new after the acknowledged look"); + // Reload keeps the marker; nothing was written to the service. await page.reload({ waitUntil: "networkidle" }); await page.getByTestId("personal-goal-home").waitFor({ state: "visible", timeout: 15_000 }); @@ -95,6 +119,32 @@ export const sinceYouLookedScenario = { assert.equal(await line.count(), 0, "The marker survives a reload"); assert.deepEqual(writes.filter((row) => !/\/api\/chat\/(sessions|capabilities|agent-model-options|projection-messages|lark|goal-results)/.test(row)), [], `no service writes for the marker: ${writes.join(", ")}`); + // Past the marker's id cap an unchanged set stays silent: 201 decisions on + // the first look, the same 201 on the return, no "new" decision. + const capped = await openWorkspacePage(browser, url, { collectCoverage, viewport: { width: 1440, height: 900 }, + beforeGoto: (cappedApi) => { cappedApi.transformStatus = (fixture) => { for (let i = 0; i <= 200; i += 1) addGate(fixture, `seen-cap-${i}`, `Approve item ${i}`); }; } }); + try { + const cappedPage = capped.page; + const cappedLine = cappedPage.getByTestId("goal-since-you-looked"); + const openCapped = async () => { + await cappedPage.locator(".personal-goal-row").first().locator(".personal-goal-link").click(); + await cappedPage.getByTestId("goal-vitals").waitFor({ state: "visible", timeout: 15_000 }); + await cappedPage.getByRole("navigation", { name: "Goal 视图" }).getByRole("button", { name: "概览", exact: true }).click(); + }; + await openCapped(); + await cappedPage.waitForTimeout(500); + const cappedMarker = await cappedPage.evaluate(() => Object.entries(localStorage).filter(([key]) => key.startsWith("loopx-pw-seen:")).map(([, value]) => JSON.parse(value))[0]); + assert.ok(cappedMarker && cappedMarker.decisionIds.length === 200 && cappedMarker.counts.decisions >= 201, `the marker keeps 200 ids and the owner's count: ${JSON.stringify(cappedMarker?.counts)}`); + await cappedPage.getByRole("button", { name: /^LoopX 管家/ }).first().click(); + await openCapped(); + await cappedPage.waitForTimeout(800); + assert.equal(await cappedLine.count(), 0, "201 unchanged decisions are not one new decision"); + await capped.close(); + } catch (error) { + await capped.close().catch(() => {}); + throw error; + } + // A browser that denies storage (the property getter itself throws) keeps // the Goal readable; only the local look memory is lost. await home(); From 7eec589674f54b5bfb2c41b17c391661976b65df Mon Sep 17 00:00:00 2001 From: song Date: Fri, 16 Oct 2026 02:54:47 +0800 Subject: [PATCH 5/5] fix(workspace): report no completion from a capped done history without a total With no owner done total, the comparison fell back to the done ids alone, so 201 unchanged completed rows (the marker keeps 200) reported one newly done item on every return, including after Mark seen. The done branch now follows the same coverage rule as decisions and results: a known total is the evidence and ids only refine it; without a total the ids count only while the marker kept every one it saw, and a capped history proves no completion. Nothing is reported rather than a guess. Node tests cover 201 unchanged with no total, a new id past the cap with no total (no evidence either way), a complete history still counting a real completion, and a total that appears later. The fixture's capped workspace now also carries 201 completed rows with no done total and stays silent on return. RFC checkpoint and user guide state the rule. Co-Authored-By: Claude Fable 5.1 Signed-off-by: song --- .../personal-workspace-contract.test.mjs | 1 + .../since-you-looked.test.mjs | 11 +++++++++++ .../personal-workspace/since-you-looked.ts | 16 ++++++++++++---- .../rfcs/goal-workspace-surface-v0.md | 8 +++++--- docs/guides/personal-workspace-user-guide.md | 5 +++-- .../since-you-looked.mjs | 18 ++++++++++++++---- 6 files changed, 46 insertions(+), 13 deletions(-) diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs index 9ad82e233e..f9a327e882 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-contract.test.mjs @@ -617,6 +617,7 @@ assert.match(page, /advanceSeenMarker\(sinceYouLooked\.goalId, sinceYouLooked\.s assert.match(page, /if \(stored && stored\.observedAt > input\.observedAt\) return;/, "No write moves the marker back behind a newer acknowledgment"); assert.match(page, /if \(look === "latest"\) comparedSeenInputsRef\.current\.set\(scope, input\);/, "Mark seen becomes the look the visit stands on, so leaving cannot undo it"); assert.match(sinceYouLooked, /function coversWholeSet\(/, "Capped or partial id sets report count growth, never unretained ids as new"); +assert.match(sinceYouLooked, /newlyDone = coversWholeSet\(previous\.doneTodoIds, null\) \? doneById : 0;/, "With no done total a capped done history proves no completion"); assert.match(page, /decisionIds: model\.userTodos\.filter\(\(item\) => item\.goalId === selectedGoal\.goalId && item\.blocking\)/, "Only items that wait on the owner are decisions; optional user actions keep the typed classification"); assert.match(page, /if \(refreshState === "loading"\) return;[\s\S]*?seenComparedRef\.current = scope;/, "The comparison waits for an owner refresh still in flight instead of latching on the snapshot it replaces"); const seenBlock = page.slice(page.indexOf("const seenStatusSource"), page.indexOf("function markSeen")); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs index b71a846feb..0554783829 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs +++ b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.test.mjs @@ -101,3 +101,14 @@ test("results past the cap follow the same rule", () => { assert.equal(compareSeenMarker(marker, input({ resultIds: ids })).resultsReturned, 0); assert.equal(compareSeenMarker(marker, input({ resultIds: [...ids, "r-new"] })).resultsReturned, 1); }); + +test("with no done total a capped done history proves nothing; within the cap the ids decide", () => { + const ids = Array.from({ length: seenMarkerIdLimit + 1 }, (_, i) => `done${i}`); + const marker = buildSeenMarker(input({ doneTodoIds: ids, counts: { open: 5, blocked: 0, done: null, decisions: 1 } })); + assert.equal(marker.doneTodoIds.length, seenMarkerIdLimit); + assert.equal(compareSeenMarker(marker, input({ doneTodoIds: ids, counts: { open: 5, blocked: 0, done: null, decisions: 1 } })).newlyDone, 0, "201 unchanged completions with no total are not one new completion"); + assert.equal(compareSeenMarker(marker, input({ doneTodoIds: [...ids, "done-new"], counts: { open: 5, blocked: 0, done: null, decisions: 1 } })).newlyDone, 0, "past the cap with no total there is no evidence either way"); + const within = buildSeenMarker(input({ doneTodoIds: ["a", "b"], counts: { open: 5, blocked: 0, done: null, decisions: 1 } })); + assert.equal(compareSeenMarker(within, input({ doneTodoIds: ["a", "b", "c"], counts: { open: 5, blocked: 0, done: null, decisions: 1 } })).newlyDone, 1, "a complete history still counts a real completion"); + assert.equal(compareSeenMarker(marker, input({ doneTodoIds: [...ids, "done-new"], counts: { open: 5, blocked: 0, done: 202, decisions: 1 } })).newlyDone, 0, "a total that appears later cannot be compared with a marker that had none"); +}); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts index 36130b40c1..d523c4a5e7 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts +++ b/apps/presentation/dashboard/src/features/personal-workspace/since-you-looked.ts @@ -69,7 +69,8 @@ function coversWholeSet(ids: readonly string[], count: number | null): boolean { /** * Compare the current observation with the stored marker. Newly done work is * the done ids not in the marker, bounded by the growth of the done count - * when the owner projects one, so a trimmed marker never inflates it. New + * when the owner projects one, so a trimmed marker never inflates it; with + * no done total, ids count only while the marker kept them all. New * decisions and results are ids not seen before while the marker covered the * whole set; past the cap only growth of the owner's count is evidence, so an * unchanged set never reports a change. Newly blocked work is the growth of @@ -80,9 +81,16 @@ export function compareSeenMarker(previous: SeenMarker, current: SeenMarkerInput const seenDecisions = new Set(previous.decisionIds); const seenResults = new Set(previous.resultIds); const doneById = current.doneTodoIds.filter((id) => !seenDone.has(id)).length; - const doneByCount = previous.counts.done !== null && current.counts.done !== null - ? Math.max(0, current.counts.done - previous.counts.done) : doneById; - const newlyDone = Math.min(doneById, doneByCount) || Math.max(0, doneByCount > 0 && doneById === 0 ? doneByCount : 0); + let newlyDone: number; + if (previous.counts.done !== null && current.counts.done !== null) { + // The owner's done total is the evidence; ids can only refine it downward. + const doneByCount = Math.max(0, current.counts.done - previous.counts.done); + newlyDone = Math.min(doneById, doneByCount) || (doneByCount > 0 && doneById === 0 ? doneByCount : 0); + } else { + // Without a done total the ids are the only evidence, and only while the + // marker kept every one it saw; a capped history cannot prove a completion. + newlyDone = coversWholeSet(previous.doneTodoIds, null) ? doneById : 0; + } const newDecisions = coversWholeSet(previous.decisionIds, previous.counts.decisions) ? current.decisionIds.filter((id) => !seenDecisions.has(id)).length : previous.counts.decisions !== null && current.counts.decisions !== null diff --git a/docs/architecture/rfcs/goal-workspace-surface-v0.md b/docs/architecture/rfcs/goal-workspace-surface-v0.md index 2543843d13..a246cda8f3 100644 --- a/docs/architecture/rfcs/goal-workspace-surface-v0.md +++ b/docs/architecture/rfcs/goal-workspace-surface-v0.md @@ -471,10 +471,12 @@ and a change that arrived during the visit without being presented is reported n browser that denies local storage loses only this memory; the Goal stays readable. "Mark seen" becomes the look the visit stands on and no write moves the marker back behind a newer acknowledgment. Stored ids are evidence only while they cover the owner's whole set: past the -200-id cap, or for a partially listed set, only growth of the owner's count reports a new decision -or result, so an unchanged set never reports a change. +200-id cap, or for a partially listed set, only growth of the owner's count reports a new decision, +result or completion, and with no done total a capped done history reports nothing, so an unchanged +set never reports a change. V5 is covered by a node test (no-change pair, trimmed marker, unavailable done count, denied -storage, 201 unchanged decisions past the cap, resolved-and-raised within it), a fixture browser +storage, 201 unchanged decisions past the cap, 201 unchanged completions with no done total, +resolved-and-raised within it), a fixture browser scenario (first visit silent, no-change return silent, work and decision changes shown and dismissed, leave-acknowledge, refresh then Mark seen then leave keeps the acknowledgment, 201 unchanged decisions silent, reload, no service write, denied storage getter) and a real-service scenario against a disposable Todo owner (optional user action silent, diff --git a/docs/guides/personal-workspace-user-guide.md b/docs/guides/personal-workspace-user-guide.md index 154772d6da..df92b38c97 100644 --- a/docs/guides/personal-workspace-user-guide.md +++ b/docs/guides/personal-workspace-user-guide.md @@ -274,7 +274,7 @@ execution permissions. 不写回服务,也不是通知。「等你决定」只计需要你介入的事项,可选提醒会列出但不计入。没有实质变化时 不显示;点「标记已读」以当前画面为基线,离开这个 Goal 则以这次进入时看到的那一眼为基线,期间到达但 没有呈现的变化下次回来仍会算入;已点过的「标记已读」不会被离开撤回。标记只保留每类最近 200 个编号, -超出后只按数量增长判断新增,不会把没保留的旧项当成新项。浏览器不允许本地存储时只是失去这份记忆, +超出后只按数量增长判断新增,没有总数时就不再报告,不会把没保留的旧项当成新项。浏览器不允许本地存储时只是失去这份记忆, Goal 照常可读。费用变化暂不纳入:用量投影还不能可靠地界定两次查看之间的区间。 When you return to a Goal's overview after a material change, one quiet line at the top says what @@ -286,7 +286,8 @@ reminder is listed but not counted. Nothing shows when nothing material changed. what is on screen the new baseline; leaving the Goal keeps the look you arrived to, so a change that landed during the visit without being shown is still reported next time, and leaving never undoes a **Mark seen**. The marker keeps the latest 200 ids of each kind; beyond that only growth of the -count reports something new, never an older item the marker did not retain. A browser that denies +count reports something new (nothing at all when there is no total), never an older item the marker +did not retain. A browser that denies local storage only loses this memory; the Goal stays readable. Spend is left out until the usage projection can qualify the exact interval between two looks. diff --git a/examples/personal-workspace-browser/since-you-looked.mjs b/examples/personal-workspace-browser/since-you-looked.mjs index 8c38209bd5..6a3563ee4e 100644 --- a/examples/personal-workspace-browser/since-you-looked.mjs +++ b/examples/personal-workspace-browser/since-you-looked.mjs @@ -119,10 +119,19 @@ export const sinceYouLookedScenario = { assert.equal(await line.count(), 0, "The marker survives a reload"); assert.deepEqual(writes.filter((row) => !/\/api\/chat\/(sessions|capabilities|agent-model-options|projection-messages|lark|goal-results)/.test(row)), [], `no service writes for the marker: ${writes.join(", ")}`); - // Past the marker's id cap an unchanged set stays silent: 201 decisions on - // the first look, the same 201 on the return, no "new" decision. + // Past the marker's id cap an unchanged set stays silent: 201 decisions + // and 201 completed rows with no done total on the first look, the same + // on the return, no "new" decision and no "newly done". + const cappedHistory = (fixture) => { + for (let i = 0; i <= 200; i += 1) { addGate(fixture, `seen-cap-${i}`, `Approve item ${i}`); addDone(fixture, `seen-done-cap-${i}`, `Completed item ${i}`); } + const first = firstGoal(fixture); + for (const group of [first.agent_todos, first.project_asset?.agent_todos]) { + if (!group) continue; + for (const key of ["advancement_done_count", "done_count", "done"]) delete group[key]; + } + }; const capped = await openWorkspacePage(browser, url, { collectCoverage, viewport: { width: 1440, height: 900 }, - beforeGoto: (cappedApi) => { cappedApi.transformStatus = (fixture) => { for (let i = 0; i <= 200; i += 1) addGate(fixture, `seen-cap-${i}`, `Approve item ${i}`); }; } }); + beforeGoto: (cappedApi) => { cappedApi.transformStatus = cappedHistory; } }); try { const cappedPage = capped.page; const cappedLine = cappedPage.getByTestId("goal-since-you-looked"); @@ -135,10 +144,11 @@ export const sinceYouLookedScenario = { await cappedPage.waitForTimeout(500); const cappedMarker = await cappedPage.evaluate(() => Object.entries(localStorage).filter(([key]) => key.startsWith("loopx-pw-seen:")).map(([, value]) => JSON.parse(value))[0]); assert.ok(cappedMarker && cappedMarker.decisionIds.length === 200 && cappedMarker.counts.decisions >= 201, `the marker keeps 200 ids and the owner's count: ${JSON.stringify(cappedMarker?.counts)}`); + assert.ok(cappedMarker.doneTodoIds.length === 200 && cappedMarker.counts.done === null, `the done history is capped with no total: ${JSON.stringify(cappedMarker.counts)}`); await cappedPage.getByRole("button", { name: /^LoopX 管家/ }).first().click(); await openCapped(); await cappedPage.waitForTimeout(800); - assert.equal(await cappedLine.count(), 0, "201 unchanged decisions are not one new decision"); + assert.equal(await cappedLine.count(), 0, "201 unchanged decisions and 201 unchanged completions with no total report nothing"); await capped.close(); } catch (error) { await capped.close().catch(() => {});