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:
@@ -122,7 +122,7 @@ function getDoneCompletionMs(task: Task): number | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDoneProcessingStartMs(task: Task, completionMs: number): number | null {
|
function getDoneProcessingStartMs(task: Task, completionMs: number): number | null {
|
||||||
const startCandidates = [task.updatedAt, task.createdAt]
|
const startCandidates = [task.createdAt]
|
||||||
.map(parseTimestampToMs)
|
.map(parseTimestampToMs)
|
||||||
.filter((value): value is number => value != null);
|
.filter((value): value is number => value != null);
|
||||||
|
|
||||||
|
|||||||
@@ -484,7 +484,7 @@ describe("TaskCard", () => {
|
|||||||
task={makeTask({
|
task={makeTask({
|
||||||
column: "done",
|
column: "done",
|
||||||
columnMovedAt: "2026-04-25T15:00:00.000Z",
|
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",
|
createdAt: "2026-04-25T13:00:00.000Z",
|
||||||
})}
|
})}
|
||||||
onOpenDetail={noop}
|
onOpenDetail={noop}
|
||||||
@@ -494,9 +494,9 @@ describe("TaskCard", () => {
|
|||||||
|
|
||||||
const timer = container.querySelector(".card-time-indicator");
|
const timer = container.querySelector(".card-time-indicator");
|
||||||
expect(timer).not.toBeNull();
|
expect(timer).not.toBeNull();
|
||||||
expect(timer?.textContent).toContain("1h");
|
expect(timer?.textContent).toContain("2h");
|
||||||
expect(timer?.getAttribute("title")).toContain("Processing took 1h");
|
expect(timer?.getAttribute("title")).toContain("Processing took 2h");
|
||||||
expect(timer?.getAttribute("aria-label")).toContain("Completed processing duration 1h");
|
expect(timer?.getAttribute("aria-label")).toContain("Completed processing duration 2h");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders files-changed metadata and timer chip in the same footer row", () => {
|
it("renders files-changed metadata and timer chip in the same footer row", () => {
|
||||||
@@ -508,7 +508,7 @@ describe("TaskCard", () => {
|
|||||||
task={makeTask({
|
task={makeTask({
|
||||||
column: "done",
|
column: "done",
|
||||||
columnMovedAt: "2026-04-25T15:00:00.000Z",
|
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",
|
createdAt: "2026-04-25T13:00:00.000Z",
|
||||||
mergeDetails: {
|
mergeDetails: {
|
||||||
commitSha: "abc123",
|
commitSha: "abc123",
|
||||||
@@ -581,31 +581,45 @@ describe("TaskCard", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
{ durationMs: 59_000, expected: "<1m" },
|
{
|
||||||
{ durationMs: 60 * 60_000, expected: "1h" },
|
createdAt: "2026-04-25T09:00:00.000Z",
|
||||||
{ durationMs: 24 * 60 * 60_000, expected: "1d" },
|
columnMovedAt: "2026-04-25T09:00:59.000Z",
|
||||||
])("formats done processing-duration label as $expected at boundary", ({ durationMs, expected }) => {
|
expected: "<1m",
|
||||||
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-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(
|
const { container } = render(
|
||||||
<TaskCard
|
<TaskCard
|
||||||
task={makeTask({
|
task={makeTask({
|
||||||
column: "done",
|
column: "done",
|
||||||
columnMovedAt: completionTime.toISOString(),
|
columnMovedAt,
|
||||||
updatedAt: new Date(completionTime.getTime() - durationMs).toISOString(),
|
updatedAt: columnMovedAt,
|
||||||
createdAt: "2026-04-25T09:00:00.000Z",
|
createdAt,
|
||||||
})}
|
})}
|
||||||
onOpenDetail={noop}
|
onOpenDetail={noop}
|
||||||
addToast={noop}
|
addToast={noop}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const timer = container.querySelector(".card-time-indicator");
|
const timer = container.querySelector(".card-time-indicator");
|
||||||
expect(timer).not.toBeNull();
|
expect(timer).not.toBeNull();
|
||||||
expect(timer?.textContent).toContain(expected);
|
expect(timer?.textContent).toContain(expected);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
it("keeps done processing-duration timer stable when clock advances", () => {
|
it("keeps done processing-duration timer stable when clock advances", () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
@@ -616,7 +630,7 @@ describe("TaskCard", () => {
|
|||||||
task={makeTask({
|
task={makeTask({
|
||||||
column: "done",
|
column: "done",
|
||||||
columnMovedAt: "2026-04-25T15:00:00.000Z",
|
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",
|
createdAt: "2026-04-25T13:00:00.000Z",
|
||||||
})}
|
})}
|
||||||
onOpenDetail={noop}
|
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(() => {
|
act(() => {
|
||||||
vi.advanceTimersByTime(2 * 60 * 60_000);
|
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", () => {
|
it("refreshes in-progress timer chip on 30s cadence", () => {
|
||||||
|
|||||||
@@ -135,7 +135,6 @@ export function streamViaCli(
|
|||||||
newSessionId: !resumeSessionId ? options?.sessionId : undefined,
|
newSessionId: !resumeSessionId ? options?.sessionId : undefined,
|
||||||
});
|
});
|
||||||
const getStderr = captureStderr(proc);
|
const getStderr = captureStderr(proc);
|
||||||
const spawnTime = Date.now();
|
|
||||||
|
|
||||||
// Register in global process registry for teardown cleanup
|
// Register in global process registry for teardown cleanup
|
||||||
registerProcess(proc);
|
registerProcess(proc);
|
||||||
|
|||||||
Reference in New Issue
Block a user