Fix snooze budget tracking and enhance break management - #2
Merged
Conversation
A 3-snooze budget could be stretched to 5 on a long break. `_snoozesLeft` was a single global counter, but micro and long breaks run independent, interleavable cycles. Snoozing a long break pushes it past `_microDue + _mergeWindow`, so `_nextKind()` flips to the already-due micro break, which fires in the gap. Completing that micro cycle refilled the shared counter, handing the still-pending long break a fresh budget on top of what it had already spent. Two changes: - `_snoozesLeft` becomes `Map<BreakKind, int>`, decremented for the active kind and refilled only by that kind's own `_finishCycle`. A long break still refills the micro budget, because it genuinely restarts that cycle. - `_resetDue` no longer refills the budget as a side effect of rescheduling a timer. Budget changes now happen only at real cycle boundaries, so the three resume paths (pause, work hours, config change) refill explicitly. Adds a regression test reproducing the exact interleave, plus a guard that each kind carries its own budget. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A break overlay in the middle of a film is the one interruption users will uninstall over. Breaks now hold while something on screen must not be interrupted, and resume when it ends. Detection is built on **idle inhibitors**, not audio: video players, browsers in fullscreen video, presentation tools and games all take one so the screen never blanks on them, while music players do not. Keying off raw audio would let a background playlist suppress breaks all day. Two sources are unioned — gnome-session (where ScreenSaver.Inhibit lands on GNOME, and which also names the responsible app) and logind (desktop-independent, so KDE and wlroots sessions degrade to something useful). Both verified live against a real inhibitor. This is a *pause*, not a deferral. The busy/DND path caps at deferCap; a film runs far longer, and a cap would guarantee the interruption the feature exists to prevent. Two deliberate semantics: - Timers are not reset on resume. Two hours of fullscreen video is still two hours of screen time, so the interval keeps running down during playback — a film must not buy the user a fresh 20 minutes. An overdue break arrives through the existing short re-warn rather than seizing the screen the instant the credits roll. - A break already on screen is left to finish. Abandoning it would log a BreakEscaped the user never earned. Also generalizes the TTL/in-flight caching out of ContextSampler into PolledValue<T> so both samplers share it, and gives Paused a reason (user / workHours / media) so the dashboard can say what it is waiting on. Behind a Settings toggle, default on. 8 new engine tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The tray icon now carries an expression: colour AND face change with how well breaks are being taken, so the app can praise, nudge, or warn without ever interrupting. Colour is never the only signal. Every mood changes the eyes or the mouth too, so the indicator still reads under a colour vision deficiency and in the monochrome tray themes some desktops apply, and the SNI ToolTip says the mood in words — a red icon with no explanation is just anxiety. The mark's two pause bars were already readable as eyes, which is what makes this possible without abandoning the brand. Faces are drawn at runtime with PictureRecorder rather than shipped as PNGs (seven moods x two sizes would be fourteen assets to keep in sync), which also lets the pulse render in-between frames. The two bundled tray PNGs and their loader are gone. Structure: - core/mood/ — pure rules over a *rolling window* of recent break responses, not day totals: judging on totals means one bad morning colours the icon red until midnight and a good afternoon can never earn its way back. - MoodTracker adds asymmetric hysteresis — praise quickly, scold slowly. Improving is adopted at once; worsening must persist across three samples, so one skipped break never turns the icon red on its own. Without this the icon flickers as breaks roll through the window, and flicker in the corner of the eye is exactly what this app exists not to be. - MoodService decides what the app feels; TrayMoodPresenter decides how it looks. Only the second half needs a rendering pipeline. - A snooze is not treated as a miss until it becomes a habit — it is a deferral the app itself offers, and punishing users for using a feature as intended would be perverse. The window is persisted so a restart is not a way to clear a warning. Animation is a four-frame acknowledgement on change, not an idle loop: every frame is a D-Bus signal plus a host re-read. Behind a Settings toggle. 17 mood tests plus a rendering guard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds twelve exercises (12 -> 24) spanning soft, medium and heavy, including the movement-based ones: forward folds, calf raises, arm circles, stairs, squats, lunges, marching and jumping jacks. Every exercise now carries an ExerciseIntensity, with a user-set ceiling in Settings (default medium). This is a physical constraint, not a preference: "stand up and do ten squats" is good advice at home and impossible in an open-plan office, and an overlay that suggests the impossible just teaches people to dismiss overlays. When opt-outs and the ceiling together leave nothing pickable the opt-outs yield first — showing an exercise someone muted is a smaller broken promise than showing one they cannot do where they are sitting. Ten new illustrations, built by extending the shared _figure primitive with fold/twist/legBend/kneeLift/stance/armsUp parameters rather than writing new painters, so a squat bends visible knees instead of merely shrinking. Two bugs were caught by rendering the frames and looking at them: jumping-jack arms swept past vertical and drew straight through the head, and rooting the arms at the spine rather than the shoulder width grazed it even after that. Also collapses the art->painter switch from twelve near-identical blocks to one line per art while keeping compile-time exhaustiveness; it would have been twenty-two blocks otherwise. Exercise ids are append-only (the exercise log references them), so existing history is untouched. 4 new picker tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Track snooze budgets separately for different break types, ensuring accurate management during interleaved cycles. Introduce a pause feature for breaks during media playback, preventing interruptions. Update the tray icon to reflect user engagement and mood, and expand exercise options with intensity tiers. Include regression tests and documentation updates for new features.