From 25222710716d9ae03a404d7b8259c41caaaee081 Mon Sep 17 00:00:00 2001 From: iamtoruk Date: Mon, 11 May 2026 22:15:37 -0700 Subject: [PATCH] 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. --- src/providers/cursor.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/providers/cursor.ts b/src/providers/cursor.ts index 6362512..ebd7f91 100644 --- a/src/providers/cursor.ts +++ b/src/providers/cursor.ts @@ -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): { 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::`. Extract from the key so the @@ -487,7 +489,7 @@ function parseBubbles(db: SqliteDatabase, seenKeys: Set): { 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()