Quieter, Sharper: What Else Shipped This Summer
The race pages have taken up a lot of the oxygen lately, but between May and August most of the work went into the app itself. Here's what changed if you're actually using RunPact to train.
Creating a Plan Is One Page Now
The old plan builder was a multi-step wizard. Wizards have a specific failure mode: you get to step four, realise a choice on step two was wrong, go back, and lose everything downstream. For a form where every answer changes the shape of the plan, that's not a minor annoyance.
Plan creation is now a single guided page. Sections open as you complete the one before them, and a live preview of the resulting plan sits alongside, updating as you go. Nothing is destroyed by going back to change your mind, because you never left.
Alongside it, several things about templates got sharper:
- Goal-first template picking. You say what race you're targeting; the recommendation is made against that race, not against a generic profile. Plan durations are per-goal rather than one range for everything.
- Short templates can't accept races they can't prepare you for. The Base Builder and Race-Sharpening blocks are now restricted to event types they can actually complete — a sharpening block that maxes out at 17 weeks was previously selectable for a 100-miler whose minimum build is 24.
- Phase min/max week inputs are coupled in the editor, so an impossible configuration can't be entered in the first place.
- Phase purpose is visible. Every phase explains what it's for, in full, rather than a truncated label.
- Intra-phase progression. Hard-session intensity now ramps through each phase instead of sitting flat, and the volume model is coherent across phases rather than assembled per-week.
- A beginner template for a first ultra, and a discovery flow for people who don't yet know which template they want.
The Training Week Stopped Shouting
If you've used RunPact's week view before, it was loud: fourteen session-type colours, coloured icons, zone badges, corner dots, rings on today's session, a green wash across everything completed, ambient pulses on the plan header, an action row on every card whether or not there was anything to do.
All of that is information. Almost none of it is information you need simultaneously.
The week view now uses four intensity tiers instead of fourteen per-type colours — easy, moderate, hard, rest — encoded as a stripe. Action buttons appear on the sessions where there's something to do (today, overdue) and reveal on hover elsewhere. Completed sessions dim to a neutral tone with a check instead of turning green and striking through. The week header merged into a single line: dates, phase, done count, duration, kilometres. The phase purpose moved into a popover on the phase pill, available when you want it and out of the way when you don't. The scrubber's duplicate stats strip is gone. Empty days show a faint "Rest" ghost instead of nothing.
The session detail modal got the same treatment: the header follows the same four tiers, the redundant session-type pill is gone (the name and the colour already say it), and completed sessions lead with your actual numbers plus a quiet "planned…" subline and an RPE cell — instead of a boxed planned-versus-actual grid you had to parse.
Nothing was removed from the app. It just stopped competing for your attention all at once.
Your Dashboard Knows What Week It Is
Two changes worth calling out:
A current-week strip on the dashboard showing the active plan's week at a glance, so you don't have to open the plan calendar to see what's coming.
The next session opens in place. Clicking the "Next Training Session" card used to navigate you away to the plan calendar. It now swaps the card's own content for the full session detail, with a back arrow to return. One less round trip for the single most common thing anyone does in the app.
Signed-in users also now land on the dashboard instead of the marketing page.
Activity Matching Covers Intervals.icu
When a synced activity comes in, RunPact tries to match it to a planned session and mark it complete. That's been live for Strava since June. It now covers Intervals.icu as well — every new import runs through the matcher, with native Intervals.icu activities normalised and matched on the same path.
Two related fixes: an activity can now only be linked to one session (the previous behaviour allowed the same run to complete two different sessions), and Strava activity types are correctly typed rather than being coerced into a generic shape.
And a small one that was disproportionately annoying: synced activities now show the right sport icon. Every non-run activity used to fall back to the bike, so a swim, a ski session and a row all came back looking like a ride. Nine sport families are now mapped — run, walk, bike, swim, paddle, snow, yoga, weights, and a neutral fallback. Session-type icons went from three glyphs to eight, one per family, which also fixed rest days, which had been displaying a bicycle.
The Flythrough Knows Which Race It's Flying
The 3D course flythrough got its biggest upgrade since launch, moving from a generic flyover to something specific to your race:
A real race clock. The HUD's elapsed time is driven by predicted per-segment pace, not a constant. Which means the time of day advances the way it actually would — slow climbs burn more daylight — and if you start at 04:00, you fly the first hours in the dark like you'd race them.
Documentary narration. The subtitle copy was rewritten calm and factual, with the real numbers: course distance, each climb's gain, length and grade, altitudes at the points of interest. Checkpoints get aid-station lines rather than summit copy.
Smooth day and night. Lighting and the sky dome are keyframe-interpolated across a fractional 24-hour clock, so sunrise and sunset ramp instead of popping between hour bands, and the sun glides rather than hopping fifteen degrees at a time. Stars and the moon fade in only on genuinely dark sky.
Landscape that belongs to the course. Trees are biome-weighted along a relief-relative alpine gradient — conifer, broadleaf, dead, palm — scaled by the course's own relief so a flat course stays neutral, with palms gated to warm latitudes. Buildings take their roof shape from OpenStreetMap tags (gabled, pyramidal, spire) instead of every structure being a box, so a village reads as a village and a church reads as a church.
A finish celebration — a crane shot pulling back off the finish line with a course stats banner.
Plus a round of performance work: the initial world build got substantially faster through parallel tile loading, the detail window's GPU bandwidth dropped by roughly 10×, and the flythrough now starts near-instantly rather than after a visible build.
And, as covered in the race pages post: every race page now links its own flythrough automatically. All 33.
Predictions and Honest Numbers
The finish-time model was retrained several times over the summer as the race corpus grew, and moved out of the web process entirely — it's now trained offline and deployed as an artefact, which cut a persistent memory problem on the backend.
More importantly, we corrected our own copy. The predictor page had been claiming a validation R² of 0.79 "on held-out races." That figure came from a shuffled split, which is not a held-out-race measurement at all. The honest metric for this model is a course-grouped cross-validated MAE of about 74 seconds per kilometre, and that's what the page says now. The training-set size was also wrong on two pages — understated at 21,000 results, when the actual set is 262,260 samples.
Both corrections were in the unflattering direction on the headline number. That's the point of checking.
Smaller Things
- Social login with Google and Strava, plus conversion analytics through the signup funnel
- Fixed spurious "invalid credentials" errors — a stale token after a browser tab slept now retries with a refreshed one, and genuinely transient auth-service failures return a 503 rather than falsely telling you your login is bad
- Purchase intent is preserved through the auth redirect, so signing up mid-checkout no longer drops you back at the start
- A public ultra race time predictor and aid-station cutoff calculators, usable without an account
- Estimated session distance is now weighted by each segment's zone pace instead of a flat average
- Back-to-back long runs are budgeted as two days and correctly placed within the week
- Security updates across FastAPI, Starlette and python-multipart
What's Next
The race corpus keeps growing, and each new race is now cheap: the ingesters exist, the page builder derives its own flythrough, and the sitemap picks up new pages automatically. Adding a race is close to just adding a race.
On the training side, the focus is the loop between what you planned, what you actually did, and what the plan does about it next.
If you bounced off RunPact earlier this year, the plan builder and the week view are both meaningfully different now. Take another look.