Domestique v2.1.0
v2.1.0 — Block periodization, B/C races, a smarter plan, and Windows/Mac reliability (2026-06-19)
The biggest release yet — and the first upload since v2.0.7. It bundles a large
Windows-feedback reliability round, a training plan that builds from your real
fitness, and a new training-science layer: an opt-in block-periodization engine,
support for B and C races with their own mini-tapers, a workout library that
stops lying about what it contains, long base rides for gran-fondo riders, a
trustworthy DFA α1 readout, an outdoor-ready workout export, and the intervals.icu
TLS fix — now on both Windows and macOS.
Everything new here is additive or opt-in — if you change nothing, your plans
look exactly as they did. One requested item (volume-scaled hard-day count) is
not in this release; see "Still on the list."
Block periodization (opt-in, new)
- Focus your build on one quality at a time. A new "Block periodization"
checkbox on the plan form reorganizes your build/peak phases into ~3–4 week
blocks, each concentrating one quality — a VO2max block first, then a
threshold block toward your event — instead of mixing every hard type every
week. Each block keeps one complementary session so you don't lose the other
qualities entirely (the Issurin "accentuated load" model). - Grounded in the evidence. The block template is built from a PubMed screen
(Rønnestad's block-VO2 cycling RCTs, Issurin, a 2019 meta-analysis — every
citation verified). The honest version: block periodization shows a VO2max/power
edge in trained cyclists but the evidence is mixed and not proven for
time-crunched amateurs — so it's off by default, offered for those who want
to try it, not forced on anyone. - It sticks. Your block choice is saved with the plan and survives every
automatic re-fit/recalc, so an adapted plan stays a block plan.
| Default plan (unchanged) | With "Block periodization" on |
|---|---|
| Every build week mixes VO2 + threshold + sweet-spot + over-under | Build1 = a VO2 block, build2/peak = a threshold block |
| Variety every week | One concentrated focus per block + 1 complementary session |
B and C races (opt-in, new)
- Plan around your whole season, not just one event. Alongside your A goal you
can now add B and C races — the plan form takes a repeatable list of events,
each with a priority. Add a local crit in the middle of your fondo build and the
plan accounts for it instead of ignoring it. - Each gets a right-sized mini-taper. A B race gets a short 2-day freshen
(trim volume, keep intensity — a pre-race opener is fine, not a smashfest); a
C race gets a single easy/opener day and is otherwise ridden through. Neither
is a full taper — your A event still owns the real peak. - No double-deloads. A B/C event that falls inside your A taper, or on an
existing unload week, is left alone — no stacking easy on easy. - Grounded in the evidence. The mini-taper window and magnitude come from a
PubMed taper screen (Mujika & Padilla, Bosquet's meta-analysis, Rønnestad's
between-races peaking — every citation verified): maintain intensity, cut volume,
keep the window short. - See them on the calendar. Event days are color-coded by priority (A / B / C).
A plan with only an A event behaves exactly as before.
Your workout library got honest
- Workouts stop hiding hard sets. The library labels a workout by its dominant
zone, so an "Endurance — Z2" file could secretly contain a VO2 set. Each workout
now carries an objective-coherence check, and incoherent files get the hidden
work surfaced in their name — e.g. "Endurance 120min — Z2 +VO2 set" — so what
you see is what you'll ride. (No files were changed; only the labels got honest.) - Long pure-Z2 base rides for gran-fondo riders. Added 24 clean steady
endurance rides from 195 up to 240 minutes (the library capped at 180 before), so
long-base weeks have proper options. Every new file passed the
classify-before-write gate. Library is now 4,220 workouts.
A DFA α1 readout you can trust
- Hard-interval drops are no longer hidden. α1 genuinely collapses below 0.30
during hard intervals (a Gimenez set), but the app used to discard those readings
as "unphysiological," so you saw a gap instead of a low value. Per-window α1 can
now read down to 0.20 (the whole-ride average is still sanity-floored), so a hard
effort shows up as a hard effort. - A confidence flag. Each DFA result now reports high / medium / low
confidence from the artifact rate, the window yield, and the activity's sport —
so a noisy reading is labelled, not silently trusted. - Running is flagged, not trusted blindly. Optical/RR jitter on runs produced
unreliable α1; running activities are now capped at "medium" confidence (the
feature stays available for runners — it's labelled, not disabled).
Outdoor-ready workouts
- Ride a structured session as a real outdoor ride. An opt-in "Outdoor
variant" on the Library download wraps any workout with a flat transit warm-up
to the climb and an easy spin home. Those extra minutes are off-plan (easy
riding that is not counted against your planned weekly load), so your plan's
numbers stay clean.
Under the hood
- The block engine is gated behind the toggle with a "default-off parity" guard, so
the default plan is byte-for-byte unchanged; fixed a latent bug where a
pyramidal/threshold distribution choice reverted to polarized on a recalc. - Focused regression tests for every feature above; the planner's known
non-deterministic coverage tests are unchanged.
Windows reliability + a plan that respects your real training
A large round of fixes driven by detailed Windows-user feedback. Three buckets:
(1) Windows stopped fighting you — your profile, API key, and the app window
now survive a restart. (2) The training plan now builds from your actual
fitness and a sane weekly load instead of blindly filling every available hour,
adds real rest, keeps hard intervals away from race day, and lets you choose your
intensity distribution instead of forcing polarized. (3) eFTP can no longer
silently rewrite your FTP and zones, the power curve self-heals, and a couple of
data/scoring inconsistencies are gone.
Windows: your settings actually persist now
- Your profile no longer resets on reopen. Weight, FTP, zones, and athlete
details stuck at defaults (e.g. weight snapping to 70 kg, FTP to 200 W) every
time you reopened the app, and saves to the profile silently evaporated. Root
cause: the profile registry was being written empty before the first-run
migration could create your default profile, so the app booted with no active
profile and fell back to built-in defaults. Fixed — your profile is created and
loaded correctly, and edits persist. - You can change your API key / athlete ID and have it stick. Same root cause
— with no active profile, credential saves had nowhere to land. Now they save to
the active profile and survive a restart. - Names with accents no longer break saving. A profile name like "Raphaël"
produced an "invalid profile id" / "Save failed: 400" because the accented
character slipped past the ID validator. Names are now folded to a safe ASCII id
(e.g.raphael), so any name saves cleanly. - The app reopens after you close it. On Windows the close button left the
background server running and the port bound, so relaunching just popped a blank
browser tab (and you had to kill it from Task Manager). The window-close path now
fully shuts the server down and exits, so the next launch starts fresh.
| Before (Windows) | After |
|---|---|
| Reopen → weight 70 / FTP 200, edits lost | Profile + edits persist across restarts |
| API key won't save / "invalid profile id" | Credentials and accented names save cleanly |
| Close → won't reopen, kill via Task Manager | Clean shutdown; relaunch works |
| "ICU rejected credentials: ICUNetworkError" | TLS certificates bundled — ICU connects |
Note: the three Windows-specific fixes (clean exit, certificate bundling, port
release) are verified in code but need confirming on an actual Windows build.
Connecting to intervals.icu (TLS) — Windows and macOS
- "Saved, but ICU rejected the new credentials: failed: ICUNetworkError" is
fixed. The frozen Windows build and the notarized macOS app shipped without a
certificate-authority store, so every HTTPS call to intervals.icu failed
verification — the "ICUNetworkError" reported on Windows, and the same failure on
the Mac mini / MacBook Air. The app now bundles thecertifiCA store and points
both platforms at it, so credential checks and syncs succeed. (This is the
recurring issue reported on GitHub.)
Your training plan got a lot smarter
- Weekly volume is based on training load, not the sum of your free time.
Before, the plan put one workout on every available day and stretched each to
your per-day time limit — if you said you could train 24 hours a week, it
scheduled ~24 hours, which is how you'd get hurt. Now the week is capped by a
load-based ceiling: the lower of your target fitness (CTL) and your recent
6-week average weekly TSS × 1.3 (a standard safe ramp). Your daily availability
is now just a per-session ceiling, not a target to fill. Example: a rider
averaging ~400 TSS/week now gets ~540 TSS (~10 h), not 24.5 h. This holds
whether your rides live on intervals.icu or as local files: if you have no
recent ride history yet (fresh install / ICU-only), the ceiling anchors on your
current fitness (CTL × 7) instead of falling back to the old availability cap. - The plan starts from your actual fitness. It used to begin every plan from a
hardcoded "post-winter" baseline, ignoring the racing and training you'd just
done. It now reads your real current CTL (from intervals.icu, or computed from
your local ride history) so the ramp starts where you actually are. - Real rest weeks and rest days. With sane volume, the planner's unload weeks
(every 4th week, lighter load) are visible again, and excess easy days are
converted to genuine rest days — so a normal week gets at least one day off
instead of seven days of training. (The "no recovery days" complaint was a
side-effect of the over-scheduling above.) - No VO2max the day before your A event. The taper kept prescribing hard
intervals right up to race day. Now any hard session within the final 2 days
before your target event is demoted to a short, easy opener. A pre-race sharpener
is still allowed a few days out — just not a smashfest on the eve. - You choose your intensity distribution. Polarized was forced on every plan.
You can now pick Polarized (default — mostly easy + a little very hard),
Pyramidal (more threshold work), or Threshold / Sweet-spot from the plan
form. Switching models changes only the kind of hard work (where your intensity
minutes go), not the total load, hard-session count, or easy volume — and your
choice is remembered and respected when the plan auto-adjusts.
| Before (plan) | After |
|---|---|
| Volume = sum of your available hours (up to ~24 h/wk) | Volume capped by real load (target CTL / recent TSS × 1.3) |
| Every plan starts from a fixed post-winter baseline | Starts from your actual current CTL |
| Rarely a rest day; hard to see unload weeks | ≥1 rest day per normal week; unload weeks visible |
| VO2max intervals on race eve | Final 2 days before the event are easy openers |
| Polarized forced | Choose polarized / pyramidal / threshold |
The plan stays correct when it auto-adjusts
Domestique re-optimizes your plan after you miss a session or sync new rides. Two
fixes make those automatic adjustments trustworthy:
- Your event is never forgotten. When the plan rebuilt or rebalanced itself it
used to lose your event date and details — which silently disabled the race-day
protections below. Your goal event now survives every auto-adjustment. - No hard session sneaks back onto race week. The "no VO2max before your event"
rule previously applied only when you first generated the plan; a later
auto-adjustment could quietly put intensity back on the final days. The guard now
re-applies on every rebuild, reforecast, and missed-session re-fit — so once your
taper is set, it stays a taper.
FTP / eFTP
- eFTP can no longer silently rewrite your FTP and all your zones. After 7
days of sustained upward eFTP drift the app used to overwrite your FTP
automatically — "applied it without asking", as the code itself put it — which
cascaded into every power zone. Many riders find intervals.icu's eFTP unreliable,
so this is now off by default. The drift is still detected and shown (with a
banner and a manual "Accept" button), so you decide whether to apply it. If you
want the old automatic behavior, opt in witheftp_auto_apply: truein your
user preferences.
Power & data
- The power curve self-heals when efforts are missing. Your peak power could
read far too low (e.g. Pmax 693 W vs 1229 W on intervals.icu, with CP/W′ showing
"—") because the backfill that pulls best-effort data only ran when the curve was
completely empty — a few stale edge rides were enough to block it forever. It now
triggers whenever in-window rides are missing their effort data, so the curve
fills in correctly. (Full effect needs a working ICU connection — see TLS above.)
Workout library
- Removed an impossible "45 min in Z7" workout. Two
anaerobic_rampfiles
were corrupt staircases ramping to ~600% FTP. They slipped past the
dangerous-workout screen because it treated anything with "ramp" in the name as a
legitimate ramp test. The bad files are gone and the exemption is tightened to
genuine FTP/ramp tests, so nothing physically impossible can be scheduled. - Workout scores are consistent everywhere. The workout library and the
/api/workoutsview computed a workout's difficulty score from slightly
different zone math for ramp segments, so the same file could score 5 in one
place and 6 in another. Both now use identical ramp-aware zone accounting.
Under the hood
- Added focused regression tests for every fix above (profiles, TLS, app relaunch,
power-curve backfill, impossible-workout guard, volume ceiling, rest weeks, taper
eve, eFTP opt-in, distribution choice, score consistency). - Fixed a wellness test that depended on the developer's local data instead of
isolating its fixture (no app behavior change — the TSB calculation was already
correct).
Still on the list (not in this release)
All reported bugs are fixed, and most of the original feedback (block
periodization, B/C races, honest zone-mixing library, outdoor realism, DFA α1
reliability, more variety) shipped above. Two items remain deliberately deferred:
- Volume-scaled hard-day count — capping hard sessions on low-volume weeks so
they aren't intensity-dominated. Built and tested, but at realistic event volumes
it conflicts with the planner's hard-type coverage rules: a typical build week's
load only supports ~2 hard sessions, while the coverage rules want 3. Doing it
right needs those rules made volume-aware (or the cap scoped to only genuinely
low-volume weeks), so it's parked rather than shipped half-working. - Progression-based plan creator — generating workouts from a progression model
rather than picking from a fixed library. A larger project for a later release.