Add pomodoro, microbreaks, breathing guide, screen dimming, presentation mode, goals, multi-monitor, and activity manager

Major feature release (v0.1.3) adding 15 new features to the break timer:

Backend (Rust):
- Pomodoro cycle tracking with configurable short/long break pattern
- Microbreak scheduling (20-20-20 rule) with independent timer
- Screen dimming events with gradual opacity progression
- Presentation mode detection (fullscreen app deferral)
- Smart break detection (natural idle breaks counting toward goals)
- Daily goal tracking and streak milestone events
- Multi-monitor break overlay support
- Working hours enforcement with per-day schedules
- Weekly summary and natural break stats queries
- Config expanded to 71 validated fields

Frontend (Svelte):
- 6 new components: BreathingGuide, ActivityManager, BreakOverlay,
  MicrobreakOverlay, DimOverlay, Celebration
- Breathing guide with 5 patterns and animated pulsing halo
- Activity manager with favorites, custom activities, momentum scroll
- Confetti celebrations on milestones and goal completion
- Dashboard indicators (pomodoro/microbreak/goal) moved inside ring
- Settings reorganized into 18 logical cards
- Breathing pattern selector redesigned with timing descriptions
- Break activities expanded from 40 to 71 curated exercises
- Sound presets expanded from 4 to 8
- Stats view with weekly summary and natural break tracking

Also: version bump to 0.1.3, CHANGELOG, README and CLAUDE.md updates
This commit is contained in:
Your Name
2026-02-07 15:11:44 +02:00
parent 460bf2c613
commit a339dd1bb3
28 changed files with 3792 additions and 448 deletions
+51 -6
View File
@@ -106,10 +106,55 @@ export function getCategoryLabel(cat: BreakActivity["category"]): string {
return categoryLabels[cat];
}
/** Pick a random activity, optionally excluding a previous one */
export function pickRandomActivity(exclude?: BreakActivity): BreakActivity {
const pool = exclude
? breakActivities.filter((a) => a.text !== exclude.text)
: breakActivities;
return pool[Math.floor(Math.random() * pool.length)];
/** Pick a random activity, optionally excluding a previous one.
* When config is provided, respects disabled/favorite/custom activity settings. */
export function pickRandomActivity(
exclude?: BreakActivity,
config?: {
disabled_builtin_activities?: string[];
favorite_builtin_activities?: string[];
custom_activities?: Array<{ category: string; text: string; is_favorite: boolean; enabled: boolean }>;
favorite_weight?: number;
},
): BreakActivity {
const disabled = new Set(config?.disabled_builtin_activities ?? []);
const favorites = new Set(config?.favorite_builtin_activities ?? []);
const weight = config?.favorite_weight ?? 3;
// Build pool: enabled builtins + enabled customs
let pool: BreakActivity[] = breakActivities.filter((a) => !disabled.has(a.text));
// Add enabled custom activities
if (config?.custom_activities) {
for (const ca of config.custom_activities) {
if (ca.enabled) {
const cat = (["eyes", "stretch", "breathing", "movement"].includes(ca.category)
? ca.category
: "movement") as BreakActivity["category"];
pool.push({ category: cat, text: ca.text });
}
}
}
// Exclude previous
if (exclude) {
pool = pool.filter((a) => a.text !== exclude.text);
}
if (pool.length === 0) {
return exclude ?? breakActivities[0];
}
// Build weighted pool: favorites appear `weight` times
const weighted: BreakActivity[] = [];
for (const a of pool) {
const isFav = favorites.has(a.text) ||
(config?.custom_activities?.some((c) => c.text === a.text && c.is_favorite) ?? false);
const count = isFav ? weight : 1;
for (let i = 0; i < count; i++) {
weighted.push(a);
}
}
return weighted[Math.floor(Math.random() * weighted.length)];
}