Skip Cursor bubble rows that lack a createdAt timestamp

Bubble rows without createdAt were defaulting to new Date(), which
misattributed historical or undated usage to Today and inflated the
daily chart. Now filtered at the SQL level and skipped in application
code.

Based on the bubble-side fix from #262 by @darthrevanyunka.
This commit is contained in:
iamtoruk 2026-05-11 22:15:37 -07:00
parent 3b71650f24
commit 2522271071

View file

@ -329,7 +329,8 @@ const USER_MESSAGES_QUERY = `
// the whole template. The original combined string is preserved as
// BUBBLE_QUERY_SINCE for any caller that doesn't want the cap.
const BUBBLE_QUERY_SINCE_HEAD = BUBBLE_QUERY_BASE + `
AND (json_extract(value, '$.createdAt') > ? OR json_extract(value, '$.createdAt') IS NULL)`
AND json_extract(value, '$.createdAt') IS NOT NULL
AND json_extract(value, '$.createdAt') > ?`
const BUBBLE_QUERY_SINCE_TAIL = `
ORDER BY ROWID ASC
`
@ -458,6 +459,7 @@ function parseBubbles(db: SqliteDatabase, seenKeys: Set<string>): { calls: Parse
}
const createdAt = row.created_at ?? ''
if (!createdAt) continue
// The JSON `conversationId` field on bubbles is empty in current
// Cursor builds. The real composerId lives in the row key
// `bubbleId:<composerId>:<bubbleUuid>`. Extract from the key so the
@ -487,7 +489,7 @@ function parseBubbles(db: SqliteDatabase, seenKeys: Set<string>): { calls: Parse
const costUSD = calculateCost(pricingModel, inputTokens, outputTokens, 0, 0, 0)
const timestamp = createdAt || new Date().toISOString()
const timestamp = createdAt
const userQuestion = takeUserMessage(userMessages, conversationId)
const assistantText = blobToText(row.user_text)
const userText = (userQuestion + ' ' + assistantText).trim()