Skip to content

Commit 3c26d66

Browse files
committed
fix(dashboard): order recent completions across goals by completion time
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
1 parent 8636bbd commit 3c26d66

10 files changed

Lines changed: 83 additions & 14 deletions

File tree

‎apps/presentation/dashboard/package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@
5757
"smoke:team-report": "tsc --ignoreConfig --target ES2022 --module ES2022 --moduleResolution Bundler --jsx react-jsx --skipLibCheck --strict --rootDir src --outDir node_modules/.cache/team-report src/features/personal-workspace/team-artifact-content.tsx src/vite-env.d.ts && node smoke/team-report-smoke.mjs",
5858
"build:chat:vite": "tsc --noEmit && vite build --config vite.chat.config.ts",
5959
"smoke:chat-upgrade": "LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node ../../../examples/chat-bundle-upgrade-browser-smoke.mjs",
60-
"test:conversation-returns": "node --experimental-strip-types src/data/conversation-returns.test.mjs"
60+
"test:conversation-returns": "node --experimental-strip-types src/data/conversation-returns.test.mjs",
61+
"smoke:recent-completions": "tsc --ignoreConfig --target ES2022 --module CommonJS --moduleResolution Node --ignoreDeprecations 6.0 --resolveJsonModule --esModuleInterop --jsx react-jsx --skipLibCheck --strict --types node --outDir /tmp/loopx-recent-completions-smoke smoke/recent-completions-smoke.ts && NODE_PATH=\"$PWD/node_modules\" node /tmp/loopx-recent-completions-smoke/apps/presentation/dashboard/smoke/recent-completions-smoke.js"
6162
},
6263
"dependencies": {
6364
"@fontsource-variable/geist": "^5.3.0",
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import assert from "node:assert/strict";
2+
import { recentCompletions } from "../src/features/personal-workspace/recent-completions";
3+
import { todoGroupSchema } from "../src/data/status";
4+
import type { WorkspaceAgentTodo, WorkspaceGoal } from "../src/features/personal-workspace/personal-workspace-model";
5+
6+
const done = (id: string, completedAt?: string | null, extra = {}): WorkspaceAgentTodo => ({
7+
todoId: id, text: id, done: true, status: "done", taskClass: "advancement_task", completedAt, ...extra,
8+
});
9+
const goal = (id: string, agentTodos: WorkspaceAgentTodo[]): WorkspaceGoal => ({
10+
goalId: id, title: id, agentId: "example-agent", agentTodos, activationState: "active", agentSentence: "", nextSentence: "", state: "已安排",
11+
});
12+
const old = goal("first-in-sidebar", [done("old", "2026-01-01T00:00:00Z", {updatedAt: "2027-01-01T00:00:00Z"})]);
13+
const newer = goal("last-in-sidebar", [
14+
done("no-clock"), done("invalid", "2026-02-30T00:00:00Z"),
15+
done("offset", "2026-09-27T10:00:00.000001+08:00"),
16+
done("latest", "2026-09-27T02:00:00.000002Z"),
17+
done("monitor", "2027-01-01T00:00:00Z", {taskClass: "continuous_monitor"}),
18+
done("deferred", "2027-01-01T00:00:00Z", {status: "deferred"}),
19+
done("open", "2027-01-01T00:00:00Z", {done: false, status: "open"}),
20+
]);
21+
const names = (goals: WorkspaceGoal[]) => recentCompletions(goals).map(row => row.text);
22+
assert.deepEqual(names([old, newer]), ["latest", "offset", "old"]);
23+
assert.deepEqual(names([newer, old]), names([old, newer]), "sidebar order cannot change newest work");
24+
assert.equal(newer.agentTodos[0].todoId, "no-clock", "projection never mutates task order");
25+
const tieA = goal("a", [done("same", "2026-09-27T10:00:00+08:00")]);
26+
const tieB = goal("b", [done("same", "2026-09-27T02:00:00Z")]);
27+
assert.deepEqual(recentCompletions([tieB, tieA]).map(row => row.key), ["a:same", "b:same"]);
28+
const parsed = todoGroupSchema.parse({recent_completed_advancement_items: [{
29+
todo_id: "retained", done: true, text: "Kept body", completed_at: "2026-09-27T02:00:00Z",
30+
}]});
31+
assert.equal(parsed.recent_completed_advancement_items?.[0].completed_at, "2026-09-27T02:00:00Z", "status parser retains the recent lane and clock");
32+
assert.deepEqual(names([goal("unknown", [done("undated")])]), []);
33+
console.log("recent-completions-smoke: ok");

‎apps/presentation/dashboard/src/data/status.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export const todoItemSchema = z.object({
8686
required_capabilities: z.array(z.string()).optional(),
8787
note: z.string().optional().nullable(),
8888
evidence: z.string().optional().nullable(),
89+
completed_at: z.string().optional().nullable(),
8990
updated_at: z.string().optional().nullable(),
9091
completion_validation_required: z.boolean().optional().nullable(),
9192
completion_validation_sha256: z.string().optional().nullable(),
@@ -111,6 +112,7 @@ export const todoGroupSchema = z.object({
111112
advancement_done_count: z.number().optional(),
112113
items: z.array(todoItemSchema).optional().default([]),
113114
deferred_items: z.array(todoItemSchema).optional(),
115+
recent_completed_advancement_items: z.array(todoItemSchema).optional(),
114116
});
115117

116118
export const todoIndexItemSchema = todoItemSchema.safeExtend({

‎apps/presentation/dashboard/src/features/personal-workspace/manager-brief.tsx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { workspaceHomeLaneForGoal } from "./personal-workspace-model";
66
import { goalWorkKind, presentGoalActivity } from "./goal-activity";
77
import { GoalIdentityMark, useExecutionDetail } from "./goal-activity-view";
88

9+
import { recentCompletions } from "./recent-completions";
10+
911
const briefRowLimit = 3;
1012

1113
type BriefRow = { goal: WorkspaceGoal; key: string; meta?: ReactNode; text: string };
@@ -50,10 +52,8 @@ export function ManagerBrief({ goals, onSelectGoal }: { goals: WorkspaceGoal[];
5052
const queued = active.filter((goal) => goal.state === "已安排" && goalWorkKind(goal) === "none").length;
5153
const executionRead = active.some((goal) => goal.execution && goal.execution.kind !== "unknown");
5254
const executionPending = active.some((goal) => !goal.execution);
53-
const completed = active.flatMap((goal) => goal.agentTodos
54-
.filter((todo) => todo.done && todo.status !== "deferred")
55-
.map((todo) => ({ goal, key: `${goal.goalId}:${todo.todoId}`, text: todo.text })));
56-
const completedTotal = active.reduce((sum, goal) => sum + Math.max(goal.doneTodoCount ?? 0, goal.agentTodos.filter((todo) => todo.done).length), 0);
55+
const completed = recentCompletions(active);
56+
const completedTotal = active.reduce((sum, goal) => sum + Math.max(goal.doneTodoCount ?? 0, goal.agentTodos.filter((todo) => todo.done && todo.status === "done" && todo.taskClass === "advancement_task").length), 0);
5757
const runningEmpty = executionRead
5858
? queued ? t("brief.runningEmptyQueued", { count: queued }) : t("brief.runningEmpty")
5959
: executionPending ? t("brief.runningReading") : t("activity.executionUnknown");

‎apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-model.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export type WorkspaceHomeLane =
2424
| "stopped";
2525

2626
export type WorkspaceAgentTodo = {
27+
completedAt?: string | null;
2728
resumeWhen?: string | null;
2829
resumeReady?: boolean | null;
2930
resumeReceiptId?: string | null;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { parseTodoTimestampMicros } from "../../../../../../loopx/control_plane/runtime_timestamp.js";
2+
import type { WorkspaceGoal } from "./personal-workspace-model";
3+
4+
/** Cross-Goal order is independent of sidebar grouping and last-edit time.
5+
* Undated work belongs to completion totals, never a fabricated recent rank. */
6+
export function recentCompletions(goals: WorkspaceGoal[]) {
7+
return goals.flatMap(goal => goal.agentTodos.flatMap(todo => {
8+
if (!todo.done || todo.status !== "done" || todo.taskClass !== "advancement_task") return [];
9+
const completedAt = todo.completedAt?.trim();
10+
const instant = completedAt ? parseTodoTimestampMicros(completedAt) : null;
11+
return instant === null ? [] : [{
12+
goal, key: `${goal.goalId}:${todo.todoId}`, text: todo.text, instant,
13+
}];
14+
})).sort((a, b) => a.instant === b.instant ? a.key.localeCompare(b.key)
15+
: a.instant > b.instant ? -1 : 1);
16+
}

‎apps/presentation/dashboard/src/views/dashboard-page.tsx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ type TodoExplorerItem = {
215215
};
216216

217217
type PersonalAgentTodoItem = {
218+
completedAt?: string | null;
218219
resumeWhen?: string | null;
219220
resumeReady?: boolean | null;
220221
resumeReceiptId?: string | null;
@@ -717,6 +718,7 @@ function personalTodoResumeReceiptId(todo: TodoItem) {
717718
function personalAgentTodoFromItem(todo: TodoItem, row: GoalDirectoryRow): PersonalAgentTodoItem {
718719
const latestValidationRevision = todo.completion_validation_revision_history.at(-1);
719720
return {
721+
completedAt: todo.completed_at ?? null,
720722
resumeWhen: todo.resume_when ?? null,
721723
resumeReady: todo.resume_ready ?? null,
722724
resumeReceiptId: personalTodoResumeReceiptId(todo),
@@ -833,7 +835,7 @@ function personalAgentTodoFacts(row: GoalDirectoryRow): {
833835
.map((todo) => todo.todo_id?.trim())
834836
.filter((value): value is string => Boolean(value)),
835837
);
836-
const recentCompleted = (assetTodos?.recent_completed_advancement_items ?? [])
838+
const recentCompleted = (queueTodos?.recent_completed_advancement_items ?? assetTodos?.recent_completed_advancement_items ?? [])
837839
.filter((todo) => !todo.todo_id?.trim() || !seenTodoIds.has(todo.todo_id.trim()))
838840
.map((todo) => personalAgentTodoFromItem(todo, row));
839841
const firstOpen = items.find((todo) => !todo.done);

‎docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ Transfer the hierarchy, not the artwork or untested claims:
7474
routine activity; preserve missing authority, stale information and failures.
7575
- Use typography, spacing and restrained state accents from the existing design
7676
system. Motion explains verified transitions, never invents busy workers.
77+
- Recent-completion previews sort all loaded Goals by recorded completion time
78+
before limiting rows. Sidebar order, subsequent edits and refresh times are
79+
not completion evidence. Undated work stays in cumulative totals; deferred
80+
work and monitor cycles do not masquerade as delivered results.
7781
- Keep creation/connect, direct owner chat and team work discoverable. A simpler
7882
screen must not conceal unresolved work or reduce permitted owner discovery.
7983

‎examples/personal-workspace-browser/fixture.mjs‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -597,9 +597,9 @@ export async function installApi(page, { goalSubagentConfigurationEnabled = true
597597
{ done: false, index: 5, role: "agent", status: "open", task_class: "advancement_task", text: idlessLongTitle, title: idlessLongTitle },
598598
{ done: false, index: 7, role: "agent", status: "open", task_class: "advancement_task", text: "Full queue follow-up", title: "Full queue follow-up", todo_id: "todo-progress-full" },
599599
scheduledDeferredTodo,
600-
{ done: true, index: 1, role: "agent", status: "done", task_class: "advancement_task", text: "Completed A", title: "Completed A", todo_id: "todo-progress-a" },
601-
{ done: true, index: 2, role: "agent", status: "done", task_class: "advancement_task", text: "Completed B", title: "Completed B", todo_id: "todo-progress-b" },
602-
{ done: true, index: 3, role: "agent", status: "done", task_class: "advancement_task", text: "Completed C", title: "Completed C", todo_id: "todo-progress-c" },
600+
{ done: true, index: 1, role: "agent", status: "done", task_class: "advancement_task", completed_at: "2026-08-01T00:00:00Z", text: "Completed A", title: "Completed A", todo_id: "todo-progress-a" },
601+
{ done: true, index: 2, role: "agent", status: "done", task_class: "advancement_task", completed_at: "2026-08-03T00:00:00Z", text: "Completed B", title: "Completed B", todo_id: "todo-progress-b" },
602+
{ done: true, index: 3, role: "agent", status: "done", task_class: "advancement_task", completed_at: "2026-08-02T00:00:00Z", text: "Completed C", title: "Completed C", todo_id: "todo-progress-c" },
603603
{ done: true, index: 6, role: "agent", status: "done", task_class: "continuous_monitor", text: "Completed Monitor", title: "Completed Monitor", todo_id: "todo-progress-monitor" },
604604
],
605605
deferred_items: [
@@ -622,9 +622,9 @@ export async function installApi(page, { goalSubagentConfigurationEnabled = true
622622
],
623623
open: 3,
624624
recent_completed_advancement_items: [
625-
{ done: true, index: 1, role: "agent", status: "done", task_class: "advancement_task", text: "Completed A", title: "Completed A", todo_id: "todo-progress-a" },
626-
{ done: true, index: 2, role: "agent", status: "done", task_class: "advancement_task", text: "Completed B", title: "Completed B", todo_id: "todo-progress-b" },
627-
{ done: true, index: 3, role: "agent", status: "done", task_class: "advancement_task", text: "Completed C", title: "Completed C", todo_id: "todo-progress-c" },
625+
{ done: true, index: 1, role: "agent", status: "done", task_class: "advancement_task", completed_at: "2026-08-01T00:00:00Z", text: "Completed A", title: "Completed A", todo_id: "todo-progress-a" },
626+
{ done: true, index: 2, role: "agent", status: "done", task_class: "advancement_task", completed_at: "2026-08-03T00:00:00Z", text: "Completed B", title: "Completed B", todo_id: "todo-progress-b" },
627+
{ done: true, index: 3, role: "agent", status: "done", task_class: "advancement_task", completed_at: "2026-08-02T00:00:00Z", text: "Completed C", title: "Completed C", todo_id: "todo-progress-c" },
628628
],
629629
total: 9,
630630
},
@@ -667,7 +667,13 @@ export async function installApi(page, { goalSubagentConfigurationEnabled = true
667667
index_exists: false, raw_index_records: 0, unique_runs: 0, latest_runs: [],
668668
});
669669
fixture.attention_queue.items.push({
670-
agent_todos: { items: [], open_count: 2, source_section: "Agent Todo", total_count: 2 },
670+
agent_todos: {
671+
items: [], open_count: 2, source_section: "Agent Todo", total_count: 3, advancement_done_count: 1,
672+
recent_completed_advancement_items: [{
673+
done: true, status: "done", task_class: "advancement_task", todo_id: "todo-other-goal-completed",
674+
text: "Newest cross-goal result", completed_at: "2026-08-04T00:00:00Z",
675+
}],
676+
},
671677
goal_id: "multi-agent-projection",
672678
project_asset: {
673679
agent_todos: { items: [], open: 2, done: 0, total: 2 },

‎examples/personal-workspace-browser/goal-activity.mjs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,11 @@ export const goalActivityScenario = {
134134
await page.getByTestId("personal-home-lane-needs_you").locator(".personal-home-goal-card").count(),
135135
"The brief and the needs-you lane agree",
136136
);
137-
assert.ok(await page.getByTestId("personal-brief-completed").locator(".personal-brief-row").count() > 0, "Recently completed work is surfaced");
137+
assert.deepEqual(
138+
await page.getByTestId("personal-brief-completed").locator(".personal-brief-row strong").allTextContents(),
139+
["Newest cross-goal result", "Completed B", "Completed C"],
140+
"Recent work survives status parsing and is sorted across Goals before taking three rows",
141+
);
138142
await page.screenshot({ path: resolve(outputDir, "goal-activity-sidebar.png"), animations: "disabled" });
139143
await running.locator(".personal-goal-link").click();
140144
await page.locator(".personal-channel-activity", { hasText: "执行中" }).waitFor();

0 commit comments

Comments
 (0)