From 087d22f1fd7de3136e2a3178966d1e06707ab6fc Mon Sep 17 00:00:00 2001 From: crib Date: Fri, 28 Nov 2025 18:39:47 +0100 Subject: [PATCH] fix: Stopwatch mode and daily stats improvements Bug Fixes: - Removed progress bar from stopwatch mode (only shows in pomodoro/break) - Fixed daily stats not resetting at midnight when app stays open - Fixed focus time tracking to only count completed tasks, not stopped sessions Improvements: - Added daily reset check interval (checks every 60 seconds) - Added daily reset check on app visibility change - Focus time now accurately reflects completed work only - Session time tracking separated from daily focus time Technical: - Added dailyResetCheckInterval for periodic day change detection - Added sessionStartSeconds to track focus time per session - Removed real-time focus time updates from timer intervals - Focus time only added in completeTask() method - Progress bar conditionally rendered based on isStopwatchMode flag --- main.js | 59 ++++++++++++++++++++++++++++++++++------------------- src/main.ts | 44 ++++++++++++++++++++++++++------------- src/view.ts | 34 +++++++++++++++--------------- 3 files changed, 86 insertions(+), 51 deletions(-) diff --git a/main.js b/main.js index 84a1aca..03a7d2b 100644 --- a/main.js +++ b/main.js @@ -763,19 +763,21 @@ var ImmerseView = class extends import_obsidian2.ItemView { cls: "immerse-timer-time", text: this.plugin.formatTime(this.plugin.currentTimerSeconds) }); - const progressWrap = activeCard.createEl("div", { cls: "immerse-progress-wrap" }); - this.progressBarEl = progressWrap.createEl("div", { cls: "immerse-progress-bar" }); - let progressPercent = 0; - if (this.plugin.isBreakMode) { - const breakDuration = this.plugin.pomodoroCount % this.plugin.settings.longBreakInterval === 0 ? this.plugin.settings.longBreakMinutes * 60 : this.plugin.settings.pomodoroBreakMinutes * 60; - progressPercent = (breakDuration - this.plugin.currentTimerSeconds) / breakDuration * 100; - } else { - const workDuration = this.plugin.settings.pomodoroWorkMinutes * 60; - progressPercent = (workDuration - this.plugin.currentTimerSeconds) / workDuration * 100; + if (!this.plugin.isStopwatchMode) { + const progressWrap = activeCard.createEl("div", { cls: "immerse-progress-wrap" }); + this.progressBarEl = progressWrap.createEl("div", { cls: "immerse-progress-bar" }); + let progressPercent = 0; + if (this.plugin.isBreakMode) { + const breakDuration = this.plugin.pomodoroCount % this.plugin.settings.longBreakInterval === 0 ? this.plugin.settings.longBreakMinutes * 60 : this.plugin.settings.pomodoroBreakMinutes * 60; + progressPercent = (breakDuration - this.plugin.currentTimerSeconds) / breakDuration * 100; + } else { + const workDuration = this.plugin.settings.pomodoroWorkMinutes * 60; + progressPercent = (workDuration - this.plugin.currentTimerSeconds) / workDuration * 100; + } + this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`; + if (progressPercent >= 100) + this.progressBarEl.addClass("immerse-overtime"); } - this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`; - if (progressPercent >= 100) - this.progressBarEl.addClass("immerse-overtime"); if (!this.plugin.isBreakMode) { const timeInfo = activeCard.createEl("div", { cls: "immerse-time-info" }); timeInfo.createEl("span", { text: `Est: ${this.plugin.formatTimeHuman(task.estimatedMinutes)}` }); @@ -1264,19 +1266,29 @@ var ImmersePlugin = class extends import_obsidian4.Plugin { // Focus time tracking (in seconds for accuracy) this.focusSecondsToday = 0; this.secondsWorkedOnCurrentTask = 0; + this.sessionStartSeconds = 0; + // Track seconds at start of current session // Status bar element this.statusBarEl = null; // Reminder system this.reminderCheckInterval = null; this.notifiedReminders = /* @__PURE__ */ new Set(); + // Track which reminders have been shown + // Daily reset check interval + this.dailyResetCheckInterval = null; } - // Track which reminders have been shown async onload() { await this.loadAllData(); this.checkDailyReset(); + this.dailyResetCheckInterval = window.setInterval(() => { + this.checkDailyReset(); + }, 6e4); document.addEventListener("visibilitychange", () => { - if (!document.hidden && this.isTimerRunning) { - this.syncTimerFromTimestamp(); + if (!document.hidden) { + this.checkDailyReset(); + if (this.isTimerRunning) { + this.syncTimerFromTimestamp(); + } } }); this.registerView( @@ -1329,6 +1341,10 @@ var ImmersePlugin = class extends import_obsidian4.Plugin { onunload() { this.stopTimer(); this.stopReminderSystem(); + if (this.dailyResetCheckInterval) { + window.clearInterval(this.dailyResetCheckInterval); + this.dailyResetCheckInterval = null; + } } async loadAllData() { const loaded = await this.loadData(); @@ -1440,6 +1456,10 @@ var ImmersePlugin = class extends import_obsidian4.Plugin { task.isActive = false; this.data.completedToday++; this.data.lastActiveDate = new Date().toDateString(); + if (this.activeTaskId === taskId && this.sessionStartSeconds !== void 0) { + const sessionTime = this.secondsWorkedOnCurrentTask - this.sessionStartSeconds; + this.focusSecondsToday += sessionTime; + } this.archiveCompletedTask(task); this.updateDailyStats(task); if (this.settings.enableCelebrations) { @@ -1665,6 +1685,7 @@ var ImmersePlugin = class extends import_obsidian4.Plugin { this.timerStartTimestamp = Date.now(); this.pausedTimeRemaining = 0; const initialSecondsWorked = this.secondsWorkedOnCurrentTask; + this.sessionStartSeconds = this.secondsWorkedOnCurrentTask; let alertShown = false; this.refreshView(); this.updateStatusBar(); @@ -1675,8 +1696,6 @@ var ImmersePlugin = class extends import_obsidian4.Plugin { this.currentTimerSeconds = elapsedSeconds; this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds; task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60); - const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds; - this.focusSecondsToday = newFocusSeconds; this.updateStatusBar(); this.updateTimerDisplay(); if (!alertShown && this.currentTimerSeconds >= task.estimatedMinutes * 60) { @@ -1704,6 +1723,7 @@ var ImmersePlugin = class extends import_obsidian4.Plugin { this.timerStartTimestamp = Date.now(); this.pausedTimeRemaining = this.currentTimerSeconds; const initialSecondsWorked = this.secondsWorkedOnCurrentTask; + this.sessionStartSeconds = this.secondsWorkedOnCurrentTask; this.refreshView(); this.updateStatusBar(); this.timerInterval = window.setInterval(() => { @@ -1717,8 +1737,6 @@ var ImmersePlugin = class extends import_obsidian4.Plugin { if (task.actualMinutes !== actualMinutes) { task.actualMinutes = actualMinutes; } - const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds; - this.focusSecondsToday = newFocusSeconds; } this.updateStatusBar(); this.updateTimerDisplay(); @@ -1804,8 +1822,6 @@ var ImmersePlugin = class extends import_obsidian4.Plugin { if (task && !this.isBreakMode) { this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds; task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60); - const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds; - this.focusSecondsToday = newFocusSeconds; } this.updateStatusBar(); this.updateTimerDisplay(); @@ -1837,6 +1853,7 @@ var ImmersePlugin = class extends import_obsidian4.Plugin { this.isStopwatchMode = false; this.activeTaskId = null; this.secondsWorkedOnCurrentTask = 0; + this.sessionStartSeconds = 0; this.timerStartTimestamp = 0; this.pausedTimeRemaining = 0; this.updateStatusBar(); diff --git a/src/main.ts b/src/main.ts index 937f5c0..eace2f4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -46,6 +46,7 @@ export default class ImmersePlugin extends Plugin { // Focus time tracking (in seconds for accuracy) private focusSecondsToday: number = 0; private secondsWorkedOnCurrentTask: number = 0; + private sessionStartSeconds: number = 0; // Track seconds at start of current session // Status bar element statusBarEl: HTMLElement | null = null; @@ -54,16 +55,28 @@ export default class ImmersePlugin extends Plugin { private reminderCheckInterval: number | null = null; private notifiedReminders: Set = new Set(); // Track which reminders have been shown + // Daily reset check interval + private dailyResetCheckInterval: number | null = null; + async onload() { await this.loadAllData(); // Check and reset daily stats this.checkDailyReset(); + // Start daily reset check interval (check every 60 seconds) + this.dailyResetCheckInterval = window.setInterval(() => { + this.checkDailyReset(); + }, 60000); + // Handle visibility changes to sync timer when app comes back to foreground document.addEventListener('visibilitychange', () => { - if (!document.hidden && this.isTimerRunning) { - this.syncTimerFromTimestamp(); + if (!document.hidden) { + // Check for day change when app becomes visible + this.checkDailyReset(); + if (this.isTimerRunning) { + this.syncTimerFromTimestamp(); + } } }); @@ -135,6 +148,12 @@ export default class ImmersePlugin extends Plugin { onunload() { this.stopTimer(); this.stopReminderSystem(); + + // Stop daily reset check interval + if (this.dailyResetCheckInterval) { + window.clearInterval(this.dailyResetCheckInterval); + this.dailyResetCheckInterval = null; + } } async loadAllData() { @@ -279,6 +298,12 @@ export default class ImmersePlugin extends Plugin { this.data.completedToday++; this.data.lastActiveDate = new Date().toDateString(); + // Add focus time from this session (only on completion) + if (this.activeTaskId === taskId && this.sessionStartSeconds !== undefined) { + const sessionTime = this.secondsWorkedOnCurrentTask - this.sessionStartSeconds; + this.focusSecondsToday += sessionTime; + } + // Archive task for historical reporting this.archiveCompletedTask(task); @@ -563,6 +588,7 @@ export default class ImmersePlugin extends Plugin { // Store initial values const initialSecondsWorked = this.secondsWorkedOnCurrentTask; + this.sessionStartSeconds = this.secondsWorkedOnCurrentTask; // Track session start for focus time let alertShown = false; // Full refresh to show the active task card @@ -583,10 +609,6 @@ export default class ImmersePlugin extends Plugin { this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds; task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60); - // Update focus time - const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds; - this.focusSecondsToday = newFocusSeconds; - // Light update - only timer display, no full refresh this.updateStatusBar(); this.updateTimerDisplay(); @@ -626,6 +648,7 @@ export default class ImmersePlugin extends Plugin { // Store the initial seconds worked to calculate delta const initialSecondsWorked = this.secondsWorkedOnCurrentTask; + this.sessionStartSeconds = this.secondsWorkedOnCurrentTask; // Track session start for focus time // Full refresh to show the active task card this.refreshView(); @@ -648,10 +671,6 @@ export default class ImmersePlugin extends Plugin { if (task.actualMinutes !== actualMinutes) { task.actualMinutes = actualMinutes; } - - // Update focus time based on elapsed seconds - const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds; - this.focusSecondsToday = newFocusSeconds; } // Light update - only timer display, no full refresh @@ -782,10 +801,6 @@ export default class ImmersePlugin extends Plugin { // Update actual time worked this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds; task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60); - - // Update focus time - const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds; - this.focusSecondsToday = newFocusSeconds; } // Light update - only timer display @@ -826,6 +841,7 @@ export default class ImmersePlugin extends Plugin { this.isStopwatchMode = false; this.activeTaskId = null; this.secondsWorkedOnCurrentTask = 0; + this.sessionStartSeconds = 0; this.timerStartTimestamp = 0; this.pausedTimeRemaining = 0; this.updateStatusBar(); diff --git a/src/view.ts b/src/view.ts index f30e779..5f54ed2 100644 --- a/src/view.ts +++ b/src/view.ts @@ -173,24 +173,26 @@ export class ImmerseView extends ItemView { text: this.plugin.formatTime(this.plugin.currentTimerSeconds) }); - // Progress bar - store reference for updates - const progressWrap = activeCard.createEl('div', { cls: 'immerse-progress-wrap' }); - this.progressBarEl = progressWrap.createEl('div', { cls: 'immerse-progress-bar' }); - - let progressPercent = 0; - if (this.plugin.isBreakMode) { - const breakDuration = this.plugin.pomodoroCount % this.plugin.settings.longBreakInterval === 0 - ? this.plugin.settings.longBreakMinutes * 60 - : this.plugin.settings.pomodoroBreakMinutes * 60; - progressPercent = ((breakDuration - this.plugin.currentTimerSeconds) / breakDuration) * 100; - } else { - const workDuration = this.plugin.settings.pomodoroWorkMinutes * 60; - progressPercent = ((workDuration - this.plugin.currentTimerSeconds) / workDuration) * 100; + // Progress bar - only show in pomodoro/break mode, not stopwatch + if (!this.plugin.isStopwatchMode) { + const progressWrap = activeCard.createEl('div', { cls: 'immerse-progress-wrap' }); + this.progressBarEl = progressWrap.createEl('div', { cls: 'immerse-progress-bar' }); + + let progressPercent = 0; + if (this.plugin.isBreakMode) { + const breakDuration = this.plugin.pomodoroCount % this.plugin.settings.longBreakInterval === 0 + ? this.plugin.settings.longBreakMinutes * 60 + : this.plugin.settings.pomodoroBreakMinutes * 60; + progressPercent = ((breakDuration - this.plugin.currentTimerSeconds) / breakDuration) * 100; + } else { + const workDuration = this.plugin.settings.pomodoroWorkMinutes * 60; + progressPercent = ((workDuration - this.plugin.currentTimerSeconds) / workDuration) * 100; + } + + this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`; + if (progressPercent >= 100) this.progressBarEl.addClass('immerse-overtime'); } - this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`; - if (progressPercent >= 100) this.progressBarEl.addClass('immerse-overtime'); - // Time info - store reference for actual time updates if (!this.plugin.isBreakMode) { const timeInfo = activeCard.createEl('div', { cls: 'immerse-time-info' });