fix(FN-2581): align done timer duration with task lifecycle timestamps

- Update TaskCard done-duration calculations to use createdAt as the start baseline for completed tasks
- Refactor TaskCard timer tests to assert against TaskStore-backed timestamps and avoid brittle elapsed-time assumptions
- Cover done-state timer behavior with expanded test scenarios for start/end timestamp combinations
- Remove unused provider code in pi-claude-cli to satisfy lint and keep the full test/build gates green
This commit is contained in:
Fusion
2026-04-26 00:26:48 -07:00
committed by gsxdsm
parent 89c599abf6
commit cddf7bbd64
3 changed files with 70 additions and 33 deletions

View File

@@ -122,7 +122,7 @@ function getDoneCompletionMs(task: Task): number | null {
}
function getDoneProcessingStartMs(task: Task, completionMs: number): number | null {
const startCandidates = [task.updatedAt, task.createdAt]
const startCandidates = [task.createdAt]
.map(parseTimestampToMs)
.filter((value): value is number => value != null);

View File

@@ -484,7 +484,7 @@ describe("TaskCard", () => {
task={makeTask({
column: "done",
columnMovedAt: "2026-04-25T15:00:00.000Z",
updatedAt: "2026-04-25T14:00:00.000Z",
updatedAt: "2026-04-25T15:00:00.000Z",
createdAt: "2026-04-25T13:00:00.000Z",
})}
onOpenDetail={noop}
@@ -494,9 +494,9 @@ describe("TaskCard", () => {
const timer = container.querySelector(".card-time-indicator");
expect(timer).not.toBeNull();
expect(timer?.textContent).toContain("1h");
expect(timer?.getAttribute("title")).toContain("Processing took 1h");
expect(timer?.getAttribute("aria-label")).toContain("Completed processing duration 1h");
expect(timer?.textContent).toContain("2h");
expect(timer?.getAttribute("title")).toContain("Processing took 2h");
expect(timer?.getAttribute("aria-label")).toContain("Completed processing duration 2h");
});
it("renders files-changed metadata and timer chip in the same footer row", () => {
@@ -508,7 +508,7 @@ describe("TaskCard", () => {
task={makeTask({
column: "done",
columnMovedAt: "2026-04-25T15:00:00.000Z",
updatedAt: "2026-04-25T14:00:00.000Z",
updatedAt: "2026-04-25T15:00:00.000Z",
createdAt: "2026-04-25T13:00:00.000Z",
mergeDetails: {
commitSha: "abc123",
@@ -581,31 +581,45 @@ describe("TaskCard", () => {
});
it.each([
{ durationMs: 59_000, expected: "<1m" },
{ durationMs: 60 * 60_000, expected: "1h" },
{ durationMs: 24 * 60 * 60_000, expected: "1d" },
])("formats done processing-duration label as $expected at boundary", ({ durationMs, expected }) => {
vi.useFakeTimers();
const completionTime = new Date("2026-04-25T20:00:00.000Z");
vi.setSystemTime(new Date("2026-04-25T23:00:00.000Z"));
{
createdAt: "2026-04-25T09:00:00.000Z",
columnMovedAt: "2026-04-25T09:00:59.000Z",
expected: "<1m",
},
{
createdAt: "2026-04-25T09:00:00.000Z",
columnMovedAt: "2026-04-25T10:00:00.000Z",
expected: "1h",
},
{
createdAt: "2026-04-25T09:00:00.000Z",
columnMovedAt: "2026-04-26T09:00:00.000Z",
expected: "1d",
},
])(
"formats done processing-duration label as $expected at boundary",
({ createdAt, columnMovedAt, expected }) => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-04-26T12:00:00.000Z"));
const { container } = render(
<TaskCard
task={makeTask({
column: "done",
columnMovedAt: completionTime.toISOString(),
updatedAt: new Date(completionTime.getTime() - durationMs).toISOString(),
createdAt: "2026-04-25T09:00:00.000Z",
})}
onOpenDetail={noop}
addToast={noop}
/>,
);
const { container } = render(
<TaskCard
task={makeTask({
column: "done",
columnMovedAt,
updatedAt: columnMovedAt,
createdAt,
})}
onOpenDetail={noop}
addToast={noop}
/>,
);
const timer = container.querySelector(".card-time-indicator");
expect(timer).not.toBeNull();
expect(timer?.textContent).toContain(expected);
});
const timer = container.querySelector(".card-time-indicator");
expect(timer).not.toBeNull();
expect(timer?.textContent).toContain(expected);
},
);
it("keeps done processing-duration timer stable when clock advances", () => {
vi.useFakeTimers();
@@ -616,7 +630,7 @@ describe("TaskCard", () => {
task={makeTask({
column: "done",
columnMovedAt: "2026-04-25T15:00:00.000Z",
updatedAt: "2026-04-25T14:00:00.000Z",
updatedAt: "2026-04-25T15:00:00.000Z",
createdAt: "2026-04-25T13:00:00.000Z",
})}
onOpenDetail={noop}
@@ -624,13 +638,37 @@ describe("TaskCard", () => {
/>,
);
expect(container.querySelector(".card-time-indicator")?.textContent).toContain("1h");
expect(container.querySelector(".card-time-indicator")?.textContent).toContain("2h");
act(() => {
vi.advanceTimersByTime(2 * 60 * 60_000);
});
expect(container.querySelector(".card-time-indicator")?.textContent).toContain("1h");
expect(container.querySelector(".card-time-indicator")?.textContent).toContain("2h");
});
it("uses createdAt for done duration when updatedAt equals completion timestamp", () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-04-25T18:00:00.000Z"));
const { container } = render(
<TaskCard
task={makeTask({
column: "done",
createdAt: "2026-04-25T10:00:00.000Z",
columnMovedAt: "2026-04-25T12:30:00.000Z",
updatedAt: "2026-04-25T12:30:00.000Z",
})}
onOpenDetail={noop}
addToast={noop}
/>,
);
const timer = container.querySelector(".card-time-indicator");
expect(timer).not.toBeNull();
expect(timer?.textContent).toContain("2h");
expect(timer?.textContent).not.toContain("<1m");
expect(timer?.getAttribute("title")).toContain("Processing took 2h");
});
it("refreshes in-progress timer chip on 30s cadence", () => {