Skip to content

Commit ab08180

Browse files
fix(ui): sync tasks with missing timestamps
1 parent 716dbbd commit ab08180

2 files changed

Lines changed: 44 additions & 3 deletions

File tree

ui/src/ui/controllers/workboard.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1799,6 +1799,41 @@ describe("workboard controller", () => {
17991799
expect(state.cards[0]).toMatchObject({ status: "running" });
18001800
});
18011801

1802+
it("keeps task lifecycle sync active when task timestamps are missing", async () => {
1803+
const host = {};
1804+
const state = getWorkboardState(host);
1805+
const linked = {
1806+
...sampleCard,
1807+
status: "running",
1808+
sessionKey: sampleTaskSessionKey,
1809+
runId: "run-1",
1810+
taskId: "task-1",
1811+
updatedAt: 5,
1812+
} satisfies WorkboardCard;
1813+
const { updatedAt: _ignored, ...taskWithoutUpdatedAt } = sampleTask;
1814+
state.loaded = true;
1815+
state.cards = [linked];
1816+
state.tasksByCardId.set("card-1", sampleTask);
1817+
const client = createClient({
1818+
"tasks.list": { tasks: [{ ...taskWithoutUpdatedAt, status: "completed" }] },
1819+
"workboard.cards.update": {
1820+
card: { ...linked, status: "review", updatedAt: 6 },
1821+
},
1822+
});
1823+
1824+
await syncWorkboardLifecycle({
1825+
host,
1826+
client: client as never,
1827+
sessions: [],
1828+
});
1829+
1830+
expect(client.request).toHaveBeenNthCalledWith(2, "workboard.cards.update", {
1831+
id: "card-1",
1832+
patch: { status: "review" },
1833+
});
1834+
expect(state.cards[0]).toMatchObject({ status: "review" });
1835+
});
1836+
18021837
it("applies fresh failed task lifecycle sync after a newer task update", async () => {
18031838
const host = {};
18041839
const state = getWorkboardState(host);

ui/src/ui/controllers/workboard.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,11 @@ function sessionUpdatedAtValue(session: GatewaySessionRow): number | undefined {
10071007
return typeof session.updatedAt === "number" ? session.updatedAt : undefined;
10081008
}
10091009

1010+
function taskLifecycleSourceUpdatedAt(task: WorkboardTaskSummary): number | undefined {
1011+
const updatedAt = taskUpdatedAtValue(task);
1012+
return updatedAt > 0 ? updatedAt : undefined;
1013+
}
1014+
10101015
function taskSessionKeyMatchesCardSession(
10111016
cardSessionKey: string,
10121017
taskSessionKey: string | undefined,
@@ -1251,14 +1256,14 @@ export function getWorkboardLifecycle(
12511256
session,
12521257
state: "running",
12531258
targetStatus: "running",
1254-
sourceUpdatedAt: taskUpdatedAtValue(task),
1259+
sourceUpdatedAt: taskLifecycleSourceUpdatedAt(task),
12551260
};
12561261
case "completed":
12571262
return {
12581263
session,
12591264
state: "succeeded",
12601265
targetStatus: "review",
1261-
sourceUpdatedAt: taskUpdatedAtValue(task),
1266+
sourceUpdatedAt: taskLifecycleSourceUpdatedAt(task),
12621267
};
12631268
case "failed":
12641269
case "cancelled":
@@ -1267,7 +1272,7 @@ export function getWorkboardLifecycle(
12671272
session,
12681273
state: "failed",
12691274
targetStatus: "blocked",
1270-
sourceUpdatedAt: taskUpdatedAtValue(task),
1275+
sourceUpdatedAt: taskLifecycleSourceUpdatedAt(task),
12711276
};
12721277
}
12731278
}
@@ -1329,6 +1334,7 @@ function shouldSkipStaleLifecycleStatusSync(card: WorkboardCard, lifecycle: Work
13291334
return (
13301335
Boolean(lifecycle.targetStatus) &&
13311336
typeof lifecycle.sourceUpdatedAt === "number" &&
1337+
lifecycle.sourceUpdatedAt > 0 &&
13321338
lifecycle.sourceUpdatedAt < card.updatedAt
13331339
);
13341340
}

0 commit comments

Comments
 (0)