Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions src/components/conversations/session-details-dialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,54 @@ describe("SessionDetailsDialog", () => {
expect(mockGet).not.toHaveBeenCalled()
})

it("falls back to completed summary timestamps when stats duration is missing", () => {
const completedSummary = summary({
status: "completed",
created_at: "2026-06-10T10:00:00.000Z",
updated_at: "2026-06-10T10:02:30.000Z",
})
const statsWithoutDuration: SessionStats = {
...fullStats,
total_duration_ms: 0,
}

const { getByText, queryByText } = renderWithIntl(
<SessionDetailsDialog
open
onOpenChange={() => {}}
summary={completedSummary}
stats={statsWithoutDuration}
/>
)

expect(getByText("2.5m")).toBeTruthy()
expect(queryByText("0ms")).toBeNull()
})

it("does not derive an in-progress duration from timestamps", () => {
const liveSummary = summary({
status: "in_progress",
created_at: "2026-06-10T10:00:00.000Z",
updated_at: "2026-06-10T10:02:30.000Z",
})
const statsWithoutDuration: SessionStats = {
...fullStats,
total_duration_ms: 0,
}

const { queryByText } = renderWithIntl(
<SessionDetailsDialog
open
onOpenChange={() => {}}
summary={liveSummary}
stats={statsWithoutDuration}
/>
)

expect(queryByText("2.5m")).toBeNull()
expect(queryByText("0ms")).toBeNull()
})

it("shows the title, agent, and status in the identity header", () => {
const { getByText } = renderWithIntl(
<SessionDetailsDialog
Expand Down
22 changes: 21 additions & 1 deletion src/components/conversations/session-details-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,26 @@ function formatDuration(ms: number): string {
return `${trimZero((minutes / 60).toFixed(1))}h`
}

function parseTimestampMs(value: string): number | null {
const ms = Date.parse(value)
return Number.isFinite(ms) ? ms : null
}

function resolveSessionDurationMs(
summary: DbConversationSummary,
stats: SessionStats | null
): number {
const statsDuration = stats?.total_duration_ms ?? 0
if (statsDuration > 0) return statsDuration
if (summary.status !== "completed") return 0

const startedAt = parseTimestampMs(summary.created_at)
const endedAt = parseTimestampMs(summary.updated_at)
if (startedAt == null || endedAt == null || endedAt <= startedAt) return 0

return endedAt - startedAt
}

/**
* One label-above-value cell inside a responsive `<dl>` grid. Stacking the
* label on top keeps values left-aligned and tight against their label (no
Expand Down Expand Up @@ -242,7 +262,7 @@ export function SessionDetailsDialog({
ctxUsed,
ctxMax
)
const durationMs = stats?.total_duration_ms ?? 0
const durationMs = resolveSessionDurationMs(summary, stats)
// Never coerce an unknown `used` to 0 — some parsers infer the model's
// context cap without any usage figure, so render "— / max" rather than a
// bogus "0 / max".
Expand Down
Loading