4 Commits

Author SHA1 Message Date
fe42d42500 Release v1.1.5: Bug Fixes & Report Enhancements
This release addresses multiple critical bugs and enhances the reporting experience.

## Bug Fixes
- Fixed: Actual time tracking now persists correctly across Pomodoro breaks
- Fixed: Timer actual time no longer resets to 0 when resuming work after breaks
- Fixed: Stopwatch mode timer bar now displays correctly
- Fixed: Daily stats counter no longer increments when canceling/stopping tasks
- Fixed: Report quick select buttons now update date fields automatically
- Fixed: Today's focus time calculation corrected (previous day restart issue resolved)

## Enhancements
- Added visual glow animation to active quick filter buttons in Reports
- Quick filter buttons now show active state with pulsing effect
- Date inputs update automatically when clicking quick filter options
- Improved UX with visual feedback for selected time ranges

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 20:06:56 +01:00
087d22f1fd 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
2025-11-28 18:40:08 +01:00
a74fd244b0 Updated README.md 2025-11-26 09:47:32 +01:00
7fbb789fdd Updated README.md 2025-11-26 09:46:01 +01:00
8 changed files with 176 additions and 64 deletions

View File

@@ -4,7 +4,7 @@ A powerful task management and focus timer plugin for [Obsidian](https://obsidia
![Immerse Banner](https://img.shields.io/badge/Obsidian-Plugin-7c3aed?style=for-the-badge&logo=obsidian&logoColor=white)
![License](https://img.shields.io/badge/License-MIT-green?style=for-the-badge)
![Version](https://img.shields.io/badge/Version-1.1.4-blue?style=for-the-badge)
![Version](https://img.shields.io/badge/Version-1.1.5-blue?style=for-the-badge)
## 🎯 Overview
@@ -82,10 +82,6 @@ All settings available in Settings → Immerse:
- Daily note integration (auto-log completed tasks)
- Custom lists with names, emojis, and colors
## 🙏 Credits
Heavily inspired by [Blitzit](https://www.blitzit.app/) - a fantastic productivity app. Immerse brings that experience into Obsidian.
## 🤝 Contributing
Contributions welcome! Fork, create a feature branch, and open a PR.
@@ -107,5 +103,7 @@ MIT License - see [LICENSE](LICENSE) file.
**Links:** [Repository](https://git.cribdev.com/crib/immerse) • [Issues](https://git.cribdev.com/crib/immerse/issues) • [Blitzit](https://www.blitzit.app/)
<p align="center">
<em>Made with ❤️ for the Obsidian community • Inspired by Blitzit ⚡</em>
Made with ❤️ for the Obsidian community
<br><br>
<em>✨ Coded with assistance from <a href="https://claude.ai">Claude.ai</a></em>
</p>

61
main.js
View File

@@ -763,6 +763,7 @@ var ImmerseView = class extends import_obsidian2.ItemView {
cls: "immerse-timer-time",
text: this.plugin.formatTime(this.plugin.currentTimerSeconds)
});
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;
@@ -776,6 +777,7 @@ var ImmerseView = class extends import_obsidian2.ItemView {
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)}` });
@@ -980,6 +982,10 @@ var ReportView = class extends import_obsidian3.ItemView {
constructor(leaf, plugin) {
super(leaf);
this.selectedListIds = [];
this.activeQuickFilter = "Last 7 days";
// Track active filter
this.startDateInput = null;
this.endDateInput = null;
this.plugin = plugin;
const today = new Date();
this.endDate = today.toISOString().split("T")[0];
@@ -1021,12 +1027,20 @@ var ReportView = class extends import_obsidian3.ItemView {
const filtersSection = container.createEl("div", { cls: "immerse-report-filters" });
const dateRow = filtersSection.createEl("div", { cls: "immerse-report-filter-row" });
new import_obsidian3.Setting(dateRow).setName("Start Date").addText((text) => {
text.setValue(this.startDate).onChange((value) => this.startDate = value);
text.setValue(this.startDate).onChange((value) => {
this.startDate = value;
this.activeQuickFilter = null;
});
text.inputEl.type = "date";
this.startDateInput = text.inputEl;
});
new import_obsidian3.Setting(dateRow).setName("End Date").addText((text) => {
text.setValue(this.endDate).onChange((value) => this.endDate = value);
text.setValue(this.endDate).onChange((value) => {
this.endDate = value;
this.activeQuickFilter = null;
});
text.inputEl.type = "date";
this.endDateInput = text.inputEl;
});
const quickFilters = filtersSection.createEl("div", { cls: "immerse-report-quick-filters" });
quickFilters.createEl("span", { text: "Quick select: ", cls: "immerse-filter-label" });
@@ -1041,12 +1055,24 @@ var ReportView = class extends import_obsidian3.ItemView {
text: filter.label,
cls: "immerse-quick-filter-btn"
});
if (filter.label === this.activeQuickFilter) {
btn.addClass("active");
}
btn.addEventListener("click", () => {
const today = new Date();
this.endDate = today.toISOString().split("T")[0];
const startDate = new Date(today);
startDate.setDate(startDate.getDate() - filter.days);
this.startDate = startDate.toISOString().split("T")[0];
if (this.startDateInput)
this.startDateInput.value = this.startDate;
if (this.endDateInput)
this.endDateInput.value = this.endDate;
this.activeQuickFilter = filter.label;
quickFilters.querySelectorAll(".immerse-quick-filter-btn").forEach((b) => {
b.removeClass("active");
});
btn.addClass("active");
this.renderReport(container);
});
});
@@ -1264,20 +1290,30 @@ 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;
}
async onload() {
await this.loadAllData();
this.checkDailyReset();
this.dailyResetCheckInterval = window.setInterval(() => {
this.checkDailyReset();
}, 6e4);
document.addEventListener("visibilitychange", () => {
if (!document.hidden && this.isTimerRunning) {
if (!document.hidden) {
this.checkDailyReset();
if (this.isTimerRunning) {
this.syncTimerFromTimestamp();
}
}
});
this.registerView(
VIEW_TYPE_IMMERSE,
@@ -1329,6 +1365,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 +1480,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 +1709,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 +1720,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 +1747,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 +1761,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 +1846,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 +1877,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();

View File

@@ -1,7 +1,7 @@
{
"id": "immerse",
"name": "Immerse",
"version": "1.1.4",
"version": "1.1.5",
"minAppVersion": "0.15.0",
"description": "A Blitzit-inspired task management and focus timer plugin. Plan your day, track time with Pomodoro technique, and crush your tasks with satisfying checkoffs.",
"author": "Crib",

View File

@@ -1,6 +1,6 @@
{
"name": "immerse",
"version": "1.1.4",
"version": "1.1.5",
"description": "A Blitzit-inspired task management and focus timer plugin for Obsidian",
"main": "main.js",
"scripts": {

View File

@@ -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,17 +55,29 @@ export default class ImmersePlugin extends Plugin {
private reminderCheckInterval: number | null = null;
private notifiedReminders: Set<string> = 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) {
if (!document.hidden) {
// Check for day change when app becomes visible
this.checkDailyReset();
if (this.isTimerRunning) {
this.syncTimerFromTimestamp();
}
}
});
// Register views
@@ -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();

View File

@@ -14,6 +14,9 @@ export class ReportView extends ItemView {
startDate: string;
endDate: string;
selectedListIds: string[] = [];
activeQuickFilter: string | null = 'Last 7 days'; // Track active filter
startDateInput: HTMLInputElement | null = null;
endDateInput: HTMLInputElement | null = null;
constructor(leaf: WorkspaceLeaf, plugin: ImmersePlugin) {
super(leaf);
@@ -83,16 +86,24 @@ export class ReportView extends ItemView {
.setName('Start Date')
.addText(text => {
text.setValue(this.startDate)
.onChange(value => this.startDate = value);
.onChange(value => {
this.startDate = value;
this.activeQuickFilter = null; // Clear active filter when manually changing dates
});
text.inputEl.type = 'date';
this.startDateInput = text.inputEl; // Store reference
});
new Setting(dateRow)
.setName('End Date')
.addText(text => {
text.setValue(this.endDate)
.onChange(value => this.endDate = value);
.onChange(value => {
this.endDate = value;
this.activeQuickFilter = null; // Clear active filter when manually changing dates
});
text.inputEl.type = 'date';
this.endDateInput = text.inputEl; // Store reference
});
// Quick filters
@@ -111,12 +122,33 @@ export class ReportView extends ItemView {
text: filter.label,
cls: 'immerse-quick-filter-btn'
});
// Set active class if this is the default active filter
if (filter.label === this.activeQuickFilter) {
btn.addClass('active');
}
btn.addEventListener('click', () => {
// Update dates
const today = new Date();
this.endDate = today.toISOString().split('T')[0];
const startDate = new Date(today);
startDate.setDate(startDate.getDate() - filter.days);
this.startDate = startDate.toISOString().split('T')[0];
// Update date inputs
if (this.startDateInput) this.startDateInput.value = this.startDate;
if (this.endDateInput) this.endDateInput.value = this.endDate;
// Update active filter
this.activeQuickFilter = filter.label;
// Update button states
quickFilters.querySelectorAll('.immerse-quick-filter-btn').forEach(b => {
b.removeClass('active');
});
btn.addClass('active');
this.renderReport(container);
});
});

View File

@@ -173,7 +173,8 @@ export class ImmerseView extends ItemView {
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' });
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)}%`;
if (progressPercent >= 100) this.progressBarEl.addClass('immerse-overtime');
}
// Time info - store reference for actual time updates
if (!this.plugin.isBreakMode) {

View File

@@ -757,15 +757,38 @@
border: 1px solid var(--ft-border);
color: var(--ft-text);
cursor: pointer;
transition: all 0.2s ease;
transition: all 0.3s ease;
font-size: 13px;
white-space: nowrap;
position: relative;
}
.immerse-quick-filter-btn:hover {
background: var(--ft-primary);
color: white;
border-color: var(--ft-primary);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
}
.immerse-quick-filter-btn.active {
background: var(--ft-primary);
color: white;
border-color: var(--ft-primary);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2),
0 0 20px rgba(99, 102, 241, 0.4);
animation: pulse-glow 2s ease-in-out infinite;
}
@keyframes pulse-glow {
0%, 100% {
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2),
0 0 20px rgba(99, 102, 241, 0.4);
}
50% {
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.3),
0 0 25px rgba(99, 102, 241, 0.6);
}
}
.immerse-report-generate-btn {