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
This commit is contained in:
2025-11-28 18:39:47 +01:00
parent a74fd244b0
commit 087d22f1fd
3 changed files with 86 additions and 51 deletions

33
main.js
View File

@@ -763,6 +763,7 @@ var ImmerseView = class extends import_obsidian2.ItemView {
cls: "immerse-timer-time", cls: "immerse-timer-time",
text: this.plugin.formatTime(this.plugin.currentTimerSeconds) text: this.plugin.formatTime(this.plugin.currentTimerSeconds)
}); });
if (!this.plugin.isStopwatchMode) {
const progressWrap = activeCard.createEl("div", { cls: "immerse-progress-wrap" }); const progressWrap = activeCard.createEl("div", { cls: "immerse-progress-wrap" });
this.progressBarEl = progressWrap.createEl("div", { cls: "immerse-progress-bar" }); this.progressBarEl = progressWrap.createEl("div", { cls: "immerse-progress-bar" });
let progressPercent = 0; let progressPercent = 0;
@@ -776,6 +777,7 @@ var ImmerseView = class extends import_obsidian2.ItemView {
this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`; this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`;
if (progressPercent >= 100) if (progressPercent >= 100)
this.progressBarEl.addClass("immerse-overtime"); this.progressBarEl.addClass("immerse-overtime");
}
if (!this.plugin.isBreakMode) { if (!this.plugin.isBreakMode) {
const timeInfo = activeCard.createEl("div", { cls: "immerse-time-info" }); const timeInfo = activeCard.createEl("div", { cls: "immerse-time-info" });
timeInfo.createEl("span", { text: `Est: ${this.plugin.formatTimeHuman(task.estimatedMinutes)}` }); timeInfo.createEl("span", { text: `Est: ${this.plugin.formatTimeHuman(task.estimatedMinutes)}` });
@@ -1264,20 +1266,30 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
// Focus time tracking (in seconds for accuracy) // Focus time tracking (in seconds for accuracy)
this.focusSecondsToday = 0; this.focusSecondsToday = 0;
this.secondsWorkedOnCurrentTask = 0; this.secondsWorkedOnCurrentTask = 0;
this.sessionStartSeconds = 0;
// Track seconds at start of current session
// Status bar element // Status bar element
this.statusBarEl = null; this.statusBarEl = null;
// Reminder system // Reminder system
this.reminderCheckInterval = null; this.reminderCheckInterval = null;
this.notifiedReminders = /* @__PURE__ */ new Set(); this.notifiedReminders = /* @__PURE__ */ new Set();
}
// Track which reminders have been shown // Track which reminders have been shown
// Daily reset check interval
this.dailyResetCheckInterval = null;
}
async onload() { async onload() {
await this.loadAllData(); await this.loadAllData();
this.checkDailyReset(); this.checkDailyReset();
this.dailyResetCheckInterval = window.setInterval(() => {
this.checkDailyReset();
}, 6e4);
document.addEventListener("visibilitychange", () => { document.addEventListener("visibilitychange", () => {
if (!document.hidden && this.isTimerRunning) { if (!document.hidden) {
this.checkDailyReset();
if (this.isTimerRunning) {
this.syncTimerFromTimestamp(); this.syncTimerFromTimestamp();
} }
}
}); });
this.registerView( this.registerView(
VIEW_TYPE_IMMERSE, VIEW_TYPE_IMMERSE,
@@ -1329,6 +1341,10 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
onunload() { onunload() {
this.stopTimer(); this.stopTimer();
this.stopReminderSystem(); this.stopReminderSystem();
if (this.dailyResetCheckInterval) {
window.clearInterval(this.dailyResetCheckInterval);
this.dailyResetCheckInterval = null;
}
} }
async loadAllData() { async loadAllData() {
const loaded = await this.loadData(); const loaded = await this.loadData();
@@ -1440,6 +1456,10 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
task.isActive = false; task.isActive = false;
this.data.completedToday++; this.data.completedToday++;
this.data.lastActiveDate = new Date().toDateString(); 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.archiveCompletedTask(task);
this.updateDailyStats(task); this.updateDailyStats(task);
if (this.settings.enableCelebrations) { if (this.settings.enableCelebrations) {
@@ -1665,6 +1685,7 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
this.timerStartTimestamp = Date.now(); this.timerStartTimestamp = Date.now();
this.pausedTimeRemaining = 0; this.pausedTimeRemaining = 0;
const initialSecondsWorked = this.secondsWorkedOnCurrentTask; const initialSecondsWorked = this.secondsWorkedOnCurrentTask;
this.sessionStartSeconds = this.secondsWorkedOnCurrentTask;
let alertShown = false; let alertShown = false;
this.refreshView(); this.refreshView();
this.updateStatusBar(); this.updateStatusBar();
@@ -1675,8 +1696,6 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
this.currentTimerSeconds = elapsedSeconds; this.currentTimerSeconds = elapsedSeconds;
this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds; this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds;
task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60); task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60);
const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds;
this.focusSecondsToday = newFocusSeconds;
this.updateStatusBar(); this.updateStatusBar();
this.updateTimerDisplay(); this.updateTimerDisplay();
if (!alertShown && this.currentTimerSeconds >= task.estimatedMinutes * 60) { if (!alertShown && this.currentTimerSeconds >= task.estimatedMinutes * 60) {
@@ -1704,6 +1723,7 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
this.timerStartTimestamp = Date.now(); this.timerStartTimestamp = Date.now();
this.pausedTimeRemaining = this.currentTimerSeconds; this.pausedTimeRemaining = this.currentTimerSeconds;
const initialSecondsWorked = this.secondsWorkedOnCurrentTask; const initialSecondsWorked = this.secondsWorkedOnCurrentTask;
this.sessionStartSeconds = this.secondsWorkedOnCurrentTask;
this.refreshView(); this.refreshView();
this.updateStatusBar(); this.updateStatusBar();
this.timerInterval = window.setInterval(() => { this.timerInterval = window.setInterval(() => {
@@ -1717,8 +1737,6 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
if (task.actualMinutes !== actualMinutes) { if (task.actualMinutes !== actualMinutes) {
task.actualMinutes = actualMinutes; task.actualMinutes = actualMinutes;
} }
const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds;
this.focusSecondsToday = newFocusSeconds;
} }
this.updateStatusBar(); this.updateStatusBar();
this.updateTimerDisplay(); this.updateTimerDisplay();
@@ -1804,8 +1822,6 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
if (task && !this.isBreakMode) { if (task && !this.isBreakMode) {
this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds; this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds;
task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60); task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60);
const newFocusSeconds = Math.floor((this.data.totalFocusMinutesToday || 0) * 60) + elapsedSeconds;
this.focusSecondsToday = newFocusSeconds;
} }
this.updateStatusBar(); this.updateStatusBar();
this.updateTimerDisplay(); this.updateTimerDisplay();
@@ -1837,6 +1853,7 @@ var ImmersePlugin = class extends import_obsidian4.Plugin {
this.isStopwatchMode = false; this.isStopwatchMode = false;
this.activeTaskId = null; this.activeTaskId = null;
this.secondsWorkedOnCurrentTask = 0; this.secondsWorkedOnCurrentTask = 0;
this.sessionStartSeconds = 0;
this.timerStartTimestamp = 0; this.timerStartTimestamp = 0;
this.pausedTimeRemaining = 0; this.pausedTimeRemaining = 0;
this.updateStatusBar(); this.updateStatusBar();

View File

@@ -46,6 +46,7 @@ export default class ImmersePlugin extends Plugin {
// Focus time tracking (in seconds for accuracy) // Focus time tracking (in seconds for accuracy)
private focusSecondsToday: number = 0; private focusSecondsToday: number = 0;
private secondsWorkedOnCurrentTask: number = 0; private secondsWorkedOnCurrentTask: number = 0;
private sessionStartSeconds: number = 0; // Track seconds at start of current session
// Status bar element // Status bar element
statusBarEl: HTMLElement | null = null; statusBarEl: HTMLElement | null = null;
@@ -54,17 +55,29 @@ export default class ImmersePlugin extends Plugin {
private reminderCheckInterval: number | null = null; private reminderCheckInterval: number | null = null;
private notifiedReminders: Set<string> = new Set(); // Track which reminders have been shown private notifiedReminders: Set<string> = new Set(); // Track which reminders have been shown
// Daily reset check interval
private dailyResetCheckInterval: number | null = null;
async onload() { async onload() {
await this.loadAllData(); await this.loadAllData();
// Check and reset daily stats // Check and reset daily stats
this.checkDailyReset(); 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 // Handle visibility changes to sync timer when app comes back to foreground
document.addEventListener('visibilitychange', () => { document.addEventListener('visibilitychange', () => {
if (!document.hidden && this.isTimerRunning) { if (!document.hidden) {
// Check for day change when app becomes visible
this.checkDailyReset();
if (this.isTimerRunning) {
this.syncTimerFromTimestamp(); this.syncTimerFromTimestamp();
} }
}
}); });
// Register views // Register views
@@ -135,6 +148,12 @@ export default class ImmersePlugin extends Plugin {
onunload() { onunload() {
this.stopTimer(); this.stopTimer();
this.stopReminderSystem(); this.stopReminderSystem();
// Stop daily reset check interval
if (this.dailyResetCheckInterval) {
window.clearInterval(this.dailyResetCheckInterval);
this.dailyResetCheckInterval = null;
}
} }
async loadAllData() { async loadAllData() {
@@ -279,6 +298,12 @@ export default class ImmersePlugin extends Plugin {
this.data.completedToday++; this.data.completedToday++;
this.data.lastActiveDate = new Date().toDateString(); 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 // Archive task for historical reporting
this.archiveCompletedTask(task); this.archiveCompletedTask(task);
@@ -563,6 +588,7 @@ export default class ImmersePlugin extends Plugin {
// Store initial values // Store initial values
const initialSecondsWorked = this.secondsWorkedOnCurrentTask; const initialSecondsWorked = this.secondsWorkedOnCurrentTask;
this.sessionStartSeconds = this.secondsWorkedOnCurrentTask; // Track session start for focus time
let alertShown = false; let alertShown = false;
// Full refresh to show the active task card // Full refresh to show the active task card
@@ -583,10 +609,6 @@ export default class ImmersePlugin extends Plugin {
this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds; this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds;
task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60); 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 // Light update - only timer display, no full refresh
this.updateStatusBar(); this.updateStatusBar();
this.updateTimerDisplay(); this.updateTimerDisplay();
@@ -626,6 +648,7 @@ export default class ImmersePlugin extends Plugin {
// Store the initial seconds worked to calculate delta // Store the initial seconds worked to calculate delta
const initialSecondsWorked = this.secondsWorkedOnCurrentTask; const initialSecondsWorked = this.secondsWorkedOnCurrentTask;
this.sessionStartSeconds = this.secondsWorkedOnCurrentTask; // Track session start for focus time
// Full refresh to show the active task card // Full refresh to show the active task card
this.refreshView(); this.refreshView();
@@ -648,10 +671,6 @@ export default class ImmersePlugin extends Plugin {
if (task.actualMinutes !== actualMinutes) { if (task.actualMinutes !== actualMinutes) {
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 // Light update - only timer display, no full refresh
@@ -782,10 +801,6 @@ export default class ImmersePlugin extends Plugin {
// Update actual time worked // Update actual time worked
this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds; this.secondsWorkedOnCurrentTask = initialSecondsWorked + elapsedSeconds;
task.actualMinutes = Math.floor(this.secondsWorkedOnCurrentTask / 60); 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 // Light update - only timer display
@@ -826,6 +841,7 @@ export default class ImmersePlugin extends Plugin {
this.isStopwatchMode = false; this.isStopwatchMode = false;
this.activeTaskId = null; this.activeTaskId = null;
this.secondsWorkedOnCurrentTask = 0; this.secondsWorkedOnCurrentTask = 0;
this.sessionStartSeconds = 0;
this.timerStartTimestamp = 0; this.timerStartTimestamp = 0;
this.pausedTimeRemaining = 0; this.pausedTimeRemaining = 0;
this.updateStatusBar(); this.updateStatusBar();

View File

@@ -173,7 +173,8 @@ export class ImmerseView extends ItemView {
text: this.plugin.formatTime(this.plugin.currentTimerSeconds) text: this.plugin.formatTime(this.plugin.currentTimerSeconds)
}); });
// Progress bar - store reference for updates // Progress bar - only show in pomodoro/break mode, not stopwatch
if (!this.plugin.isStopwatchMode) {
const progressWrap = activeCard.createEl('div', { cls: 'immerse-progress-wrap' }); const progressWrap = activeCard.createEl('div', { cls: 'immerse-progress-wrap' });
this.progressBarEl = progressWrap.createEl('div', { cls: 'immerse-progress-bar' }); this.progressBarEl = progressWrap.createEl('div', { cls: 'immerse-progress-bar' });
@@ -190,6 +191,7 @@ export class ImmerseView extends ItemView {
this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`; this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`;
if (progressPercent >= 100) this.progressBarEl.addClass('immerse-overtime'); if (progressPercent >= 100) this.progressBarEl.addClass('immerse-overtime');
}
// Time info - store reference for actual time updates // Time info - store reference for actual time updates
if (!this.plugin.isBreakMode) { if (!this.plugin.isBreakMode) {