From 3f5fc40b2255ef6e50826d90dc3bb4ab77012484 Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Sat, 31 Jan 2026 18:04:53 -0500 Subject: [PATCH] ## CHANGES MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added IPC/API to fetch earliest recorded stats timestamp instantly 🧭 - Stats DB now computes earliest date across key tracking tables accurately 🧠 - Settings modal displays “since YYYY-MM-DD” alongside stats database size 📅 --- src/main/ipc/handlers/stats.ts | 9 ++++++ src/main/preload/stats.ts | 4 +++ src/main/stats-db.ts | 34 +++++++++++++++++++++++ src/renderer/components/SettingsModal.tsx | 21 +++++++++++++- src/renderer/global.d.ts | 2 ++ 5 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/main/ipc/handlers/stats.ts b/src/main/ipc/handlers/stats.ts index 21ac7bc9..2f6e8563 100644 --- a/src/main/ipc/handlers/stats.ts +++ b/src/main/ipc/handlers/stats.ts @@ -244,6 +244,15 @@ export function registerStatsHandlers(deps: StatsHandlerDependencies): void { }) ); + // Get earliest stat timestamp (for UI display) + ipcMain.handle( + 'stats:get-earliest-timestamp', + withIpcErrorLogging(handlerOpts('getEarliestTimestamp'), async () => { + const db = getStatsDB(); + return db.getEarliestStatTimestamp(); + }) + ); + // Record session creation (launched) ipcMain.handle( 'stats:record-session-created', diff --git a/src/main/preload/stats.ts b/src/main/preload/stats.ts index 69ea98ee..17f92ca0 100644 --- a/src/main/preload/stats.ts +++ b/src/main/preload/stats.ts @@ -178,6 +178,10 @@ export function createStatsApi() { // Get database size in bytes getDatabaseSize: (): Promise => ipcRenderer.invoke('stats:get-database-size'), + // Get earliest stat timestamp (null if no entries) + getEarliestTimestamp: (): Promise => + ipcRenderer.invoke('stats:get-earliest-timestamp'), + // Record session creation (for lifecycle tracking) recordSessionCreated: (event: SessionCreatedEvent): Promise => ipcRenderer.invoke('stats:record-session-created', event), diff --git a/src/main/stats-db.ts b/src/main/stats-db.ts index 5948c703..cb067c28 100644 --- a/src/main/stats-db.ts +++ b/src/main/stats-db.ts @@ -666,6 +666,40 @@ export class StatsDB { } } + /** + * Get the timestamp of the earliest stat entry in the database. + * Checks query_events, auto_run_sessions, and session_lifecycle tables. + * Returns null if no entries exist. + */ + getEarliestStatTimestamp(): number | null { + if (!this.db) throw new Error('Database not initialized'); + + // Query minimum start_time from each table and find the overall minimum + const queryEventsMin = this.db + .prepare('SELECT MIN(start_time) as min_time FROM query_events') + .get() as { min_time: number | null } | undefined; + + const autoRunMin = this.db + .prepare('SELECT MIN(start_time) as min_time FROM auto_run_sessions') + .get() as { min_time: number | null } | undefined; + + const lifecycleMin = this.db + .prepare('SELECT MIN(created_at) as min_time FROM session_lifecycle') + .get() as { min_time: number | null } | undefined; + + const timestamps = [ + queryEventsMin?.min_time, + autoRunMin?.min_time, + lifecycleMin?.min_time, + ].filter((t): t is number => t !== null && t !== undefined); + + if (timestamps.length === 0) { + return null; + } + + return Math.min(...timestamps); + } + /** * Run VACUUM on the database to reclaim unused space and optimize structure. * diff --git a/src/renderer/components/SettingsModal.tsx b/src/renderer/components/SettingsModal.tsx index 7be7692e..066e0c0b 100644 --- a/src/renderer/components/SettingsModal.tsx +++ b/src/renderer/components/SettingsModal.tsx @@ -339,6 +339,7 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro // Stats data management state const [statsDbSize, setStatsDbSize] = useState(null); + const [statsEarliestDate, setStatsEarliestDate] = useState(null); const [statsClearing, setStatsClearing] = useState(false); const [statsClearResult, setStatsClearResult] = useState<{ success: boolean; @@ -379,7 +380,7 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro setSyncError('Failed to load storage settings'); }); - // Load stats database size + // Load stats database size and earliest timestamp window.maestro.stats .getDatabaseSize() .then((size) => { @@ -389,6 +390,21 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro console.error('Failed to load stats database size:', err); }); + window.maestro.stats + .getEarliestTimestamp() + .then((timestamp) => { + if (timestamp) { + const date = new Date(timestamp); + const formatted = date.toISOString().split('T')[0]; // YYYY-MM-DD + setStatsEarliestDate(formatted); + } else { + setStatsEarliestDate(null); + } + }) + .catch((err) => { + console.error('Failed to load earliest stats timestamp:', err); + }); + // Reset stats clear state setStatsClearResult(null); } @@ -1894,6 +1910,9 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro {statsDbSize !== null ? (statsDbSize / 1024 / 1024).toFixed(2) + ' MB' : 'Loading...'} + {statsEarliestDate && ( + (since {statsEarliestDate}) + )} diff --git a/src/renderer/global.d.ts b/src/renderer/global.d.ts index 0434e27b..e1d696d8 100644 --- a/src/renderer/global.d.ts +++ b/src/renderer/global.d.ts @@ -2211,6 +2211,8 @@ interface MaestroAPI { }>; // Get database size in bytes getDatabaseSize: () => Promise; + // Get earliest stat timestamp (null if no entries exist) + getEarliestTimestamp: () => Promise; // Record session creation (launched) recordSessionCreated: (event: { sessionId: string;