Skip to content

Domestique v2.1.0

Choose a tag to compare

@platypus45 platypus45 released this 19 Jun 08:39

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 the certifi CA 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 with eftp_auto_apply: true in 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_ramp files
    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/workouts view 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.