Skip to content

feat: add coherence preset with optional 4s post-inhale hold#5

Open
SHJordan wants to merge 1 commit into
marekkowalczyk:masterfrom
SHJordan:feat/coherence-preset
Open

feat: add coherence preset with optional 4s post-inhale hold#5
SHJordan wants to merge 1 commit into
marekkowalczyk:masterfrom
SHJordan:feat/coherence-preset

Conversation

@SHJordan

@SHJordan SHJordan commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

This PR introduces an opt-in short-hold pattern via a new coherence preset, 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

  • New preset 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.
  • New CLI flag --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.
  • 3-phase state machine: the render loop now transits INHALE → HOLD → EXHALE instead of INHALE → EXHALE. HOLD is rendered with a static full bar and a single terminal-bell cue.
  • Pause/resume from any phase snaps back to INHALE on resume; the interrupted phase is not counted toward breaths.
  • Config gains hold_s: int. ratio_str returns the 3-number form (4-4-4) when hold > 0. print_presets formats the ratio column as in-hold-ex and shows bpm from the full cycle.
  • parse_ratio no 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 coherence preset, 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 constraints
  • dev/breathe-cli-spec.md v2.0 — §2 C1 + §3 manual tests 26–35

Tests

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): drives main() end-to-end and asserts each rejection path exits non-zero.
  • Helpers in TestRemainingTime / TestPresets / TestBreathingBase / TestDurationRounding / TestCompletion updated to thread hold_s through cycle_s and phase_dur.

Verification

  • breathe --preset coherence smoke test: coherence · 4-4-4 · 10:00 ... header, INHALE/HOLD/EXHALE labels cycle as expected.
  • Pause/resume via pty driver (/tmp/opencode/test_pause_resume.py): 5/5 runs pass. Pressing space during INHALE or HOLD freezes the bar and shows ; pressing space again resumes with the INHALE label.
  • tail ~/.breathe_log.csv shows correct rows with ratio=4-4-4 and breaths=N matching 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 / −48
  • dev/breathe-cli-spec.md +47 / −8
  • README.md +13 / −13
  • CLAUDE.md +4 / −5
  • pyproject.toml +1 / −1

Bumps VERSION and pyproject.toml to 2.0.

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.
@marekkowalczyk

Copy link
Copy Markdown
Owner

@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 4-4-4-4.

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?

@marekkowalczyk

marekkowalczyk commented Jun 7, 2026

Copy link
Copy Markdown
Owner

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants