feat: add coherence preset with optional 4s post-inhale hold#5
Conversation
The new `coherence` preset (4 in / 4 hold / 4 out, 5 bpm, 10 min) is
the only breathing pattern that includes a retention phase. The hold
is opt-in via `--hold` on the coherence preset only, hard-capped at
4 seconds, and rejected on every other preset and on custom ratios.
Long holds cross into Valsalva territory and have no clinical
evidence base in HFrEF.
The render loop now transits INHALE → HOLD → EXHALE (3 phases).
HOLD is rendered with a static full bar and a single terminal-bell
cue. Pause/resume from any phase snaps back to INHALE; interrupted
phases are not counted toward breaths.
This relaxes the original C1 safety constraint; the new policy
("hold is a property of the coherence preset, not a free
parameter") is documented in dev/breathe-cli-spec.md v2.0 and
CLAUDE.md.
Bumps VERSION and pyproject.toml to 2.0.
|
@SHJordan Thank you for sharing your work. I need to think about this PR for a moment as it is a significant modification of the original design philosophy and purpose. I was thinking about adding different breathing modes — like box breathing etc. but I need to do more research. Also, I'm not sure about the interface. I'm partial towards box breathing notation like If we go the route of relaxing C1 or keeping it true only in some modes, I think the more universal 4-part notation makes more sense than a special flag. What do you think? |
|
@SHJordan you have inspired me to draft a spec to extend the app to other breathing patterns. If implemented, would it consume your use case? Please comment here or raise issues to specific parts of the spec. |
This PR introduces an opt-in short-hold pattern via a new
coherencepreset, the only place in the app where a retention phase is supported. The hold is hard-capped at 4 seconds; longer holds cross into Valsalva territory and have no clinical evidence base in HFrEF.What this changes
coherence: 4 s in / 4 s hold / 4 s out, 10 min, 5 bpm (50 cycles per session). Opt-in only — not part of the time-of-day auto-select.--hold SECS(0–4): only valid with--preset coherence. Rejected on every other preset, on custom--ratio, and on bare use, with explicit safety messages.INHALE → HOLD → EXHALEinstead ofINHALE → EXHALE.HOLDis rendered with a static full bar and a single terminal-bell cue.INHALEon resume; the interrupted phase is not counted toward breaths.Configgainshold_s: int.ratio_strreturns the 3-number form (4-4-4) when hold > 0.print_presetsformats the ratio column as in-hold-ex and shows bpm from the full cycle.parse_rationo longer rejects 3-number ratios with a safety message — it just rejects them as a wrong format, since the hold is now passed via--hold.Safety policy (intentional relaxation of C1)
The original C1 constraint ("no breath retention") is rewritten. The new policy is: hold is a property of the
coherencepreset, not a free parameter. The 4 s cap matches the upper bound of short-hold protocols in the HFrEF literature without crossing into Valsalva mechanics. Documented in:CLAUDE.md— safety constraintsdev/breathe-cli-spec.mdv2.0 — §2 C1 + §3 manual tests 26–35Tests
44 → 66 unit tests, all passing. The 22 new ones cover:
TestCoherencePreset(7): definition, cycle = 12 s, bpm = 5, duration rounding, ratio_str, hold-at-max invariants.TestHoldValidation(8): drivesmain()end-to-end and asserts each rejection path exits non-zero.TestRemainingTime/TestPresets/TestBreathingBase/TestDurationRounding/TestCompletionupdated to threadhold_sthroughcycle_sandphase_dur.Verification
breathe --preset coherencesmoke test:coherence · 4-4-4 · 10:00 ...header, INHALE/HOLD/EXHALE labels cycle as expected./tmp/opencode/test_pause_resume.py): 5/5 runs pass. Pressingspaceduring INHALE or HOLD freezes the bar and shows‖; pressingspaceagain resumes with the INHALE label.tail ~/.breathe_log.csvshows correct rows withratio=4-4-4andbreaths=Nmatching 12 s/cycle.Files
breathe.py+84 / −30 (now 835 lines; pre-existing 700-line hard cap is exceeded; refactor deferred to a separate PR).test_breathe.py+185 / −48dev/breathe-cli-spec.md+47 / −8README.md+13 / −13CLAUDE.md+4 / −5pyproject.toml+1 / −1Bumps
VERSIONandpyproject.tomlto 2.0.