Changelog

Recent updates and improvements.

newOctober 20, 2026

Version 1.2.0

**Build 1** · 20 October 2026 **A generated plan speaks the phone's language.** A German iPhone showed "Heutiges Workout" over "Easy Run (Zone 2) – Run 8.8 km at easy conversational pace…": the built-in plan generator wrote every session's title, description and block names into the plan as fixed English. Runs, strength, conditioning, race simulations, ATHX, warm-ups and cool-downs, recovery, rest days and the weeks around a race now come in the phone's language. Distances follow the locale ("8,8 km" in German), paces the chosen unit. Exercise and station names stay English, as in the catalogue — search and linking depend on them. - Applies to newly generated plans - Substitutes have a name - Strength days - Empty lists - Undoing a manual deload - "[low-completion adapted]" - Reset - Load - Runs - Outside the plan too - Link by hand - German names are recognised - AI prompt - Library in the training tab - One menu everywhere - Replace a run - Put something into the AMRAP - Rowing is metres - AMRAP shown twice - "Apply to similar" and "Copy" - AMRAP back - Limits - Now - Works for every recording - A header over a gap - The tip describes the measurement, and the numbers are formatted without `String(format:)`. **Verified:** not built, not run (new `HeartRateRecoveryTests`). The thresholds for "Excellent/Good/Fair/Needs Work" (5 / 2 / 0 bpm) are the old ones and have not been checked against real races. · · · **A training plan without a race, built for your own goal.** "Can the app be used well without a race? Just enter as the goal that you want to get better at something, or stronger, or more muscle mass, and then no race." Until now a plan without a race date was a race plan without a date: you had to choose a format, every strength session carried stations, the peak carried simulations, the plan ended in a taper, and the plan card said "HYROX". - In the plan wizard - The plan - Get stronger - Build muscle - Endurance & running - Overall fitness - Get better at… - Reps and weights - With an external AI - Plan card - A race later - Progress toward your goal - Get stronger - Build muscle - Endurance & running - Overall fitness - Get better at… - "Get better at…" - Version 1.2.0 (Build 1) - Per set - The set count stays - The newest wins - Progression as an offer, not written in - AMRAP, EMOM and For Time stay out of it: their numbers are rounds, not a set prescription. **Applies at once, to existing cards and plans too:** what was entered before this build carries no date and counts as older than any training. An already imported plan with "@100kg" therefore opens with the last training, and the 100 appears as the offer. **Verified:** none of it. There was no Xcode in this environment: the code has not been built and the tests have not been run (new `LastTrainingTests` with 14 tests; adjusted `PlanPrescriptionSurvivesTheStoreTests`, `PrescriptionOwnershipTests`, `OfficialTimesAndLapMetresTests`). Neither the Watch nor the iPhone was looked at, not the chip and not the animation on 40 mm.
improvedOctober 6, 2026

Version 1.1.9

**Build 1** · 6 October 2026 **Four reports from one tester: the division on the Watch, "Pairs" in the prompt, "HYROX" instead of "ATHX" on the plan, race reminders without a race.** All four have the same core: something that belongs to a planned race was read from somewhere else. - Division on the Watch - Prompt - "HYROX" instead of "ATHX" on the plan card - Reminders **Build 1** · 5 October 2026 · · · **Watch: the crown and the next set — and what of the plan reaches the wrist.** From a tester's video: "scrolling with the crown does not really work well or precisely yet" and "the weight is not pushed up to 80 although I had 80 in the set before" — set 2 opened at 76, right under the line "Last: 80 kg × 6". - Crown - Next set - The plan's prescription (found while reproducing it) - Spellings - Title or exercise - A run by another name is a run - A run written as a time - A number without a unit - What cannot be read is kept - Sections - Display - The workout import - Plan day - Today card - A written-out simulation - Action button - Double-tap gesture - The zone screen - Tapping the display - In "My Workouts & Runs" a tap on a card now offers "Duplicate"; in the Workout Studio it is in the More menu and in the card's context menu. The copy is named like every new card (suggestion "Name copy") and opens in the editor right away. - The copy is a card of its own: its own ids for every exercise, section and superset, and nothing that tied the original to a plan day, a race or a start. - On the way **Build 1** · 4 October 2026 · · · **Field data stage 2: level rungs and race planner from the loaded field.** So far the analysis read the loaded field only for the field curve, the station badges and the station targets. The division's level rungs (top 10 / 25 / 50 / 75 %) stayed on the shipped tables — for doubles, Pro doubles and relay that means estimates without station targets, while the card beside them drew the real field. - Level card - Station targets - Race planner - Watch - Deliberately unchanged - Deadly Mile and Deadly ERG are walked in their own order - Fixed on the way - Heart rate from Whoop, Garmin and the like - ATHX race calendar - ATHX best lifts in the wizard - ATHX circuit on the Watch - Plan - Tonight's test crash - The history now reads every plan - A session of an archived plan opens for reading - Past weeks in the new plan are now the exception - The current week keeps its ticks - The archive dialog now says that completed sessions stay in the history. - In the workout history (Health workouts) a linked workout counted as unlinked after archiving and offered "Add to Training Plan" again. **Verified:** iPhone and Watch app built, type-check times measured, 4 new tests in `TrainingHistoryTests`. In the simulator the Training tab without an active plan and with an archived one (`TrainingHistoryTour`, `-NoTrainingPlan -SeedArchivedPlan`): the session is in the list and opens with sets, loads and note. The unit suite ran twice: the first time the test process crashed once in the middle of an AMRAP test (a memory error on a background thread, no test failed), the second run was fully green with 1909 tests. Not verified: the whole path "archive → wizard → new plan" in the simulator, and the AI import with the switch. · · · **Smaller button type.** In the new design the labels were 18 pt (wide buttons) and 14 pt (small ones) — in capitals, which read about a third larger than ordinary text of the same size. A "Continue" outweighed the section titles above it. They are 15 pt and 12 pt now, one step below the text beside them. The buttons keep their height and with it the tap target. The original design is unchanged. Verified: a render of the whole button family beside title, body and small text; the complete unit suite (1905 tests, all green). Not verified: the individual screens in the simulator. · · · **ATHX: simulation and tracking walked through.** An ATHX simulation on the Watch (40 and 46 mm) walked zone by zone and photographed, plus the iPhone tracker and the race detail. Found and fixed: - The Watch did not know the season - The endurance zone counted down from 22 minutes instead of 24 - No countdown without a plan - The zone line was cut off - Entering a score - Feed Zone and Recovery Zone count down - Scores can be added afterwards - iPhone tracker - A MetCon ended only at the cap no longer counts as completed. The Watch's finish screen also shows the MetCon time, it used to show two of three scores. When logging a past race, "72,5" with a comma was saved as 0. **Not fixed, still open:** - The Watch has no list of the circuit's seven legs to tick off. That exists on the iPhone only. - The 2027 comparison values are scaled from 2026 (there is no 2027 leaderboard yet) and marked as estimated. **Verified:** all four targets, type-check times measured, the complete unit suite (1905 tests, all green; 7 new in `ATHXTrackingTests`). Watch simulator at 40 and 46 mm with `-DemoSimulation athx -DemoWalk` (now season 2027 with targets), the number pad with `-DemoScorePad`. iPhone with the new tour `ATHXTrackingTour` (`-DemoATHX break|empty|scored`). Not verified: real devices, the tap at the end of the break (the simulator has no haptics), the last gate and the transfer of the race from the Watch to the iPhone (the demo walk stops before it), and Save on the score sheet was not pressed in the tour. · · · **ATHX: walked through once as a user.** Plan wizard, generated plan, AI prompt and race planner with ATHX as the format. Found and fixed: - Wrong season - Last-used values overwrote the plan - Run-led weeks - Sessions longer than set - AI prompt - Wizard - Race planner - The plan's race-simulation day always described the 2026 zones. The note "paces estimated from your goal time" appeared even when the pace came from your own entry. "Quick Workout" was cut off on the Training tab. The icon buttons on the Race tab had no or a wrong VoiceOver label. **Found, not fixed:** - The race calendar knows only two ATHX events (November 2026) and none for 2027. A race in April is created by hand through the plus on the Race tab. - The wizard does not ask ATHX athletes for their current maxes. The AI prompt knows your weights only when you have logged them in the app. - The recovery session describes mobility and contains two 1 km runs. The plan's simulation names a shorter duration than its description. - The zones are named in English in the wizard (Strength, Endurance). **Verified:** the complete unit suite (1898 tests, all green), 7 new tests, all four targets, type-check times measured. The generated plan and the prompt were produced for a race in April 2027 and read, the wizard and the race planner photographed step by step in the simulator (new tour `ATHXJourneyTour`). Not walked through: an ATHX simulation on iPhone or Watch, race detail and analysis of an ATHX race (the simulator holds none), the AI import of a real reply. · · · **Deadly Dozen: official times and place in the field.** Race detail of a Deadly Dozen race now has "Official times". You search your result by name or team name on the official leaderboard (leaderboard.deadlydozen.com), pick it, see recorded against official and apply. All twelve Journeys and twelve Labours take the official times. Because Journeys and Labours are the whole race there, recorded transitions drop out and the finish equals the official one. A result of another shape (a different number of Journeys or Labours) is not applied. With the times comes the place in the field: for every split the rank among the finishers of your division at that event and the fastest time there. Under the segments this becomes the "Field" card: your level (the middle of your own split standings), strengths and potential (splits clearly ahead of or behind that level), the average on Journeys against the one on Labours, and the list of all splits with time, rank and "Top x %". There is no distribution curve as for HYROX; the leaderboard gives ranks, not a distribution. The standing also goes into the AI hand-offs (training plan, analysis export, race export). **Verified:** the complete unit suite (1893 tests, all green), 4 new tests against the leaderboard's real answers, all four targets, type-check times measured. The card was rendered with a result's real data and looked at. Not verified: search and fetch inside the sheet on a device. I made the requests themselves by hand against the leaderboard; the sheet uses the same addresses. · · · **Buttons: second pass.** Another 92 hand-built buttons are converted to the shared style: tinted and neutral secondary buttons, small actions inside cards, icon buttons and delete buttons, in 23 files. 584 lines of one-off styling went with them. A button standing in a row beside one that fills its cell now fills too. About 150 buttons are converted in all. Deliberately left alone: - 82 selection chips (on/off, selected/not). Those are switches, not actions; they need a shared style of their own. - 21 cards that are tappable as a whole and hold several lines of text. - The live screens (race, run, workout on the iPhone) and set logging: controls sized for a moving hand and dense rows. - 32 orange text links. **Verified:** the complete unit suite (1889 tests, all green), the iOS target builds, one screenshot pass through Today, Training and Races in the new design. Not verified: every converted button individually; the conversion ran by fixed rules over simple buttons (one text, or a symbol with a text). · · · **Buttons: a grid instead of a jumble.** The rows of the workout card on the Today page were three colours and three widths: "Move" fixed at 80 points and therefore broken inside the word in German, two stretched buttons beside it, a blue and an orange one above. A row of actions is now a grid: cells of one width, one height, one line of text. On the card: Start · iPhone and Start · Watch (the Watch emphasised as the usual way, the iPhone when no Watch is paired), below them Move, Complete, Skip, and for a detected workout "Extra workout" and "Count as training". **Race ribbon.** Three faults. The longest bar ran out of the card on the right, because the gaps between segments were not counted. The figure on the right was the sum of the segments, not the finish: a race whose transitions were never recorded came out shorter and stood there as the faster one. And a station area recorded on the Watch was a single block of walking and work. The bar is now the finish, a station area is drawn as corridor, station, corridor, time no segment accounts for is a grey segment of its own at the end, the time is written as hours:minutes and the gap to the fastest race stands beside it. In the new design runs are orange, stations light and transitions dark. **Official times repair a race logged afterwards.** The reason "Stuttgart looks faster": the logged race had no roxzone at all, its 65:41 was runs plus stations only (1:15:13 with the roxzone). "Official times" now fills the roxzone per round of a logged race from the official checkpoints and clears its "skipped" mark. After that the finish is right everywhere, not only on the ribbon. **Station progress.** The screen read the whole format: station times from doubles (shared work) stood on one line with solo times. It now follows the division and variant of the Analyse page and names them at the top. The chart is redrawn: one quiet line through all points, a filled dot for measured, a hollow diamond for projected, and below it "x of y values projected from reduced volume". The colour fill that ran out of the card is gone, and "Best" stands inside the chart instead of being cut off at the edge. **Lap length per lap.** Often the first and the last lap differ from the ones between. The "Lap length" sheet now has a section "Laps with a length of their own": every lap with a switch, with its own field when on (prefilled with the Watch's value) and that lap's pace, plus the shortcut "First and last lap are different". Every other lap takes the standard length above. **Verified:** the complete unit suite (1889 tests, all green), all four targets, type-check times measured. Rendered and looked at: the button styles with the German labels (light and dark) and the new station chart. Not verified: the ribbon and the "Lap length" sheet on screen, the workout card inside the app. Open: about 310 buttons in the old look. · · · **Buttons: one shared style ("light on dark").** The app had about 370 hand-built buttons, each with its own fill, radius and font. The look now lives in one place (`GRButtons`): the main action is the strongest contrast on the screen (white on dark, black on light) in condensed capitals, everything else is a hairline, orange no longer fills a button but only marks, deleting is a red outline. 46 buttons are converted in this step: every formerly orange-filled main button (31), the buttons in the iOS default style (11) and the buttons of the cards built today. The original design keeps its orange-filled buttons, the Watch is unchanged. **Verified:** the iOS target builds. Not verified: the unit suite and the other three targets after the conversion, the buttons on a device. Open: the tinted secondary buttons, icon buttons and text links (about 320 places) and two two-line card buttons in session detail. · · · **Field data in what is handed to an AI.** Three places hand data to an AI: the training plan prompt, the analysis export and the export of a single race. None of them contained the comparison with the field; the AI was asked to "address weaknesses" and had to guess them from raw times. All three now carry the same block from the newest complete HYROX race: place in the field, the finish times for top 25 %, median and top 75 %, then one line per run and station with your time, the share of the field behind you, the time of athletes at your level and the difference. After that the strengths, the largest losses in order as the plan's priorities, the sum as a ceiling (explicitly not a goal time), losses on runs against losses on stations, and the run pacing. Conclusions are passed on, not the distribution. For doubles it says that station times are the team's shared work. The block is English and metric like the rest of the prompt, stations are spelled as the plan grammar spells them. In the plan prompt the race history now also has each race's station times (it had run splits only) and the transitions, without which runs and stations did not add up to the total. **Loading field data: instructions first.** The screen looked like "the app opened a website", with the instructions small at the bottom edge. It now opens on a page of its own with four numbered steps and an "Open HYRESULT" button. Above the website there is then a bar in the brand colour with the current step, and the button below says "Open a result first" while none is open. **The hint for Open and Pro too.** The app ships percentile tables for these divisions, and because those counted as "field present", a simulation in Open produced neither the hint on the Today page nor a field card. The hint now appears for every division you race or simulate in that has no loaded field, with its own text when only the tables exist. The field card offers the loading under a simulation as well. Once a field is loaded, the analysis reads it instead of the shipped table, so card and analysis mean the same field. Ladders, planner and Watch stay on the shipped tables. A dismissed hint makes way for the next. **Verified:** the complete unit suite (1885 tests, all green), 30 tests in the file, all four targets, type-check times measured. The AI block was generated from the real data of the Karlsruhe race and read. Not verified: the new instruction page and the bar on a device. · · · **Lap length: a card of its own in race detail.** The lap length was already what everything is computed from, but its control sat hidden in the edit sheet of each single run row. Above the segments there is now a "Lap length" card: the length in use ("per format" or "set by you"), with average lap time and average pace side by side. The lap time is what you ran. The pace is that time over the length of the lap and only changes with it. When the Watch's measured value differs by more than 4 %, it is shown there explicitly as an estimate. "Adjust" opens a sheet for the whole race: the length in 10 m steps or as a number, the pace with that length live, "set to the measured average", "use the Watch's value for each lap" and "reset to the format's length". The analyses read pace through this length without exception; checked again, no place reads the sensor pace directly. **Strengths and potential from the field.** The field card in race detail and the Weakness Analysis now open with a reading against the field: your place in the field (finish time), up to three strengths (splits clearly above your own level) and up to three splits with potential, each with your time, the time of athletes at your level and the difference. Then the sum: what bringing every split up to your own level is worth, as a new finish time and a new place in the field. And the cheapest split: where five per cent faster pass the most people, because the field is densest there. Runs count, not only stations, and a corrected lap length is respected. In the analysis one complete race of the division is enough. For Open and Pro singles the values come from the shipped tables, for everyone else from the loaded field. **Verified:** the complete unit suite (1883 tests, all green), 4 new tests (28 in the file; the "34" in the section below was miscounted, it was 24), all four targets, type-check times of the new screens measured. The "Lap length" card and the potential block were rendered and looked at, the block with the real data of the Karlsruhe race. Not verified: the "Lap length" sheet and the block inside the analysis on a device. · · · **Race planner: a saved plan lost its times on opening.** Opening a saved plan sets its division, and exactly that triggered the code a manual change of division runs: every station time was rebuilt from defaults. It hit every plan outside the default division, so doubles, Pro and all women's divisions. The time jumped without anything having been touched. The same happened to the lap length of Deadly Dozen and DEKA variants. On top of that the saved plan was loaded again on every return from a sub-screen, overwriting what had just been changed. A saved plan is now loaded exactly once and after that only changed by you. **Race planner: reduced-volume times taken as full ones.** "Burpees 1:30" was the time for 40 m from a half simulation, entered as a time for 80 m, in the prefill and as the best time in the suggestion. Every station time now refers to the station's full volume: measured in full it counts as it is. At reduced volume it is projected up (40 m in 1:30 is about 3:29 for 80 m) and only used when too few were measured in full. Below a third of the volume it does not count at all. A best time is always a measured one when there is one. **Race planner: starts from your last race.** A new plan now takes the times of your last race in this format and division first, run pace and every station. One race is enough; it used to need two values per station, and after the first race in a division it showed defaults. Then each station is checked for a faster time since, measured in full in a simulation. If there is one, it is used. Two safeguards: a time more than 40 % below the race's counts as a gate tapped early and is not taken, and a projection never beats a measurement. For run pace only a complete simulation over the same lap length counts. With no race yet, the median applies as before. The plan summary says which race the times start from and how many stations were measured faster since. **Loaded field in the analysis (stage 1).** For a division the app ships no data for (all doubles, relay) the analysis now reads the field you loaded from HYRESULT: the field distribution shows the division's finish, runs and stations, the station badges get their percentages, and Weakness Analysis, the radar, the station benchmark and the Station Power Curve grade a pair for the first time. Station targets come from the field at the level of your own finish time. The cards say where the values come from. Open and Pro singles keep their shipped data. The level ladders, the race planner and the Watch are untouched. **Loading field data without a race of your own.** The field travels with every result of a division, not only your own. "Load field data" opens HYRESULT inside the app: pick an event, pick the division, open any result, "Load field from this result". The Today page shows a hint when there is no field for the division of your next planned HYROX race, or of your races and simulations of the last four months. The hint can be dismissed per division. The same button stands in the analysis where it used to say only "no station splits". **Field card: a curve to move along.** Tapping a row opens that split's curve large: the peak dashed, your time in colour, a time axis below. Dragging across the curve shows, for any time, the place in the field and how many athletes are there. One row is open at a time, so the list stays compact. **Roxzone in doubles: checked against real data.** The exported recording of the Karlsruhe race could be set against the sixteen official checkpoints. The split made from step rate was wrong at seven of eight stations, for example 76 seconds of corridor before the SkiErg where the mats say 8, and 84 seconds after the rower instead of 14. Whoever is not working stands, paces or walks alongside, and to the watch that looks like roxzone. For doubles and relay the Watch therefore no longer uses the step rate but your own average per crossing, which was closer at every station of that race. It learns from official times you take over. For everyone: one crossing is at most 60 seconds, no officially measured one was longer than 34. Two of the real traces are now a test. **Verified:** the complete unit suite (1879 tests, all green), 34 tests in the new file, all four targets, type-check times of the changed screens measured. The expanded curve and the hint were rendered with the real field data and looked at. Confirmed on the device: fetching and applying official times (the exported recording carries them). Not verified: the race planner on screen (the rules are tested, opening a saved plan is not), the "Load field data" screen with the embedded website, dragging across the curve with a finger, and the analysis cards with real doubles history (that needs three races of the division). · · · **Field overview per run and station.** A hyresult result page holds, beside the athlete's own times, the distribution of the whole division for every run, every station, the roxzone and the finish; for Pro Doubles Men that is 39,182 results. The app loads that page anyway when official times are taken over, and now keeps the field per division, with no further request. Race detail has a "Field" card under the segments: per row your time, a small curve of the field with your line in it, the peak (the time most athletes need, the middle of the fullest bin) and "Top x %". The percentage is the one the page shows. The card appears for every complete HYROX race of that division, simulations included, once the field has been loaded. This gives station figures for doubles too, which the app ships none for. **Roxzone in and out from the official checkpoints.** The page states the race clock at all sixteen checkpoints. From those, the way into and out of every station follows exactly, instead of spreading the total the way the recording measured it. The finish time is now right to the second: the page's own roxzone figure is a sum of rounded splits and was 4 seconds off the finish for the test race. When the result is listed under another division than the recording, the division is set on apply (the Watch could not select Pro Doubles so far). Found along the way: - The link of an older race imported the newest race's times. For a result link the athlete page was read first, and it lists every race. The result page itself is read now. - Read directly, the result page returned eight stations and no run. It holds the data in two shapes, and the incomplete one was found first. **Verified:** 6 more tests (21 in this file), all four targets. The real result page of the Karlsruhe race went through the whole path as a file: 16 splits, roxzone 4:06, finish 1:11:55, a field with 18 splits, and the card was rendered with that data and looked at. Not verified: the network fetch itself inside the sheet, and the card inside race detail on a device. · · · **Lap metres: measured, shown, and corrected for all laps at once.** A lap is often not exactly the kilometre, and then pace and analysis are off. In race detail every run row now shows the measured distance when it differs from the lap length by more than 4 % ("≈ 940 m measured"). Measured means the lap's time over the pace the Watch recorded, from GPS outdoors and from your stride indoors. The edit sheet of a lap sets the distance in 10 m steps or as a typed number (it was 100 m steps), takes the measured value with one tap, and can apply the distance to every lap of the race. Pace and the analyses already used the corrected distance. New: wherever run times are compared (lap by lap between two races, best lap, lap averages, the trophy in race detail), a corrected lap is converted to the race's own lap length. 4:30 over 900 m counts as 5:00 there, not as a faster lap. **Official times onto a recorded race.** Race detail of a HYROX race now has "Official times" above the segments. Paste the link of your result page on hyresult.com, load, check recorded against official, apply. Each run takes its official time, each station area the official station time plus its roxzone. The roxzone is spread over the stations the way the recording measured it (area minus official station time) and scaled to the official roxzone total. Heart rate, weights, notes and the measured run distance stay. If the new finish is more than 10 % away from the recording, the sheet warns that this is probably someone else's result. **Roxzone.** The Watch times a station area gate to gate and splits it into roxzone and station afterwards. One crossing may now be at most 90 seconds, it was 180. In a doubles race the step rate beside the working partner looks like walking the roxzone, and with the old limit that part of the station could be counted as roxzone. For real races the official times (above) are the exact way. "Share & Export" also has "Export recording (JSON)" with the step rate of every station area, so a wrong split can be checked against real data. **Training load no longer starts at zero.** The curve began at 0 on the first day of the six months and took about two months to reach where you already were, and the green band with it. Twelve weeks of run-up are now read from Apple Health, and the model starts at the daily mean of the first four weeks from the first recorded session. Days before the first session are no longer shown as rest days. The curve is rebuilt once on the next launch. **Watch: Pro Doubles can be selected.** The HYROX division picker had no Pro Doubles Women, Men or Mixed. A Pro pair could only start with the Open loads on the wrist. **Today page reviewed.** Found and fixed: - The weekly summary said "Untrained, +88 fresh" under a load card reading "Overreaching, −40". It was computed before the load model had finished and never refreshed. - "Legs & Glutes" appeared twice in muscle recovery, with two different times. Two muscle groups share that name on purpose; now one row, with the longer time. - Muscle names wrapped inside a word in German. - Today's suggestion ended in "…" after three lines. - The readiness age read "vor 8 m" in German, which looks like metres; now "vor 8 Min.". - "0.0/10" and "77.5 kg" with a point instead of a comma in German; the weight now follows the unit setting too. - Two recovery tips were English in the German app. - "13 (0 min)" for tracked workouts when no duration is known. - The "Training progress" chart ran from 0 to 100: scores around 70 sat at the top over two empty thirds, and the labels 90 and 100 were printed on top of each other. The axis now starts just below the lowest score. Found along the way: - The HYRESULT import under "Log past race" imported no run times on a German iPhone. Rows were recognised by the text "Run 1", which is "Lauf 1" there. Rows are now matched by kind and number. **Verified:** 10 new tests, all four targets, two screenshot passes of the Today page (before, after). The unit suite ran in full before the Today fixes: 1865 tests, one failure in an existing DEKA test that passes when run alone (it shares the race store with other tests). Not verified: fetching a real hyresult page in the new sheet, and the new chips on a 40 mm watch. **Build 1** · 1 October 2026 · · · **Strava: the official connect button and write access only.** For Strava's review, which allows more than ten connected athletes. Settings now show Strava's own "Connect with Strava" button instead of a hand-built row, with the "Compatible with Strava" logo below it. GritRace now asks only for permission to upload. Read access used to be requested as well but was never used: GritRace reads no Strava activities. Anyone already connected need not do anything. **Verified:** all four targets and two screenshots of the connection screen (not connected, connected). **Build 1** · 29 September 2026 · · · **Exercise pictures when reordering.** The "Reorder" screen in the workout editor showed the same green dumbbell symbol for every exercise, although almost all of them have a picture. It now shows each exercise's picture, as the exercise library does: your own photo first, then the app's picture, for stations the station picture, and the symbol only when none of these exists. The same goes for the second reorder screen in the day detail, the cards in the editor, the header of the block editor and the preview of the AI workout import. Found along the way: - A picture was only found when the name matched the catalogue exactly. "Walking Lunges" did not find "Walking Lunge". For the picture, whatever the name matching already recognises is now enough: a plural, a different word order, a translation, a small typo or an equipment word. Training history is unaffected; linking two names there is still yours to do. - "Rowing" showed a muscle-group symbol instead of the rowing picture. The catalogue entry "Rowing" has no picture of its own, but the station picture of the same machine does, and it now wins over the muscle-group symbol. - The second reorder screen always named stations with their HYROX names; it now uses the session's own format. **Verified:** 3 new tests, the full unit suite (2061 tests), all four targets and a screenshot of the Reorder screen with the sample gym day. **Build 1** · 28 September 2026 · · · **Benchmark data: from the field data, not beside it.** Since April the app has shipped real distributions for HYROX (hyresult, about 252,000 Open Men, 126,000 Open Women, 50,000 Pro Men, 22,000 Pro Women), Deadly Dozen (leaderboard, whole population) and DEKA (global leaderboard, age groups). It graded against tables beside them that contradicted them. The tables are now derived from those distributions. - HYROX Open - HYROX Pro - HYROX Doubles - Deadly Dozen - DEKA - Station badges ("Top X %") - HYROX relay - Honest where no data exists - Found along the way - Found in the screenshots - Runs were scaled by the format's run count: for Deadly ERG that meant twelve mile-long erg pieces instead of three. - Shortened runs counted as full ones. - The estimated roxzone from station areas was missing; a flat 40 s per station stood in its place. - Stations from half simulations and training times at any rep count counted as full stations. Now: pace times the shape's distance, the measured roxzone, stations projected to full volume, and training only at the matching volume. On the demo data the prediction now comes to exactly the measured 1:07:55, where about four minutes were missing before. **Division and variants, smaller cases.** - The Deadly Dozen division picker also offered the other gender ("Solo Male" for Solo Female races only). After picking it, the tab was empty. - The weakness card in race history mixed all divisions but graded against one division's targets. - The race ribbon mixed divisions. - "Your best parts" excluded older Track races that carry no variant stamp. - Projecting partial simulations used a fade pooled across all variants. - Two halves of different variants could be assembled into one race. - "N excluded" did not count stopped recordings, so someone with only those saw neither cards nor the switch. **Other cards.** - HR Profile: the only card that read around the filter, with no division or variant and partial simulations always included. A Deadly Mile got the Track's station names. - Station Efficiency: picked the "most efficient" station by seconds per rep against seconds per metre. Each station is now compared with its own history ("Most improved" / "Slipped most"), and distance stations appear without a measured distance. - Station Profile (radar): against the tier's targets it was a full octagon for almost everyone. It now shows stations relative to your own level, like the power curve. - Race Comparison: "PR" stood over the previous best even when the latest race was faster. It now reads "Previous best" in that case. - Run Pace Profile: the axis no longer starts at 0:00, which had flattened the curve. - Run Drift, Transition Times and Run Pace Profile use the shape of the selected variant. **AI export.** Station bests were looked up by position in the format's list; for a Deadly Mile the times landed on other Labours. Race comparison, pace profile, station bests and summary are now separated per division and variant. Planned races list their own variant's stations with its loads. The readiness correlation no longer mixes formats. **Translations.** Several strings showed English on a German phone: "Excellent/Good/Fair" and "Better/Worse/Stable" in HR Recovery, "Negative Splitter" and "Even Pacer" in the run profile, "HYROX: 4 races" and the sentence under Transition Times. **Verified:** 5 new tests, among them HYROX without a variant, the variant stamp of older races, the fatigue curve with one slow station, the station time in the log including heart-rate load, and DEKA STRONG against FIT. On top: the full unit suite (2046 tests), all four targets and a tour through the whole Analyse tab with screenshots, every group expanded. **Build 1** · 27 September 2026 · · · **Roxzone and exercise: no live guess any more, a better estimate afterwards.** During a station the Watch guessed from the pedometer whether you were in the roxzone or at the exercise and showed it, buzzing at every change. In the middle of the exercise it often jumped to "roxzone out". Two causes: - From the second station on, a single "learned" threshold replaced each station's own (rowing 0.55 steps/s … farmers carry 1.10). It sat above all of them, so walking counted as exercise. - The first outlier decided: a few quick seconds during wall balls or broad jumps already counted as the way out. The Watch now shows no phase during a station. The countdown and the finish prediction run gate to gate anyway and are unchanged. After the exit gate the app splits the whole step trace into three pieces (in, exercise, out) and places the boundaries where they explain the trace best overall, so a single outlier can no longer move a boundary. If the trace does not separate, for example because a farmers carry is walking too, it uses your usual roxzone time instead of inventing a split. Each station's step trace is now saved too, about a kilobyte per station, so the split can be recomputed later and refined on real races. **Verified:** 7 new tests with typical traces, including the burst mid-exercise, a slow patch in the roxzone, a station right at the gate and a trace without contrast. On top: screenshots of the station screen at 40 and 46 mm, the full unit suite and all four targets. **Build 1** · 24 September 2026 · · · **Pounds on the Watch.** The Watch was never given the unit from Settings and showed every weight in kilograms, the new number pad included. The unit now travels with the settings from iPhone to Watch, in that direction only, so an older state on the Watch cannot turn back the choice made on the iPhone. The set screen, the crown, plus/minus, the number pad and "Last" work in the chosen unit. Storage stays in kg, as everywhere. Lists, results, ATHX scores and the station loads in a race follow the unit too. Weights are also written in the language's format now ("82,0 kg" instead of "82.0 kg" in German). Found along the way: with pounds selected, the AI workout import wrote the estimated maximum weights into the prompt in pounds, although the reply is read in kg. It now always says kg. **Race-day checklist: one plan instead of two.** With Pro, the checklist now leads to the fuelling plan. It used to open an old timer with fixed times that contradicted the plan: at a 09:30 start it said "meal 06:30", the plan "05:30". Without Pro the simple timer stays; it is then the only morning plan. **Small things.** Tapping OK on the number pad without typing no longer changes anything, so rounding cannot shift the weight. The break between segments is called "ÜBERGANG" in German instead of "TRANSITION", matching "Übergänge" in the comparison. For the Deadly Mile the Watch shows "400m" instead of "400m · 0 kg" for the bodyweight legs. The week strip in the plan is split up internally, because the compiler hung for 3–5 seconds on one spot, which can cost a build in the Cloud. **Comparison: "Heart rate per segment" is gone, the heart rates sit in the grid.** The separate table dated from when heart rate was only a small figure beside pace and weight, and two races could not be compared through it. By now it stands under every run and every station in its race's column, and the table only repeated it. With three races it also wrapped onto two lines ("164 / bpm"), and its columns did not stand under the times. Avg HR, Peak HR and the ATHX zone scores now use the same column grid as the time table: the number large, the unit small beside it, the bar underneath across the column. Peak HR only appears when a race has a peak, instead of as a row of dashes. **Comparison and station details for every format and variant.** The comparison took runs, stations and lap distance from the format instead of the race. For HYROX, Deadly Dozen Track and DEKA FIT that is the same thing, which is why it went unnoticed. A Deadly Mile, however, has three Labours and one run, and a DEKA STRONG no run at all. The race's own shape now applies everywhere. Both selection screens (race history and simulations) also let two variants in together, such as Track and Mile or FIT and STRONG. Now only the same variant; partial against full is still allowed. Found along the way: a station's details (weight, reps, distance) were looked up by its place in the race's list, read as a place in the format's list. For every default variant that is the same number; for the Deadly Mile it gave another Labour's details. Affected on the Watch were the station screen and the "up next" line; on the iPhone the comparison, the plan overview in race history and two places in the race detail view. The race detail view also listed all twelve Labours for a Mile. Every detail is now looked up through the station itself. **Comparison: stations as lines again, now properly separate.** In 1.1.8 the stations had become a bar chart, because the lines of two races with the same name had merged into one. The runs have shown the right thing as lines since then, so the stations are lines again too: one per race, keyed by its place in the comparison instead of its name. Every station stands on the axis, the untimed ones included, and the last one keeps its label. **Start of 1.1.9.** 1.1.8 is in review at Apple. Everything from here on belongs to 1.1.9.
improvedSeptember 23, 2026

Version 1.1.8

**Build 1** · 23 September 2026 · · · **Fuelling: enter the start time right there, page tidied up.** The row on Today said "Add the start time for exact times" and opened a page where that was not possible. The only field for it sat in the race planner, behind a toggle, and the fuelling page pointed at it with a sentence at the very bottom. Now the page opens with what the plan is built from: race, start time and weight, each one tap to change. Whatever is missing is marked in orange. Without a start time, an "Add start time" button stands where race morning goes. After saving, race morning appears at once, reminders included. Then comes what to do: the protocol with its grams and the slider, the loading days, race morning, during the race. The three paragraphs of research findings (what it buys, what it costs, drinking) are now folded under "Why this amount?" instead of opening the page. The protocol and weight cards from the foot of the page have moved into the header. The loading days no longer need a start time, only the race day. Until now there was no loading day, no Today row and no reminder without a start time, in exactly the days when the start time is usually not known yet. **Race planner: start times in another time zone.** Editing a race whose start was in another time zone showed the phone's clock. A race at 08:00 in Chicago opened as 15:00, and saving without changing anything turned it into 15:00 in Chicago. A race in Sydney opened on the previous day. Showing and saving now go through the same tested pair of functions as the new entry on the fuelling page. **Comparison: stations as bars, runs as clean lines.** The lines were grouped by the race's name. Two races with the same name thus became one line: through the first race's stations, back to the start and through the second's. The stations are now a bar chart, race beside race per station, because there is no progression from SkiErg to sled push that a line could show. Stations without a measurement keep their place on the axis. The runs stay lines, now distinct per race. The "8" label at the right edge was also missing. **Verified:** 7 new tests (start time round trip via Chicago and Sydney, loading days without a start time) and the 72 existing fuelling tests. A new UI tour enters the start time on the fuelling page and checks that race morning appears. On top: screenshots with and without a start time, the comparison tour, the full unit suite, all four targets and the type-check measurement. · · · **Watch: type numbers in directly.** One tap on weight or reps selects the field, then the crown sets it. A tap on the number in the selected field opens a number pad: digits, decimal point, delete, OK. The old value is highlighted and the first key replaces it, so "8" and OK is two taps. The same works for metres, calories and time, which is typed like a microwave ("130" is 1:30). The selected field shows a small keyboard glyph. The pad replaces the old entry sheets with a text field, which took two screens and a handful of taps. It refuses values beyond what the crown itself can reach. **A raised weight carries into the next set.** It used to do so only within a plain exercise. In supersets, written-out rounds and AMRAPs the second set reopened at the plan's weight, because the list it carried from is emptied every time the exercise changes. The value now comes from the whole session's record. When the plan asks for the same weight in both sets, the next set opens with exactly what you just lifted. On a ramp (60 → 65 → 70) the ramp continues from where you actually are. Going from a warm-up to a working set, the plan decides. For reps: same plan, same number as just now; when the plan prescribes a change (12 → 10 → 8), the target is shown. **"Last" is now always your last training.** From set 2 on, the tiles showed a grey "prev" with the set you had just done, in English, beside a "Last" line from the previous training. Now there is one line: "Last: 62.5 kg × 8", set against set from the last training. Before, the newest training was not guaranteed to be the one found; it is now sorted by date. An exercise that appears twice (warm-up and main part) is compared with the same place each time. AMRAP rounds are nobody's "Last". Workouts logged on the iPhone count now too, which spell their names differently. **Race and simulation screens: the most important number is the biggest.** What you read constantly has grown: pace on the run, the countdown at a station, plus heart rate, plan delta, SEG and TOTAL. What you read once got smaller: station name, "Up next" (now one line), units. On the large watches the main tile gets more width than the one beside it. With a plan, the plan is the reference and the comparison with your last race steps aside there. The finish prediction only shows where there is room. Found along the way: - On the 40 mm watch the run screen did not fit at all: "Sled Push 50m · 152 kg" wrapped and pushed SEG and TOTAL off the bottom. - The heart rate in the footer showed as "•••". - At a real race time ("TOTAL 32:14" instead of the demo's "0:18"), the total would have been cut off on 42 and 46 mm. **Today tab:** The coloured stripe on the left of the readiness card is gone. The ring and the zone name already carry the colour. **Verified:** 22 new tests. The number pad and set screen in the 40 mm simulator, the race screens at 40, 42, 46 and 49 mm, each with and without a plan and at a real race time. On top: the full unit suite, all four targets and the type-check measurement. · · · **New design: second pass, now on the screens below the tabs too.** I searched for everything that went around the switch: - numbers in monospaced type (69 places), now in the logo's condensed digits - remaining corner radii - gradients on progress bars, rings, buttons, onboarding and share cards - coloured glows under buttons and shadows under cards - tinted icon tiles - the large titles on screens below the tabs (Workout Studio, workout history, stats and so on), which still used the old type - orange icons in card headers (21 places), the workout library, the simulations, the Analysis groups and Settings None of this changes anything in the original design. **Four icons did not exist.** Every symbol in the app is now checked against the system's symbol list. "Field distribution" in Analysis had always shown an empty icon, and so had the heart-rate warning in Settings and rowing for Apple Health workouts. All three now have a real symbol. **Along the way:** while converting, I had built an expression the type checker gave up on after 25 seconds. That would have been a failed Xcode Cloud build. The measurement caught it before anything was pushed. A second one in today's pace chart has been defused as well. **Verified:** screenshots of all four tabs plus readiness, the workout library, simulations and Settings. On top: the type-check measurement (no changed expression above 300 ms), the full unit suite and all four targets. The Watch and the home screen widgets deliberately keep the original design. · · · **A new workout is named when it is saved.** Until now only the workout builder asked, and the editor when its name field was empty. These saved without asking: - the editor, once a name was already filled in, for example from the builder, - "Use for the day", - the AI workout import, - a shared workout. The library filled up with "Imported Workout" and "Strength + AMRAP". Now every place that puts a **new** workout into the library asks. The name it would otherwise get is prefilled, confirming is one tap, and a name already taken is pointed out. Saving an existing workout again does not ask, since its name is right there in the field. **Training tab: uniform tiles.** "Quick Workout" and "Free Run" were centred under two left-aligned cards, and the two rows used different gutters, so the columns sat offset. All four tiles are now built the same way and the columns line up. In the new design, "Today's session", "DD Testing Battery" and 27 more icons also get the quiet tile instead of the coloured disc. The plan countdown said "days to race" in English on a German phone; that is translated now. **Verified:** 3 new tests and a screenshot of the Training tab. On top: the full unit suite and all four targets. · · · **Pace progression (Analysis → Training) was drawn wrong.** - Swift Charts joined both trend lines into *one*: Zone 2 ran to September, jumped back to race pace's first point in August and carried race pace on in green. That gave two green lines and no orange one. - Two outliers stretched the axis to 3:20–8:20/km, squeezing both lines into the middle fifth. - The caption said "lower is faster" above an axis where faster is up. - The arrows pointed the other way too. Each line now has its own colour. The axis follows the trend, with round pace marks. Caption and arrows say "higher is faster". Verified: 4 new tests and a screenshot with a season of Zone 2 and race-pace runs, outliers included (DEBUG `-SeedPaceHistory`). · · · A new design, with a switch back **GritRace now looks like its icon.** The app used to read as "AI-generated": SF Rounded everywhere, glass cards, a violet-blue-orange glow, colour discs behind every icon and orange everywhere. The icon has a language of its own, a "GR" of track lanes in copper on charcoal, that the interface never picked up. The new design does: - flat charcoal instead of a glow - flat cards with a hairline instead of glass - sharp corners (4–10 instead of up to 22 pt) - titles, labels and numbers in the logo's condensed heavy type, body text in plain SF Pro - orange only for buttons, links, your best time and what is active; decorative icons are quiet - instead of gradients, a lane line on the edge, as in the icon (best time, readiness) **Quick way back:** Settings → Appearance & Language → "New design". Switching it off restores the previous design immediately, without a restart or an update. Both designs ship complete, and "off" is the old design value for value (compared by screenshot and checked by test). The Watch deliberately keeps the previous design: its race screens are measured on 40 mm and were not redesigned. **Along the way:** after a reinstall the screenshot tour photographed only the Health prompt and still passed. **Verified:** 3 new tests (off = original, on = flat, the switch also reads "NO" from a launch argument). On top: before/after screenshots of all four tabs in both positions, the type-check measurement (no changed expression above 300 ms), the full unit suite and all four targets. · · · **A false "Restore training plan" prompt.** Anyone who had deleted or archived their plan was told on the next launch that "your training plan could not be loaded after the update". Tapping Restore brought back the plan that had just been deleted. Deleting keeps a backup on purpose, and the prompt only asked whether a backup existed. It now appears only when the plan file is there and cannot be read. Found while photographing the app for the design probe. Verified: 3 new tests and the full unit suite. · · · The Ultra's Action button, and starting a run **A run starts when you start it.** A run block's clock used to start the moment the workout reached it: at START, while the phone was still going into a pocket, and after a sled push, while you were still walking to the track. That walk was in every run's time and pace. Now the Watch shows READY, the distance and the target pace, and only "Start run" (a tap, a double tap or the Action button) starts the time, GPS distance, pedometer, announcement and auto-pause. The session's total time and the Health workout keep running. Three exceptions, on purpose: an interval rep right after its recovery jog (the jog ends on schedule), a run inside an AMRAP, EMOM or For Time (the section's clock is what counts) and the open cooldown at the end. Tapping "Next" on a waiting run skips it, and no zero-second run is saved. The iPhone shows "Run ready — start on Watch". **The Ultra's Action button works in workouts now.** In a race it still advances the gate. In a workout it presses what the double tap presses: the set's check mark, a hold's start and done, skip rest, resume after a pause, and the start of a waiting run. The double tap keeps working. It also works with the display dimmed. The app knows which button is in front because every button with a double tap also registers itself for the Action button while it is shown. **The first press opens GritRace and no longer starts anything by itself.** It used to start the loaded race immediately, even from a jacket pocket. Now it opens the app. If it starts no workout, watchOS shows a line saying so (according to Apple there is no other way). If the app is already open with the START screen of a workout or a loaded race in front, the press starts exactly that. **Verified:** 9 new tests (which runs wait, start, pause, skip, ending early, which button gets the press), the full unit suite and all four targets. The Action button has not been tried on a real Ultra yet; that only works on the wrist. · · · AI plan: prompt reviewed, a plan written from it and imported **An AI plan can hold AMRAP, EMOM and For Time now.** The plan import knew `Rounds` and nothing else. An AMRAP the AI wrote into a plan day arrived as a list walked once. Even the single-workout import called EMOM and For Time "not supported yet", though the Watch has run both since the workout builder. Both imports now read the same sections: `AMRAP 20`, `EMOM 12`, `E2MOM 16`, `For Time 3 rounds cap 25`, `Section Mobility`. The prompt teaches them with an example. **A locked gym workout keeps its sections.** When a workout with a warm-up AMRAP, mobility and a WOD was placed in a plan, only the exercises arrived, without their frames. That was true for the AI import and for the wizard's own plan, which also reused the same block ids every week. Every day now gets its own copy with all sections. The prompt also tells the AI what the locked workout contains, so it lands on a fitting day. **Plans were built for the men's division.** The wizard asks no division and always took the first one in the list, the men's, in every format. A woman got a HYROX plan with a 152 kg sled push. Now the division from Settings or the Watch applies. The workout builder, the editor and the simulation start default to your division too. **The prompt says what the wizard asked.** New are the division and, for every station, its race volume and load for that division ("sled_push — 50m · 102kg"). Without a booked race, plan length and start date used to be missing entirely. The example in the prompt, which an AI copies most literally, used to say "Station sled_push 1000m", a kilometre of sled. Station ids now use the grammar's own spelling (`ski_erg`, not "SkiErg"), and machines outside a station carry their catalogue names (Rowing, SkiErg, BikeErg, Air Bike). **Smaller import bugs:** a recovery under `Run 2x400m` inside `Rounds` was woven between blocks *before* the round. `Station wall_balls 10x +2` lost its station. A plan day consisting only of "Rowing 5000m" took that as its title and stayed empty. **Verified:** I printed the prompt for a woman with no booked race and a locked gym day, and read it. Then I wrote a 4-week plan as the AI would, with EMOM, a climbing AMRAP, For Time, E2MOM, a mobility section, the rower and the gym day in three weeks. It imported without a single warning, every section arrived, and each gym day has its own blocks. On top: 20 new tests, the full unit suite and all four targets. **Build 1** · 22 September 2026 · · · Five improvements from the review **The library is ordered by last change.** Since the date on a card moves with every edit, the dates stood visibly out of order — an edited card said 22 Sept between two from August. What was worked on last is at the top now. **"Fetch from Watch" asks what should come back.** The Watch keeps every workout it was ever sent — including the ones you deleted on the phone on purpose. Until now they all came back. Now the line says how many are missing here, "Choose" opens the list, and only what you tick comes back. The same goes for "Restore rescued workouts": the rescued copy is the library as it was and can hold deleted cards too. **German search terms.** Every exercise in the catalogue has an English name, and "Rudern", "Kniebeuge" or "Bankdrücken" found nothing. Now they do — in every exercise picker in the app. "Rudergerät" finds the machine, "Rudern" the machine and the barbell variants, and "Bankdruecken" typed without umlauts works too. **Exercises in the editor through the grid picker.** The workout editor's search sat at the very bottom of the list with its results below the field — under the keyboard — and had no endurance filter. "Add exercise", "Add to section", "Add to every round" and "Replace exercise" now open the same grid as the workout builder: filters, search above the keyboard, pictures, and "New" for an exercise that is missing. The title says what it is open for. Several exercises can be ticked in one go and arrive in that order. Run and pause sit beside it as one button. **"Empty plan" is the third way in.** It was a third door on the plan tab beside the two that became one yesterday. It is a card on the first step now: build a new plan, start with an empty one, or paste one you have. **Checked:** eleven new tests (order, German search including "Rudergerät does not find the barbell", the choice when restoring, detached plan days), the editor tour now including "search a machine, tap it, it lands in every round", the full unit suite, all four targets. · · · Review of this version's big changes: seven bugs, three of them mine from yesterday **The AI route from the assistant lost four things.** Skipping the old AI questionnaire also skipped the start date, the import mode (whole plan or phase by phase), the locked workouts and simulations, and the free-text notes — and because the workout list was only loaded in that step, **no** locked workout reached the prompt, not even the ones from the assistant. After the questions there is now a short "Only for the AI" step with exactly those four things; nothing the assistant already asked appears there again. Workouts locked in the assistant arrive pre-selected. The phase mode also splits the plan being made, instead of cutting a twelve-week plan as six. And "I already have a plan" no longer warns about training days nobody chose. **A rowing machine from the list became 2,000 reps.** The catalogue says rowing is measured in metres — the editor ignored that, fell back to the name, and read "2000m" as a rep count. The air bike lost its 30 calories to the default 20. Both now go the same way as in the workout builder. **Editing details inside a round took the round apart.** Saving from the pencil wrote round one's block into every copy whole, including its round number. For a round from an AI or photo import, the edited exercise then stood three times in round one. This predates this version — but it is probably one of the walls you hit. **Unlinking a super-set** inside a round unlinked round one only. **"Add exercise to every round" and "Replace exercise" opened the search at the bottom without scrolling there** — on a long workout that looked like a tap that did nothing. The list goes there now. **Replacing took the new exercise's catalogue defaults** — "Wall Balls 1 × 40" became "Thruster 3 × 10". Now the slot's sets, reps and rest stay, the weight never carries over, and a change of unit (reps → metres) takes the new exercise as it comes. Inside an AMRAP the replacement is an AMRAP member instead of a 3 × 10. "Replace exercise" also sits in the pencil sheet now, not only behind a long press. **The round count could be set to 1** — the frame vanished with its stepper, and nothing could make it two rounds again. The stepper starts at 2. **What's New and the App Store text** knew nothing since yesterday. The moved AI plan especially needs saying: anyone looking for the old card will not find it. Two new cards and the store text are updated. **Checked:** eight new regression tests, both tours again, the full unit suite, all four targets. · · · One way to a plan instead of two "Can we not unify plan creation? … so that there is only one questionnaire and therefore only one Create plan button." There were two doors with two different questionnaires. The assistant asked twelve things; "AI Training Plan" asked five of them and nothing else. That was not only untidy: everything the smaller one did not ask, the prompt took from the **active** plan's config — which does not exist precisely when a first plan is being built. So the prompt opened with "No active plan yet", and **goal time, experience level, plan length, race date, weaknesses and division were never in it**. You had answered all of them, just in the other door. There is one button now: **"Create or add a plan"**. - The **first question** is how you want to start: build a new plan, or paste one you already have. - Then comes the questionnaire you prefer — unchanged, with one step in front of it. - The **last question** is who writes the plan: GritRace itself, or an external AI. Your answers become the prompt, all of them. The Pro gate sits where it sat before: at the AI, not at the assistant. **Checked:** a tour that asserts there is only one entry, that the first question appears, and that both routes are offered at the end. Plus the full unit suite and all four targets. Auditing the AI prompt — whether everything newer really lands in it, with a real run from prompt to plan to import — is the next step. · · · Rounds can be edited, and the ergometers are findable "When I want to edit it afterwards — replace single exercises, or add one to the round-based block — I hit walls and can hardly edit it at all." A round is not a container. It is N copies of the same blocks lying next to each other in the workout, drawn once with "3 rounds" over them — so every change made to the drawn round has to be written into the copies. Editing details and deleting already did that; adding, replacing and changing the count did not exist at all. - "Add exercise to every round" - The number of rounds - "Replace exercise" - Linking a super-set inside a round **Build 1** · 21 September 2026 · · · Intensity curve: twelve blocks were four bars "This view has a bug, I think." In the session report the intensity curve drew four bars for twelve completed blocks, the bars ran off the top of the chart, and the block names stood in grey **inside** them instead of underneath. One cause for all three: the x value was the block's **name**. An interval session repeats its names — four 833 m repeats are four blocks all called "833 m" — and on a name axis equal values are a single bar that the marks **stack** onto. So the four bars were sums of heart rates, far above a scale that ends ten beats over the hardest block. Two things then made it look like a drawing fault: a bar with no base given starts at 0 while the scale starts around 110, so the part below was painted straight over the axis labels. Every block now carries its own position, every bar knows its base, and a zone line outside the scale is not drawn at all. Four bars become twelve, and the names sit under the chart. **Checked:** five tests on the series (one bar per block, the order, repeated names, pauses and blocks without a pulse, shortened long names) — and, for the first time, a picture: `-SeedIntervalSession` writes exactly this session so the screen can be reached at all. The demo data produced no finished workout of any kind before, which is why nobody had ever seen this chart. · · · Four reports: the edit date, scrolling names, three save buttons, the comparison **An edited workout now carries the date it was edited.** The card showed the day the workout was built, and that never changed — the merge on save deliberately keeps the stored card's date so an id the rest of the app already holds does not age underneath it. There are two dates now: the creation date stays where it is needed, and an edit date is stamped when you save yourself. The card shows the later one. A plan day arriving again in the background is not an edit and leaves the date alone. **Names no longer set off walking while there is room to the right.** "The text does not really need to scroll yet, there is plenty of space to the right of it." A scrolling name fills the width it is given — and a spacer beside it is just as greedy, so the two split the row and the name got half of it. It affected the simulations list, race history, the picker in Compare, Analysis, the training plan, and three places on the Watch. The name column now takes what is left, and the time on the right still sits on the right. The line underneath (date · format · partial) no longer wraps. **One save instead of three.** "One save would be enough." The simulation planner had "Save" at the top, "Save plan" at the bottom and "Save simulation" right below it — and the two at the bottom were not even the same action: one closed the planner, one stayed. There is exactly one button at the bottom now, called "Save simulation" for a simulation. "Send to Apple Watch" and "Start on iPhone" still save on their own, because an unsaved simulation cannot be sent; the footer says so now. **Compare: recorded transition times are visible again.** "The blue race was tracked with transitions back then, but you cannot see that in this overview any more." The transition row was gated on the FORMAT — a Deadly Dozen has no roxzone, so it was never drawn for one, although the recording held 16:31 of it and the total above counted it. The parts stood minutes short of the whole, and exactly one of the three races looked slow for no visible reason. The recording now has a say, not just the format. Two formatting faults on the same screen went with it: the row labels were untranslated ("Total time", "Labour total", "Roxzone total"), and with three races the label had about 70 pt left and came out as four characters — the columns are narrower with three races now, and a long label shrinks instead of truncating. The headings also take the right plural. **Checked:** new tests for the edit date (an edit stamps it, a background upsert does not clear it, cards stored before the field still decode) and for the transition row (a tracked race earns one, a race without does not get an empty one, one of three is enough, HYROX always keeps it). Plus the full test suite and all four targets. · · · Rescued data is visible now — and workouts can be read back from it When a record cannot be read, the app puts the original file aside before anything is written over it. That copy used to be invisible: no screen named it, no path read it back. A rescued card was kept and still lost. - Settings → Data & Backup → "Rescued data" - "My Workouts & Runs" → "…" → "Restore rescued workouts" **Build 1** · 15 September 2026 · · · Crash when deleting a block fixed In the workout builder, "Delete block" on the last block closed the app. Every block card was bound to a POSITION in the list. Deleting animates the card out; on its way out it drew its intensity switch once more and read a position that no longer existed ("Index out of range"). Cards are now bound to the block's id, and a card on its way out gets the value it was drawn with. The exercises inside a block (warm-up, AMRAP, EMOM, For Time) and the exercise cards in Quick Workout were built the same way. Both are changed the same way. **Checked in the simulator:** built three blocks, deleted the last one (an open AMRAP), then the first, then the only one left, and in a list removed the last exercise and then the only one. In Quick Workout, added two exercises and removed the last. No crash, no new crash report. · · · Workout from a photo of the class card is free "From a photo of the class card" under "My Workouts & Runs" no longer needs Pro. The tile carries no PRO badge, the import opens directly instead of the paywall, and the paywall no longer lists it as a Pro perk. "What's New" mentions it without "(Pro)". The card is still read on the iPhone only. Folding explanations The explanations under "Race the Roxzone", "Roxzone per round" and "Transition break" show two lines and "Show more". A tap opens the full text, "Show less" folds it again. The section is mostly switches again rather than paragraphs. · · · Simulation: texts for "Between run and station" New texts in a simulation's "Between run and station" section. Text only, nothing changes in behaviour: - Info button (?) - Transition break - Race the Roxzone - Roxzone per round - For DEKA each says "transition corridor" and "zone". The Roxzone therefore stays a real, run time. A purely virtual Roxzone that is only added on top is deliberately not built. Fixed along the way: the lap read "1.0 km" on a German screen → "1 km" or "1,6 km". The "This gives you" summary wrapped onto two lines since "8 Stationen" ("8 × 1.000 / m") → one line, "8 × 1 km". **Build 1** · 14 September 2026 · · · Simulation: the lap follows "How much" A half HYROX simulation halved every station and still left eight full kilometres between them. The choice now sets the lap too, in every format that has runs: - Half / Quarter - Full - Still adjustable - Applies to the quick choice, "Advanced editing" and a simulation a training plan prescribes as half or quarter. Another format or variant takes the new shape's lap. Another division leaves a lap set by hand alone. ATHX has no runs and stays as it is. - With every run deselected, the lap and "0 × … m" disappear from the quick view. **Checked in the simulator:** HYROX Half → 500 m and "8 × 500 m", Quarter → 250 m, "+" → 300 m, Full → 1.0 km, Deadly Dozen Half → 200 m. Fixed along the way: "8 Station" → "8 Stationen", and the planner's headings appended an "s" ("Stations", "Laufs" on a German screen) → "Stationen", "Läufe", "Zonen". Four explanations in the planner (smart pacing before demanding and easy stations, the unchanged total run time, ATHX targets) were still English on a German device. · · · Workout builder: a workout made of blocks The workout generator knew one goal per workout: race prep, strength or run. Since yesterday it had sections, but only before or after the main part. "Warm up, then mobility, then conditioning in rounds, then ten minutes of AMRAP" could not be built that way, let alone strength with a run in front of it. So the generator is rebuilt: **a workout is made of blocks.** **How it works.** "My Workouts & Runs" → "Create new workout or run" (the same from the plan tab, the editor and "Alternative Training" → "Generate" for a plan day): - Add block - Then the next block. Only the block just added is expanded; every other one shows a single line ("Conditioning · 3 stations · 4 rounds · with runs · ~48 min"). Tap to expand, "Done" to collapse. - Each block's menu: move up/down, duplicate, **save as my block**, delete. - Adding a block offers **Templates** (Gym WOD, Strength + finisher, Run + strength, Race sim light) and **My blocks**. A block of your own is added collapsed; press and hold to delete it from the list. **In the header:** block count and estimated duration, a timeline in the blocks' colours, the format (as soon as stations are involved), the **intensity** for all blocks and the **target duration**. Any block can have its **own intensity**. The target duration adapts only strength and conditioning blocks (sets and rounds). A time cap, EMOM intervals and a run's minutes stay as you set them. **The block kinds:** - Warm-up / list - Strength - Conditioning - AMRAP - EMOM - For Time - Run - Every block has a name and a **rest after the block**. **The preview** below shows each block under its own heading ("BURN OUT · 8 MIN"), with duration, run distance and "Apple Watch" when the workout needs the Watch. From there: **Save** (confirm the name, then the workout opens), **Editor**, **AI** and, for a plan day, "Use for this day". "Import via AI app" and the strength profile are in the "…" menu at the top right. **New on the Watch:** - EMOM - For Time - A chip at the top says which block and which round you are in ("R2/4"). - A workout that is nothing but an EMOM with one-minute intervals runs as an EMOM on an older Watch too. Anything else is walked once there. **Removed because it was a duplicate:** the old Smart Generator, including yesterday's section cards. Everything it could do now lives in a block. The rule "after a hand edit, rounds and target duration are locked" is gone as well, because every block is built on its own. **Checked in the simulator:** built a warm-up (Jump Rope, Mountain Climber), mobility (World's Greatest Stretch, tick off only), conditioning in 4 rounds and an AMRAP "Ausbrennen" with Wall Balls and a ladder, saved it and found it again in the library. "Start on iPhone" is missing there, because an AMRAP needs the Watch. Fixed along the way: - A swipe down closed the builder with all its blocks, without asking. Once something is built that no longer works, and "Cancel" asks "Discard this workout?". - Station loads at 100 % intensity - "1 exercises", "1 rounds", "1 stations", "1 sets" → singular. - Creating an exercise in the builder whose name already exists ("Push Ups"): the question "This exercise may already exist" appeared and vanished a moment later together with the create sheet. Nothing was created, nothing selected. The exercise picker now opens from the builder itself instead of from the block card, and the question stays. - "25 reps" and "1:30 rest" appeared in English on a German device too (preview, editor, Watch) → "25 Wdh.", "1:30 Pause". · · · Duplicate exercises: detect, ask, merge An AI import wrote "Push Ups" where the catalogue says "Push-Up", and the app only compared identical names. The exercise was created a second time, its history split in two, and "last time" knew only half of it. **Detection.** Names count as the same exercise when they differ only in case, hyphens, spaces, word order or a plural ("Push Ups" / "Push-Up", "Walking Lunges" / "Lunge Walking", "DB Snatch" / "Dumbbell Snatch"); common German names ("Liegestütze" → Push-Up, "Kniebeugen" → Squat, "Klimmzüge" → Pull-Up, also without umlauts as in "Liegestuetze"); and small typos in long names ("Russain Twist"). A name that differs only by equipment ("KB Goblet Squat" / "Goblet Squat") is offered but not preselected, because it is often a different exercise. Deliberately never confused: front/back squat, incline/decline, pull-up/push-up, hack/back squat, push press/push-up. **At an import** (AI plan, AI workout, class-card photo): before saving, "Already in your exercises?" shows one row per name, with "Use existing" or "Keep as new" and the reason. The answer is remembered. The next import links a known name silently and does not ask again about one kept as new. **When creating an exercise** ("New" in the exercise picker, library, Quick Workout, editor): if the name matches an existing one, the app asks "This exercise may already exist", with "Use "Push-Up"" or "Create anyway". For an identical name there is only "use". **Retroactively, once after the update:** after What's New, the app checks every stored exercise name and shows "Duplicate exercises": per group a switch, the name to keep (the catalogue name is proposed, otherwise the most used) and why they belong together. "Apply" renames everywhere: custom exercises, saved workouts, the active and archived plans, finished workouts, the performance log and the "last time" values (a real value beats an estimate, otherwise the newer wins). Unticked groups are remembered as separate. "Later" asks again on the next launch. Any time: Exercise Library → "…" → "Find duplicate exercises". Only a store in which something actually changed is written, so an unreadable file can never be overwritten with an empty one. **Checked in the simulator:** the demo data really did contain "Burpees" beside "Burpee". After merging, a second search finds nothing. A pasted AI workout with "Liegestuetze" and "Burpees" asks before saving; "Push Ups", linked once already, is taken silently. Creating "Push Ups" offers "Use "Push-Up"". Also fixed: an exercise created with the same name as one of the athlete's own was silently dropped on save, and the picker selected nothing. **Build 1** · 13 September 2026 · · · AMRAPs and lists inside the workout generator In the last build "Build by hand" was only a button in the generator that opened the editor on an empty workout. Sections are now part of the generator itself: under **Race Prep / Conditioning**, below the stations, there is a **Sections** area with "Add AMRAP" and "Add list". **Each section is a collapsible card.** Collapsed it is one line ("Finisher · 12 min · 2 exercises · after the main part"); expanded: - Kind - Name - Time cap - Exercises - Any number of sections, e.g. a warm-up AMRAP before, a mobility list after. **What it becomes:** the live preview shows every section under its own header ("FINISHER · 12 MIN"). Saved, it is an ordinary workout with sections: the editor frames them, the Watch runs the AMRAP in rounds and asks for the reps of the unfinished round at the end, and the library hides "Start on iPhone" because an AMRAP needs the Watch. **Rules that apply:** - Rounds - Target duration - Sections only, no stations - Station loads - Edited by hand - The **AI** button receives the sections as part of the brief. The separate "Build by hand" card is gone again. The editor is reached through "Edit" in the preview. Also fixed: the German "%lld Whd." typo now reads "Wdh.". · · · "Build by hand" now lives in the workout generator A gym card is conditioning, so that is where it sits now: **Create new workout or run → Race Prep / Conditioning → Build by hand**. The card is at the very top, above format and stations, and opens the editor with sections, the AMRAP time cap and the ladder. Saving works as before in the editor (with a name); the generator then closes too, and the workout heads "My Workouts & Runs". From "Alternative Training" for a plan day, the workout you built is taken for that day straight away. That leaves the library with two ways in, **side by side** instead of stacked: "Create new workout or run" and "From a photo of the class card" (PRO). The list below moves up by one button height. On the Watch, when setting up a simulation, the German subtitle now says "Du läufst sie" instead of "ihn" (the Roxzone and its counterparts are feminine nouns). · · · There is no "gate button" on the Watch The plan screen said "Press the gate button 2× to advance". The Watch has no such button: the gate is the **screen**, tapped deliberately several times, never the Double Tap gesture. On the Ultra the **Action button** works too, if it is turned on. Now: "Tap the screen 2× to advance", or "… or press the Action button …". The two short hints during a race now also say *where* to tap: "At the gate, tap the screen 2×" and "At the exit, tap the screen 2×" instead of "Tap 2x at the gate ->". · · · Tile race layout for large watches, in every format After the sketch for the wrist: two big values side by side and "Up next" at the foot, on the run and station screens, for HYROX, Deadly Dozen, DEKA and ATHX. **Run:** **PACE** on the left. On the right **PLAN** with the countdown against the plan, or **PULSE** without one. The countdown is the number you act on while running; the pulse then sits small in the row below, alongside plan delta and finish prediction. Below that SEG · TOTAL on one line, and at the very bottom the "Up next" card, e.g. "SLED PUSH 50m · 152 kg". **Station:** name with load or distance, then **PLAN** (or **TIME** without a plan) and **PULSE**, SEG · TOTAL, and at the bottom "Up next: RUN 3/8 · 1000m · 5:00/km" or "FINISH". **Where:** from 44 mm and on the 42 mm Series 10/11, i.e. watches with at least ~220 pt of height. 40/41 mm keep the stacked layout; the tiles do not fit there. **What the card says is right:** it reads the same routing as the gate tap and READY. A test walks races in all four formats without breaks and at every gate compares the announcement with what actually starts (for ATHX including the mandatory feed and recovery zones). **Clicked through in the simulator on 40, 42, 46 and 49 mm.** Found and fixed along the way: - At the start of every lap the right tile flipped from PULSE to PLAN for a moment, because the countdown arrives one tick later. The same thing briefly pushed the header up on 40 mm. The plan decides now, with "--:--" until the first value. - On 42/44 mm the station screen was too tall: the header slid under the clock and the card at the bottom was cut off. The times now share one line, name and tile values are one size smaller, and the finish prediction appears on the run screen only there. - With the pulse in the times row it read "SEG 0… GE… 0…". The pulse now sits in the row under the tiles. - "JOURNEY 2/12" was cut to "JOURNE…" because the detail had equal priority; now the detail truncates first. - On 40 mm the "tap at the gate" hint pushed the header off screen for its first 8 seconds; small watches no longer show it. - Newly translated: "Puls", VoiceOver "Current pace". For testing: `-DemoSimulation dd|deka|athx` loads a simulation and `-DemoWalk` clicks it through automatically (debug builds only), so every screen can be checked on every watch size. · · · Simulation on the Watch: the break always says what is next, and everything fits Feedback from a HYROX simulation on a real wrist: "Sometimes the transition is missing what comes next, and sometimes it is there. And the screen is still filled too big." Both were true, and I clicked both through in the simulator on the smallest (40 mm) and the largest watch (Ultra 49 mm). **"Sometimes missing" had a pattern.** The transition break comes twice a round, before the station and before the next run. The screen only knew stations, so before every run it showed a bare clock. Now it says "UP NEXT: RUN 2/8 · 1000m · 5:00/km", before a station the station with its load or distance, and at the end "FINISH". It reads the same numbers READY routes from. A test walks real simulations (HYROX, stations only, mixed skips, Deadly Dozen first half, ERG/Mile/Strong) and at every break compares what was announced with what READY then starts. **"Filled too big", measured on 40/41 mm:** - Transition break - Station - Headers - Run with a plan - On small watches pace, countdown, station name and timer are slightly smaller. Large watches keep their sizes. **Fixed in passing:** - "RUN x/y" counted against all rounds instead of the race's runs, so a Deadly ERG showed "RUN 1/12". - Missing German texts on the Watch: "Up next", VoiceOver for the finish prediction, "Show what this workout contains". · · · Photograph the card from your class "Just take a picture … and it gives me the WOD." Under "My Workouts & Runs → From a photo of the class card" you scan the card (the camera finds its edges and straightens it) or choose a photo. It is read on the iPhone — no upload, no model, no network. **Pro feature:** without Pro the entry is marked PRO and opens the paywall, which now lists it too. **Built against your real photo, not an invented card.** The Thursday card looked different from what I had pictured: two columns, plus a logo, a date and a footer; the ladder is ONE line, "5-10-15-20-25-.....", above four exercises that carry no number of their own; the sprint is a sentence ("Sprint for 20 sec. after each Round"); the heading reads "Mobilty:"; and the text recogniser reads "Seil." as "Seifi,". Exactly that recognition — the text and position of every line — is now the test. Result: Warm Up · AMRAP 8, Mobility to tick off, Oberkörper · AMRAP 30 with 5 → 10 → 15 → 20 … and the sprint as 20 s. Name: "Hauptwerk Donnerstag". **Before anything is saved, you see three things:** - the workout the way the Watch will run it — sections, time caps, rungs, tick-off items; - the text it came from, to correct — the preview follows every keystroke, and a "Seifi," is one word to fix; - everything on the card that was not used (logo, date, footer). Nothing disappears silently. Then "Save to My Workouts", or "Edit in detail first" in the editor. **What the card may write:** the quantity first or last ("10 Burpees", "Burpees 10", "250 m Ski", "15 cal Air Bike", "20 sec Sprint"), sets × reps, weights including "2x24kg", the time cap on either side ("AMRAP30", "30' AMRAP"), ladders as a sequence or as "+5 reps per round". A falling sequence that ends, like "21-15-9", is a scheme and is never guessed into a ladder. EMOM and For Time are imported as a list — with a notice. The AI workout import now understands the same text grammar; the instructions to the AI contain an example that a test runs through the parser. **Fixed in passing:** - "Ski 250m" asked the wrist for 1 rep — only known names ("carry") derived distance as the unit. - The library estimated an AMRAP 30 from its exercises walked once instead of from its time cap. · · · AMRAP and rep ladders — build the gym card by hand The starting point was a photo of a WOD card from a HYROX class: a warm-up AMRAP 8, a mobility list without a single number, then "Upper body" as an AMRAP 30 — floor press, front squats, swings and lunges, five more reps every round, plus a 20-second sprint. **"AMRAP is missing completely." It was not, quite — and that was worse.** AMRAP was a setting on the WHOLE workout, and the one screen that could set it was never reachable: its trigger was declared and never assigned. Had anyone reached it, round 2 would have overwritten round 1, because the round wrap never reset the finished exercises. An AMRAP 30 would have kept its last pass and nothing else. That screen is deleted. **Sections.** A workout is now made of parts: an AMRAP section loops against a time cap, a plain section is walked through once. In the workout generator under "Race Prep / Conditioning → Sections" you add them, give them a name and a time cap (quick picks from 5 to 60 min, plus ±30 s), and add the exercises of one round. An exercise without numbers ("Tick off only") is a mobility item you simply confirm on the Watch. **The ladder.** Under every exercise in an AMRAP: "+5 reps per round", with a preview "5 → 10 → 15 → 20 …". Only the step is stored; each round's rung is derived, so nothing about it has to survive a crash. **On the Watch.** In an AMRAP the clock counts down and names the round in progress ("R3") — on every screen that shows a clock, not just the run screen, because a WOD of presses and swings has no run in it. The rep field jumps to the new rung each round. When time runs out mid-exercise, the Watch asks: "TIME'S UP — how many?" (crown or ±, starting at 0: a fumbled confirm must not claim work nobody did). The score then reads "6 + 47": six whole rounds plus everything done in the round the clock cut. **Results.** The summary shows each AMRAP section's score and average round time; the iPhone companion shows the section, the round in progress and the rounds done. History, details, CSV export, the notification and Strava now all read the same score — each used to read a field that held only the LAST section, and the warm-up disappeared. **What an AMRAP does not do: remember "last time".** The app remembers the last values per exercise and fills them into future sessions. A ladder would have taught it "Floor Press 30 reps" — the top rung — and overwritten next strength day's 3×8; the other way round, a strength day would have overwritten the ladder start of a WOD. An AMRAP now gives nothing, takes no reps, and only fills an empty weight. And six rounds of one set came back into the training plan as a six-set exercise — doing the day again asked for six sets per round. **Watch only.** An AMRAP needs a clock that counts down, a round wrap and the question at the end; the iPhone tracker has none of them. "Start on iPhone" is no longer offered for these workouts, and where it is still reached, a notice explains why. **Fixed in passing:** - A pause landing on the open "time's up" question (the iPhone's pause button) made "Save" close the question and move nothing on. - The editor title read "New Workout" on a German device. - The VoiceOver label "Target pace" had no German translation. **Build 1** · 9 September 2026 · · · And station times scale with you now The last step out of the roxzone fix, and the one that makes the decomposition real. `stationDefaults` was **one number per station for everybody** — 32:00 in total. The same tables put an elite athlete at 28:12 of station work and a back-of-field one at 44:06. The constant was only right in the middle, and only while the roxzone was wrong by about as much the other way. The two errors cancelled: that is why the **total** was believable and every **part** was not. That number now comes from the ladder too — each rung states its station targets and its pace, and everything between is interpolated. A default plan therefore reproduces its own ladder: | Rung | Runs | Stations | Roxzone | Goal | Ladder | |---|---|---|---|---|---| | Sub-75 | 40:48 | 28:12 | 6:00 | **1:15:00** | 1:15:00 | | Sub-85 | 46:24 | 31:49 | 6:48 | **1:25:01** | 1:25:00 | | Sub-100 | 55:28 | 37:01 | 7:28 | **1:39:57** | 1:40:00 | | Sub-114 | 61:20 | 44:06 | 8:32 | **1:53:58** | 1:54:00 | Three seconds at every rung. Two days ago the same plan was out by between +3:48 and −12:08 — and before the roxzone fix it read 1:29:52 at 5:00/km instead of 1:14:12. **Autofill gets better from this, not more optimistic.** It used to squeeze pace and station times until the sum matched, and because the stations were frozen the pace had to do all the work. Anyone asking for a target time got paces **below** the percentile they had asked for. **ATHX stays out on purpose.** Its zone durations are the organiser's; only the loads are the athlete's. A plan that stretched them for a slower athlete would be describing a different event. · · · Roxzone and transition break now stand side by side "Aren't those two the same thing?" — a fair question, because in the planner they sat **two hundred lines apart** under near-identical names. They are opposites. **Roxzone: you run it.** The corridor is part of the race — from the track to the station, as at an event, and the time counts. **Transition break: you walk.** A pause to get from the treadmill to the station in the gym. The Watch times it live and ends it when you tap READY. Both now sit under one heading — "Between run and station" — in the planner and on the Watch, with a line on when to take which: simulating a race in a hall, run the corridor; training in a gym where you have to reach the machine first, take the break. **And on Deadly Dozen and ATHX the Roxzone switch is gone.** Those formats have no corridor — the walk from the track to the station is already inside the journey and the station. The switch was offered anyway and did nothing there but swap a badge. That was the other half of the confusion. In passing: the Watch test target had not been runnable since the app was renamed — its test host and its module import both still carried the old name, and the scheme failed before the compiler ever saw it. Both halves fixed. · · · The roxzone was priced three times too high "At least the 1:07 min is set very high." It was — and the 1:07 was the smaller half of it. **The app held two readings of one number at once.** `estimatedRoxzoneSingle` was defined as half of it, so the parent meant the whole round — which is how the Watch countdown and the race analysis used it. The planner fed that same value into a field it labelled "per direction" and doubled it at six call sites. Both halves were internally consistent; the seam between them was one line. **And the formula itself was too high.** `50 + (pace − 240) × 0.28` was a literal, with the app's own benchmark ladder saying something entirely different beside it and nothing holding the two together. At 5:00/km a HYROX plan booked **17:52 of corridor** where the field-calibrated ladder — 395,452 results — budgets 6:00. **DEKA was worse and nobody had noticed:** 22:20 of transition against 20:50 for all ten zones combined. The walk between stations was priced above the stations. Now there is **one number and it is a round — in and out together.** The same unit the race analysis already reports, and the only one you can measure; nobody times half a corridor. The `× 2` is gone everywhere. **The curve is derived, not invented.** Every rung of the ladder states a total, station targets and a run pace — what is left over IS its corridor budget. Divide by the round count and you have one round; interpolate between rungs for everything else. HYROX lands on 45 s (Sub-75) to 64 s (Sub-114) per round, DEKA on 15 s. Change the ladder and the estimate follows — the same rule `DekaMarks` already uses for its thresholds. What that does to goal times, at 5:00/km: **1:29:52 → 1:18:00**. And at Sub-85 pace the planner now hits the ladder within twelve seconds, where it used to miss by fifteen minutes. **Two things will change that look like a regression.** On the wrist, racing exactly to plan used to build a phantom cushion of about eighteen minutes and read "ahead of plan" the whole way — that now sits at zero. And Plan vs Actual recomputes for old races, because every race carries its plan: "fifteen minutes under plan" becomes "roughly on plan". Both are the correction, not the fault. **Removed in passing:** `roxzoneOverrides` — a field nothing ever wrote, which gave `roxzoneTime(for: n)` an `n` that could not change the answer. The Watch countdown now reads the plan instead of recomputing from its pace; a hand-set value used to be invisible to the wrist. And the roxzone history bypassed `analysisRaces`, so demo races could pre-empt your own. **What stays open, stated plainly:** station times are a fixed constant (32:00) where real ones scale with the athlete. While the roxzone was eleven minutes too fat the two cancelled; now the default plan runs optimistic at slower paces. Anyone who autofills to a target sees none of it — and gets cleaner numbers, because nothing is being squeezed against an inflated corridor any more. **Build 1** · 7 September 2026 · · · Postscript II: the Action button needs a background mode "GritRace can't be selected on the Watch." The metadata in the bundle is correct — watchOS indexes the app and instantiates the intent, which the simulator log shows. The one provable difference from the reference implementation that demonstrably works: it declares `WKBackgroundModes = [workout-processing]`, and we declared no mode at all. This is in no Apple documentation — the Action button article never mentions the key. It is developer-verified, not Apple-stated. So it is recorded here as what it is: the most likely cause, not the proven one. The difference from the background mode that cost a release weeks ago matters: `location` was **not a valid value** of that key, `workout-processing` is one — and it is the right declaration for an app running a workout session on the wrist. The guard against the location crash reads `UIBackgroundModes` and is unaffected; the tests prove it. · · · Postscript to build 173 The build passed, the archive too — and App Store Connect refused it at ingestion: **ITMS-90626, "Invalid Siri Support"**. An App Intent's description may not contain the word "Apple", and mine read "Starts the race currently loaded on your **Apple** Watch." It now says "on your Watch". Nothing else changed. The interesting part is where the check lives: not in the build, not in the archive, but at ingestion — Xcode validates the structure of the metadata, never its values. Same family as the background mode that cost a release weeks ago after a clean archive. · · · The Ultra's Action button advances the gate Settings → Apple Watch → Gate → "Action button". Off by default. **You still start on screen.** That is the point that makes this possible at all: Apple gates the button's next action on a RUNNING workout session, not on the button having started it — "in most cases you want to donate a next action regardless of whether the user presses the Action button or launches the session from within your app". So: tap START on the Watch as always, and from then on every press of the orange button advances a gate. A wet screen and a wrist held down stop mattering — it is a real button. The press goes through the same function as the screen tap, so it inherits every guard the gate already has: the three-second block against false advances, the undo, the haptics, the HealthKit lap event. A second place that decides what a gate tap means is a second place for the two to drift apart. **What the app cannot do, stated plainly next to the switch:** there is no API to ask whether you assigned the button to GritRace. None. So the switch is off until you turn it on, the instructions sit in the text below it rather than in a confirmation, and the screen tap stays the guaranteed path. Ultra only — no other watch has the button. And because a press does not announce that it came from a button, the same test applies here that led to the double tap being removed the week before: a gesture the wrist detects on its own fails it. A press on a recessed button you had to assign in Settings first is a decision. · · · The race screen had dead zones Found while checking the Digital Crown idea, and probably the real cause of "sometimes it just doesn't take the tap". A `VStack` is layout only. It hit-tests where its children draw — and both race screens are built around `Spacer`s. A tap landing in the gap between two cards never reached the gate gesture. From the outside that is indistinguishable from the watch having ignored you. One line per screen, purely additive: no new gesture, no new control, no change to what a tap means. Just more of the screen accepting the tap you were already making. **Build 1** · 6 September 2026 · · · The comparison table reads as a comparison now Four complaints in one paragraph, and all four had the same cause: the columns had been sizing themselves. **The time is on top.** Journey or labour time is the number in question; the pace under it, the heart rate under that. It used to be one line of three values joined by dots — and wherever one of them was missing, the rest slid. **The bar spans the whole column** instead of sitting at the edge as a 10-point stub. And it is no longer scaled from zero: two laps of a race differ by seconds on a base of minutes, so 4:38 and 4:55 were drawn as 94 % and 100 % — two visibly equal bars in a table whose only job is to show which one is longer. The fastest and slowest time in **that row** now set the ends, with a floor so the fastest bar stays a bar rather than a dot. **The two races stand close together.** Twelve points of air between the columns was enough to make the eye travel. **And nothing shifts any more — neither up nor sideways.** Every column is the same width, in every row; every row draws the same lines, empty ones included. A line one column has and its neighbour does not is exactly the shift this was about. **Two numbers that said nothing, fixed in passing:** the volume (`1 km`, `50 m`) was repeated in each column although it is the same for both races — it now sits once under the station's name. And ten metres of a 1 km SkiErg is 1.6 seconds, so both races read "0:01/10m"; long pieces get their rate per hundred metres. On HYROX the lap is exactly a kilometre — "4:25" above "4:25/km" was the same number twice, so the pace line is dropped there. · · · The overview charts are taller 90 points for eight runs of two races: the two lines lay on top of each other although thirteen seconds per kilometre separate them. 150 points and a fourth gridline — same data, visible gap. · · · The temperature at the gun Next to "Outdoor" in the race analysis it now says how warm it was at the start. The source is Apple Health: watchOS stamps temperature and humidity onto an outdoor workout when it **starts** — so it is the value at the gun, not the value now. A weather service asked afterwards could not do that: it no longer knows the hour. When there is none — an indoor race, one recorded on the phone, an older watchOS — nothing is drawn. No placeholder, no zero. · · · The fuelling card appears as soon as a race is planned It used to show up only once weight *and* start time were entered — which is precisely when you no longer need it to get set up. It now stands there from the planned race onwards and says what it is still missing: "Add your weight" or "Add the start time for exact times". The times still only become correct once the start time is there — that was never the question. **Build 1** · 2 September 2026 · · · Race morning is a timeline now "It's just a simple list." True — and there were four separate problems in that sentence. **No position.** A list of times is a plan. On the morning itself you are *inside* it, and the one question you have then — what is next, how long have I got — cost reading six rows and doing arithmetic. There is a rail now, and it fills as the morning passes: the line between two nodes grows with the clock, so a glance says "closer to the gel than to breakfast" without reading a single number. The next step carries a countdown, and one line at the top says what is coming. **No shape.** Rows of equal weight claim every step matters equally. They do not: one is next and the rest are context. Past nodes recede, take a checkmark instead of a dot and are struck through; the next one is the only thing on the card with a filled background and a ring around its node. **No sign you could tap.** Every editable row now carries a chevron, and the header says it once in words rather than repeating it under every row. **And the gun looked like a meal.** It is the thing everything else is measured from, and it was a row like any other. It closes the rail, in the format's own colour, with the chequered flag. **Collapsed in passing:** the same `switch` over step names lived in **four** files — screen, timeline, Today row and notification scheduler. A new kind would have arrived as "Reminder" in a notification while the screen named it properly. And the position is decided in the model, not the view: it has edges. The step happening **right now** is neither past nor upcoming — and a view working that out inline is wrong at exactly the minute it matters. · · · Have a plan written — and read it back in Two steps, as with the training plan: build and copy a prompt, paste the answer and look at it before anything is saved. **The prompt carries your numbers.** Weight, race, start time in the venue's clock, the tier, the day's gram target. "Write me a carb-loading plan" returns an article; "82 kg, 790 g on Saturday, gun at 09:30" returns a timetable. **What it does not ask for is the amount.** That is computed here — from the guidelines, with the slider you set — and a model asked for it will produce a different one with the same confidence. It is asked for what it is good at and the app is not: turning grams into meals, by name. And your own words go at the very top, above everything computed — allergies, what you cannot stomach early, what is in the cupboard. **The prompt is a contract, and both halves are code.** This project learned that rule expensively on the training-plan prompt: there, the prompt once demanded a line the parser rejected outright, and a grammar block called itself complete while omitting three required tokens. So grammar and parser live in **one** file here, and a test runs the prompt's own example through its own parser. If the example does not parse, nothing will — which is exactly how the other prompt shipped broken twice. **The format survives what models do.** A clock time *or* minutes before the start, both. A missing minus is read as "before" — otherwise breakfast lands three hours into the race. The typographic minus counts too. A code fence does not get in the way. Prose around it is **named**, not swallowed: the warning list is the product here, because a silent drop is exactly how a plan arrives missing a meal and nobody notices. **An import replaces the morning, it does not add to it.** Otherwise the computed steps would sit between the model's and you would have two breakfasts twenty minutes apart. Reset brings the computed plan back at any time. · · · The fuelling plan is yours now **The slider is at the top, and the plan comes straight after.** It used to sit third, under the weight and the protocol — so you scrolled past a number before reaching the control that sets it. The amount *is* the decision here; everything below is its consequence. What explains the plan rather than being it — the tier and the weight — now sits underneath. **And every point of the race morning can be changed.** Move the time, write down what you will eat, leave a step out, add one of your own. The note is the important half. A computed plan says "pre-race meal, 240 g of carbs" — correct, and still a decision to make at half past five. A plan that says "porridge, banana, honey, the big mug" is one you can follow half asleep. So the note field is the widest thing on the screen, and the time sits above it rather than the other way round — and it is shown as a **clock time**, not "three hours before": an alarm is set for 05:30. **The plan does not freeze in the process.** The obvious approach — compute once, save, edit the copy — would be the wrong one. Everything in it hangs on four things that keep moving: weight, start time, predicted finish, slider. A saved copy stops following them. Move the race by an hour and it still points at the old gun. So only the **difference** is stored. Move the race and every untouched step moves with it — the one you dragged to 06:15 stays where you put it, because you said so. And a step you set back to its computed minute loses its exception rather than recording the same number again. **The reminders and the Today row see the same edits** — otherwise the card would announce a step you had struck out. · · · The sequence in the race planner Three complaints, the most concrete first: **"one is pushed down and the others are not."** A station's chips decided **per row** whether they fit on one line. The widths differ by a character — "2 × 24 kg" fits, "2 × 12.5 kg" does not — so a single station in the middle of the list dropped onto two lines while its neighbours stayed on one. A list whose rows disagree about their own shape reads as broken even when every row is individually correct. It is decided **once** now, from the widest station in this format and division, and every row obeys. **And the load was there twice.** "240 m · 2 × 24 kg" as text, directly above chips that say the same thing and can be changed as well. Two copies of one fact, and the row was the taller for it. The text is gone; the chips are the truth. Only a skipped station keeps the summary — it has no chips. **In the space that frees up is what you asked for: your best time.** Not the fastest ever, but the fastest **at the load currently set**. A 6 kg wall ball says nothing about a 9 kg one, fifty reps nothing about a hundred. Turn the weight down and the number changes with it — or disappears, if you have never done it that way. That is what makes it useful for judging rather than a trophy. From three performances the **usual** value stands beside it: "Best 2:14 · usually 2:31". One fast day is not a plan. · · · The slider — and what the evidence actually allows The amount is your decision now. A slider between the low and the high end of the published band, with both ends labelled in **your** grams rather than as "low" and "high" — the point of the slider is the difference, and the difference is food. **The default now sits in the middle, not at 0.75.** That leaned on a hybrid argument that turned out to be the wrong mechanism, and the same literature says supercompensation *above* normal stores shows no consistent benefit at high intensity. Starting someone near the ceiling of the most aggressive protocol is not a neutral default. **Under the slider is the finding that actually carries the decision:** above about 10 g per kilo, no study has found more glycogen — only more food. A 2025 meta-analysis finds no correlation between amount and supercompensation across 465 to 875 g a day, and the highest dose ever compared head to head is 10 g/kg. **What it buys** — measured against your own predicted duration. Over 90 minutes: fatigue postponed by roughly a fifth. Between 60 and 90 minutes, where most people here land: **no measured effect**. And the sentence that helps you decide: every trial that showed a gain compared against a *genuinely* low-carbohydrate diet. The one trial with an adequately fed control arm found nothing. **What it costs.** Fullness is the one complaint that reliably rises with the amount — the 2026 dose-response trial found no other GI symptom increasing from 6 to 10 g/kg. And the weight: a kilo of water bound to the glycogen, not fat — **with the honest addition that the most recent controlled trial found no gain at all at 10 g/kg**. The famous "3 g water per gram of glycogen" comes from a single 1970 paper, the ratio ranged from 1:3 to 1:17 depending on how much subjects drank, and several studies found no association whatsoever. Plus the reassurance: there is no weigh-in in this sport. **And on drinking I wrote the opposite of what was asked for.** "Drink plenty" is not a harmless suggestion here: the international consensus on exercise-associated hyponatraemia names drinking beyond thirst as the **primary cause**. On top of that, drinking more stores no extra glycogen — resynthesis was identical whether 400 ml or 3,170 ml was replaced. So what it says is: drink to thirst. · · · Fuelling now sits under its race "I'd like this in the upcoming-races card, under the race." It was a card of its own above them — and that was one card too many for one fact. Fuelling is a **property** of a race, not a competitor to it: two cards meant you read the race, then read a gram figure, and joined the two yourself. Under the race, the sentence finishes where it starts. Indented, smaller, in the brand colour — and still absent on every day that has nothing to say. **And the gear on the Races tab lands where racing settings live.** It used to open the settings root, three taps from anything about racing. A tab's own gear belongs on that tab's own page; everything else is still one Back away. New on that page: your **body weight** — the number every figure in the fuelling plan stands on — and a switch for the **fuelling reminders**. The switch is checked inside the scheduler, not at the call site: a scheduler that can be reached around its own setting will be. **Found in passing:** capture mode promised in its comment to suppress What's New, and did the opposite. The gate is an *equality* — "last seen" against "current" — and what was written was an invented version 999.0.0. That guaranteed the two differed and the sheet appeared every time. It cost a tap on every capture, and the comment made it look handled. · · · Fuelling on Today — only when there is something to say The second half of your sentence was the actual specification. A card that is always there reporting "no race coming up" is furniture in the one place that should be about this morning. So it exists on exactly three kinds of day: a **loading day** with the gram target, **race morning** with the next step and its time, and — once everything is eaten — not at all. No empty card, no header over a gap. The time is in the **venue's clock**, and the amount stands beside it: the point of a card on Today is not having to open anything. A tap still opens the fuelling plan. **The decision lives in the model, not the screen.** It has four states whose edges are easy to get wrong — the last step already past; race day in another time zone; the day on which a one-day fuel-up says nothing yet. Inside a `body` none of it would be testable. **And checking in the simulator, exactly that bit fired:** the demo race was two days out, the fuel-up tier runs for one day — the card was missing, and rightly so. That was the rule working, not a bug. The demo generator now creates a planned race for tomorrow, with a real start time and a body weight, so the fuelling surfaces can be reached at all. Before this there was no planned race in the demo data whatsoever — which is why the race-day checklist, the fuelling plan and the reminders had never been seen in the simulator. · · · Looked at in the simulator — and a band that stopped too early The readiness bands were built, tested and never seen. So: seen. **What is right:** the bands draw, in German, with a comma rather than a point — "Dein üblicher Bereich: 44 ms bis 53 ms". Wrist temperature sits in absolute degrees around 35 and is not squashed into a flat line. The strain card correctly has no band and still has its points. And sleep is exactly the case the auditors reported as broken: the recommendation band runs from seven hours open-ended to the top, carries its own wording — "the same target for everyone, not your own average" — and marks only today's point rather than every short night. **What was not right:** the band stopped one point short. The newest day — the one you are looking for — sat visibly outside a region that ended just before it. The cause was steps instead of a line: a step is centred on its point, so the last one reached only that far and no further. A baseline drifts anyway; it does not step. · · · The app opens on the race you are heading for "I have a DD race now but I'm always shown HYROX first." Nine screens carried HYROX as their starting value — the analysis, the plan analysis, the dashboard card, the race planner, "log a past race", the plan wizard, Smart Generate, the workout builder and the plan tab. It is the format the app was built around, so it reads as a harmless default. And it is exactly wrong for someone with a Deadly Dozen in three weeks who opens the analysis to see where they stand. The order is now: **your next planned race** — everything is heading for it, and it is the only one of the four that points forward. Else **the last one you ran**, because with nothing planned, what you last did is the best guess. Else **the format you last picked for the Watch** — older, but chosen. And only then HYROX, as the one honest guess. A race **today** still counts as upcoming. That is the single day you least want to be changing the format yourself. **What it does not do:** override your choice. It is a *starting* value, read once when a screen is built — not on every appearance. A resolver re-running on every tab switch would snap the picker back each time, which is worse than the wrong default it replaces. A test reads the source and checks exactly that. · · · Notifications — and where a tap takes you **Phase 4 of the fuelling module, and the repair of something that affected the whole app.** Of the six kinds of notification the app schedules, exactly **one** did anything when tapped — and it sent a personal best to the *Races* tab, which is not where one is shown. The other five opened the app wherever you had last left it. A reminder to eat three hours before the gun landed on the analysis screen from the day before yesterday. Every notification now carries its destination — not in its identifier, but as its own data beside it. An identifier is the key for cancelling; the moment it is also a routing instruction, renaming one breaks the other silently. The race-day reminder opens **that** race's checklist, the fuelling reminders the fuelling plan, training reminders training, a personal best the analysis. It is shown as a sheet over the right tab: you tapped a reminder, you want the thing, you want to be back where you were. And a race that no longer exists says so, rather than showing an empty sheet. **The fuelling reminders themselves:** one per loading day in the morning with that day's gram target, one per race-morning step at its own minute. With the amount in the text, because a notification saying only "eat now" sends you into the app to find out how much — the very trip it was supposed to save. All in the **venue's time zone**, or they are only right while you are still at home. They re-time themselves whenever something they depend on changes — weight, start time, predicted finish. Reminders left from an older plan would otherwise announce the wrong amount at the wrong hour. **And a second scheduler is gone.** The fuelling timer scheduled its own, from six hard-coded steps with no body weight in them, and only if you happened to open that screen. Two schedulers for one thing is how an athlete ends up being told to eat twice. · · · The fuelling plan gets a screen — Phase 3 On the planned race, beside the race-day checklist. There, because every figure in it is timed backwards from *this* start and scaled to *this* predicted finish — anywhere else it would be a screen that has to ask which race you meant. **Pro.** **Your weight, first.** Without it nothing appears, and that is not an error state but the only honest answer: every figure is grams per kilogram. The app takes the **freshest** source — a reading from this morning beats a Health sample from three weeks ago, and the other way round. What you type goes into the same store the weight chart reads; there is no second weight that only the meal plan knows about. If the last reading is over a month old it is marked "out of date" rather than passed off as current. **Your protocol, with its reason.** Not "HYROX → load carbs", but: *"Your plan predicts about 96 minutes. Full glycogen loading is what the guidelines put behind events over 90 minutes."* The tier follows your own prediction — that is the difference from any calculator on the web. **Grams become food.** Nobody knows what 640 g of carbohydrate looks like on a plate. So beside it stands roughly what that means — rice, bananas, bagels, spread across the day. A target you cannot picture is a target you do not hit. **The race morning** runs towards the gun, on the venue's clock. And the steps that are common practice rather than consensus carry a small question mark — the top-up ninety minutes out and the last fast carbs. An athlete who cannot see the strength of the evidence cannot weigh it. **What is missing says so itself.** No start time, no morning — but the daily target stays. No predicted finish, the gentlest tier, with the reason. No shared "not enough data" shrug. **And during the race** it is gels, not a rate. Under three quarters of an hour: "nothing to carry". · · · Addendum: two numbers the evidence overturned A research pass held every number in the fuelling module against the consensus documents. Most held. Two did not. **The tier below ninety minutes was too low.** It stood at 5–7 g/kg over three days — that is an athlete's *training* intake, not what the guidelines recommend before a race. There it is **7–12 g/kg over 24 hours** (Burke narrows it to 7–10). And one day is enough: 10 g/kg over 24 h took muscle glycogen from 95 to 180 mmol/kg, with no further rise on days two and three. Since most of a HYROX field finishes inside ninety minutes, that was precisely the tier most people land in. **And the hybrid rationale was the wrong one.** "More muscle, therefore more carbohydrate" does not hold: adding arm work to maximal leg work raises VO₂max in trained athletes by **0 %** — aerobic turnover is limited centrally, by cardiac output, not by how much muscle is switched on. And a load fills arm and leg alike: three days at 10 g/kg raised triceps 22.8 % and vastus 22.5 %, with no difference between them. What **does** hold is an intensity argument, and for this sport it is the stronger one: a HYROX is repeated high-intensity work — the only measurements that exist put a recreational field at 170.9 bpm and 6.3 mmol/L lactate over 86 minutes — and in exactly that mode, starting with 396 rather than 287 mmol/kg of glycogen produced about a third more high-intensity work. So the honest claim is **"start well fed"**, not "hybrid burns more". Against it, from the same literature: loading *above* normal stores shows no consistent benefit at high intensity, while depletion clearly costs. **And the sentence that stands over all of it:** carbohydrate oxidation, RER and muscle glycogen have **never been measured** in a HYROX, a Deadly Dozen, a DEKA or an ATHX. Every number in this module is transferred from another sport, and none of it may be presented as measured in this one. Smaller, alongside: the fibre recommendation is marked practitioner consensus — the one systematic review of it found nothing of adequate quality, 3.5 % of 289 screened papers meeting inclusion criteria. It is offered as a suggestion, never as a target. · · · The fuelling plan does the arithmetic — Phase 2 All of it, with no screen attached. Three surfaces will ask the same questions — the race tab, a Today card, the notifications — and each of them working out "how many grams today" for itself is exactly the road to three answers. **No weight, no plan.** Everything here is grams per kilogram. A default of 70 kg would be wrong for almost everybody by enough to matter: at 8 g/kg that is a 160 g error for a 90 kg athlete, most of a day's difference. The plan refuses and asks for a weight. An impossible weight — pounds typed into a kilogram field — is refused rather than computed. **The loading tier follows your own prediction, not the event's name.** This is the advantage over any calculator on the web: the app knows you run 1:22. The consensus documents put real supercompensation behind events over ninety minutes — recommending two days at 10 g/kg to someone who will be done in an hour is not cautious, it is wrong. So: under 90 minutes eat well, 90–120 the middle tier, above that the full one. **The hybrid point stays a placement, not a multiplier.** That a HYROX moves more muscle than a run of the same length is physiologically sound — and nobody has ever put a biopsy needle into a HYROX field. So the app places the athlete at the **upper end of the published band** and not one gram past it. The test checks exactly that ceiling. **Every number carries its evidence grade.** "10–12 g/kg over 36–48 hours" comes from a consensus document with trials behind it. "A gel at station four" is a practitioner's sensible guess about a race nobody has measured. Printed in the same typeface with the same confidence, the athlete cannot tell them apart — and the second is the one that needs a hedge. The views can read the grade. **During the race it is doses, not rates.** A rate per hour is not actionable when both hands are on a sandbag. Under about three quarters of an hour the answer is zero — a gel you do not need is one more to drop. Rounded down: one too few costs a slightly emptier tank, one too many costs your stomach in the middle of a hundred wall balls. **Fixed in passing:** the body-measurement store decoded its file whole and came home empty-handed on a single corrupt record — then wrote that emptiness back on the next save. It now goes through the same element-by-element decoding the races use, and an unreadable file is no longer overwritten. Body weight is becoming load-bearing; losing it to one bad record would have taken the plan with it. · · · A race now has a start time The first step towards the fuelling module, and at the same time the repair of something that had been quietly wrong for a long time: the fuelling timer counted down to `race.date` — an hour nobody chose, because the planner only asked for a date and took the time from the moment you happened to open the form. "Eat three hours before the start" was an instruction against an invented clock. **The switch is off by default**, and honestly so: when you enter a race, the start list usually does not exist yet. With no start time the race-day card says so instead of counting down to the placeholder. **And the venue's clock.** A race in Chicago starts at 08:00 Chicago time whatever your watch says on the flight over. The time zone is set on the race — seeded with your own, which is right for almost everyone and wrong exactly when it matters. Every time in the fuelling timer is the local clock at the race, and carries the place in its name as soon as that is a different clock from yours — the way an airline prints a departure. The zone list opens on what is plausible: where you are, and the zones of races you have already entered. The rest is behind the search field. Existing races are unchanged — they read "start time unknown" and stay at midday on their own day until you set one. · · · A race has a day "In places the app states different days." It did — and not because anything was miscalculated, but because **a race date was stored as an instant instead of as a day.** **The hour nobody chose.** The planner seeded the date with "today + 2 months", carrying whatever hour you happened to be looking at the screen. The picker below it shows only the date and never touches the time. So a race entered at 22:40 sat at 22:40 for good. It was shown nowhere and measured against everywhere. **And then two camps counted differently.** Half the app measured the distance between two **instants**, half between two **midnights**. For a race stored at 00:30, looked at noon the day before: twelve and a half hours is less than a day, so the planner said **"In 0 days"** while the race-day checklist, the widget and readiness said **1**. Same race, same second, two answers. One place answers this now, and it counts midnights — which is what a human means by days. **A genuinely wrong day that would never have surfaced here.** The built-in race calendar stores its dates as UTC midnight (`2026-04-11T00:00:00Z`) and read them as instants. In Germany that renders as 11 April, 02:00 and looks perfect. In New York it is **10 April, 20:00**, so every race in the calendar showed a day early — and tapping "add to my races" copied that day into your own race, its countdown, its widget, its notification and its training plan. A date with no time is not an instant, and reading it as one is a bug that hides exactly where the developer lives. **Existing planned races** move to midday on their own day at the next launch — twelve hours from either edge, so no time zone and no clock change can shift the day again. Recorded races are untouched: their time of day is real measurement. · · · The range on every value — and what the auditors found in it Every signal on the Readiness screen now shows the region it is read against: **HRV**, **sleep**, **resting HR**, **respiratory rate** (which had no chart at all) and **wrist temperature**, all through one card instead of two idioms. **What the region claims — and what it does not.** It is your usual range: a point inside it is not a different day from the one before. It is explicitly **not a map of the score**. For HRV the width is exactly the threshold the score reacts to; for resting HR the edge sits at half a standard deviation where HRV's sits at a whole one; for respiratory rate the score uses absolute thresholds and never asks for a spread at all. A first draft of that comment claimed band and score could *never* disagree — true for one signal in three. And "inside" does not mean "full marks": HRV sitting exactly on its baseline scores 0.7 of 40. **A band per day, not one rectangle over everything.** Every stored day carries the baseline it was actually read against. A rectangle built from today's would hand every old night a verdict it never got — 55 ms was an ordinary day when the baseline was 55, and would be an exception once it has drifted to 70. The band now follows the baseline as a ribbon. **The width no longer follows the time-range picker.** It was computed from the *visible* history — 7, 30 or 90 days — while the score computes over 60. Tapping "7D" changed the width, and one and the same night was a change on one tab and unremarkable on another with nothing about the night having moved. The spread is now taken once, from the full record. **Sleep is deliberately different.** Seven hours is a recommendation for everyone, not your own range — and seven, not eight, because the insight list on the same screen already calls 7–8 h optimal. Two different targets on one page had painted a 7½-hour night green above and marked it an exception below. Only an own range marks exceptions at all: under a recommendation a chronically short sleeper would get a dot every night, and a mark on every point marks nothing. **Small things that surfaced.** The fill is grey, not green — the green on this same screen means "the score is full here", which is the one thing this band does not say. The caption's numbers go through the locale. The old dashed baseline was laid over the chart with hand-computed pixel arithmetic and was **not drawn at all** once it fell outside the data — precisely when it matters. And the strain card had lost its points, because they hung off the band. · · · Wrist temperature — and everything that hung on it "It says the temperature is above baseline. In truth it is below." **`appleSleepingWristTemperature` is an absolute reading, not a deviation.** Every layer was built on the opposite belief: the comment above the fetch asserted it, the unit table wrote "°C deviation from baseline" beside it, the field on the readiness record is named for it, the sentence on screen put a "+" in front, the AI prompt sent it as `temp=%+.2f°C`, and the demo generator invented values between −0.3 and +0.4 for a field HealthKit was filling with ~35. Apple's own documentation for the identifier says only "the wrist temperature during sleep" and settles the question in neither direction — which is how the wrong reading survived. The deviation the Health app shows is computed inside that app and never exposed. **The fault cost ten points every night.** Read as a deviation, 35.22 falls off the end of a curve whose last branch is "> 0.8 → fever", so the component sat at zero — on every device with a Series 8 or newer, every day. The readiness of **42** would have been a **52** with a correctly read, slightly-below-baseline night. And "7 of 7 signals" was counting a number that meant nothing. **The baseline is now your own.** The mean of your last 28 nights, tonight excluded (otherwise a very warm night pulls up its own reference). Below seven nights there is no baseline and therefore no statement — the component sits out rather than inventing one. All three numbers are stored: the reading, the baseline and the band. **And the baseline is on the chart — as a range.** In Apple's model it is a single number; on a chart a band is the honest shape. What makes a night remarkable is not being off that mean, it is being off it by **more than your own night-to-night spread**. That spread is the band's width — the same rule (0.5 × standard deviation) the app already applies to HRV. New card under the readiness history: the nights in absolute degrees, the green band around them, the baseline as a dashed line, and every point in the colour the score gives it. **How a colder night counts.** Before: anything below baseline got full marks, and at exactly −1.0 °C it dropped to 0.6 — a hundredth of a degree moved the finished readiness by four points. Both vendors that publish their rule treat it symmetrically: WHOOP counts "significant deviations (up or down)" against recovery, and Oura's support page says the contributor flags a temperature "above or below" — and, verbatim, "body temperature can only lower your Readiness Score, not boost it". So now: **full marks inside the band** (at baseline is as good as it gets; a cool night does not earn more than a normal one), **above it** down to zero across about one degree, **below it** at half that slope with a floor at one half. Deliberately not equal: a cool bedroom, a follicular phase and an energy deficit all lower a sleeping wrist, and this app cannot tell them apart — half a slope is what that uncertainty is worth. On the warm side the confounders are not benign. Past four degrees either way it is not a body, it is a watch on a bedside table: no penalty at all. **A year of stored nights has been converted.** The reading is kept and the false deviation recomputed against each night's own preceding nights, so the chart has its history from the first launch. The stored **scores** are left alone: each is a record of what the app said that day, and the inputs to recompute one honestly were never persisted. · · · The suggested goal time on the Watch "It shows a wrong goal time." Three faults stacked, and the bottom one cost the most. **A HYROX race did not recognise its own division.** HYROX has two names for each division: "Open Men" — a display string — and `hyrox_open_men`, the id. The "does this race belong to this division?" test compared against the **first**, while five of its six callers pass the **second**. Those are never equal. So **every** HYROX division filter in the app matched nothing: no station record, no clean-sheet streak, no best-race estimate — and no best time to derive a goal from. It is the rule this project has already learned, in a place nobody thought to look for it: a display string is never a data key. **With no best time a table stepped in — its slowest rung.** For HYROX Open Men that is Sub-114, so the Watch opened announcing "GOAL 1:54:00", the cut-off of the field's bottom quarter, presented as a target you had chosen. And a table describes **one** variant — DEKA's has five kilometres of running in it — so for a DEKA STRONG or a Deadly Mile it was another race's number as well. Both are gone: the suggestion is now your **own best less three per cent**, else the time your **own race plan** adds up to. With neither, it says "SET GOAL", which is honest. **And the Watch did not have the history.** Results travel wrist to phone and only planned races come back, so the Watch was deriving from an empty shelf. The phone now sends a small table along: one best time per shape actually raced, under the same keys the goals use. **Small things from the same screen.** The Goal tile — the one you tap — still read the *loaded* race's goal while the pill above it had long since shown the *picked* format; choosing Deadly Dozen left HYROX's number on the tile. The time was truncated to the minute (1:22:47 printed as "1:22:00"). The pace beside it was the **stored** one, not the one the goal asks for. The goal editor opened on the time tab because a suggestion counted as "a goal" — and one tap froze the guess. Two further copies of the same pace arithmetic used the format's constants instead of the variant's. And the pace wheel said "min/km" even in miles. · · · Today's workout now reaches the Watch at once A daily workout went out on the transfer channel **alone** — the one this app's own architecture calls "queued, guaranteed *eventual* delivery". Guaranteed is not the same as prompt: iOS hands such a thing over when it suits, which is minutes on an idle phone. The fast channel was reserved for auto-start. Every plan now takes the fast path **first**, whenever the Watch is reachable — which it is whenever you are looking at it. The guaranteed copy still goes, as the backstop for a wrist out of range. Both carry the same payload, and the receiver files by identity: what arrives twice is stored once. **And the Watch can now ask.** Everything was a push: the phone decides when to send, the wrist waits. That holds until a transfer is lost, the app is reinstalled, or you edit a session while the Watch is out of range. On opening it now asks once — and the answer takes the same path as an ordinary send. **Two brakes came out with it.** Superseding an undelivered transfer used to decode every waiting one in full just to read its identity — several whole workout plans during a plan edit, at exactly the moment you are waiting. The identity now travels as a short string beside the payload. And a workout with no plan link — a Quick Workout, say — used to cancel **every** waiting transfer, including plan days still on their way. · · · The upcoming-race cards are bigger — and say more They were 130 × 130 points and carried the format, the name, the date and a day counter: enough to recognise the entry, not enough for the question you actually have when you look at it — **what am I aiming for here?** The goal time, the division and the variant all live on the plan, and none of them reached the card. They do now: the goal as its own line with the planned pace beneath it, and above it the shape of the race — because a Deadly Strong and a Deadly Track share a format and nothing else. An A-race carries its priority as a dot. **And a card without a plan now says so.** There used to be simply a gap where the target belongs. An entry with only a date is a reminder, not a plan — and making it look like one hides the very thing still to do. · · · The races themselves now sit at the top of Analysis Everything on that tab describes races in the aggregate — your level, your weakest station, how your splits trend. The one thing missing was the obvious one: **the race you just ran.** It lived under Races, and Races is where you go to plan. Above the analysis there is now a strip of small chips, one per race — date, finish time, format — tappable, with a trophy on your best. They are exactly the races the cards below analyse: same division, same filter. So a chip cannot open a race that no card on the screen counts. Simulations are marked as such. · · · Planned races on the Watch: a list instead of four pages Every row drew the **full** plan summary — name, badges, goal and all twenty-four segments. Four planned entries came to about a hundred lines of text on a 176-point screen. Collapsed, a row is now a row: name, format, goal, days to go. A tap opens it and shows the whole plan along with "Load plan" — the moment you want the segment list is the moment before you load it anyway. **And a real race now carries a red border.** That is the one distinction that counts at a glance on a wrist: a simulation can be repeated on Thursday, a race cannot. Until now that difference was carried by a five-point grey icon.
improvedAugust 30, 2026

Version 1.1.7

**Build 3** · 30 August 2026 · · · HR zones: the distribution came from segment averages The card took each segment's **mean** heart rate, decided one zone from it, and booked the segment's whole duration there. A HYROX run is about five minutes — so the distribution inside a segment, the very thing the card is named after, was destroyed exactly where it forms. A ten-minute run climbing 140 → 180 bpm averages 160. That lands in Z3, so all ten minutes were reported as Z3. The truth from the same curve: 27 % Z2, 33 % Z3, 33 % Z4, 7 % Z5. Four minutes above the Z4 threshold vanished, and the "High Intensity" tile read **0 % instead of 40 %**. The error points systematically inward: a mean always lies between the extremes, so the card could essentially never show Z1 or Z5. **The real figure already existed.** After every race the seconds per zone are computed from the HealthKit samples themselves — every sample, weighted by the gap to the next, gaps over a minute discarded. Nothing in the app read it. It is read first now; only where it is missing (races from before the field existed, or without Health data) does the segment estimate stand in — and the bar then says "estimated". **And an invented bar is gone.** When nothing could be measured the card returned `[20, 20, 20, 20, 20]` — a perfectly even five-zone bar, drawn like a measurement. The race is simply not shown now. Also: the card filtered by neither division nor variant (the section above it did), struck-out segments counted, and the header named every race in scope while the list below drew the last five. · · · Compromised Running: the grade described nobody The card graded pace degradation on set thresholds — under 5 % "Elite", under 10 % "Strong", 15–25 % "Needs Work". The percentile tables shipped in the same app say otherwise: **HYROX Open Men** — fast end +18.6 %, median +16.1 %, slow end +18.9 %. **Deadly Dozen Solo Male** — fast end +8.9 %, median +24.2 %, slow end +55.2 %. So an average HYROX athlete was told "Needs Work", nobody in the field ever reached the top two grades, and the tooltip claimed "elite athletes stay under +10 %" — contradicted by the data in the same binary. The three-point HYROX spread across the whole field says something else too: there, the fade is mostly a property of the **course**, not of the athlete. Showing it as a personal grade was wrong twice over. It is now measured **against the field** for your format and division, from exactly those tables. Instead of an invented grade it says where you stand, with the field median beside it so the number can mean anything at all. For DEKA and ATHX there is no such basis (DEKA's tables repeat one run distribution ten times, ATHX has no runs); there the card says so rather than grading. **Three more from the same card:** the section was gated on Pro alone, so an ATHX athlete only ever saw a prompt to complete a race with four run splits — a thing their format cannot contain. Half simulations went unmarked into the headline and the trend line, although the metric measures something different there (legs 1–2 against 3–4 instead of against 7–8). And four of the five grade labels were raw text, so they stood in English on a German device. · · · Four reports from the race planner **A Save button where the typing ends.** The only one sat at the top in the navigation bar — the form is long, you finish at the bottom, and the next thing to hand there was "Add to Training Plan", which is not saving. Save now sits at the foot of the form, running the same action as the one above (not a copy — a copy would have had to copy the plan-day scheduling and the plan-sync check with it). **You now decide value by value.** The suggestion offered take-all or dismiss-all, so agreeing with eleven numbers out of twelve meant taking the twelfth or losing the lot. There is now "Choose…": every row with the value it would replace beside it, ticked individually. **And it says what kind of number it is.** The suggestion was never your *best* value but your **typical** one — a time-weighted median that counts recent races more heavily. That is the honest basis for a plan, but it was nowhere stated. The sheet lets you switch between **Typical** and **Best**; for the best, only results still within reach count. **Auto-fill from a target time now hits the target.** It distributed a percentile across the runs and stations — the shape the field data suggests for that standard. But a percentile of the parts does not sum to the same percentile of the whole: the Deadly Dozen tables put a median race at 4,115 s while their own journeys and labours add to 4,002, and at the 25th percentile the gap is four minutes. After distributing, the values are now scaled so the total really is your target; rest stays fixed, because rest cannot be scaled away. **And the distance can be typed.** A generated block carries whatever the generator divided out — 433 m from 5,200 m over twelve legs — and the stepper added 50 to *that*: 483, 533, but never 400. The field now takes the number directly, and + / − snap to the nearest 50 instead of walking an off-grid value further off it. · · · Race Splits: the cut belonged to the format, not to the race The midpoint came from `format.runCount` — which is 12 for **every** Deadly Dozen. But a Deadly ERG is three erg pieces against twelve Labours: all three landed in the first half, the second was six short Labours, and **every ERG race was decorated "Negative Splitter — you finish stronger than you start, elite pacing"**, however it had actually gone. A Deadly Mile fell entirely into the first half, had no second one at all, and vanished from the card without a word. Deriving the cut from the race's own shape would not have fixed it either — the ladder "run 2n−1, station 2n" assumes runs and stations alternate one for one, and three runs among twelve Labours never do. The split now follows the **order actually performed**: the middle of what you did. That needs no assumption about shape, holds for every variant, and is also what "first half" means to you. **And the verdict now measures you, not the course.** The second half is structurally the harder one — HYROX opens with the sleds and closes with lunges and wall balls. Measured against zero, you were handed "Positive Splitter — start more conservatively" for running the race the way it is built. It now compares against your **own usual split**, and while there are too few races for that, the card says so instead of judging. Three more from the same card: the roxzone was inside station times for gate-tapped races and missing entirely for logged ones — the same performance, opposite verdicts; variants shared one list, one average and one bar scale; and that average summed **absolute seconds** across races of very different lengths, though the percentage that would have normalised it was already there and never read. · · · Race comparison paired by array position `for i in 0..<min(a.count, b.count)` — across two different recordings. Any difference in what was recorded shifts the whole list: start tracking one leg late in one race and every run was measured against the wrong one. For the stations, the **name came from one race** and the **time from the other**. Pairing is by identity now — run number and station id — and only for segments both races actually performed. A struck-out one counts in neither. **And another race shape is not a reference.** The best time was found across every variant: a Deadly Mile is quicker on the clock than a Track race, so it would have won any comparison — against stations it does not even contain. The comparison now uses the same shape only, with the same test the race ribbon and the records list have always used. · · · The transitions were still there — because the planner computed them itself You asked whether it was old plans versus new. It was neither: the planner screen worked its four rows out **again itself**, in a second copy of the same arithmetic. I had fixed the model, and the copy kept its Deadly Dozen branch — a plan the model priced at 41:39 still read 53:03 on the screen that built it. The four figures ask the plan now. That closes the class, not just this one case. Two things fell out of it: the **planned rest** was stored on the plan and never read by the model — the editor counted it into its total, the goal time for the Watch did not. And the **organiser's mandated rest** (ATHX's forty minutes of Feed and Recovery zones) sat inside the estimate without a row of its own. Now it has one. · · · Plan vs Actual: the same comparison existed twice in the code Once in the card, once hand-copied into the analytics export — and both copies carried the same faults. One funnel for both now. **Struck-out segments still counted.** The segment editor promises in its own footer that "not performed" drops out of every average. This card was the one that kept counting it — with whatever time had been recorded. **Runs were paired by array position.** With a leg missing, the card compared Journey 2's time against Journey 1's target, and so on through the race. **The card ignored the chosen division and never named the race.** The gate above it filtered by division, the card did not: with an older Solo race and a newer Pairs one, the section headed "Solo" showed the Pairs race, station by station, against Pairs targets. It could not be noticed, because nothing said which race was on screen. Now it does. **A lap with its own distance.** The plan holds one lap length for the whole race; correct a single lap afterwards and its time was still measured against the full one. **And a penalty is not a pacing error.** The finish time carries a minute per penalty and the plan models none, so two minutes at the judges' table appeared as two minutes lost on the course. They stand apart now. Plus a colour that meant two things on one card: the bars painted any deviation over 15 % red, **including downward** — a station twenty per cent faster than planned stood red while the line above it called the same direction green. · · · The Pacing Advisor has a time axis now — and knows your goal It carried a date on every entry and used it for nothing but sorting. A race from two seasons ago therefore set your average pace, your evenness and your target just as strongly as yesterday's. Results are now weighted by age, with the same six-month half-life the planner has long used for its suggestions — one curve for both screens instead of two answers to one question. And the suggestion only draws on what is still **reachable**. A best from three years ago belongs in a records list, but "run 1 % faster than that next month" is not advice, it is arithmetic on someone you may no longer be. **The bigger one: the card knows the goal you set.** It computed a "target pace" of its own from your best, while the app holds a goal time per race shape that the Watch derives its pace from. Two numbers, both called a target, on two screens, with no relation to each other. Where a goal exists for this shape, the card shows the pace **it** asks for and says so. Only without one does the suggestion from your best remain. And where the two disagree, that stands underneath — because the gap is the information: either your recent form does not yet carry the target pace, or you have already run faster than you set out to. · · · The ring now describes the race, not itself The TIME SPLIT percentages divided each slice by the **sum of the slices** — so they always came to 100 %, however long the race had actually taken. Everything no segment claimed disappeared: every penalty minute, the wall-clock of whatever you had struck out, and any stretch a pause left between two segments. The basis is the race time now. Penalties get a slice of their own, and whatever is left is called "Other" and is visible rather than divided away. In a cleanly gate-tapped race that remainder is near zero — which is exactly why it is worth seeing when it is not. · · · Deadly Dozen's transition time is gone — not renamed Last time I renamed the "Transitions" row to "Changeovers" and left the time in, arguing that the benchmark ladder is built on 24 changeovers. That was the wrong half to fix. A gate tap ends the journey AT the station and starts the labour there. In a recorded race every second is therefore inside a journey or a labour — there is no third bucket. The walk from the track to the kettlebell is already in the numbers, and a separate line counted it a second time: twelve invented minutes on a plan. The row disappears by itself, and the estimated total drops to journeys plus labours — which is also what your own recording shows. · · · The bar labels overlapped Twelve journeys across a phone leave about fifty points per bar, and "5:52/km" needs more than sixty — so the labels sat on top of each other in pairs and neither could be read. They now stand **upright inside the bar**: turned on its side a label needs the bar's width for its height, and every bar has that. · · · And a correction to myself The prefill from training took a block's **wall-clock**. With three sets and ninety seconds of rest that measures the rest too — but a station in a race is one unbroken set. The suggested labour times came out markedly too slow because of it. Only a single continuous set counts now; anything with several sets stays out, because it is a different thing rather than a slower version of the same one. · · · The Pacing Advisor mixed race shapes — and called progress inconsistency It filtered on format and division but never on the **variant**. Deadly ERG's three "runs" are 1,609 m erg pieces, Track's twelve are 400 m laps, DEKA MILE runs ten of 160 m. Pooled, they produce an average pace that occurred in no race and a target driven by whichever shape has the shortest legs. The app has an axis for exactly this question and uses it for the ladders, the personal bests and the race ribbon — here it was missing. One shape counts now: the most recent race in scope. **"Even splits" is a question about one race, not about a career.** The value was the spread of race AVERAGES over all time — so an athlete who got faster produced spread by improving and was marked inconsistent, while the tile beside it called the same movement "↑ Better" in green. Two readings of one list, contradicting each other. It now measures the spread **within** a race: did you hold an even pace? That is also what the race detail's own pacing card has always measured. **The trend threshold was absolute.** Five seconds per kilometre is forty seconds over a HYROX and two over a Deadly Mile — one constant meant a different verdict per variant. It is now a share of the pace. **Three words stood in English on a German device.** "↑ Better", "↓ Slower" and "→ Stable" went into the tile as plain text and were never translatable — they were not even in the catalogue. And the card had a private pace formatter with a hardcoded "/km", so an imperial athlete read kilometres here and miles two cards down. **And a run-less race gets an honest answer.** A Deadly Strong has no journeys. Instead of "complete two races" — which the athlete has long since done — it now says this division's races contain no runs. Also: "1% faster than your personal best" was not true, because the pool deliberately includes simulations and a personal best in this app means an official race. It now says "best recorded pace". · · · The per-leg pace came from GPS — though the lap length is known The race detail showed **Ø 4:51/km** at the top and **Ø 5:45** in the bar chart below it, for the same twelve journeys. Worked out: 23:20 over 12 × 400 m is 4:51/km. The header was right. The Watch writes `duration / (GPS metres / 1000)` at the end of each journey. GPS cuts the corners of a tight lap and measures about 338 m of a 400 m one — that is 18 % too slow, exactly the gap between the two numbers. But in a race the lap length is **known**, from the format and the variant. Measuring something you already know is only a way to be wrong. The pace is now computed when it is **shown**, from duration and lap length, instead of read off the stored sensor value. That fixes two things at once: every race already recorded is right retroactively, with no migration — and a correction takes effect immediately. Before, the segment editor wrote only the end time, and the pace beside it stayed the old sensor reading forever. **Every** place that hung on it was converted: the chart and the consistency card, the split list, the CSV and TCX exports, the Strava description, the AI prompt, race comparison, the share card, the weekly digest, the run-pace profile and fade index in Analysis, the history on the Watch, the ghost comparison during a race — and the race planner's prefill, which would otherwise have turned a measurement error into a training target. **Four things that hung on it, fixed too:** An entered **actual distance** now counts. It was a label and nothing more: neither the average pace nor the distance sent to Health and Strava read it. **Races reach the pace progression.** Every race leg was written to the performance log with `distanceMeters: 0` and so failed the card's own 400 m filter — it showed workouts only, never a race. **Skipped legs** were drawn in the chart while the caption below counted them out. The **distance stepper in the segment editor** compared against the race's own lap when opening and against the format constant when saving. For any variant with a different lap — Sprint 200 m, ERG 1609 m, DEKA MILE 160 m — merely opening and saving stamped an override onto a segment that had none. **And the unit is right.** Header, average line, goal line, bars, axis and legend appended a fixed "/km" while the row between them converted — on an imperial device one screen carried two units. The AI prompt had it the other way round: it was sending imperial display values where every number must be metric. · · · The Watch was getting the workouts — and merging them on arrival They arrived. Filing them, the library fell back last to a signature of name plus movements — deliberately without loads, without reps and **without a date**. That is the right test for "the athlete edited this workout" and the wrong one for two days of the same recurring session: "Race-Pace Intervalle" every Tuesday has one signature, but a different id and a different date each week. The plan sends its week in order. So every repeat landed on **one** card, and that card ended up holding the latest day's date — the Watch asks "is this today?" and found nothing. Two complaints, one line. A session id is an identity: a plan that carries one is matched by it or not at all. With the collecting point gone, two consequences handled too: past plan days are cleared on arrival (yesterday stays — a session finished after midnight is still that session), your own workouts never. And only the next eight days travel, or editing a 26-week plan would send a hundred and fifty cards to your wrist. · · · "Transitions" in Deadly Dozen — the name was wrong, not the time A transition zone is a HYROX rule and a DEKA one. Deadly Dozen has none, and the row invited you to look for a part of the race that does not exist. What it does have is the changeover: off the track, pick up the kettlebell, and back. Two per round — which is exactly what the benchmark ladder is built on (24 changeovers, 20 s at the top rung rising to 35 at the bottom). The row now reads **Changeovers (24×)**, and those twelve minutes are explained rather than asserted. · · · The prefill can finally see what you did in training It asked races and simulations only. Train the twelve Labours every week and race twice a year, and you were offered your own two race days — while a Watch session of exactly those movements, with a real duration, a real load and a real rep count per block, sat unread beside it. It counts now, volume-aware: a set of twenty reps against a station of sixty is projected up through the same curve the records and the progress chart use, and below a third of the volume it is not used at all. A race is still the truest sample; a training set of the same station is the next truest. Together they are simply more. · · · Small things from the day **The name in Smart Generate.** Every generated workout was called "Race Prep — DD", so the library was a column of one name. The name can now be tapped and changed right in the preview, and saving shows it once more — with a note if something is already called that. After saving, the screen closes and the saved workout opens. **Day capsules of equal height.** In the training plan the height followed the content, so a two-line title stood beside a "Rest" and the row came out ragged. Every capsule now takes the height of the tallest in its row. · · · The weakness card named the wrong station It resolved a station by its NUMBER, against the station list WITHOUT a variant. A Deadly Mile records its three Labours as 1, 2, 3 — but the full twelve's first three are three different Labours. Every time landed on the wrong movement, and the card named as a weakness a station that was never performed. The segment has carried its own station id all along; it is read now. Both cards in this section had hand-built the same access, and neither left out skipped stations — "not performed" counted as "performed slowly". That now sits in one funnel, used by the radar chart too, and reduced volume is projected up before any comparison. **A race with no runs was setting the performance class.** A Deadly Strong is over in half an hour without a single step of running. Left in the pool it made you an elite athlete — and then every station target was an elite one and everything looked weak. Only races the table actually applies to count now. **Target and times now come from the same day.** The targets were derived from your BEST time while the station times were AVERAGED across every race, so the gap between your best day and your ordinary one sat invisibly inside every percentage. The reference is now the median of your comparable races. **And it ranks by seconds, not percent.** A 60-second station at 78 s is 30 % over and costs 18 seconds; a 300-second one at 360 s is 20 % over and costs a full minute. The old ranking named the first — while the level card in the same screen named the second. One screen, two answers to one question. The card shows both: the percentage and the seconds per race. · · · Two cards that never appeared for half a field Below the slowest rung the classification answers "none" — and that is the common case, not the rare one. HYROX and Deadly Dozen returned no station targets at all there, so every card built on them vanished silently for exactly the athletes with the most to gain. DEKA had long fallen back to its slowest rung; the argument was never DEKA-specific. **And an athlete who races one format saw nothing in the history.** As soon as there was only one format, that branch was hardcoded to HYROX instead of that one format. Someone who only runs Deadly Dozen was shown "record a race of this format to see your level" beside a full history. · · · A goal belongs to a race The Watch showed the same target over every variant: 30 minutes — including over a Deadly Dozen Track race, whose own table does not start until fifty. The cause was a single key for everything. The app even recorded which format the goal had been set for, and then never asked. A goal now belongs to the **shape** of the race: format, variant and division. Those three decide how long the race is — Deadly Strong and Deadly Track differ in every one of the twelve legs, and Pairs cut-offs are 0.78× Solo. Change the variant, change the division or load a plan, and the goal is re-read. For a shape you have never set a goal for, one is **derived** rather than borrowed: from your own best comparable time, sharpened a little — and failing that, from the lowest rung of the table for exactly this shape. An existing goal moves, on first launch, into the format it was once set for; none is lost. The whole table now travels to the Watch instead of one number — otherwise the phone would have written its HYROX goal into the Track slot on your wrist. · · · "Your level" and "Next level" are one card They were two, and they answered the same question — the second one by restating it: "cut four minutes off your total time." That is arithmetic, not a plan. The app holds your station splits and, for every rung of every table, its per-station targets; the difference names the two or three stations that **are** the gap. Below it stands how much of the gap they cover between them — three stations worth forty seconds of a four-minute shortfall is a very different message. Two states the old card had no answer for: at the top of the table, and — far more common — slower than its slowest rung. There it simply vanished, which is half of a HYROX field. Now it speaks in both. Short recordings are projected to full volume before any comparison, and below a third of the volume they are not used at all. ATHX is scored per zone and therefore has no finish-time rungs — it says so now, instead of leaving a heading over nothing. **Build 2** · 28 August 2026 · · · Four reports from the road **The widget said "race" when only a simulation was planned.** Two causes, the same one twice: `nextIsRace` was hardcoded `true` in the snapshot, and the second path — the widget reads the planned races directly as a fallback — could not know at all, because its own model never decoded the field. Both tell the truth now, and the countdown is headed "SIM" instead of "RACE". **The Watch complication showed only a flag, no numbers.** The reason was not the rendering but a gap in the sync: only **planned** races travel to the Watch — finished ones go the other way. So the complication filtered on "complete" and found nothing. A compact summary now rides along: one finished time per format, with its date, PR flag and division — small enough for the settings channel, unlike the history. **"Button inside a button" on the paused screen.** Three buttons used the shorthand `Button("RESUME") { … }` without a style of their own — watchOS then draws its own background **under** the capsule, and the grey shows around it. The race pause screen had it right from the start; this one did not. **The "NEXT" card says more.** The block that is genuinely coming next is now larger than the two behind it — all three used to be the same size, so it had to be picked out of a list, mid-run. Below it is new: **how many kilometres of running are left in total** — the current block's remainder plus every run block after it. "Two blocks left" says nothing about effort; four kilometres does. · · · Personal records from half the work The PR card tested a label instead of the volume. A half recording saved as a *race* counted as a full effort — and could take the record. And where it did project, it did so **linearly**, although the second half of a station is the expensive half. The station itself decides now: how much was *this* station cut back? It is then projected with the same curve the progress chart uses, and the best-of selection compares projected times rather than raw ones — before, whoever did the least work simply won. Below a third of the volume the effort does not count at all. · · · · · · Deadly Dozen: the ladders left a quarter of the race unexplained There is no public field data for Deadly Dozen — the official leaderboard has a station filter but no API, no export and no aggregates. So the targets remain estimates. **What they no longer are is incoherent.** The arithmetic showed it: **every tier left 13 to 27 minutes unexplained.** Sub-50 asked for 18:50 of Labours and 18:00 of running — against a 50-minute cut-off. Sub-110 asked for 47:45 and 34:57 against 110 minutes. If that leftover were transition time it would be **33 seconds per changeover at the fastest tier and 68 at the slowest**. A changeover here means stepping off a running track and picking up a kettlebell. Sixty-eight seconds is not a transition, it is a rest. So anyone hitting every target in a tier finished 13 to 27 minutes **inside** the tier they were aiming at — the same fault DEKA's ladder had. The station targets are now scaled so each tier closes: 24 changeovers at 20 seconds for the fastest tier, rising to 35 for the slowest. The run paces are untouched — 3:45 to 7:17 per kilometre are independently plausible. **One side-finding worth pausing on:** the needed factor came out at 1.27 to 1.40. The HYROX tables, written by the same hand from the same kind of reasoning, turned out to be 25 to 39 % too fast when measured against real data. Two independent ladders, the same optimism, the same magnitude. That does not prove the new Deadly Dozen level is right — only that the old one was wrong in a direction we can now name. The derived ladders (women ×1.12, pairs ×0.78, relay ×0.65) inherit the correction. · · · · · · The HYROX benchmarks now rest on real field data Checking them turned up something: the target times were **hand-estimated**, not derived from results. The evidence was unambiguous — all 41 values were divisible by five, and the Deadly Dozen table wrote its own derivation in the margin ("60 reps ~1.5s/rep"). **How far off, the field shows:** the Sub-60 tier's SkiErg target was 2:30 — the actual top 10 % of the field need **4:06**. The target was 39 % faster than the very best, and for 1000 m of SkiErg after two kilometres of running simply not humanly possible. Sled Pull was 32 % out, rowing 25 %. **And the ladder stopped too early.** The slowest rung for Open Men was Sub-90, while the field's median is around 1:40. So half of every start list was slower than the slowest tier, got sorted into it anyway — and was duly told every single station was a weak spot. That was the cause of eleven out of thirteen. **The rungs are now the percentiles themselves:** top 10 %, top 25 %, median, bottom 25 %, built from the published station splits of **395,452 solo results**. Each tier's run pace is derived so the arithmetic closes: hit every station target in a rung and you finish inside that rung's cut-off. DEKA was already held to that rule; HYROX never was. **Honest stays honest:** only Open Men and Open Women rest on data. Pro, Doubles and Relay have no public per-station distribution — their tables remain estimates, and the app now knows which is which. Along the way: a tier's colour and symbol were chosen by matching the **label text** ("Sub-60" → yellow). With new tiers they would all have turned grey — and a women's ladder is slower throughout, so colouring by time would paint every women's tier as the lesser one. Both now hang off the rank within their own ladder. · · · · · · The Watch opens where the day points "Can it intelligently decide which screen is shown at launch?" It was always the race page. So anyone whose plan said "60 min Zone 2" landed on a gate button and had to swipe first — every time. The day now decides: **race page** when a race or simulation is due or a plan is loaded; **workout page** for an ordinary session. When both apply the race wins — it has a start time to hit. Three properties mattered more than the rule itself: **Decided once.** The plan list keeps syncing with the iPhone. Unchecked, the screen would slide sideways while you are reading the other page. **You win.** One swipe and the app never touches the page again. **Patient.** "Nothing known yet" is not the same as "nothing today". The workout is fetched asynchronously and the planned race waits on an iCloud reconcile. While nothing is there, nothing is decided — otherwise the answer would be settled before the data that answers it arrives. · · · · · · The progress chart compared times of different volumes "Something seems wrong here." Correct, and it was more than one thing. The chart plotted raw station times from recordings of **different volumes** and joined them with one line. A halved session therefore landed at the floor of the chart — and pulled the **best-performance line** down with it. After that every honest full effort read as a regression. That is exactly what the screenshot showed: the drop to 0:50 and the dashed line beneath it. Every shortened session is now **projected up to the full volume** and **marked** as projected — diamond, dashed, and a "≈" before the number in the header. Tapping a point shows both: the projected value and the time actually on the clock. The formula had been in the app all along (T(N) = k·N^b with its own exponent per station family); the chart simply never used it. **Below a third of the volume nothing is shown at all.** There the projection's error exceeds its message — the function refuses by design, and the point is dropped rather than drawn raw. **Four more faults on the same screen:** - The diamonds were incomplete: the reduced-volume test required a *training* entry, so a half recording saved as a *race* counted as full. The race comparison had already fixed this same trap — the chart never received the correction. - Points were identified by **date**. Two simulations on one day collapsed into one. - The screen read races **around the funnel** and filtered on "complete", while the format picker above it used the funnel — the two could disagree about which races exist. - The station list ignored the **variant**: twelve Labours even for someone who only runs Deadly Mile (three). And a dead copy of the same chart — no callers at all, the same best-line fault, no volume handling whatsoever — has been deleted. · · · Station benchmarks: eleven "weak spots" out of thirteen were no coincidence The target times come from the ladder your **best total time** places you in. That best time was chosen without any check: **No shape check.** A Deadly Strong is twelve Labours with no running and therefore far shorter than a Track race. Counted as "best", it puts you in a far too fast ladder — and then **everything** is a weak spot. The guard for this exists in the app and is used in five places; not here. **No division check.** The times shown are filtered by division, the ladder was not. So a Pairs result could set the targets for a Solo card. Both are in place now. **And when no judgeable reference exists, the card says so** — instead of reaching for the nearest ladder. With a button beside it: **"Add a past race"**. That screen existed already; it just was not reachable from the one that misses the data. · · · · · · The start screen, second pass Four observations from using it, all implemented and checked in the simulator. **GPS and the tap count sat too centrally.** Two things you cannot press sat in the middle of three you can. They are now at the very bottom, in an info section of their own. **And it says more than it used to.** Four facts instead of two — the additions are the ones you otherwise notice too late: **whether the iPhone is in reach** (without it there is no spoken coaching, and the result waits to be delivered) and **whether Health access is granted** (without it no heart rate is recorded at all). Battery was deliberately left out — watchOS shows it one swipe away, and repeating what the system gives for free is the duplication this app has turned down before. **Planned sessions are no longer below the fold.** The full-width "Planned races" row and the goal capsule became **two tiles side by side** — the same shape the workout page has used for a while. Both are visible without scrolling. The goal keeps its **value** on the tile; a tile reading just "Goal" would be a step back from a capsule that said "5:00 /km". **And START moved down.** On top of the anchored area, the button carried insets of its own above and below, which pushed it up and left a band of black between it and the page dots. A detail along the way: the info section first had an "INFO" heading — at rest it was the last thing visible above the button, so it stood over nothing. A rule does the same job and cannot be left dangling. · · · · · · The start screen, walked through as a user This screen was the app's first and has barely changed since. Three things did not hold up. **You could not see what was tappable.** Five tiles in the same visual language, two of them dead: GPS and the tap count looked like buttons and were not. Both lost their capsule and are now plain status text. So the rule holds again — **a capsule means you can tap it** — and the goal gained the same chevron the format block carries, so all three interactive things say so the same way. **The only thing you could do to a loaded plan was delete it.** The "PLAN ACTIVE" banner was not a button — only the ✕ on the right was. Tapping the plan's name to see what it contained did nothing; missing slightly lost the plan. While the same plan was **not** loaded, that banner was a button with a chevron. Now it is one in both states. **And you could not check what you were about to run.** The detail view existed — segment list, goal, skipped stations — but only for *planned* races, before loading. Afterwards only the name remained. Tapping the banner now opens it for the loaded plan; the same view, not a second one. Removing the plan lives inside it, behind a question, instead of being the only button on top. Along the way: "2× tap" now reads "Gate: 2× tap" — the bare number never said what it was about. The full sentence is in the new plan view. · · · The screen speaks the same language as the others again Measured, not felt: the race page carried **2.4 hardcoded numbers for every design token**; the settings screen carries 0.4. It was the first and never followed the scale the later ones use. The most visible symptom was the **left edge**: banner, format block and status row sat at 10 points, "Planned races" and START at 13. Three points is not something you notice consciously — you only see that the edges do not line up. There is now **one** page margin, from the scale. The cards' inner padding likewise, and the three raw whites gave way to the palette's text colour. Plus a fault only the simulator revealed: the blue "Planned races" capsule peeked out from under the anchored START button as it scrolled beneath it. The anchored area now has a ground of its own. And "Transition timer" in the race setup read "Übergangsti…"; a toggle gives its label only what the switch leaves over. · · · · · · Three capsules become one row Division, format/variant and the shape were three separate capsules fighting over the same width on a 162 pt screen. Now one block of two lines: the race's name on top, division and shape below it. ``` 🏁 Track Race Solo · 12× 400m ``` Measured, not estimated: the longest variant name ("DEKA STRONG", 78.4 pt) fits the smallest watch at 10 pt. The second line has a case that does not — "Age Group 18–64 · 10× 500m" is 130.7 pt against 103 — which is what the scale floor is for; it must never wrap. The block is tappable as the pill was, and GPS and tap count sit below it as pure status. · · · It said "12 Zones" for every format — including those that have no zones The pill wrote "\(n) Zones" for every run-less format. But Deadly Dozen counts **Labours** and HYROX counts **Stations** — and `stationLabel` has always known that. It is used now, in the form "12× Labour": the "×" carries the count, so the noun stays singular and nobody has to glue an "s" onto a translatable word. The same line appeared a second time in the format picker — there with exactly that glued-on "s" which the comment beside it had already criticised once. Fixed too. · · · And a translation that meant something else The Deadly Dozen variant "Track Race" was German **"Rennen tracken"** — "to record a race". What is meant is the track variant with 12×400 m. It now reads "Track-Rennen". The key is used in this one place, so nothing else hangs on it. · · · · · · The Watch race page now scrolls — START stays put "Right now everything on the front page feels cramped and unclear." Counted: the page carried **eleven** things inside a fixed height — division, GPS, tap count, format/variant, goal, last time, the plan banner, the breath switch, START and planned races. After the clock inset and the page dots, about 200 pt remain on the Ultra. Roughly 18 pt each. Hence the cramped feel, and hence the truncating text. **Nothing was removed.** The content now scrolls with the crown, and **START is anchored at the bottom** — the one thing you open the screen for is never scrolled away. The order follows the question you are actually asking: *what am I about to run?* (plan or race-day banner first) → *what shape is it?* (format/variant, division, GPS, taps) → *what am I aiming at?* (goal, last comparable time) → *what else is there?* (breath coach, planned races). The sideways swipe to the WORKOUT page is unchanged, and the workout page itself is untouched. · · · · · · The screen after discarding — and what made it collapse After discarding a race the start screen sat as a narrow strip at the bottom until you swiped once. Reproduced in the simulator and narrowed down. The cause is an implicit `.animation` over the whole router: it was meant to cross-fade between states, but it swaps two trees that have nothing in common — the race is a horizontally paging `TabView` that ignores the bottom safe area, the start screen is a `NavigationStack` holding a `TabView` of its own. The result was not a half-finished transition but a **stuck** layout: screenshots one and four seconds after the discard are identical. Two obvious remedies demonstrably did **not** help — neither an explicit fade per branch nor a separate identity for the start screen. Only removing the animation fixed it. The cross-fade that was actually wanted now sits one level down, between the run and station views, where both sides are the same shape. · · · Settings were lost when the sheet was swiped away Found while going through every setting: the goal and the division were persisted **only** on tapping "Done". But a watchOS sheet is swiped away far more often than scrolled to the bottom of — and then the change was silently discarded. The screen had accepted the input and thrown it out. Both are now saved on dismissal too. The HR warning always wrote immediately; now they all behave the same. **For reference, what the Watch actually sets itself:** the goal (pace or target time), the division, and the HR warning. Tap count, max heart rate, haptics, breath coach and auto-detection are deliberately read-only — the phone owns them, and two writers for one setting is how the pair ends up disagreeing. · · · One more German string that stayed German in English The Watch race-day banner read "RENNTAG — TIPPEN ZUM LADEN", hardcoded. Translated now. · · · · · · The variant can now be changed on the Watch "You should be able to change the variant on the Watch too." Until now the Watch had no control for it at all — the format pill offered only "Change Format", and that **deletes the loaded plan**. Afterwards the same stale key answered with the same wrong variant, so sacrificing the plan did not even help. Tapping the pill now asks what you want to change: **variant** (keeps the plan with all its targets, skipped stations and its name) or **format** (removes it, which is why it stays marked destructive). The variant picker shows the shape behind each entry — "Track Race · 12×400m Journeys + 12 Labours" against "Deadly Strong · 12 Labours only" — because the shape is the whole difference. With no plan loaded the Watch remembers the choice as before; with a plan it corrects the plan. DEKA behaves the same way, and ULTRA stays hidden there. · · · German text that stayed German in English Five dialogs were hardcoded in German rather than translated: "Discard recording?", "Stop race?" and their buttons, the "Close" in the race result, and the confirmation for minimising a card. On an English device they sat in German in the middle of an English app. Both languages now, like everywhere else. · · · · · · "Deadly Strong" was there because an old key answered On the Watch start screen, above a loaded half Deadly Dozen, the pill read **"Deadly Strong · 12 Zones"** — a variant with no runs at all. Wrong, and not correctable. The chain: `RaceManager.ddVariant(…)` asks the running race first, then the loaded plan — and otherwise falls back to `gritrace.ddVariant`, the key the planner writes when you change the variant there. Its own comment has always said "for a manual start **without a loaded plan**". The code never checked. So a plan that names no variant was answered from an old, unrelated decision — possibly months ago. **A plan's silence is an answer**, not a gap: it means "the format's default shape". That is how it behaves now. The fallback stays where it was built for — a manual start with no plan — and DEKA gets the same rule, because fixing one and leaving the other is the surest way for the next person to find the unfixed one. Also: the start sheet ("Simulation → start on Watch") wrote no variant onto the race at all. It builds the default shape — and a decision belongs written down, or someone else fills the gap. It is stated now. · · · "LAST 51:16" compared two different sessions Beside it stood the last time — and it came from `lastRace(for:)`, which asks **only about the format**. So a half Deadly Dozen was headed by the time of your last **full** one. The number was real; the comparison was not. The same line also read the format picker's selection while the pill three lines above read the loaded plan — with a plan loaded, the two could point at different formats. It now compares the same **shape**: same format, same variant, same volume. Two partial simulations only count together when they covered roughly the same ground — a third of a race and half of one are different sessions. When nothing comparable exists, it shows **nothing**. That is the same honesty this app already chose for its benchmark ladders: "there is no ladder for this shape" is the right answer, not a scaled-up guess. · · · · · · Long names now scroll past "When the names are too long here, please let them rotate through so you can read the whole name." You name a simulation yourself, and what tells two of them apart is usually at the **end** — "DD-Simulation mit Squats 6 Runden". That is exactly what the row cut off: three cards in a row read "DD-Simulation mi…", "DD-Simulation nu…" and "DD · Deadly Stron…". The line now walks: it stands still for a good second, moves left, pauses at the end and comes back. Not an endless loop — in a loop the end butts against the beginning, and in a list that seam reads as a second name. Only what genuinely does not fit moves; short names stand still and cost nothing. Anyone with "Reduce Motion" switched on in Accessibility gets the calm version — VoiceOver reads the whole name either way. It is not only the simulations list: **six** places show a self-chosen race name in a truncating row — simulations, race history (twice), the Plan tab, Analysis and the simulation comparison. All six now scroll. **Under the hood:** the Watch already had something like this, the iPhone did not. Rather than a second version, the component now lives once in `Shared/` and both devices use it — the comments in it are the residue of things that went wrong once, and a second copy is the surest way for the next person to get the version without the fix. · · · · · · The set counter on the Watch fits on one line again "Why does the set display break to a second line here? There's still plenty of room." Measured — and it is the other way round from how it looks. "Satz 2 von 3" is **83.1 pt** wide at 14 pt; "Set 2 of 3" is only 64.9. Beside it sit the set stopwatch (43 pt) and the "+ Set" button (43 pt). What is left for the counter is 43.9 pt on the smallest watch and 78.9 on the largest — so in German it fits on **no** size, and on the Ultra it is 4.2 points short. The room you can see is the consequence: once the counter breaks to two lines the left column shrinks, and the spacer beside it takes the 36 pt that were given up. The gap is the symptom, not the cause. It now reads "Set 2/3" — 57.7 pt, and it reads the same in both languages — and the counter may only shrink, never wrap. · · · Five more rows on the Watch that ran out of their frame in German If one does it, more do: German is consistently wider, and these screens were built in English, where everything fit. Found and measured: - Recovery blocks - The overtime display - The heart-rate pill - "End & Save" - "START RACE" **Build 2** · 22 August 2026 · · · The import now asks, instead of only warning "When it finds problems, it should ask whether to fill them in as best it can." There were two checks: one reads the plan's mechanics (empty blocks, zero minutes), the other compares it against the ORDER you placed (your weekdays, your length, the race it has to reach). What was missing is the third question — the one a coach asks while reading: *is that meant to be like this?* Two cases the import can now notice, each with a repair you accept on its own: **An interval with no warm-up.** Three or more hard repeats with nothing easy in front of them. Accepted, it adds one — the distance derived from the session's own volume, between 1.5 and 3 km, carrying YOUR Zone 2 pace. If the app has never measured one, the field stays empty: a pace nothing measured does not go on screen. **An exercise left standing after the loop.** Run and burpees, five times — but the burpees ended up after the loop and therefore happen once at the end. Accepted, they move into EVERY round, each one behind its run. Both answers are equally easy. A question with only an apply button is a nag, and then the whole box gets scrolled past. **And the rules stay quiet on purpose.** An easy run at the front IS a warm-up, label or not. Two repeats are not an interval set. A strength day opening with its heaviest lift is a coach's choice. A cool-down after the loop belongs exactly where it is. Two loops in one session is a shape the rule cannot reason about, so it says nothing rather than guessing. The reason is already in the code for the older check: a warning that is wrong often enough gets clicked away like every other warning, and takes the right ones with it. Nothing is changed silently. Repairs are applied when you confirm the import, so you can change your mind until the last moment. **Five rules instead of two, across the plan.** Alongside the warm-up and the exercise behind the loop, the import now also notices: a session that ends on a hard effort (add a cool-down); repeats with no recovery between them (add one after each, never after the last — a session does not end on a rest nobody ordered); and a simulation carrying written blocks when its content is the linked race. The recovery is derived from the repeat when a pace is prescribed — half the working time, clamped to 60–180 seconds. Without a pace, 90. A starting point, not a prescription; you edit it like any other block. **"Accept all"** does it in one tap when you were going to agree anyway — and the same button takes it all back. **Build 2** · 21 August 2026 · · · The transition now says what is coming next "When I create a simulation without runs, the transition screen doesn't show which exercise comes next." The screen asked the wrong question: `activeRunCount == 0` — what the FORMAT has, not what THIS race contains. A Deadly Dozen Track whose runs you deselected in the planner still reports twelve. So the code fell through to nothing, and between two Labours you stood there with a timer, a READY button and no announcement. It is the same distinction the gate flow already carries: "has this race any runs" and "does run n exist" are two different questions. A run the variant does not have — or that your plan struck out — IS a station. The answer now lives in `RaceManager.upcomingStation`, where the state and the plan sit together, instead of being derived in the view. A struck-out station is stepped over rather than reported as nothing: deselecting one exercise must not swallow the announcement for the next. The run screen had a second, separate version of the same question. It was correct — but a copy is a second place to forget the fix. Both now ask the same source.
improvedAugust 20, 2026

Version 1.1.6

**Build 145** · 20 August 2026 · · · An expression one Mac compiles and the other does not "The compiler is unable to type-check this expression in reasonable time" — building on the second Mac, with the release version of Xcode instead of the beta. `paceSessionData` ended in a long chain: `Array(…).compactMap { … }.enumerated().map { (i, t) in (i + 1, t.0, t.1) }`, four filters and two closures ahead of it, and finally an UNLABELLED tuple converting into the property's labelled tuple type. Exactly the shape the type checker solves in a second on one machine and gives up on at the next — different Xcode versions have different type checkers. It is written out as loops with explicit types now: the same thing for the reader, nothing left to infer for the compiler. Then the whole app target was measured with `-warn-long-expression-type-checking` rather than waiting for the next failure. The second worst case was real code and stood there TWICE verbatim: the same `filter.flatMap.filter.compactMap.min` chain — with an array `+` inside the flatMap and a `??` inside the compactMap — once in `raceBestTime` and once in `bestCombinedTime`, over two seconds each. Both now go through one `raceBest(for:)`, written as a loop. Eight expressions over 250 ms remain, the largest of them a `#Preview` — none anywhere near what fails. And then stopped looking at time, and looked at shape A second failure on the same Mac — `RacePlannerView.swift:837` — showed the measurement here is a weak proxy: that site had not appeared in my list at all. Wall-clock time on one machine says nothing about solver steps on another. So the search went structural, by shape rather than duration: large view body × many chained modifiers. Every modifier wraps the previous type, so a long chain is ONE deeply nested generic the type checker has to solve in a single go. The ranking confirmed the failing case and found four more — the worst being the app's root view: - `GridRaceApp.swift` — 266 lines, **28 modifiers** → five stages - `WorkoutBuilderView.swift` — 338 lines, 17 modifiers → three stages - `TrainingPlanViews.swift` — 184 lines, 20 modifiers → three stages - `GridRaceWatchApp.swift` (watch start screen) — 135 lines, 20 modifiers → three stages - `RacePlannerView.swift` — 280 lines, 16 modifiers → four stages Each stage is its own type-check unit. The rendered view is unchanged: same chain, named in the middle. Proven rather than assumed: modifier ORDER is meaningful, so all five chains were compared before and after position by position — **101 modifiers, the same set in the same order everywhere.** **Build 145** · 19 August 2026 · · · "Keep them" now actually keeps them — Monday to Wednesday stay "On the AI import I did say it should keep them, but it deleted the trainings from Monday to Wednesday of this week." Two holes between that button and the saved plan, and I built the first one myself yesterday. **The main fault.** Yesterday I stopped duplicate weeks by no longer prepending an archived week that overlaps the new plan — two rows for one calendar week was exactly what had been reported. But the archived row is the one carrying the training and the new one is empty, so leaving it out made Monday, Tuesday and Wednesday disappear. Formally the sessions were still in the archive, in the workout history and in the training load — but in the plan, where you look, they were gone. Completed days are now **written back into the new plan by date** rather than only moving to the archive. Whole: status, completion date, logged sets, RPE, notes and the linked race. And only where the new plan has nothing completed on that day — so the archive can never overwrite newer training. **The second fault, found in the same pass.** When you paste a revised week, `mergeWeeks` replaced the WHOLE week in one line, with no protection at all. Exactly the shape already documented and fixed for its neighbour `replaceDaysInActivePlan`; this is the copy beside it that nobody carried the fix across to — and it is the one the AI revision path actually runs through. It now replaces day by day, a completed day stays, and the message tells you how many were kept. · · · Analyse tab, continued Workout History is back in first place, and the 32 sections sit in named bands you can fold shut. **Build 145** · 19 August 2026 · · · The Analyse tab is tidied up 32 sections sat in a single column, all open at once, in an order that had grown rather than been chosen: four heart-rate cards stood between the race splits and the run cards, and three all-time tables sat among the per-race analyses. The families were always there; nothing had ever named them. They are now bands you can fold open and shut: **Race Analysis** - This race - Body & pace - Over time - Where you stand - How you respond - Body & volume - Workout History is back at the very top - The race ribbon moved to Race Analysis - The ribbon, pace progression and fitness can now be minimised too **Build 145** · 19 August 2026 · · · A simulation now counts in full towards the cardio curve "Yesterday I had a heart rate of 165 for 30 minutes in a simulation and cardio didn't move. This morning I jogged easy for 70 minutes and the curve goes up." The suspicion is right, and the backup shows it to the second. The Deadly Dozen half simulation on 18 Aug is 11.5 minutes of running at 132–168 bpm **and** 12.4 minutes of stations at 152–174. Only the running reached the cardio load: **27 TRIMP instead of 59**. The session lost 55 % of its load, and what it lost was the harder half. The cause is a half-finished conversion. The path that computes TODAY already counts stations, with a comment describing this exact bug. The path that rebuilds the HISTORY in bulk was never converted — and that is the path the curve is drawn from. Both now go through the same `stationTrimp`. No special case for strength was needed: TRIMP weights heart rate exponentially. A bench press at 110 bpm costs about 0.6 per minute; a devil press at 169 costs 2.6. The formula has always distinguished them — it was simply never asked. **Honestly:** even computed correctly, the easy jog remains the bigger cardio stimulus — 98 TRIMP against 59. TRIMP is duration times intensity, and 76 minutes is three times 24. The difference is that 59 is visible in the curve and 27 is not. Because the history is rebuilt, every past day containing station work shifts upward. · · · Auto-pause: you decide when It was a fixed twelve seconds. There is now a slider from 5 to 45 seconds under Training ▸ Running, with a line beneath it saying what the chosen number means. The value travels to the watch like every other setting. The step threshold scales with it: three steps in twelve seconds is really a RATE of a quarter step per second. Left fixed, a six-second window would have been far stricter and a thirty-second window far looser — the same setting would have meant two different rules at its two ends. The history buffer scales too: a fixed 20 s would have trimmed away exactly the baseline a 30-second window needs, and it would then have answered every question with "no steps". Closed at 5 seconds at the bottom: any shorter and the window cannot outlast the few seconds of thin detection when you raise your wrist to read the watch — the very first version's bug. · · · The heart-rate warning was stuck to the bottom edge of the screen "Why is the BPM warning not really integrated but a fixed banner at the bottom edge?" — because a brace was in the wrong place. Moving the section from the Racing page to the Heart Rate page closed the `Form` too early, so the section sat BESIDE the list rather than inside it. Two views side by side get stacked: the list fills the space and the section is pushed to the bottom edge. The page title hung off the section too. It is where it belongs now. **Build 145** · 19 August 2026 · · · Archiving locally instead of on the Cloud: the build number now lives in the project Xcode Cloud was assigning the build number itself; the project still said `1` everywhere. A local archive would have gone up as 1.1.6 (1) and been rejected, because 1 is not higher than 144. It is now **145**, on all four shipping targets in Debug and Release — if the app, the watch app and the two widget extensions disagree on the build number, App Store Connect rejects the upload as well. The eight test targets stay at 1; they never reach the archive. · · · Pace adaptation: an accepted suggestion made a fast target slower The detector was always careful — cardiac efficiency rather than raw pace, a real baseline of older sessions, at least five qualifying runs, a 10 % ceiling on the reading, half of it applied, clamped to the athlete's best-ever pace. The APPLY was not. It wrote ONE value into EVERY future block of that type. But one intensity routinely holds several targets: 4×1000 m at 5:10/km and 10×400 m at 4:45/km are both `vo2max`. The anchor was simply whichever block the plan listed first, so the faster one was rewritten to the slower one's improved pace: `@4:45` came out as 5:01, under a banner announcing an improvement. What is applied now is a **factor**, not an absolute pace. Every block scales from its own target, is clamped to best-ever individually, and can never come out slower. A twelve-week plan's deliberate progression survives instead of being flattened to one number. Three more holes closed in the same pass: - The ratchet - No time window - Mean, not median **Build 39** · 18 August 2026 · · · The race ribbon — races, simulations and training in one view Every recording of the same race shape is one bar, segmented in the order you actually ran it. All bars share one scale and one left edge, and the width IS the finish time — the fastest bar is visibly the shortest, and a segment that grew is a segment you can see grow. Tap one and it names itself and gives the time. The last bar is not a recording. It is **what your training says**: every station at the time it actually takes you in training, every run at your own race pace. "Does my training cover my race" finally has an answer in units where the question means something. Three rules, so that a comparison is one: - Same race shape only - The training bar is built from the newest recording - Races do not count as training - One point per session - The line is a rolling median over three sessions - Splits under 400 m no longer count, and neither does a race pace slower than 7:00/km. Zone 2 is allowed up to 9:00/km — tired legs are still an easy run. - Each series keeps its own colour. They used to be assigned by order, so a month without race-pace running swapped them silently. - The figure below is the trend value, not the last session — otherwise the number contradicts the line above it. · · · The written run analysis is withdrawn for now A German headline over English body text, and above it a generated verdict about a single run — the one card here that asserts rather than measures. It is hidden until the wording is trustworthy in both languages. · · · The duplicated weeks now actually go away Today's fix only stops NEW duplicates from being written. The existing ones were already on disk — and the next import would have archived the plan together with its duplicate rows and prepended all three again. The bug was propagating itself. Rows covering the same calendar week are now merged DAY BY DAY on load. Per date, the version that was performed wins; a day only one row knows about is carried over. Nothing performed is lost, nothing planned is lost, and the week stays trainable instead of being locked as history. · · · Four things off your list - A partial simulation's preview now shows what was actually planned - The breath coach is a beta and has to be switched on deliberately - A run is planned as time AND distance, and the pace falls out of the two - The Watch's HR warning - One tap on BACK rewound two sets - Checking off set 2 threw away the still-queued set 1 - A late set completion dragged the athlete back into a finished exercise - "Send to Watch" stayed hidden - A rest that simply ran out was never banked - The height profile and the climb belonged to the previous session - The hold screen read the whole workout history ten times a second - The BACK button after a transition did nothing - The predicted finish counted runs that do not exist - `/uploads` has never heard of `sport_type` - A `<Track>` with no points in it is not valid TCX - 201 means "accepted", not "it worked" - "Max HR" was the largest block AVERAGE - `RunStyle` existed twice, identical apart from whitespace. The "Alternative training" sheet still carried the whole run generator of the deleted Custom mode; nothing called `buildSession` any more. - A remote command's dispatch was written out twice — which is exactly why the dedupe guarded only one of the two channels. · · · Addendum: three race findings, adversarially confirmed The four findings I had left as "already fixed according to the architecture map" were checked again against today's code — one investigator and two skeptics each. Three were real. - The Watch started runs the variant does not have - In-race stations were counted and named against the full format list - The format pill took its name from the picker and its shape from the manager - Every snapshot now carries a revision - The HR warning is editable on the Watch again - The division travels both ways - The HR warning sits where you look for it - On HYROX nothing from the HR settings ever arrived - The HR warning saves as you change it - `Rounds 4` - Every block knows which round it is in - Prompt, grammar and example all know the token - `<n>cal` and `<n>s` / `<n>min` - A number with no unit follows the station - The measure comes from the registry now **Build 38** · 17 August 2026 · · · The AI plan import, actually run end to end once The prompt had been read, never answered. This time the whole chain ran for real — two target races, saved workouts AND planned simulations in one pass, prompt emitted, answered as the model, the answer fed through the importer. The first run came back with one warning per session. None of them was a real fault. - A title is allowed to be just a title - A sentence before the plan costs a warning, not the plan - "Each token exactly once" - Recovery belongs BETWEEN the repeats - The "STATIONS list above" - The grammar called itself "all valid tokens" and omitted three mandatory ones - The worked example is built for YOU - Two sessions on one day - Week numbers - Kilograms stay kilograms - Days until the race - A reference to a figure the prompt does not contain - Station times with their volume - No training history, no load block - Readiness 70 from zero signals - Race history - Simulations know their shape - The review prompt counted the PREVIOUS plan's weeks - "Replace weeks only" duplicated your history - The import button outlived its own parse - "Modify specific days" wrote on the validate tap - "Phase by phase" plus "specific days" was a dead end - "Plan looks good!" in green - Six warnings were English literals - The "sessions shorter than ordered" check - The equipment match - Cable machine - Found alongside - The three cards in "Why no built-in AI?" - A loaded simulation does not start as a "race" - A "Copy findings" button - Single lines - The reference tokens existed in the DSL alone - JSON now knows the references too - The examples in the prompt - The title - The "Compact format" switch is gone - JSON stays where it earns its keep - `activateImportedPlan` silently shifted the whole plan - The "Custom Start Date" field - `mergeWeeks` passed `startDate: nil` - The prompt - It is asked in the configure step - Defaulted to your next training day from today - The prompt says it out loud - The same date everywhere - The silent shift no longer fires - A discarded run took its GPS track into the next workout - A phantom workout after a HealthKit failure - "Save to Health" had no duplicate guard - The manual pause was never reset - Archived plans decoded all-or-nothing - Deleted sample races returned on the next launch - The "30 days" weight comparison - Plan compliance - GPS elevation gain - A backup merged race entries by the wrong id - iCloud receipt wrote around the store's own save - The activity detail forced **en_US** and baked "Today"/"Yesterday" in as literals — on a German device. - AVG PACE - Confetti and the success haptic **Build 37** · 16 August 2026 · · · A segment you did not do is not a result - "Bear Crawl nicht mehr gemacht, aber ist noch mit rein gekommen." - The flag existed already - All three totals counted skipped segments - The timestamps stay - The day knew, the week did not - A funnel now answers the question from both sources - Only a real race colours a week - And a simulation placed on several days finds its own day again - A switch on the "Your Library" card - `SimRef` is the second channel - An unknown id now says so - The saved-simulation filter existed in four hand-copied versions, two of them missing `!isSampleRace`. Now `RaceStore.simulationTemplates`. · · · The card decks now fit the card you are looking at - An HStack centres - The container now measures every page and takes the height of the one on screen - One carousel for both places - Six switches moved - The Watch keeps what belongs on the start line - The haptics switch really does apply to both devices now - Every settings save zeroed the Watch's target pace - Auto-station detection would have armed itself on the iPhone - The pace alert for training runs would have inherited the race target pace - The max-HR field on the Watch accepted input and discarded it - "Off" was drawn in the brand colour on three rows - Every readout on the Watch now says where it is set - "You have no idea what you are supposed to do." - Everything on the iPhone, including what happens on the Watch - What you read once no longer stands in the way - Expanded the first time, and the safety rule is card one - What stays on the screen is what is still useful after the tenth read - The breath coach is now also in Settings under "Apple Watch", beside the run display. One screen, two entrances. · · · One transition time, two totals - The simulation editor's summary hung its transition row on whether the format has a transition zone. For Deadly Dozen the answer is no — so it listed journeys and labours, called their sum the training duration and showed **53:37**, while the overview reported **1:06:37** for the same plan: thirteen minutes of walking, quietly counted three files away. The row now hangs on the TIME. · · · The breath coach can be planned with the simulation - Until now it existed only on the wrist, seconds before the start. It is now in the simulation planner — and **only there**, never in race planning. Two locks, because this is the one place it must stay quiet: the field is written only for a plan marked as training, and a race refuses to tick regardless. · · · Version 1.1.6 (Build 36) — 13 August 2026 · · · An automatic pause can now end automatically - The threshold was never the problem - The real cause was an echo - A detector one second old reports a standstill - And a report that was declined left the detector latched - The recovery jog is watched now - Why no test caught it - The height profile now belongs to the run, not the block - Headline ascent, descent and the curve are session-scoped now - Exactly one figure stays with the block: M/KM - Measured against 1.1.4 - Four highlights rather than three - Fifteen notes - `APP_STORE_1.1.5.md` - You had planned five rounds, and the editor said so nowhere - Now there is a frame around it with "5 rounds" over the top - Where the frame appears - Interval runs too - An edit in the round you see applies to all of them - While you tick sets, the rounds stay apart - Cards kept appearing without you saving anything - The reason was not an oversight but a double duty - Now there are two - On the Watch nothing changes - The cards already in there do not disappear by themselves — you can safely delete them, and no new ones will follow. · · · A deleted simulation now really disappears - Deleting was not the inverse of saving - It now distinguishes whose session it is - A mis-tap no longer becomes a session at all - The iPhone had no discard - A session labelled "0 min" showed a curve spanning 75 minutes - First: the card did not reload on switching - Second: the time window was borrowed - A match is now a proposal, not an answer - The tiles above it - On every session, across the whole app - The cause is a half-finished change - It now counts the gap to the next reading - Both counting sites now go through the same function, including the one feeding the AI prompt. A test asserts that credited time **can never exceed the span the readings cover** — precisely the one assertion that would have prevented this from the start. · · · Stations show their picture - The pictures were on disk all along - 22 mappings for Deadly Dozen, HYROX and DEKA - Rounds were capped at 5 - Rest between sets could not be set to 0 - The live preview read `dd_kb_goblet_squat`
improvedAugust 12, 2026

Version 1.1.5

**Build 34** · 12 August 2026 The settings screen is tidy - Seventeen sections sat on one single list - Now six doors instead of seventeen rooms - Reminders now sit with the thing they remind you about - The Watch run display moved from "Race Controls" to "Training & Runs" - Not a single switch changed behaviour; every section moved verbatim. · · · The Watch stops at the traffic light — and sets off again with you - If you stop during a run, the Watch now stops the clock by itself - Measured from steps, not from GPS - Five seconds without a step counts as standing - A pause you pressed yourself never resumes on its own - An automatic pause never swallows a button press - Switchable - Applies to run blocks in a workout only - If you were listening to music on the iPhone, the coaching still came out of the Watch - The Watch's only route to those headphones would be to take them - So the device that owns the headphones now speaks - It works with any headphones on the iPhone - Every doubt falls back to the Watch - No, this is not reserved for Apple - Found along the way and fixed in the same pass - The pause forgot which card you were on - The reason sits one level deeper - The horizontal page comes back too - The curve could never fall - The card now draws the elevation relative to the block start, with a sign - The scale spans the run's valley and peak instead of starting at zero, with a fine line at starting elevation as soon as you drop below it. A window under 10 m is not drawn any narrower — otherwise forty centimetres of air-pressure fluctuation on flat ground fill the whole card. - Without a barometer no profile is drawn at all - "Ø GRADE" was "M/KM" divided by ten - "DESCENT" had no German translation - Found along the way and fixed in the same pass - The progress glow stayed dark the whole way through a 10 km Zone 2 block - A block in progress now moves the ring with it - Simulations still landed on Strava as "Run" - The type now comes from a single place, across four callers: **pure run → Run, pure strength training → Weight Training, runs and stations mixed → HIIT.** The upload path speaks the older vocabulary (`Workout`), which is the only one it understands. - Pause a run and the info pages were gone - The run screen now stays put during a pause - Found along the way and fixed in the same pass **Build 33** · 11 August 2026 "My Workouts" and "My Simulations" are the same size - The left card was one line of text taller than the right one - The space for the second line is now **reserved**, not merely allowed. That puts the two count lines at the same height as well, and it holds at larger type sizes — which a fixed height would not. **Build 32** · 11 August 2026 Both widget extensions now have a privacy manifest - App and Watch app had one, the two widget extensions did not - Deliberately shorter than the app's - Verified in the built bundle, not assumed: both `.appex` carry the file, on iOS and watchOS. **Build 31** · 11 August 2026 "Target pace 12:17–13:20 /km" above a plan that says 5:00/km - The target pace was too slow by a factor of 2.5 - The same calculation sat in the Watch's live target pace - And twice a pace was compared against a lap time - On HYROX the lap is exactly one kilometre **Build 30** · 11 August 2026 The last labels: the Watch calls the race by its name - A loaded DEKA STRONG introduced itself as "DEKA FIT · 10× 0m" - The iPhone tracker announced "RUN 2" after every zone - The text export listed ten run lines for a run-less race **Build 29** · 11 August 2026 Logged past races now have stations too - Four Analyse cards stayed empty for every logged past race - The rule for de-duplicating a migrated race was written out by hand twice in the source **Build 28** · 11 August 2026 The rest of the variant axis: pace, preview, rest, leaderboard - The pace of a DEKA MILE was off by two thirds - A planned STRONG showed ten runs on the Watch - A zones-only race never got the planned rest - The leaderboard separated the variants and then measured them by the format's yardstick anyway - The ghost comparison and the target-time back-calculation **Build 27** · 11 August 2026 No more award for a shape that has no ladder - A DEKA STRONG took Elite, the Clean Sheet and the record all at once - The format's personal best fell for it too - The legacy bridge was gender-blind in a single line - Strava uploaded a run-less race as a 5 km activity - The planner showed "500 m" for a DEKA MILE **Build 26** · 11 August 2026 Addendum to the DEKA pass: one self-inflicted bug, and three dead ends - I had taken the division away from Deadly Dozen and ATHX - After a format switch no chip was marked - The plan field for the DEKA variant was dead code - A DEKA race started by hand on the Watch was always FIT - DEKA ULTRA is no longer selectable **Build 25** · 11 August 2026 DEKA now has a variant axis — and Deadly Dozen inherits a fix along with it - DEKA STRONG could not be run - The plan did not carry the variant at all - A race is only comparable with its own shape - DEKA teams had no gender - Every division is measured against the ladder of its own gender **Build 24** · 11 August 2026 DEKA run-through: half of what turned up applies to HYROX and Deadly Dozen too - The division picker on the Watch was free text - A shared store for all formats - "Plan vs Actual" always showed the run card empty — in German - The race shape knew only Deadly Dozen's variants - The station benchmark card counted roxzones as stations - Three cards resolved the division themselves - On the detail page, in the history and on the shared card, the raw id was shown **Build 23** · 11 August 2026 A half simulation no longer sets targets for a whole race - The damage was not in the suggestion, it was in accepting it - Now your order applies - And no longer linearly - Every row now names the volume its time comes from - A full target race was pre-ticked under half numbers - On ATHX the card offered to overwrite the organiser-fixed zone durations - A Deadly Mile was graded against all twelve labours - A second way around the funnel is closed **Build 22** · 11 August 2026 DEKA: one hour was labelled "World Class" - The ladder contradicted itself - And reality says the same - The zone targets were the realistic part - The marks are derived from the tiers, not written down beside them - Deadly Dozen is deliberately untouched **Build 21** · 11 August 2026 A race's stations now count towards the heart as well - A station's heart rate was recorded and cost nothing - Why nobody noticed - It is counted with the same guard as the runs - The curve is recomputed once **Build 20** · 11 August 2026 What a weight means is now written where you enter it - The app knew two meanings for one kilo figure and used the same word for both - Only the exception gets a label - A blanket "Total weight" would have been wrong twice over - The Watch and the iPhone contradicted each other on race day - The dialog that changes a station weight asked for "Weight (kg)" - Once, in the right place - Adjacent finding, fixed along the way **Build 19** · 11 August 2026 DEKA: the women were training with the 65+ weights - The official chart has four columns, and the second one is called "Female 14+ / Male 65+" - Looked up in the rulebook itself - Zones 2, 5 and 7 now know the youth values - The head of the file claimed weights that appear nowhere in the rulebook - Not yet covered and deliberately named **Build 18** · 11 August 2026 A strength exercise now counts with all its sets — and a simulation only once - Four sets were booked as one - The sets of previous sessions are backfilled - The same simulation stood twice in the day - Days already stored twice - "2 × 10 kg" instead of "10 kg" **Build 17** · 11 August 2026 A planned simulation opens the race flow — and the button says what it starts - The Watch had to guess which simulation was meant - A simulation now always opens the race flow - The START button says what it opens - Today tab: two buttons for the same thing, with an unrelated card between them **Build 16** · 10 August 2026 The result view now tells you what you actually did - "~37 min" after a workout that took 1:16 - Why the estimate is so far off is further down in my answer: it counts three seconds per rep, 90 seconds of rest between sets and **twenty** seconds between two exercises. So rests are counted — only the planned ones, not the real ones. - The exercise list shows sets and reps - Intensity curve: the exercise names were grey on red **Build 15** · 10 August 2026 Deletion pass: 1,290 lines, every one of them checked against its callers - Dead views (690 lines) - Dead manager APIs (84 lines) - Half of the "Quick Start" chain (15 lines) - The iOS dashboard widget inside the watchOS widget file (459 lines) - Left alone, because they are features and not corpses **Build 14** · 10 August 2026 Station training is real training now - It was the app's only training mode without a Health session - Heart rate comes from the live builder of a **running** `HKWorkoutSession`. Station training started none — so there was no value to subscribe to at all: no readings, no average in the result. - And the HR pill on the run screen read `trainer.currentHeartRate`, a property **nobody in the whole project ever wrote to**. It was always nil, the pill never appeared. - A station attempt now opens a real session - Only its own session is ended - Pause and resume are now passed through to the session, so the Health entry does not count a breather as work. **Build 13** · 10 August 2026 Elevation: a block value and a session value are two different numbers - The ascent card divided the whole session's ascent by the distance of a single block - On closer inspection the actual bug was a different one, and worse - On a Watch **with** a barometer the next measurement wrote the cumulative figure straight back. So the zeroing had no effect — and that is why it was never noticed. - On hardware **without** a barometer that very write-back is guarded and never fires. There the zeroing held, and the saved workout kept only the last block's ascent. **The same line, the opposite bug, depending on the wrist.** - Both quantities now exist side by side: `elevationGainMeters` stays the session, `blockElevationGainMeters` is derived from a baseline taken at the block change. The card takes the block value, because it divides it by the block distance. Three tests, one of them exactly the case I first fixed the wrong way round. **Build 12** · 10 August 2026 The units setting finally reaches the pace · A backfill could overwrite recordings - Anyone switching to imperial has so far turned kilograms into pounds and then gone on reading kilometre paces everywhere - Nothing changes for metric - The export and the text sent to the language model stay **deliberately** metric: there a fixed unit is worth more than the athlete's preference. The reasoning is written down in the code. - The HealthKit backfill could overwrite other recordings - And this afternoon's invariant confirmed once more: `Models.swift` is compiled on its own in the widget target, so `WeightFormatter` had to travel with it. The error duly appeared as "cannot find WeightFormatter in scope" — in the middle of `Models.swift`, on a perfectly ordinary build. **Build 11** · 10 August 2026 Four confirmed findings: two headers over gaps, an unreachable trophy, a calorie value - "Pacing Advisor" could stand over a gap - "HR Recovery" inevitably stood over a gap on ATHX - The trophy in station training could never appear - When editing a set afterwards, calories and level stayed put **Build 10** · 10 August 2026 Alternative training: "Quick" out, saved simulations in - Your saved simulations were not reachable from here - "Quick" gave way for it - Templates only, not recordings - The session that gets inserted stays linked to the simulation **Build 9** · 10 August 2026 - "Start on Apple Watch" now reads "Start again" for a session that has been completed **Build 8** · 10 August 2026 Three patterns instead of three individual cases The cross-check delivered not just findings but **repetitions**. A mistake that shows up three times is not threefold carelessness, it is a missing rule. All three are now in `CLAUDE.md`. - A funnel exists so that nothing goes around it - The workout history was the sixth bypass - Explicitly **not** a bypass: `RaceHistoryView` shows the demo races on purpose, with its own banner and delete button. Checked instead of assumed. - When you replace something, delete the half you replaced - A block value and a session value are not the same number - On the side: the last raw `print()` in the release build now goes through `Log.d` — the second one had disappeared with the old decoder anyway. **Build 7** · 10 August 2026 56 open findings cross-checked — 38 held up, 18 did not. Ten of those fixed. **The cross-check itself.** Every finding got a reviewer whose job was to **disprove** it: does the code really say that? Is the path reachable? Is it deliberate — this repo justifies its intentions in comments? And: was it already fixed today? 18 of 56 did not survive that. They stay in `BACKLOG_1.1.5.md` with the reasoning, so nobody reports them again. **Two buttons that discarded work now ask** - START on the Watch's workout page overwrote a session in progress - STOP in station training threw away the attempt just run - The kilometre split was the average since the block began - Calories were a flat 9 kcal per minute of total duration - The run card said "/mi" over a number in seconds per kilometre - The Weekly Digest counted the demo races as your history - The HealthKit permission texts came from the project file instead of the maintained `Info.plist` - A self-confirmed deload was shown as "Auto-deload active" - The distance in the generator preview read a UserDefaults key that nobody writes **Build 6** · 10 August 2026 The total time is now on the rest screens too - The set rest and the EMOM rest were missing the session's total time **Build 5** · 10 August 2026 The personal-best notification now says which day it came from - Fix: "New personal best: Bear Crawl" on a day without any bear crawls - A finding now carries the **date of the performance** it celebrates, separate from the moment of detection. The notification puts it first, **except** when the performance really was from today — then the date would be nothing but noise. - The pace part has no single date it could point to. It stays silent about it rather than guessing. - The new field decodes backwards-compatibly: a finding written before the field existed stays readable. Otherwise every stored record would have become unreadable — the rule for that has been in `CLAUDE.md` for a while. - And the detector now reads through `analysisRaces` - Six tests, among them the backwards compatibility and the "performance was today" case (no date line). **Build 4** · 10 August 2026 Three ways a session threw away work that was already done - Fix: after a crash the exercise ended at the next check mark - Fix: a skipped block lost its logged sets - Fix: the hold timer jumped to zero on every pause - Eight tests. The crash is genuinely re-enacted: one manager writes a checkpoint, a **second, fresh** manager restores from it — nothing survives except the file. - Two invariants in `CLAUDE.md`: state that `beginBlock` derives from the plan must be re-derived when restoring. And: anything that should survive a pause must not live in a watchOS view's `@State`. **Build 3** · 10 August 2026 The rest screen named the exercise that had just finished · The bezel no longer cuts through text - Fix: "Next" named the wrong exercise and the wrong set in a superset - The rest screen looked ahead to `plan.blocks[idx + 1]` and hard-coded the set to 1. But a superset runs by **alternating**, not in array order — the array neighbour is exactly the exercise you have just finished. And a superset partner the alternation comes back to already has sets behind it, so "set 1" is never right for it. - Your hunch about the third set is exactly right - The fix is a deletion - The glow bezel no longer cuts through text - So one of the two stops being opaque — the content: it **fades out before it reaches the line**. A little longer at the top, where the glow itself is strongest and the system clock sits. Fixed elements sit in the safe area and stay untouched; only what scrolls through the edge goes soft. One modifier for all screens (`grBezelSafe`), so it does not get reinvented per view. - The set timer shows digits only **Build 2** · 10 August 2026 Five stores did not survive a single corrupt record — and then wrote the loss to disk - The core - Workout library - PerformanceLog - Training day - Race segments - Station training history - `SurvivableDecoding` is the one answer to this - Nine tests - Learned along the way and written down in `CLAUDE.md` **Build 1** · 10 August 2026 1.1.4 is in review at Apple — from here on 1.1.5 is running - `MARKETING_VERSION` to 1.1.5 - No "What's New" for 1.1.5 until there is something to say - 30 German strings were marked as machine-translated - Left open: 259 entries carry `stale`. Some of them are marked wrongly, because the text sits inside a string interpolation and the extraction doesn't see it. Simply deleting them would therefore be a way to lose live strings — that needs a pass of its own.
improvedAugust 10, 2026

Version 1.1.4

**Build 10** · 10 August 2026 Die Muskel-Kurve zeigt endlich den einzelnen Tag · Drei stille Fehler im Belastungspfad - Deine Simulation von gestern *hat* gezählt — die Kurve konnte es nur nicht zeigen - Jetzt steht die Tagesarbeit als eigener Balken darunter - Und die Beschriftung log - Fix: Die einmalige Nachtragung konnte sich selbst abschalten, bevor sie etwas getan hatte - Fix: Nachträglich gefundene Herzfrequenz erreichte die Trainingsbelastung nicht - Die Neuberechnung sagt jetzt, wenn sie aufgibt **Build 9** · 10 August 2026 What's New misst jetzt gegen 1.1.3, nicht gegen den eigenen Beta-Verlauf - Fix: „Was ist neu" hat Fehler als behoben gefeiert, die es in 1.1.3 nie gab - Und deutlich kürzer - Dieselbe Korrektur in `APP_STORE_1.1.4.md` **Build 8** · 10 August 2026 Der Rahmen zählt Sätze und läuft nicht mehr zurück · Die Workout-Karte sagt endlich, was sie ist - Fix: Der Rahmen sprang zurück — bei Supersets - Gezählt wird jetzt **aufgezeichnete Arbeit**: erledigte Sätze gegen geplante, über den ganzen Plan. Abgeschlossene Blöcke und Satzergebnisse wachsen nur, also **kann** das Ergebnis nicht mehr fallen. Ein Block ohne Sätze — Lauf, Pause — wiegt eins und ist erledigt oder nicht; sein Fortschritt ist Distanz oder eine Uhr, kein Satz. - Die Rechnung liegt jetzt als reine Funktion vor und ist ohne laufende Sitzung prüfbar. Neun Tests, darunter der Superset-Rücksprung und die Übergabe zwischen laufendem und abgelegtem Satzspeicher, bei der genau einen Moment lang beide dieselben Sätze halten. - Fix: Der Rahmen war in den Rundungen unterschiedlich dick - Der Rahmen: härtere Kante unten, mehr Leuchten oben - Die Workout-Karte sagt jetzt, was sie ist - Fix: Warum bei manchen HYROX stand — auch wenn es Deadly Dozen war **Build 7** · 10 August 2026 Ein Plan-Import konnte deine Trainingshistorie löschen · Der Glow zählt jetzt Sätze - Der Fortschritts-Glow auf der Uhr rückt jetzt mit jedem abgehakten Satz weiter - Fix: „Bestimmte Tage ändern" hat abgeschlossene Wochen überschrieben - Ein absolvierter Tag ist eine Aufzeichnung, kein Plan - Die Datumsauswahl beginnt jetzt bei heute - Und du erfährst es - Der Vollersetzungspfad direkt daneben hat seit jeher auf abgeschlossene Einheiten geprüft und gefragt. Der Merge-Pfad tat beides nicht, und für ihn gab es **keinen einzigen Test**. Jetzt vier. - Was nicht verloren war und es auch nie war: die Rohdaten. Trainingsverlauf, Workout-Ergebnisse und Health sind eigene Speicher. Zerstört wurde die Rechenschaft des Plans über sich selbst — und daraus speisen sich jede Abschlussquote, jede Serie und jede Wochenzusammenfassung. **Build 6** · 9 August 2026 Supersets überlebten den KI-Import nicht · Simulation startet als Simulation · Reps-Bereich · Der Generator schrieb deine Auswahl zurück - Fix: Der KI-Import hat jedes Superset verschluckt — und die KI war unschuldig - Die **Einrückung entscheidet**, wo eine Gruppe endet — das ist die Grammatik und nichts kann sie ersetzen. Schreibt ein Modell die Kopfzeile und lässt die Mitglieder linksbündig, wie es in der Praxis passiert, werden die **nächsten zwei** gepaart **und du bekommst eine Warnung**, statt dass geraten und geschwiegen wird. - Eine Kopfzeile mit nur einer Übung darunter ist kein Superset — die Markierung wird wieder entfernt, sonst behandelt die Uhr sie als Gruppe von eins. - Eine Leerzeile beendet eine Gruppe, so wie sie einen Absatz beendet. - Und der Prompt sagt es jetzt unmissverständlich - Fix: Eine geplante Simulation wird auf der Uhr als Simulation gestartet, nicht als Workout nachgebaut - Neu: Wiederholungen sind beim Freien Krafttraining wählbar - Fix: Der Smart Generator hat deine Auswahl überschrieben - Fix: Sätze und Runden ließen sich mit gesetzter Zieldauer nicht ändern - Fix: Der KI-Bildschirm bot immer „30 min" an - Dazu die Diagnose, die ich mir gespart hätte, wenn ich geraten hätte: Der Verdacht lag auf dem Handbearbeitungs-Modus. Der war es nachweislich nicht — `manualBlocks` hat genau einen Schreiber, den „Übernehmen"-Knopf im Editor, und der KI-Bildschirm hat keinerlei Verbindung dorthin. **Build 5** · 9 August 2026 Meine Workouts & Läufe · Watch-Feinschliff · Hinweise für die KI · Store-Texte - „Meine Workouts" heißt jetzt „Meine Workouts & Läufe" - Beide Bibliotheken öffnen mit einem Knopf, der sagt was er tut - Watch: der große FERTIG-Knopf ist vom Trab-Bildschirm verschwunden - Watch: „Lauf fertig" heißt jetzt „Weiter" und steht unten - Watch: der Fortschritts-Glow beginnt unten in der Mitte - KI-Import: ein Feld für das, was kein Auswahlmenü fassen kann - Fix: Der KI-Import zeigte beim zweiten Öffnen noch die erste Konfiguration - Fix: Der Konfigurationsblock im KI-Import waren in Wahrheit drei Blöcke - Projektkonfiguration, beides schon lange falsch und beides bisher unbemerkt - What's New erscheint noch einmal - Neu: `APP_STORE_1.1.4.md` **Build 4** · 9 August 2026 Der Schlüssel, den ich in Build 3 gesetzt habe, gibt es nicht - Fix: App Store Connect hat Build 3 abgelehnt — wegen meiner eigenen Zeile - Der Absturz bleibt behoben - Und die Deklaration wird auf der Uhr gar nicht gebraucht - Der Guard akzeptiert jetzt nur noch `UIBackgroundModes` - Der Test hielt die falsche Annahme fest **Build 3** · 9 August 2026 Der Watch-Absturz bei Intervallen · Eine behaltene Aufzeichnung zählt jetzt überall - Fix: Die Watch-App stürzte eine Sekunde nach dem Start jedes Outdoor-Workouts ab - Und die Uhr darf es jetzt wirklich - Fix: Die Trab-Pause bekam den Stationsbildschirm - Fix: Der Höhenmesser hat sich auf den Hauptthread verlassen, statt auf ihn zu wechseln - Fix: Eine abgebrochene Simulation wurde im Start-Sheet wieder als Vorlage angeboten - Fix: Der Strava-Upload lief für eine behaltene Teilaufzeichnung nie an - Fix: Eine abgebrochene Aufzeichnung verlor ihren geplanten Renneintrag und ihren Namen - Fix: Der gesamte Analyse-Tab war für eine abgebrochene Aufzeichnung gesperrt - Fix: Im Rennvergleich war eine abgebrochene Aufzeichnung sichtbar, aber nicht auswählbar - Fix: Der Plan buchte die geplante statt der tatsächlichen Dauer - Fix: Ein nicht beendetes echtes Rennen öffnete kein Erholungsfenster - Fix: Der KI-Datenexport ließ abgebrochene Einheiten weg - Fix: Demo-Rennen wurden neben eine echte Teilaufzeichnung gesetzt **Build 2** · 9 August 2026 Deine Punchlist, komplett · Rennen zählen endlich für die Trainingsbelastung · Höhenmeter vom Barometer · Strava bekommt die Karte - Fix: Eine Simulation hat die Trainingsbelastung nie bewegt — kein einziges Rennen hat das je getan - Deine bisherigen Rennen werden einmalig nachgetragen - Nebenbefund, im selben Zug behoben: der Trainingsverlauf warf alles älter als 90 Tage weg - Ehrlichkeit auf dem Heute-Tab - Fix: Die Herzfrequenz wird durchgehend gelesen, nicht in 10-Sekunden-Mittelwerten - Fix: Eine Simulation ist kein Rennen mehr - Fix: Eine verkürzte Simulation beschrieb sich mit der vollen Vorgabe - Fix: Eine abgebrochene, aber gespeicherte Aufzeichnung wird jetzt behandelt wie eine abgeschlossene - Neu: Workout und Rennen speichern jetzt dasselbe - Die Zonenzeiten des Rennens werden nachträglich aus Health gerechnet - Fix: Die Herzfrequenz der Satzpause wurde dem folgenden Block angerechnet - Fix: `averageHeartRate` bedeutete je nach Pfad etwas anderes - Neu: HF-Nachholung auch für Workouts - Fix: Der KI-Prompt eines Rennens beschrieb die volle Vorgabe - Fix: Die Composite-Karte nahm halbvolumige Stationszeiten als Bestwerte - Fix: CSV-Distanzspalte, Überschrift des Pace-Diagramms und der „Lap Distance"-Stepper - Fix: Ein am iPhone getrackter Lauf hatte gar keine Herzfrequenz — und niemand holte sie nach - Neu: Deadly Sprint als DD-Variante - Neu: Der Abstieg wird gemessen, angezeigt und gespeichert - Fix: Halbes Volumen wurde an weiteren Stellen als volle Vorgabe ausgegeben - Fix: Die Durchschnitts-Pace wurde aus der offiziellen statt der gelaufenen Distanz gerechnet - Fix: Ein Stations-Rekord konnte nie ausgelöst werden - Neu: Die Stufe wird angezeigt und in neue Pläne übernommen - Fix: Die Renn-Erinnerung wird jetzt zentral gesetzt - Fix: Ein iPhone-getracktes Rennen hatte gar keine Herzfrequenz - Fix: „Simulation planen" auf dem Trainings-Tab tat gar nichts - Fix: Die konfigurierte Simulation verlor Titel und Portion - Ehrlicher: Die Last-Kachel auf „Heutige Aktivität" heißt jetzt „ca." - Watch: Der Glow liegt jetzt auf jedem Trainingsbildschirm — und die letzten zwei alten Fortschrittsbalken sind weg - Fix: Die letzte Wiederholungs- und Kilogramm-Zahl hinter der Übung war eine andere als die letzte - Fix: Höhenmeter beim Laufen kamen vom Treppenzähler - Fix: Der Lauf kam bei Strava ohne Karte an, mit zu wenigen Herzfrequenz-Punkten - Fix: Intervalle mit Trab-Pause - Fix: Der letzte Satz am iPhone hat die Uhr nicht weitergeschaltet - Geprüft und in Ordnung: Pausenzeiten gehen sehr wohl an die Uhr - Neu: Einen abgehakten Satz wieder öffnen - Fix: Das Alter wurde nicht gespeichert - Fix: Eine aus der Session-Ansicht gestartete Simulation hat das verknüpfte Rennen überschrieben - Fix: Die Renn-Erinnerung nannte das falsche Datum und eine Division, die gar nicht hinterlegt war - Neu: Eine gelaufene Simulation bleibt als Vorlage erhalten - KI-Pläne planen jetzt richtige Simulationen - Watch: Der Fortschrittsbalken ist weg — der Fortschritt liegt jetzt als dezenter Glow auf dem Rand - Fix: Die Aufzeichnungsprüfung hat deine Sprint-Simulation am vollen Feld gemessen **Build 1** · 6 August 2026 Die Rennvorhersage kannte deine Simulationen nicht · 39 englische Texte - Fix: Die Rennvorhersage hat die Stationen weggelassen und trotzdem eine Zielzeit ausgegeben - Fix: „Ein Rennen abschließen, um Benchmarks zu sehen" — neben acht abgeschlossenen Einheiten - Und wenn wirklich nichts da ist, sagt die Karte, was fehlt - Fix: 39 englische Texte quer durch die App **Build 1** · 29 June 2026 Watch Übungs-Detailansicht: Überlappungen oben & unten behoben - Zurück-Button überlappte den Übungstitel und die Navigationsleiste war überladen (Progress + Zähler + Timer + Herzfrequenz brachen neben die Systemuhr um). Die `.topBarTrailing`-Toolbar zeigt jetzt nur noch Progress-Balken + „x/y"-Zähler. - Timer und Herzfrequenz in eine kompakte Stat-Zeile direkt unter dem Übungsnamen verschoben (statt in die Navigationsleiste). - Titelzeile bekommt einen Top-Inset, damit der Übungsname garantiert frei vom Back-Button steht. - Notizen kollidierten mit dem Pause-Button: Ursache war `.frame(height: screenGeo.size.height)` — bei Supersatz-Badge/zusätzlichem Inhalt lief der Inhalt über und überlappte die Notizen. Jetzt `.frame(minHeight:)`, sodass höherer Inhalt scrollt statt zu überlappen. Watch ↔ iPhone Live-Workout: Sync- & Navigationsfehler behoben - Übung in der Watch-Übersicht antippen brach das ganze Training ab: In `WatchExerciseDetailView` ist die Übersicht eine TabView-Seite — `returnToWorkout()` rief `dismiss()` auf und verließ damit die komplette Workout-Ansicht. Jetzt wird per `onReturn` zurück zur Übungsseite gewechselt (`pageIndex = 0`). - Abgeschlossene Übungen in der Watch-Übersicht waren nicht mehr antippbar. Tap-Handler erlaubt jetzt das Antippen jedes Blocks (außer dem aktuellen) — abgeschlossene Blöcke werden über `jumpToBlock` wieder geöffnet (`completedBlockIds`/`superSetProgress`/`allBlockSetResults` für den Ziel-Block zurückgesetzt), sodass sie erneut bearbeitet/wiederholt werden können. - Satz auf dem iPhone für eine andere Übung abgehakt landete fälschlich beim aktuell auf der Uhr offenen Block (z. B. „pull down" abgehakt → erster Satz von „Nordic curl" abgehakt): `RemoteWorkoutCommand.setCompletion` enthält jetzt die `blockId`. `handleRemoteSetCompletion` springt auf der Uhr zuerst zur Ziel-Übung (`jumpToBlock`) und wendet den Satz dort an. Dedup-Hash nutzt jetzt `blockId` statt `currentBlockIndex`. - Auto-Ende des Trainings trotz übersprungener/übersprungener Übungen: Neue Hilfsfunktion `nextIncompleteBlockIndex(after:)` sucht den nächsten unfertigen Übungs-/Stations-/Lauf-Block (vorwärts, dann zurück zu früher übersprungenen/„vorbeigesprungenen" Blöcken). `finishBlock` und `skipBlock` beenden das Training automatisch erst, wenn wirklich alle Übungen abgeschlossen sind. Watch Workout-Ansicht: Progress Bar + Tile-Größen - Progress bar (Segmente/Balken), Block-Counter, Laufzeit und Herzfrequenz aus dem Scroll-Content entfernt und als Toolbar-Item (`.topBarTrailing`) in die Navigationsleiste verlegt — direkt neben dem Zurück-Button - Durch den frei gewordenen Platz: Tiles füllen mehr Höhe, Pause-Button und Notizen werden nicht mehr gequetscht - Haupt-Zahlen in KG-, Reps-, Distance- und Duration-Tiles: 20 pt → 22 pt - Stepper-Buttons (–/+) in allen Tiles: 24×16 → 26×18 pt, Icon-Schrift 10 → 11 pt Trainingsplan: Doppelte Übungen & lange Pausen behoben - ExerciseLibraryView: Custom-Übungen mit gleichem Namen wie eingebaute Übungen werden jetzt dedupliziert — kein doppelter Eintrag (mit und ohne Bild) mehr in der Übungsbibliothek - TrainingPlanManager `buildStrengthSession`: Extra-Pause nach Alternativ-Blöcken (`alternativeBlocks`) entfernt — Alt-Blöcke sind selbst getimete Übungssegmente und brauchen keine zusätzliche Ruhezeit - Überhang-Pause am Ende des Kraftübungs-Abschnitts wird jetzt vor dem Stations-Abschnitt entfernt, um keine konsekutiven Pausen durch den Übergang zu erzeugen - `mergeConsecutivePauses`: Zusammengefasste Pausen werden auf maximal 120 s (2:00) begrenzt — verhindert extreme Pausen (z. B. 4:30, 5:00) durch aufaddierte Alt-Block-Pausen - `buildAccessorySession`: Fallback-Pause bei fehlendem Equipment von 1200 s (20 min) auf 120 s korrigiert Watch Workout-Ansicht: Layout-Überarbeitung - KG/Reps/Distance/Time-Tiles kleiner (Hauptzahl 20 pt statt 24 pt, Stepper-Buttons 24×16 statt 26×18) - Done-Button (✓) und Pfeil-Buttons (<, >) schmaler (Höhe 28 pt statt 36 pt) - Notizen aus dem Inhaltsbereich entfernt und unterhalb des Pause-Buttons platziert (nur durch Scrollen sichtbar, voller Text ohne Kürzung) - ScrollView-Wrapper: Hauptinhalt füllt genau den sichtbaren Bildschirmbereich (GeometryReader), Notizen liegen initial unterhalb des sichtbaren Bereichs - Fortschrittsbalken-GeometryReader-Variable umbenannt (barGeo) um Konflikt mit äußerem GeometryReader zu vermeiden
improvedAugust 6, 2026

Version 1.1.3

**Build 27** · 6 August 2026 Die Laufanalyse hat den Prompt statt der Antwort gezeigt · „Dein letzter Lauf" konnte nie erscheinen - Fix: In der Laufanalyse stand unser eigener Prompt - Fix: „Dein letzter Lauf" konnte gar nicht erscheinen — drei Fehler übereinander - Fix: Überschriften ohne Inhalt - Fix: Fünf englische Platzhalter **Build 26** · 6 August 2026 Der Analyse-Tab, Karte für Karte gegen echte Daten geprüft · Stufennamen sagten die falsche Zeit · DEKA und ATHX kannten ihre Divisionen nicht - Fix: DEKA und ATHX haben die Kategoriewahl ignoriert - Fix: Der Name einer Leistungsstufe hat über die eigene Grenze gelogen - Fix: 48 englische Texte im Analyse-Tab - Fix: Deutsche Zahlen - Fix: Überschriften ohne Inhalt - Der Stufentitel bricht nicht mehr mitten im Wort **Build 25** · 6 August 2026 Die Analyse-Karte war unsichtbar · Feldverteilung: Laufen und Stationen ordneten Halbes gegen Ganzes ein - Die Karte „Dein letzter Lauf" hast du nirgends gefunden, weil sie nirgends war - Fix: Feldverteilung, Reiter Laufen und Stationen - Und wenn eine Zeit schneller ist als die schnellste je erfasste, wird sie nicht mehr eingeordnet - Kalorien: keine erfundenen mehr **Build 24** · 6 August 2026 Strava bekommt endlich die ganze Einheit - Fix: Die gelaufene Distanz kam nie im Ergebnis an - Fix: Zwei Stunden zu früh - Neu: Bleibt HealthKit stumm, baut die App die Datei selbst - Die Beschreibung trägt jetzt alles, was die Aktivitätsfelder nicht können **Build 22** · 6 August 2026 Der Generator hört wieder zu · Übungsbilder überall · Einmaliger Hinweis zu den Lauf-Ansichten - Fix: „Ich will Planks" ergab drei Übungen, die keine Planks waren - Das erklärt auch den zweiten Teil - Übungsbilder erscheinen jetzt auch im Smart Generator - Neu: einmaliger Hinweis zu den Lauf-Ansichten **Build 21** · 6 August 2026 Die Analyse sagt jetzt, was die Zahlen bedeuten - Neu im Analyse-Tab: „Dein letzter Lauf" - Warum das überhaupt geht - Was sie liest - Nichts davon schreibt eine KI - Sie schweigt lieber, als etwas zu behaupten - Fix: Die Zonenzeiten wurden nie gespeichert - Fix nebenbei **Build 20** · 6 August 2026 Die Zonen rechnen jetzt wie Apple - Die Herzfrequenz-Zonen folgen ab sofort der Herzfrequenz-Reserve - Das alte Modell bleibt wählbar - Die Umstellung greift überall gleichzeitig - Deine gespeicherten Werte überstehen das Update **Build 19** · 6 August 2026 Höhenmeter mit Live-Verlauf · Deine Pausen zählen wieder · Strava hängt nicht mehr fest - Höhenmeter sind drin — mit Verlaufskurve - Fix: Deine Pausen wurden verworfen - Fix: Strava hing über eine halbe Stunde - Fix: Die Werte auf den Lauf-Karten saßen versetzt - Fix: „ZURÜCKSETZEN" brach mitten im Wort - Fix: Das iPhone konnte die Watch-App nie starten - Neu: Die Einstellungen zeigen jede Lauf-Ansicht als Vorschau **Build 18** · 5 August 2026 Kurze Fehlstarts zählen nicht mehr mit · Die Kadenz-Karte ist befüllt - Aufzeichnungen unter zwei Minuten fließen in keine Statistik mehr ein - Gelöscht wird trotzdem nichts hinter deinem Rücken - Die Karte „Sechs Werte" ist jetzt befüllt und auswählbar - Die Höhen-Karte bleibt vorerst gesperrt, und zwar bewusst **Build 17** · 5 August 2026 Der Lauf-Bildschirm gehört den Zahlen · Vier Ansichten, die der Cloud-Compiler fast nicht mehr geschafft hätte - Der „Fertig"-Button ist vom Lauf-Bildschirm verschwunden - Damit sind die Zahlen deutlich größer geworden - Fix: Auf der Ziel-Karte stand „60:…" statt der Prognose - Unsichtbar, aber wichtig: **vier Ansichten waren dem Cloud-Compiler gefährlich nahe.** Die Rennhistorie brauchte allein **zwölf Sekunden** zum Typprüfen, dazu Wochenleiste (4,7 s), KI-Import (2,6 s) und Analyse-Tab (2,3 s). Xcode Cloud rechnet mit einem anderen Komplexitätsbudget als mein Rechner — zwei Builds sind daran schon gescheitert. Alle vier sind in kleinere Einheiten zerlegt; die Rennhistorie liegt jetzt unter 0,7 s. Am Verhalten ändert sich nichts. **Build 16** · 5 August 2026 Die Trainingshistorie ist wieder da · Der Lauf-Bildschirm nutzt endlich den Bildschirm - Fix: In der Historie standen nur noch Simulationen, keine Trainings mehr - Und es hätte fast echten Schaden gegeben - Fix: Auf dem Lauf-Bildschirm wurde die zweite Wertezeile mitten durch die Ziffern abgeschnitten - Der „Fertig"-Button war ein Fünftel des Bildschirms groß - Oben lagen 55 pt totes Schwarz - Damit passen auch größere Zahlen - Und die Block-Kachel zeigte „13…" statt „13 KM" **Build 15** · 5 August 2026 Der Lauf-Screen blättert und ist einstellbar · Verworfen heißt verworfen · Pausen zählen als Pausen - Fix: Die Werte auf dem Lauf-Bildschirm werden endlich in der Größe angezeigt, in der sie gesetzt sind - Deshalb zeigt die Karte jetzt weniger und dafür groß - Fix: Eine verworfene Simulation landete trotzdem in Apple Health - Fix: „Pause 0:00" nach einem Training mit Satzpausen - Fix: In der Satzpause stand die Übung, die gerade fertig war - Drei weitere Lauf-Ansichten sind jetzt echt befüllt: Zonen, Leistung und Laufform - Die Puls-Ansicht kennt jetzt Ø und Maximum der ganzen Einheit - Technisch: alle Messgrößen werden ab sofort **namentlich angefordert** statt sich auf die Voreinstellungen der Trainingsart zu verlassen. Der Renn-Pfad startet als Intervalltraining, wo Geschwindigkeit, Leistung und Laufform gar nicht erst erhoben worden wären. - Neu: Du stellst die Lauf-Ansichten selbst zusammen - Neu: der Lauf-Bildschirm blättert vertikal - Die Ziel-Karte erscheint nur, wenn der Laufblock überhaupt eine Zieldistanz hat - Ø-Pace, GPS-Anzeige, Zielpace und Höhenmeter sind eine Wischgeste weiter gezogen - Nebenbei: **volle Zeilenbreite statt Raster hätte es schlimmer gemacht.** Vier gestapelte Zeilen in dasselbe Höhenbudget ergeben 13,4 pt — weniger als heute. Apples Trainings-App kommt damit durch, weil sie weniger Werte zeigt **und scrollt**; diese Seite scrollt bewusst nicht, damit nichts springt, wenn mitten im Lauf GPS- oder Pulsdaten eintreffen. **Build 14** · 5 August 2026 Krafttraining ist kein Lauf · Die Kardio-Kurve erfand Ruhetage · Ein Screen für eine Einheit - Fix: Krafttraining landete auf Strava als „Lauf", nur wegen des Aufwärm-Laufs - Fix: „Keine HF-Daten verfügbar — Training muss in Apple Health gespeichert sein" bei einer Simulation, die Herzfrequenz aufgezeichnet hat - Fix: Die Kardio-Belastung hat zwei Wochen echtes Training zu Ruhetagen erklärt - Fix: „Keine Herzfrequenzdaten für diese Einheit verfügbar" — über einer Übungsliste, in der jede Zeile eine Herzfrequenz zeigt - Eine vergangene Einheit öffnet jetzt überall denselben Screen **Build 13** · 5 August 2026 Der Verlauf zeigt die Analyse mit · Zwei gleichwertige Karten · Kardio-Kurve fiel ab - Eine vergangene Einheit zeigt jetzt alles auf einmal: nochmal starten, was du gemacht hast, und die Analyse dazu - Der Verlauf im Trainings-Reiter enthält jetzt auch Simulationen - Neu: „Meine Workouts" und „Meine Simulationen" als zwei gleich große Karten - Fix: „Das Workout ist nicht mehr gespeichert und kann daher nicht wiederholt werden." - Fix: Die Kardio-Belastung fiel ab, weil meine Doppelzähl-Sperre von gestern zu grob war - Fix: Mein Zeilenumbruch im Tab-Label hat „Simulationen" verschluckt **Build 12** · 4 August 2026 Simulationen haben jetzt genau ein Zuhause - Der Reiter „Rennen" enthält nur noch echte Rennen - Der Simulationen-Button führt jetzt in die Übersicht - Die Auswertung absolvierter Simulationen liegt im Analyse-Reiter - Die Trainingshistorie ist nicht mehr gedeckelt - Das Tab-Label skaliert **Build 11** · 4 August 2026 Aus „Train" wird „Training + Simulationen" - Fix: „73 % Stationen" auf einer Simulation, bei der nur die Läufe abgewählt waren - Der Reiter heißt jetzt Training + Simulationen - Der Planer öffnet jetzt kurz - Die Übernahme ins erweiterte Bearbeiten braucht keinen Code - Neu: Vergangene Trainings - Neu: Simulationen vergleichen - „Teilsim" heißt jetzt „Teilsimulation" **Build 10** · 4 August 2026 Gerade Linien in allen Diagrammen · Herzfrequenz am richtigen Ort - Alle 62 Diagramm-Linien laufen jetzt gerade von Punkt zu Punkt - Zur Punktdichte - Fix: „Keine Herzfrequenzdaten für diese Einheit verfügbar" — obwohl durchgehend aufgezeichnet - Fix: „2x am Ausgangsgate tippen" lag hinter der Seitenanzeige **Build 8** · 4 August 2026 Abgebrochene Aufzeichnungen kommen jetzt wirklich an - Fix: Eine abgebrochene, gespeicherte Simulation war weiterhin nirgends zu sehen — mein Fix von gestern saß an der falschen Stelle - Fix: Der Plantag findet die Aufzeichnung - Fix: Und die Analyse lässt sich von dort auch öffnen - Neu: Was der Plan nicht vorgesehen hat, trägt er trotzdem ein **Build 7** · 4 August 2026 Vier Stationen, die nie Top 1 % waren · Der Planer zeigt den Ablauf · · · Vier Stationen auf „Top 1 %", die es nie waren - Fix: Eine einzelne Stationszeit wurde mit der Quote des ganzen Rennens hochgerechnet - Fix: Zeiten schneller als alles je Gelaufene bekamen einen Goldpokal - Neu: Das Rennen steht jetzt als eine durchgehende Liste da - Neu: Eine Zeile sagt, was du gebaut hast - Fix: Eine Simulation, deren Reduktion nie gespeichert wurde, setzt keine Bestzeiten mehr - Neu: Ein Schalter ganz oben im Simulations-Planer — Schnell oder Detailliert - Wichtig dabei: Der schnelle Weg fragt weniger, speichert aber nicht weniger - „Das ergibt" unter der Auswahl **Build 6** · 4 August 2026 Der Karten-Durchgang: 55 Befunde · Vier Simulations-Fehler · 104 Texte auf Deutsch · · · „Renegade Row" enthält „Row" · Doppelt verbuchte Trainingslast - Fix: Dreizehn Übungen fragten nach einer Maschinenstufe statt nach Kilogramm - Neu: Die Lastart ist am iPhone umstellbar - Fix: Jede in der App getrackte Einheit wurde doppelt in die Trainingslast gebucht - Fix: Zwei Trainingskarten maßen gegen Rennen, die es nie gab - Fix: „Std/Wo Ø" zeigte die letzte Woche, nicht den Schnitt - Fix: Acht Renn-Karten zeigten die Demo-Rennen als deine eigenen - Fix: Dieselben Karten kannten deine Division nicht - Fix: Die Rennzeit-Prognose zählte jede Wechselzeit doppelt - Fix: Wechselzeiten-Karte übersah die genauer aufgezeichneten Rennen - Fix: Halbsimulationen wurden als Vollleistung benotet - Fix: Jedes komplette Deadly-Strong- und Deadly-ERG-Rennen galt als Teilsimulation - Fix: Die Wechselzeit wuchs mit dem Stations-Arbeitsaufwand statt mit der Stationszahl - Fix: Der Zeit-Donut zählte gemessene Wechselzeiten doppelt - Fix: Die Deadly-Dozen-Zielzeit ließ jeden Wechsel weg - Fix: Rennvergleich rechnete mit eigenen Faktoren - Fix: „Stations-König" und „Eiserne Serie" verglichen über Divisionen hinweg - Fix: Segment-Pokale und die Aufzeichnungs-Prüfung maßen an Demo-Rennen - Fix: Der KI-Export beschrieb Halbsimulationen als volle Rennen - Fix: „Neuestes Rennen" im Export war nicht das neueste - Fix: Stations-Bestzeiten im Export zählten den Anlaufweg mit - Fix: Auch die „Beste Teile"-Karte maß Tor zu Tor - Fix: Eine reine Stations-Einheit galt als „erste Hälfte" - Die App spricht jetzt vollständig Deutsch - Fix: Der Pace-Rechner bot eine Bestleistung an, die niemand je gelaufen ist - Fix: Die Feldverteilungs-Tabs „Laufen" und „Stationen" widersprachen dem Tab „Gesamt" - Fix: Fast jede ehrliche Hälfte war nur als „Teil" beschriftet - Fix: Eine Simulation nur aus Übungen bekam trotzdem einen Lauf vorneweg - Fix: „Ab Station 6" startete mit Lauf 1 statt Lauf 6 - Fix: Ohne Läufe wurden gar keine Wechselzeiten aufgezeichnet - Fix: Eine abgebrochene, gespeicherte Simulation war nirgends zu finden - Neu: Abgebrochene Aufzeichnung als Teilsimulation übernehmen - Neu: Verwerfen fragt nach - Fix: 0 kg sprang auf den geplanten Wert zurück **Build 5** · 3 August 2026 Die Stufenfolge, die nur im Kommentar stand · Alle Paare zählen - Fix: Die neue Bestleistungs-Regel versprach eine Rangfolge und rechnete ein flaches Minimum - Alle Hälften-Paare zählen, nicht nur eins **Build 4** · 3 August 2026 Die Wechselzeiten fehlten in jeder Hochrechnung · Feldverteilung antippbar · Fitness-Karte war unerreichbar - Fix: Deine Wechselzeiten wurden in jeder Hochrechnung unterschlagen - Die zusammengesetzte Zeit enthält jetzt eine Wechselzeit-Zulage - Feldverteilung: antippen und halten - Feldverteilung steht jetzt über der Karte „Deine besten Teile" - Fix: Die neue Fitness-Karte war nicht auffindbar — weil sie in einer View lag, die nirgends angezeigt wird - Zwei Hälften werden jetzt gepaart statt einzeln verdoppelt - Fix: Labour-Bestzeiten waren nicht nach Division gefiltert **Build 3** · 3 August 2026 Die Fitness-Kurve sagt jetzt etwas · Fehlende Segmente werden aus dem Feld ergänzt · Charts, die nicht mehr aus der Karte laufen - Die Fitness-Kurve ist in die Trainings-Analyse umgezogen und sagt jetzt etwas - Segmente, die du noch nie gemacht hast, werden jetzt aus dem Feld ergänzt statt weggelassen - Fix: Der Körperfett-Chart lief unten aus seiner Karte heraus - Charts wachsen mit dem Zeitraum - Bei vielen Messpunkten verschwinden die Punkte - Fix: Die Feldverteilung rangiert keine Zeit mehr, die schneller ist als die schnellste je in dieser Division gelaufene - Fix: Die Feldverteilungs-Karte hat deine Zeiten nie nach Division gefiltert - Fix: Der KI-Export nannte für dieselbe Simulation drei verschiedene Zeiten - „Noch nicht gemessen" heißt jetzt wirklich noch nicht gemessen - Neu: Die App erkennt jetzt, wenn ein Gate-Tap nicht angekommen ist — und sagt, wie es wahrscheinlich war **Build 2** · 3 August 2026 Eine 32-Minuten-Halbsimulation stand als „Sub-50 (Elite)“ da · Auszeichnungen ohne Volumenprüfung · 30 deutsche Texte, die nie erschienen sind · Fitness-Kurve und Muskel/Fett-Trend - Neu im Analyse-Tab: eine Fitness-Kurve - Neu bei den Körperdaten: Muskel/Fett-Verhältnis mit geglättetem Trend - Fix: Die Feldverteilungs-Karte rechnete mit einem anderen Ermüdungsmodell als die Level-Karte - Fix: „+3 heute" in der Trainingslast-Karte stand auch im englischen Build auf Deutsch - Fix: Eine halbe Simulation galt weiter als volles Rennen — jetzt ist die eigentliche Ursache gefunden - Fix: 16 deutsche Übersetzungen konnten nie erscheinen - Die Karte „Deine besten Teile, zusammengesetzt" rechnete 4:42 für ein volles Rennen - Der Aufschlag auf die Summe deiner besten Teile wird jetzt an dir gemessen, nicht angenommen - Fix: Die Analyse verglich Deadly-Dozen-Rennen ohne Rücksicht auf die Division - Fix: Der Analyse-Tab rang eine halbe Deadly-Dozen-Simulation als „Sub-50 (Elite), Top 1,3 % des Feldes" ein - Neu: „Deine besten Teile, zusammengesetzt" - Fix: Eine abgeschlossene Simulation bot weiter an, geplant zu werden - Wer über die Karte „Plane diese Simulation" geht, plant sie jetzt auf diesen Tag - Die Transition-Gehzeit ist ein Schalter, kein Zähler - Simulationen sind Vorlagen, keine Termine - Neu: Aktionsleiste im Simulations-Planer - Fix: Die Workout-Liste bekam für dasselbe Workout immer neue Karten - Ein Tag mit mehreren Einheiten lässt sich jetzt durchblättern - Fix: Die Trainingsanalyse drehte sich unbegrenzt - Rennvergleich: Herzfrequenz pro Segment als eigene Zeilen - Fix: Die Herzfrequenz des letzten Segments war in jedem Rennen erfunden - Fix: Ein zurückgenommener Gate-Tap warf die Herzfrequenz des Segments weg - Fix: Sieben Auszeichnungen wurden ohne jede Volumenprüfung vergeben - Fix: Die Bestleistung auf der Dashboard-Karte kam ungefiltert aus dem Rennspeicher - Fix: Deadly-Dozen-Rennen wurden in den Analysekarten nicht nach Division gefiltert - Fix: 14 weitere Texte erschienen auch auf Deutsch englisch - Die Composite-Karte hat jetzt auch eine Obergrenze - App-Store-Screenshots nennen die Formate auf jedem Bild **Build 1** · 2 August 2026 Zone-2-Vorschlag von 5:09 · Pro-Käufer sahen Werbung für Gekauftes · Plan-Löschen war eine Sackgasse - Wer sein erstes Rennen plant, bekommt jetzt echte Startwerte statt Formatvorgaben - Fix: Die App schlug eine Zone-2-Pace von 5:09/km vor — schneller, als der Athlet je locker läuft - Fix: Die Karte behauptete „HR-Pace-Kopplung" und rechnete etwas anderes - Ein Puls-Deckel räumt die Altlast auf, ohne gespeicherte Läufe anzufassen - Kein Vorschlag springt mehr weit - „Stationen-Bestzeiten" zeigt für ATHX jetzt Zonenwertungen statt Zeiten - Fix: Die „YOUR LEVEL"-Karte wählte das ATHX-Referenzrennen nach der kürzesten Gesamtzeit - Fix: Die ENERGY-SPLIT-Karte zeigte für ATHX „Lauf 0:00 · 0 %" - Fix: Die Trophäe für eine Segment-Bestzeit erschien auf ATHX-Zonen - Fix: Das KI-Briefing bekam die ATHX-Zonendauern unter der Überschrift „Stations:" - Fix: VoiceOver las das ATHX-Format als „0 Runs, 0 meters each" vor - Fix: Auf der Uhr gab es für ATHX eine Pace-Zielvorgabe in min/km - Fix: „Auto-Ausfüllen aus Zielzeit" skalierte bei ATHX die festen Zonenfenster - Fix: Der Renn-Rechner bot ATHX an, kappte aber jede Zone bei 10:59 - Fix: Ein geplantes ATHX-Rennen zeigte in der Detailansicht einen erfundenen „Run 1" und nur die erste Zone - Fix: Die Puls-Kurve über das ganze Rennen zeigte bei ATHX nur eine von drei Zonen - Fix: ATHX-Zonen wurden weiter als Leistung gewertet - Fix: Der Analyse-Tab meldete für ATHX einen erfundenen Einbruch - Fix: Auf der Uhr vibrierte es drei Minuten nach jedem Zonenstart - Fix: In Health landete jedes Rennen unter einer HYROX-Kategorie - Fix: Ein exportiertes ATHX-Rennen bestand aus drei „Resting"-Runden - Fix: Die MetCon-X-Ergometer fragten nach Kilogramm - Fix: Die Endurance-Zone war mit „750 m" beschriftet - Fix: Live-Aktivität, Live-Tracker, Startblatt, Simulationskarte und die Renntag-Mitteilung - Rennen kommen jetzt mit einer echten Beschreibung bei Strava an - Fix: Strava bekam vom Rennen fast keine Pulsdaten - Fix: Der KI-Plan-Prompt beschrieb ein anderes Jahr - Fix: Jedes ATHX-Rennen wurde dem Modell als „Runs: 0:00" gemeldet - Fix: Der Smart-Generator zeigte bei ATHX Regler für Laufrunden und Lauf-Skalierung - Fix: Deadly-Dozen-Athleten mit Pro sahen ihre Stationen-Benchmarks nie - Fix: Die Feldverteilung erfand eine Kurve, wo keine Daten sind - Fix: Die Stationen-Bewertung maß sich an sich selbst - Fix: Demo-Rennen konnten Benchmark- und Prognosegrundlage werden - Fix: Die Formatauswahl der Uhren-Komplikation war für alle auf Deutsch - Fix: Die gestern eingebaute Plan-Sperre hat Löschen zur Sackgasse gemacht - Die Sperre ist ersatzlos raus, nicht repariert - Der Hinweis „Dein erster Plan war gratis…" im Assistenten fällt damit weg — er stimmte so nicht. **Build 1** · 28 June 2026 Trainingsdauer-Schätzung korrigiert `estimateBlockDuration` (TrainingPlanManager) und `estimatedDurationMinutes` (WorkoutPlan) haben bisher die Zeit für Krafteinheiten stark unterschätzt: - Stations- und Übungsblöcke berücksichtigten die Pause zwischen den Sätzen (`restBetweenSetsSeconds`) gar nicht — nur eine pauschal addierte Konstante (90 s flat statt `(Sätze–1) × Pausendauer`). - Stations-Blöcke ignorierten `exerciseSets.count` und verwendeten immer einen Fixwert von 180 s, egal ob 1 oder 3 Sätze. - Timed Exercises (`durationTargetSeconds`) wurden nicht berücksichtigt. Fix: Beide Funktionen berechnen jetzt: - `Sätze × Arbeitszeit/Satz` (aus `durationTargetSeconds`, Wiederholungen × 2,5 s, oder Default 50 s) - `+ (Sätze–1) × restBetweenSetsSeconds` (Pause zwischen Sätzen, Default 90 s) - `+ 20 s Setup/Transition` pro Block Ergebnis: 3-Satz-Übung (10 Wdh., 90 s Pause) = ~5 min statt ~3,75 min.
improvedAugust 1, 2026

Version 1.1.2

**Build 68** · 1 August 2026 Preise neu gesetzt · vier Stellen im Code sagten vier verschiedene Zahlen - Pro kostet jetzt 24,99 € einmalig statt 9 $, das Monatsabo 2,99 € statt 2 $ - Die kostenlose Testphase geht von 3 auf 7 Tage - Fix: Vier Stellen im Code nannten den Preis, und keine zwei stimmten überein - Fix: Die Testphasen-Länge stand als Text in der Paywall **Build 67** · 1 August 2026 Die Uhren-Komplikationen waren in keinem einzigen Build enthalten - Fix: „GritRace" tauchte in der Komplikations-Auswahl der Uhr nie auf - Warum sie nicht einfach eingetragen war - Damit funktionieren **Race Countdown** und **Last Race** auf dem Zifferblatt: Zifferblatt lange drücken → Bearbeiten → Komplikationen → GritRace. Der Countdown lässt sich beim Hinzufügen auf ein Format filtern. **Build 66** · 1 August 2026 Der Plan gehört dir · Paywall verspricht nur noch, was Pro wirklich freischaltet - Die Pro-Grenze liegt jetzt bei der Plan-Erstellung, nicht beim Weitertrainieren - Der Hinweis steht vor dem Tipp, nicht danach - Die Paywall hat drei Dinge verkauft, die jeder ohnehin hat - Fix: Die Paywall-Liste war nie übersetzt - Fix: Ein zurückerstatteter Kauf hätte Pro behalten — und ein gültiger hätte ihn verlieren können - Der KI-Import sichert sich selbst ab - Aufgeräumt: ein Kommentar, der eine lebende Funktion für tot erklärte **Build 65** · 1 August 2026 Gespeichertes Workout startete mit allem abgehakt · Supersatz-Satz einzeln · Laufansicht lesbar - Fix: Ein gespeichertes Workout startete mit fast allem abgehakt - Fix: Ein einzelner Satz bei einem Supersatz kam nicht auf der Uhr an - Fix: In der Laufansicht lag der Titel unter dem Zurück-Pfeil - Laufansicht lesbarer **Build 64** · 1 August 2026 Gestern erkannte Workouts standen am nächsten Morgen unter „heute" - Fix: Erkannte Workouts vom Vortag blieben stehen - Fix: „Zusätzliches Workout" hat sich das Workout nicht gemerkt **Build 63** · 31 July 2026 Belastungskurve lief flach · TSB ignorierte das Krafttraining · Einheiten in den Kacheln - Fix (Regression von heute): Die Belastungskurve lief über Tage schnurgerade - Fix: TSB ließ das Krafttraining komplett außen vor - Fix: „31" neben „2780" sah aus wie ein Rechenfehler **Build 62** · 31 July 2026 Warum manche Sätze abgehakt ankamen und manche nicht · Health-Distanz Anlass war: „manchmal werden welche abgehakt, manchmal aber nicht, obwohl auf der Uhr vollständig getrackt". Die Ursache ist keine verlorene Übertragung — die Uhr schickt jedes Mal den **vollständigen** Stand aller Sätze, und jeder Empfänger hakt davon immer die **ersten n** Zeilen ab. Ein Loch wie „Satz 2 abgehakt, Satz 1 nicht" kann daraus rechnerisch gar nicht entstehen. - Fix: Die Übungskarte hat den Stand der Uhr regelmäßig verworfen - Fix: Anzeigen war weiterhin Speichern - Fix: Der Live-Tab löschte bei jedem Aufruf alles Abgehakte - Fix: Ein in der Pause abgehakter Satz war für immer verloren - Fix: Health bekam die falsche Distanz **Build 61** · 31 July 2026 Das Gewicht der vorigen Übung wurde der nächsten vorgeschlagen · Fortschrittspfeil war unsichtbar · Übungsbilder auf der Uhr - Fix: 107 kg beim Thruster, obwohl du dort zuletzt 36 kg hattest - Fix: Anzeigen war gleichbedeutend mit Speichern - Fix: Der Fortschrittspfeil war unsichtbar, wenn er etwas zu sagen hatte - Neu: Übungsbilder auf der Uhr **Build 60** · 31 July 2026 Frauen wurden bei DEKA und ATHX gegen das Männerfeld gewertet · Watch: Name antippen startet nicht mehr - Fix: Bei DEKA und ATHX entschied die Reihenfolge der Liste über dein Geschlecht - Fix: Sieben Analyse-Abschnitte zeigten bei ATHX nur eine Überschrift - Watch: Auf den Workout-Namen tippen startet nicht mehr das Training **Build 59** · 31 July 2026 Ein nachgetragenes ATHX-Rennen war 0:00 und blockierte danach jede Bestzeit Weiter im ATHX-Durchgang, diesmal Nachtragen, Teilen, Export und die KI-Prompts. - Fix: Ein nachgetragenes ATHX-Rennen wurde als 0:00 mit drei übersprungenen Zonen gespeichert - Fix: Der KI-Prompt zur Rennanalyse widersprach sich selbst - Fix: Die Share-Karte druckte den Veranstalter-Zeitplan als Erkenntnis - Fix: Das iPhone-Widget zeigte eine formatfremde Bestzeit - Fix: Die Uhr bot ATHX ein Tempo in min/km an - Fix: Die Dynamic Island zeigte durchgehend ein Laufsymbol **Build 58** · 31 July 2026 ATHX auf der Uhr: die Zonen-Vorgabe war weg, sobald ein Plan geladen war Ein systematischer Durchgang durch alles, was bei ATHX blind von den Lauf-plus-Stationen-Formaten übernommen wurde. Vier bestätigte Fehler: - Fix: Die Uhr verlor die komplette Zonen-Vorgabe, sobald ein Rennplan geladen war - Fix: Ein geplantes ATHX-Rennen zeigte auf der Uhr „RUN 1" und nur eine von drei Zonen - Fix: Die Korridor-Erkennung lief in ATHX-Zonen und schnitt jeder Zone Zeit ab - Fix: Persönliche Bestzeit und Trend-Abzeichen auf einer ATHX-Gesamtzeit **Build 57** · 31 July 2026 ATHX: die Zielgewichte werden jetzt vorgeschlagen · und die App personalisierte bisher genau das Falsche ATHX ist die einzige Stelle in der App, an der die Last **nicht vorgegeben ist, sondern die Punktzahl ist**: Zone 1 sind drei selbst gewählte Maximalgewichte (Saison 2026: 1RM Strict Press, 3RM Back Squat, 5RM Deadlift). Trotzdem standen alle sieben ATHX-Zielfelder auf 0, ohne jede Vorbelegung — während die App gleichzeitig die 20- und 22-Minuten-**Zeitfenster** der Zonen aus der Rennhistorie „personalisierte", obwohl die der Veranstalter festlegt und niemand darin schneller wird. Genau verkehrt herum. - Neu: Vorschlag für die drei Kraftwerte - Sicherheitsregeln, weil hier eine Hantel drunter steht - Übungszuordnung als strikte Positivliste, niemals über Muskelgruppen - Neu: Ausdauer- und MetCon-Ziel aus dem letzten Rennen - Ehrlich leer statt erfunden - „Leere Ziele ausfüllen" statt „anpassen" - Fix: Die festen Zonenfenster werden nicht mehr personalisiert - Fix: Die Saison gehört zum Geltungsbereich des Vorschlags - Fix: Zwei tote Eingabefelder entfernt - Fix: Die Uhr zeigt die Zielwerte - Fix: Zwei Stellen fragten immer nach den Übungen der Saison 2026 **Build 56** · 31 July 2026 Der Zeitvorschlag gehörte zum falschen Rennen · Vorbelegung rechnet jetzt mit deinen eigenen Daten Anlass war: „Hier zeigt es nur den Vorschlag für HYROX an, obwohl Deadly Dozen ausgewählt ist." - Fix: Der Vorschlag wurde berechnet, bevor du das Format gewählt hattest - Fix: Stationszeiten wurden hochgerechnet, als würde eine halbe Simulation jede Station langsamer machen - Fix: Deadly-Dozen- und DEKA-Varianten wurden ignoriert - Fix: Demo-Rennen flossen in deine Vorbelegung ein - Neu: Vergleichbarkeit wird jetzt sauber getrennt - Neu: Neuere Ergebnisse wiegen schwerer - Neu: Trainingsdaten aus dem Gym zählen mit - Neu: Das Banner zeigt, worauf es sich stützt - Neu: Auch langsamere Zeiten werden angeboten - Neu: Die Planung öffnet mit deinem Format und deiner Division - Fix: Ein Preset warf deine gemessenen Zeiten weg **Build 55** · 31 July 2026 Vergleichszahl im Rennverlauf war falsch · Trainingsbelastung kannte nur den heutigen Tag - Fix: Die Vergleichszahl im Rennverlauf verglich Unvergleichbares - Fix: Ein Lauf von gestern tauchte in der Trainingsbelastung nicht auf **Build 53** · 30 July 2026 Live umplanen: Satz, Übungswechsel und Umbauen kommen sofort auf der Uhr an Anlass war „Satz live hinzufügen wird nicht synchronisiert — die Uhr bleibt bei 1 von 2". Dahinter steckte nicht ein Fehler, sondern eine ganze Kette. Ein systematischer Durchgang durch **jede** Änderung, die man während eines laufenden Trainings am iPhone machen kann, hat folgendes ergeben: - Fix: Satz hinzufügen kam nicht an - Fix: Die Uhr hätte die neue Satzzahl trotzdem nicht angezeigt - Fix: Ganze Kategorien von Änderungen wurden stillschweigend verschluckt - Fix: Übungswechsel war bei jedem von der Uhr gestarteten Training wirkungslos - Fix: Nach einem Übungswechsel erbte die neue Übung alles von der alten - Fix: Sätze wurden beim Zusammenführen nach Position statt nach Identität zugeordnet - Fix: Die laufende Übung löschen kostete alle geloggten Sätze - Neu: „Workout ändern" im Live-Tab - Fix: Wischen zum Löschen eines Satzes war tote Geste - Fix: Manuelles Deload aktualisierte die Ansicht nicht - Fix: Die Uhr vibriert nicht mehr beim Synchronisieren **Build 52** · 30 July 2026 Simulations-Planung sitzt wieder · Stoppuhr pro Satz auch ohne Uhr - Fix: Die Stationsliste in der Simulations-Planung war verschoben - Neu: Stoppuhr pro Satz auf dem iPhone **Build 51** · 30 July 2026 Build-Fix, zweiter Anlauf: die Modifier-Kette der Übungsansicht aufgeteilt Build 50 ist an derselben Fehlerklasse gescheitert, nur an anderer Stelle. Diesmal war es nicht ein einzelner Ausdruck, sondern die **gesamte Kette** an der Seitenansicht: Für den Compiler ist so eine Kette EIN Ausdruck, und mit den drei zusätzlichen Beobachtern aus Build 49 hat sie das überschritten, was der Cloud-Compiler noch auflöst. (Warum lokal nicht: Der harte Abbruch hängt nicht an einer Zeit, sondern am Komplexitätsbudget des Lösers — und das ist von Swift-Version zu Swift-Version anders. Lokale Zeitmessungen sagen das also nicht vorher.) - Fix: Die Ansicht ist jetzt in zwei Einheiten geteilt **Build 50** · 30 July 2026 Build-Fix: Compiler-Timeout in der Watch-Übungsansicht Build 49 ist in Xcode Cloud nicht durchgelaufen — an einer Stelle, die hier lokal noch kompilierte. Der Cloud-Rechner hat ein anderes, langsameres Xcode, und ein Ausdruck, der knapp an der Grenze des Typprüfers liegt, kippt dort. Inhaltlich ist an dieser Version nichts anders als an 49. - Fix: „Compiler kann Ausdruck nicht in vertretbarer Zeit prüfen" - Vorbeugend: Die Gewichtskachel ist jetzt ausgelagert **Build 49** · 30 July 2026 Name läuft wirklich durch · Kachel antippen funktioniert · Watch → iPhone auch ohne Abhaken - Fix: Der Name stand am Ende geparkt („nian Deadlift") statt durchzulaufen - Fix: In die Kachel tippen, um mit der Krone zu ändern, ging nicht mehr - Watch → iPhone: Der eingestellte Wert wandert jetzt sofort mit **Build 48** · 30 July 2026 Watch-Layout gerade · Wischen und Antippen wieder da · Feld antippen markiert die Zahl - Fix: Bei langem Übungsnamen war das ganze Watch-Interface verschoben und nichts antippbar - Fix: Hoch/runter wischen ging nicht, nur die Krone - Fix: Wischen zur Übersicht ging nicht - Fix: „Watch nicht mehr verbunden" mitten im Training - Fix: Fertig, Abbrechen, Pause überspringen und Zurück taten bei toter Spiegelung nichts - Feld antippen markiert die ganze Zahl (app-weit) - Sync erst beim „Fertig" der Tastatur - Fix: Im Live-Training fehlten die Sätze bei Übungen, die man nicht vorher geöffnet hatte **Build 47** · 30 July 2026 Name läuft durch statt Geste zu fressen · Krone scrollt verlässlich · „Synchronisiert" heißt angekommen - Watch: Der Übungsname läuft von selbst durch — und frisst die Wischgeste nicht mehr - Watch: Scrollen auf dem Trainingsbildschirm jetzt verlässlich - Fix: „Synchronisiert" stand da, bevor irgendetwas ankam - Fix: Generiertes Krafttraining zeigte im Editor nur einen Satz **Build 46** · 30 July 2026 Pausentag-Tausch funktioniert · Verschobenes wird wieder blau Ab hier archiviert Xcode Cloud jeden Push selbst und zählt die Build-Nummer pro Push hoch — die Nummer im Projekt ist für TestFlight nicht mehr maßgeblich. - Fix: Ein Workout auf einen Pausentag legen — die Pause verschwand nicht, sie versteckte sich - Fix: Verpasstes Workout verschoben — und der neue Tag blieb rot **Build 45** · 30 July 2026 Live-Sync steht · Satzanzahl planbar · Körpergewicht bleibt bei 0 · Satzdauer sichtbar - Fix: Ein auf dem iPhone getipptes Gewicht erreichte die Watch immer noch nicht - Fix: Abhaken auf dem iPhone kam bei toter Spiegelung nie an - Fix: Start vom iPhone zeigte auf dem Handy keine Übungen - Fix: Fünf „Fertig"-Knöpfe über der Tastatur, keiner tat etwas - Planung: Satzanzahl änderbar, Gewichte und Wiederholungen pro Satz - Körpergewichtsübungen starten bei 0 kg — endgültig - Watch: Satzdauer läuft jetzt sichtbar mit - Watch: Die Pause zeigt, was aufzulegen ist — auch vor einer neuen Übung - Watch: Puls auch in der Pause - Watch: Übersicht aus der Pause erreichbar - Watch: Warnung beim Abhaken mit 0 Wiederholungen - Watch: Keine Vibration mehr bei Sync-Ereignissen - Watch: Die Krone scrollt zuerst — Werte erst nach Antippen **Build 44** · 29 July 2026 Alles, was du einträgst, kommt dort an, wo du es einträgst Gebaut und archiviert wird wieder von Hand mit einem regulären Xcode auf dem zweiten Mac — Xcode Cloud kompiliert nur zur Kontrolle. Die Build-Nummer im Projekt zählt also weiter: 1.1.2 hatte in TestFlight zuletzt Build 43, hier ist 44. - Fix: Abhaken auf der Uhr schaltete nicht zur nächsten Übung - Fix: Ein auf dem iPhone getipptes Gewicht kam nie an der Uhr an - Fix: Die Uhr verwarf Änderungen bei Workouts ohne Plan-Einheit - Fix: Die Uhr zeigte weiter das alte Gewicht, obwohl das neue angekommen war - Fix: Der Trainingsbildschirm ließ sich manchmal nicht scrollen - Fix: Auf der Uhr geloggte Werte überlebten eine Änderung vom iPhone nicht - Fix: Eine Satz-Meldung ans Handgelenk konnte still scheitern - Fix: Pause auch ohne Vorgabe - Fix: Die Zahlen-Tastatur ließ sich beim Training auf dem iPhone nicht mehr schließen **Build 26** · 28 July 2026 Pause zwischen den Übungen · Zeit-Übungen passen wieder aufs Display · Live-Training zeigt die Übungen - Fix: Nach dem letzten Satz einer Übung gab es keine Pause - Watch: Zeit-Übungen passen wieder auf einen Bildschirm - Live-Training zeigt die Übungen jetzt auch auf dem iPhone - Ausrüstungs-Auswahl im Generator entfernt - Intensität und Lauf-Skalierung gehen bis 200 % **Build 25** · 27 July 2026 Die Uhr startet das Training, das du geschickt hast · Handbearbeitung im Generator bleibt · Training nachträglich hinterlegen · Workout wiederholen - Vibration: Stärke und Länge einstellbar — und der Schalter wirkt endlich überall - Ergometer werden jetzt auf Zeit getrackt — und ausgewertet - Neues Diagramm „Ergo" in der Verlaufs-Karte - Fix: Von Hand gesetzte Gewichte kamen immer wieder zurück - Zeit-Übungen auf der Uhr sehen jetzt aus wie alle anderen Übungen - Die Krone stellt die Ziel-Dauer - Fix: Der Generator warf deine Bearbeitung weg, sobald du die Intensität bewegt hast - Fix: Nach dem Bearbeiten wurden aus zwei Runden vier - Doppeltipp funktioniert jetzt auf jedem Trainingsbildschirm - Training nachträglich einem Tag hinterlegen — auch ohne geplante Einheit - Ein absolviertes Training wiederholen - Fix: „Auf Apple Watch starten" zeigte erst nichts und danach ein fremdes Training - Läuft bereits ein Training, wird gefragt statt getauscht - Fix: Ein Training ohne Übungen öffnet keinen leeren Trainingsbildschirm mehr - Fix: Ein eigenes Workout meldete sich als Plan-Einheit ab - Zurück-Knopf auf dem Bereitschafts-Bildschirm **Build 24** · 26 July 2026 Größere Icons · Muskelgruppen erkennbar antippbar · Simulation direkt an die Uhr - Fix: Bestehende Simulationen finden ihre Analyse wieder - Tipp auf eine gelaufene Simulation öffnet die Analyse - Körpermaße: Fehlmessungen fliegen aus den Trends - Fix: Die Analyse einer gelaufenen Simulation fehlte im Plan - Simulations-Analyse auch in der Heute-Ansicht - Der Plan-Generator trainiert jetzt die richtige ATHX-Saison - Fix: Die Uhr lief beim Tracken nicht rund - Fix: Kilogramm auf Liegestützen, Wiederholungen beim Plank - Fix: Bearbeitete Trainings blieben in der Heute-Ansicht unverändert - Halbe Rennen werden mit deinem eigenen Einbruch hochgerechnet, statt einfach verdoppelt - Fix: Zwei Schalter für dieselbe Entscheidung - Alle Diagramme sind jetzt ablesbar - Teilsimulationen verfälschen die Analyse nicht mehr — und sind jetzt standardmäßig draußen - Fix: Bestzeiten konnten von halben Simulationen gewonnen werden - ATHX 2027 ist da — und beide Saisons bleiben planbar - Die eigenen Icons sind größer - Muskelgruppen: man sieht jetzt, dass man tippen kann - Simulationen gehen direkt an die Uhr **Build 23** · 25 July 2026 Analyse durchgerechnet · Fortschritt wird erkannt · Simulationen zeigen ihren Inhalt - Die Trainingslast rechnete an drei Stellen still falsch - Die Zielzeit-Prognose las nur das letzte Rennen - Hochrechnungen aus Teil-Simulationen waren um einen ganzen Stationsblock zu hoch - Der Ermüdungsindex zählte Läufe mit, die nie stattfanden - Abbruchquote, Wochenvolumen und die Balance-Schwelle - Neu: Die App erkennt echte Fortschritte und meldet sich - Geplante Simulationen zeigen endlich, was drinsteht - Die Heute-Karte zeigte das Workout zerstückelt - Charts lassen sich endlich ablesen - Neu: Simulationen korrigieren die Pläne, die sie geprüft haben - Extern getrackte Kraft-Workouts zählten unabhängig vom Körpergewicht **Build 22** · 24 July 2026 Sätze kommen zuverlässig aufs iPhone · Blöcke sauber umsortieren - Fix: Sätze fehlten in der Live-Ansicht auf dem iPhone - Fix: Auch die gemessenen Werte gingen mit - Blöcke umsortieren: eigener Bildschirm statt Ziehen in der Liste **Build 21** · 23 July 2026 Train-Umbau · Creator-Erweiterungen · iPhone zeigt die Last · Countdown stimmt · Simulation mit Division - Simulation: Division wählbar, mit Vorschau - „Neu" im Rennen-Tab öffnet wieder die Planungsmaske - Gewichte und Distanzen lassen sich endlich bequem ändern - Fix: Die Plan-Karte nannte das falsche Format - Fix: Countdown, Renn-Chip und Datum widersprachen sich - Fix: Der Countdown im Home-Screen-Widget blieb stehen - Fix: Auf der Uhr ließ sich das KG-Feld nicht mehr anwählen - Fix: Bei Supersätzen drehte die Krone die Seite statt der Zahl - Uhr: Abhaken und Pause nach oben, Pfeile darunter - Fix: Der Generator plante Übungen ein, die nie ausgewählt waren - Neuer Knopf „Alle Übungen" - Bearbeiten wirkt jetzt zurück auf die Regler - Fix: Auf dem iPhone stand kein Gewicht und keine Stufe - Fix: Am Ergometer fehlte auf dem iPhone die Messgröße - Fix: Eine auf der Uhr gedrehte Stufe kam nicht am iPhone an - Rückgängig: Die Today-Ansicht zeigt wieder nur die heutigen Health-Workouts - Nachtragen per Langdruck auf den Tag - Auch abgeschlossene Tage sind noch verknüpfbar - Verknüpfen aus der Workout-Historie heraus geöffnet - Aus drei Zielen wird eines - „Sätze" und „Runden" bedeuten endlich das, was sie sagen - Fix: „Keine Läufe" plante trotzdem einen Lauf ein - Fix: „Vor jeder Übung" tat gar nichts - Laufrunden-Auswahl auf zwei sinnvolle Optionen reduziert - Übungen per Drag & Drop umsortieren - Der Generator rechnet endlich mit der Uhr **Build 20** · 22 July 2026 Last richtig erfassen · Verpasste Health-Workouts nachtragen - Ergometer fragen jetzt nach der Stufe, nicht nach Kilogramm - Mobility- und Band-Übungen ohne Gewichtsfeld - Klimmzüge, Burpees und Planks behalten ihr Kilo-Feld - Läufe zeigen die Weste, wenn eine vorgesehen ist - Eigene Übungen: Art der Last wählbar - Stufe im CSV-Export und bei Strava - Verpasste Health-Workouts lassen sich nachtragen - Nachtragen ohne Zeitlimit - Nachgetragenes verfälscht die Gegenwart nicht mehr **Build 19** · 21 July 2026 Train-Feinschliff · iPhone-Tracking interaktiv · Absturz beim Lauf-Start behoben - „Weakness Attack" und „Kraft" sind ein Modus geworden - Stationen erscheinen nicht mehr dreimal untereinander - GPS läuft jetzt auch bei gesperrtem Bildschirm weiter - Englischer Tab heißt „Analysis" - Eigene Format-Badges statt Platzhalter - Hinweis auf Unabhängigkeit - Kalorien als eigene Messgröße - Fix: Stationen und Träger wurden in Wiederholungen statt Metern erfasst - Fix: „40m" wurde als 40 Sekunden geloggt - Eigene Übungen: Wiederholungen, Sekunden oder Meter - Icons auch auf der Uhr - Eigene Icons für alle Stationen und Muskelgruppen - Alternatives Training öffnet zuerst deine eigenen Workouts - „Eigene" heißt jetzt „Schnell" - Fix: Titel „Alternatives Training" fehlte - Fix: Wochenbeschriftung im Volumen-Diagramm überlappte - Neues Screenshot-Motiv fürs Workout-Tracking - App-Store-Screenshots jetzt zweisprachig und mit Apple Watch - Sätze bleiben zusammen — keine Übungen mehr dazwischen - Laufrunden frei konfigurierbar - Wochenplan blätterbar - Freie Workouts tragen kein fremdes Format-Etikett mehr - Name und Format jederzeit änderbar - Intensität wirkt jetzt bei allen Zielen - Realistischere Zeitschätzung bei vielen Wiederholungen - Ziel-Modi erklären sich selbst - Keine Export-Compliance-Abfrage mehr bei jedem Upload - Fix: Intervall-Läufe waren viel zu kurz - Fix: Kilometer zählten das Traben nicht mit - Farbstreifen über den Renn-Karten passt jetzt zur Karte - Analyse/Training ohne Format-Auswahl - Start-Button sitzt auf Deutsch mittig - Generiertes Workout speichern - Alternatives Training startet mit dem Generator - Abfragen erscheinen mittig statt als Blase - Fix: Schwarzer Bildschirm nach Workout-Ende auf der Watch - Watch-Zusammenfassung mit echten Kennzahlen - iPhone zeigt Übungen bei Watch-Workouts - Geplante Sätze sofort sichtbar - Watch-Trainingsscreen kompakter - Hinweis vor dem Watch-Start - Fix (kritisch): App stürzte beim Start einer Lauf-Einheit auf dem iPhone ab - Timer-Kopf schrumpft beim Scrollen - Creator zeigt sofort, was sich ändert - Seilspringen & Co. laufen auf Zeit statt Wiederholungen - Dauer reagiert endlich auf den Intensitätsregler - Zeit/Dauer in den Stationsparametern - Keine doppelten Pausen mehr - iPhone-Tracking komplett überarbeitet - Watch: großes Pause-Ziel - Workouts löschen & Erstellungsdatum - Fix: Feldverteilung verschwand beim Wechsel der HYROX-Division **Build 18** · 18 July 2026 Dashboard-Feinschliff · Smart Generator vereinheitlicht · Sprachwahl - Readiness-Karte klar interaktiv - Y-Achsen ergänzt - Tagesempfehlung gekennzeichnet - Körperdaten-Charts scrollbar - Smart Generator vereinheitlicht - Dauer reagiert auf Intensität - Warm-up nur noch auf Wunsch - Pausen bei der Übung - Hinweis statt Rätselraten - App-Sprache wählbar - Fix - Fix (Watch) - Fix: Simulationen verknüpfen sich jetzt zuverlässig mit dem Trainingsplan - Fix: Simulation auf Trainingstag einplanen - Fix: Geplante Simulation verschwindet nach Abschluss überall - Fix: 15 fehlende UI-Refresh-Trigger - Rennvergleich grundlegend repariert & erweitert - Start-Button neu gestaltet - UI-Tests repariert - Wochenübersicht bleibt aktuell - Renn-Countdown-Fortschritt ohne Plan - Aufgeräumt - Fix: „Auf iPhone starten" führte ins Leere - Fix: „Plan erweitern" hätte den Plan ersetzt - Schneller: Workout-Verlauf & Übungs-Historie - Aufgeräumt (Train-Tab) - Wochentage endlich auf Deutsch - Fix: Plan-Import „Komplett ersetzen", Plan-Einstellungen und Verlauf-Wiederherstellung - Fix: Schwächen-Erkennung sah moderne Rennen nicht - Fix: Plan-Vorschau zeigte 0–1 Wochen bei nahen Rennen - Fix: Geplante Rennen erreichen die Watch jetzt sofort - Renntag-Checkliste repariert - Event-Kalender - Unter der Haube: 0 Compiler-Warnings - Einheitliche Typografie in der ganzen App (Phase 2) - Kein Wortumbruch mehr in der Readiness-Karte - Neues Interaktionsgefühl (Phase 1) - Watch: Heutige Simulation lädt automatisch - Analyse: Lauf-Paces & KI-Export korrigiert - Rennplaner: Vorschläge sehen jetzt alle Rennen - Fix: Muskel-Radar bei iPhone-Workouts leer - Live-Mitverfolgung: iPhone-Wärme & Ruckeln massiv reduziert - Unter der Haube - Goldener Schnitt im Layout - Watch: Druck-Feedback (Phase „Watch-Feel") - Mehr Bewegung (Phase „Motion") - Mutigere Optik — app-weit ausgerollt - Semantische Farbtokens konsolidiert - Watch: Workout-Lauf-Ansicht komplett neu - Watch: Ein Toast statt Overlay-Stapel - Watch: Einheitlicher Live-Header - Watch: Typografie auf die Skala - Session-Sheet aufgeräumt - Fix: Karten minimieren per langem Druck - Fix: Schwarzer Screen beim Abschließen - Smart Generator: Ziel-Dauer wählbar - Fix: Race-Week-Banner erschien nicht bei jeder Rennwoche - Fix: Überlappende Wochen-Labels im Volumen-Chart - Fix: Readiness-Verlauf lief über die Karte - Neues erstes Öffnen — ohne Modal-Stapel - Readiness-Karte ohne Daten - Format×Division-Audit — Kategorien werden endlich überall korrekt aufgelöst - Format×Division-Audit, Teil 2 - Fix: Verschwindende Häkchen in der Live-Mitverfolgung - Neue „Was ist neu"-Ansicht - App-Store-Screenshots - Readiness-Begründungen endlich auf Deutsch - Nachträglich in Health speichern - Fix: Workout fehlte in Apple Health - Übungsbilder freigestellt - Progress-Vergleich pro Satz - Watch: Zurück = Bearbeiten - Watch: Krone zuverlässig - Watch: Zeit-Übungen (Dead Hang & Co.) - Watch: HR-Ausfälle abgesichert - Muskelgruppen verfeinerbar - Strava-Upload robust - Fix: Planprogression nach rechts verschoben **Build 17** · 17 July 2026 Watch: Pausen-Screen & smarte Reihenfolge geplanter Rennen - Übersicht im Pausen-Screen erreichbar - Skip-Icon neben dem Timer - Smarte Reihenfolge geplanter Rennen (Watch) **Build 16** · 17 July 2026 Crash-Fix · Körperdaten-Charts · Free-Strength-Konfigurator überarbeitet - Crash beim Öffnen abgeschlossener Workouts behoben - Körperdaten-Charts lesbar gemacht - Free Strength – Equipment als Radio-Button - Free Strength – Sätze & Durchgänge statt Dauer - Grundübungen-Grid - Startgewicht in der Live-Vorschau - Vollständige Übungsliste - Reformierte Pausen-Logik **Build 13** · 16 July 2026 Workout-Generator: Klassische Langhantel-Lifts · Verpasste Einheiten · START-Button - Klassische Langhantel-Lifts im Generator - Tolerantes Equipment-Matching - Verpasste Einheiten automatisch markiert - START-Button zentriert **Build 12** · 14 July 2026 Session-Detail: Laufdistanz & Recovery-Blöcke zwischen Intervallen - Laufdistanz im Session-Detail - Recovery statt Pause zwischen Intervallen - Muskelgruppen-Redesign - START-Button in Tab-Leiste **Build 11** · 14 July 2026 UI-Overhaul: Mehr Platz, Race-Tab neu, Laufdistanz & Strava-Fix - Globales Spacing-Upgrade - Race-Tab (Upcoming) - Race-Tab (Simulationen) - PlannedRaceCard-Buttons - Free Create - WorkoutEditor Inline-Suche - Laufdistanz im Workout - Strava-Aktivitätstyp-Fix **Build 10** · 14 July 2026 Smart Workout Generator, AI Workout Import, Strength Profile - Smart Generate - AI Workout Import - WorkoutSuggestionEngine - Strength Profile - Geschätzte Gewichte - DSL-Erweiterung - Interne Refaktorierung: `mergeConsecutivePauseBlocks` und `filledWithDefaults(category:)` sind jetzt internal (nicht mehr private), sodass WorkoutGenerator diese teilen kann. **Build 9** · 13 July 2026 Race Tab Fusion: Races + Planner Werden Ein Tab - Race-Tab neu strukturiert mit drei festen Sektionen: **Upcoming → Simulations → History**. - Upcoming - Simulations - History - `RacePlanAdaptation`-Erkennungslogik aus `RacePlannerTabView` integriert: Banner wird oberhalb der betroffenen Rennkarte angezeigt, im thin-material-Stil mit Brand-Akzent (kein Grün mehr). Tap öffnet das Anpassungs-Sheet. - Toolbar-Add-Button zu **Menü** konsolidiert: „Plan Race" und „Log Past Race" als Menüpunkte; redundante Einzel-Buttons entfernt. - `RacePlannerTabView.swift` gelöscht - App-Tour-Overlay gelöscht - `GettingStartedSheet` erscheint nun direkt nach Abschluss des Onboardings – keine Tour-Zwischenstufe mehr. - `AppTourView.swift` gelöscht **Build 8** · 13 July 2026 Global START Sheet - Neuer globaler **START**-Button erscheint auf allen drei Haupt-Tabs: großer Vollbreite-Button (Forged-Gradient) unter der Readiness-Karte im Today-Tab; `play.circle.fill`-Symbol in der Toolbar des Train- und Race-Tabs. - Ein Tap öffnet ein Sheet mit **drei Startpfaden**: - Workout - Simulation - Race - Der „Quick Start\"-Button im Today-Quick-Actions-Bereich entfällt (ersetzt durch den globalen START-Button). - Simulationen werden mit `isPlannedTraining = true` gespeichert → PR-Ausschluss und Partiell-Filterung in Analyse bleiben korrekt. **Build 7** · 13 July 2026 Navigation: 4 Fixed Tabs + Train Tab - Tab-Leiste auf 4 feste Tabs reduziert: **Today · Train · Race · Analyse**. Kein kontextueller Planner-Tab mehr. - Neuer **Train**-Tab (ersetzt Plan): oben das heutige Session-Hero-Karte, darunter **Meine Workouts**-Raster (2 Spalten) mit „Neues Workout"-Zelle, Quick Workout und Workout Studio. Unten die kompakte Trainingsplan-Sektion (Wochenstreifen + „Vollständigen Plan anzeigen"), alle Plan-Aktionen (KI-Import, KI-Review, Deload, Neuerstellen, Rennen verknüpfen) ins Ellipsis-Menü (⋯) verschoben. - Bestehende Installationen - Session hinzufügen - App-Tour aktualisiert: Plan→Train, Races→Race; kein Planner-Schritt mehr. **Build 3** · 10 July 2026 Intervall-Training mit aktiver Erholung, entschlackter Erststart und mehrere Live-Workout-/Sync-Fixes. Neu: Aktive Erholung im Intervall-Training - Neuer Blocktyp „Recovery" (aktive Erholung = leichtes Traben/Gehen) neben der passiven „Pause". Zeitbasiert, wird auf der Uhr als leichte Bewegung getrackt (Pedometer/GPS aktiv), nicht als gestoppte Uhr, und zählt nicht als Arbeitsblock. - Workout-Builder: eigener Bereich „RECOVERY (EASY JOG/WALK)" mit Presets + Dauer-Editor. - DSL/AI-Import: `Recovery 90s` / `Recovery 2min` wird geparst; der KI-Prompt weist jetzt ausdrücklich an, zwischen Lauf-Intervallen aktive Erholung (nicht „Pause") einzufügen. Erststart / Onboarding - Erststart reduziert auf **eine** Intro-Seite (Story: die App entstand aus dem Wunsch, die Indoor-Pace ohne GPS zu sehen) → „Los geht's" direkt in die App. Keine Preise/Kauf-Buttons mehr im Onboarding, nur ein dezenter Hinweis, dass fast alle Funktionen kostenlos sind. - Format/Kategorie/Zielzeit werden jetzt über eine „App personalisieren"-Karte oben im Today-Tab abgefragt (nur für Neuinstallationen). Behobene Fehler — Trainingsplan - Generierte Kraft-Pläne haben jetzt eine Pause zwischen aufeinanderfolgenden Übungen/Stationen (vorher liefen Übungen ohne Rest hintereinander). - Geschätzte Trainingsdauer ist jetzt genauer: Sie wird nicht mehr künstlich auf das Zeitbudget gedeckelt, sondern spiegelt den echten Session-Inhalt wider (vorher teils deutlich zu niedrig). Behobene Fehler — Live-Workout / Watch↔Handy - Satz-Häkchen für bereits erledigte Übungen verschwanden zeitweise: Completion ist auf dem Handy jetzt innerhalb einer Session monoton (verschwindet nicht mehr durch verzögerte/partielle Updates). - Sekunden-Anzeige auf dem Handy stotterte/sprang, weil zwei Geräteuhren vermischt wurden — jetzt an die handy-lokale Empfangszeit verankert. - Handy wurde warm: Live-Updates gingen gleichzeitig über HK-Mirroring **und** WCSession — die WCSession-Strecke ist jetzt ein echter Fallback (nur wenn HK-Mirroring inaktiv). Zusätzlich Echo-Schleife gestoppt (Struktur wird nur bei echter Änderung zur Uhr gesendet, nicht bei Ergebnis-Rückschreibungen). - Sync-Konsistenz: das Handy folgt jetzt einem klar geordneten Live-Stream (Verwerfen verspäteter/doppelter Updates via Watch-`sentAt`). Behobene Fehler — Rennen / Format - Beim Live-Start aus einem geplanten Rennen werden die granulare Kategorie (`formatCategoryId`) sowie DD-/DEKA-Varianten korrekt übernommen (richtige Benchmarks, ATHX-MetCon-Gewichte, Analyse-Filter). - `includeRoxzone` wird in Plan-Gesamtzeit, Live-Plandelta, Segment-Rest und Predicted-Finish respektiert (Simulation ohne Roxzone rechnet nicht mehr mit Roxzone). - ATHX-Rennen liefern jetzt eine sinnvolle geplante Zielzeit (vorher 0). - Durchschnitts-Pace respektiert reduzierte Lauf-Distanzen (Half/Quarter/Custom-Simulationen). - Benchmarks/Badges nutzen die vollständige Format-Kategorie (Female/Team/Pro) statt eines generischen Platzhalters. Analyse / Herzfrequenz - Zone-2-Pace in der Progressions-Karte war teils viel zu langsam (z. B. 7:14 statt 5:40): der Ganzlauf-Durchschnitt aus Apple Health (inkl. Warmup, Gehpausen, Cooldown) wurde als Zone-2-Pace herangezogen. Jetzt zählen nur echte, pro Kilometer als Zone 2 klassifizierte Splits, und es wird der Median statt des Mittelwerts genommen (robuster gegen Ausreißer). - Max-Puls-Einstellung erweitert: neben manuell und „220 − Alter" gibt es jetzt „Meinen Apple-Health-Max nutzen" (höchste in den letzten 6 Monaten gemessene HF). Der Max-Puls steuert die HF-Zonen und damit u. a. die Zone-2-Einordnung — mit dem echten Wert werden die Zonen korrekt. Deadly Dozen Varianten - Gravierender Fehler behoben: Beim Planen einer „Deadly Mile" (und „Deadly Strong") wurden immer die normalen 12 Labours des Standard-Rennens angezeigt. Die Varianten haben jetzt ihre eigenen Segmente: Mile = Lunges + Burpee Broad Jump + Bear Crawl (je 400m) + 400m Run; Strong = 12 Labours ohne Runs. Durchgezogen über Planung, geplante Zielzeit, live gestartetes Rennen und Ergebnis-/Analyse-Ansicht. (ERG-Variante folgt separat.) - Deadly Mile: alle drei Bewegungs-Legs sind jetzt 400m und Körpergewicht (kein Zusatzgewicht), mit realistischen Default-Zeiten für die erste Planung (Burpee Broad Jumps als langsamstes Segment) → die geschätzte Gesamtzeit passt jetzt grob (~25–30 min für Einsteiger). - Uhr: ein live gestartetes Deadly-Mile-Rennen zeigt jetzt nur die 4 Segmente (nicht alle 12). Die zuletzt gewählte DD-Variante wird vom Handy zur Uhr synchronisiert, damit auch ein manueller DD-Start auf der Uhr die richtige Variante nutzt. Race-Planner - Angenommene Pace-Anpassung wird jetzt übernommen und für neue Planungen gespeichert (pro Format). Es wird nicht erneut gefragt — erst wenn du wieder schneller wirst, kommt ein neuer Vorschlag. Beim Annehmen passt sich die angezeigte Standard-Pace auch dann an, wenn nur die Einzel-Splits schneller wurden. - Deadly-Dozen-Simulation: die Erklärung zur Transition-/Gehzeit ist jetzt klarer (fürs Gym-Training gedacht; bei echten Rennen sind die Übergänge fest) und ein invertierter deutscher Satz wurde korrigiert. Widgets - Home-Screen-Widget zeigt jetzt die richtige Division: die Kategorie neben der Bestzeit gehört wieder zur echten Bestzeit (nicht mehr zur Division des nächsten Rennens). Die Division des nächsten Rennens steht jetzt separat im Countdown. - Neues, zusätzlich auswählbares Widget „Today & Recovery": zeigt das heutige geplante Training (inkl. „erledigt") und den aktuellen Recovery-/Readiness-Score. Sonstiges - Deadly Dozen: „Labours" wird nicht mehr fälschlich zu „Prüfungen" übersetzt — der Begriff bleibt „Labours". - Strava: manueller Re-Upload eines Rennens möglich (`force`). - Race-Planner: Stations-Simulationsblöcke korrekt zeitbasiert. - `loc.py` (Übersetzungs-Tool) findet die String-Katalog-Datei jetzt unabhängig vom Arbeitsverzeichnis. **Build 1** · 28 June 2026 Alternatives Training komplett überarbeitet + HR-Bugfixes AlternativeSessionSheet – Neues interaktives Formular - Sheet komplett neu gebaut analog zu „Einheit hinzufügen" (AddSessionSheet): kein KI-Generator mehr, volle manuelle Kontrolle. - Typ-Picker mit Sparkle-Marker für empfohlenen Typ (basierend auf dem bestehenden Trainingstag). - Run-Typ-Picker (Easy / Long / Tempo / Intervals) mit automatischer Intensitäts- und Dauervorbelegung. - Distanz-Stepper (0,5-km-Schritte, 0–42 km): synchronisiert Dauer automatisch anhand des typspezifischen Pace-Werts. - Dauer-Stepper (5-min-Schritte) und Intensitäts-Menü. - Notizfeld für Beschreibung. - Blöcke werden automatisch generiert: Easy/Long Run = 1 Zone-2-Block, Tempo = Warmup+Threshold+Cooldown, Intervals = Warmup + N×VO2max + 90s-Recovery + Cooldown, Strength/Conditioning = 3-Satz-Übungsblock, Recovery = Zone-2-Walk/Jog. - Action-Buttons: „Training ersetzen" / „Als Extra hinzufügen" / „Training entfernen (Ruhetag)". Stationsname-Bugfixes in AnalyseTabView + RacePlannerView (alle Formate) - AI-Export-Stationszeilen, Race-Comparison-Deltas, Transition-Chart-Labels, Planner-AI-Export: Raw-IDs (z. B. `dd_kb_farmers_carry`) werden jetzt via `RaceFormatRegistry` in lesbare Namen aufgelöst – gilt für HYROX, DD und DEKA. HealthKit / Herzfrequenz-Bugfixes (Watch) - `WorkoutSessionManager.startSession()` ruft jetzt `requestAuthorization()` vor dem HK-Session-Start auf (analog zu RaceManager) – verhindert silent failures wenn Berechtigungen beim ersten Workout-Start fehlen. - `HealthKitManager.startWorkout()` hat jetzt einen Retry-Mechanismus (1 s Verzögerung): schlägt die HK-Session fehl, wird automatisch einmal neu versucht.
improvedJune 29, 2026

Version 1.1.1

**Build 9** · 29 June 2026 iOS Home-Screen-Widget: jetzt im Widget-Picker sichtbar - Problem: Weder das Widget noch „GritRace" tauchten im iPhone-Widget-Picker auf. Ursache: Die einzige Widget-Extension war eine reine **watchOS**-Extension (`SDKROOT=watchos`, unter der Watch-App), zwar fälschlich in die iPhone-App eingebettet — aber eine watchOS-`.appex` läuft nicht als iOS-Widget, und der `#if os(iOS)`-Dashboard-Code wurde nie für iOS kompiliert. Zusätzlich fehlte komplett eine App Group. - Neues, isoliertes **iOS-Widget-Target `GritRaceWidgetiOS`** (Bundle-ID `com.MDos.GritRace.widget`) mit eigenem Ordner + file-system-synchronized group — zieht bewusst nicht die HealthKit-gekoppelte `Shared`-Schicht rein. - `GritRaceiOSWidget.swift`: „GritRace Dashboard"-Widget (systemSmall/Medium), eigener `@main`-WidgetBundle. - `WidgetModels.swift`: self-contained, decode-kompatible Mini-Modelle (`RaceFormatType`/`HyroxRace`/`RaceCategory`) — lesen exakt das von der App persistierte JSON. - App Group `group.com.gritrace.shared` - Falsche watchOS-`.appex`-Einbettung aus der iPhone-App entfernt; iOS-Widget korrekt in die iPhone-App eingebettet (PlugIns) + Build-Dependency. - Bugfix (nach erstem Test): Widget zeigte nur „Open". Zwei Ursachen behoben: - Kategorie kam aus der globalen Einstellung `gritrace.category` (Default „Open Men") statt aus den echten Rennen. Jetzt: nächstes geplantes Rennen → letztes Rennen → Einstellung (zeigt korrekt Doubles/DD/Solo). - Countdown war tot: App schreibt `gritrace.plannedRaces` als `[HyroxRace]`, das Widget dekodierte aber ein abweichendes `PlannedRaceInfo`-Schema → Decoding scheiterte still → immer PB-Fallback. Widget liest jetzt das echte `[HyroxRace]`-Schema. `PlannedRaceInfo` entfernt. - Bugfix (zweiter Test, Widget weiter leer): `RaceStore` schrieb die geladenen Rennen nur in Datei + `UserDefaults.standard`, **nicht** in die App Group — nur `persist()` (bei Mutationen) tat das. Eine bestehende Installation, die nur neu gebaut wird, hat den geteilten Container also nie befüllt → Widget leer. Neu: `mirrorToAppGroup()` spiegelt `savedRaces` + `plannedRaces` bei jedem `load()` und nach dem iCloud-Reconcile in die App Group und ruft `WidgetCenter.reloadAllTimelines()`. - Bugfix (dritter Test, falsche Daten): Das Widget leitete alles aus rohen Rennen ab und machte 5 Dinge falsch (PB/Last Race aus unvollständigem Training, rohe `formatCategoryId` statt Kategoriename, Goal vom falschen Format, falsches Datum). Neu: Die App baut einen **aufgelösten `WidgetSnapshot`** (`RaceStore.buildWidgetSnapshot`) und schreibt ihn in die App Group; das Widget rendert ihn nur noch. - PB/Last Race: nur echte, abgeschlossene Rennen (`isComplete && raceType == .race && !isPlannedTraining && !isSampleRace`). - Kategorie: `formatCategoryId` wird via `RaceFormatRegistry.categories(for:)` zum lesbaren Namen aufgelöst (keine rohe ID mehr). - Goal: nur, wenn `gritrace.goalTimeFormat` zum Format des nächsten Rennens passt — sonst ausgeblendet. - Datum: das echte Datum des nächsten geplanten Rennens. **Build 8** · 29 June 2026 Live-Workout: Satz-Häkchen-Sync robust gemacht - iPhone-Live-Training hakte einzelne Sätze nicht ab, obwohl sie auf der Watch getrackt waren (z. B. Satz 1 leer, Satz 2 grün). Ursache: Das iPhone bildete die Watch-`setNumber` direkt auf den Array-Index ab (`si = snapshot.setNumber - 1`). Bei verschobener Nummerierung (Supersatz-Round-Robin, Satz-Zurücknahme) traf der erste Row keinen Snapshot und blieb leer. - Fix (`PhoneWorkoutCompanionView.updateAnnotatedBlocks`): Snapshots werden jetzt nach `setNumber` sortiert und der Reihe nach auf die Satz-Rows gemappt (n-ter erledigter Satz → n-ter Row). Fallback: meldet die Watch den Block als komplett, werden alle Rows abgehakt. - Fix (`WorkoutSessionManager.goToPreviousSet`): Ein zurückgenommener Satz wird jetzt auch aus `allBlockSetResults` entfernt, damit keine veralteten/duplizierten Sätze mehr ans iPhone gesynct werden. Watch Übungs-Detailansicht: Header weiter aufgeräumt - Timer und Herzfrequenz aus dem Content in die Navigationsleiste verlegt (`.topBarLeading`) — sitzen jetzt zwischen Zurück-Button und Systemuhr. - Block-Progress („x/y" + schlanke Leiste) aus der `.topBarTrailing`-Toolbar in den Content-Header unter den Übungstitel verschoben — kein Gedränge mehr neben der Uhr. - `.navigationBarTitleDisplayMode(.inline)` gesetzt und Titel-Top-Padding entfernt — der große Leerraum über dem Übungsnamen ist weg. In-App „What's New" für 1.1.1 - Konsolidierter `case "1.1.1"` in WhatsNewView mit allen Highlights über alle Builds (Home-Widget, Watch-Sync, Watch-Layout, Alternativtraining, Plandauer, Stabilität) — EN + DE in Localizable.xcstrings. **Build 1** · 27 June 2026 iOS Home Screen Widget — GritRace Dashboard Neues Widget „GritRace Dashboard" für den iOS-Homescreen (systemSmall + systemMedium): - Small: Großer Countdown in Tagen bis zum nächsten Rennen, Format-Badge (H/DD/D/AX), Renndatum, Format-Akzentfarben-Gradient. Fallback auf Personal Best wenn kein Rennen geplant ist. Race Day zeigt „GO!". - Medium: Countdown-Spalte links + Stats-Spalte rechts mit Personal Best, Zielzeit-Vergleich (✓ under / to go), letztem Rennen und Kategorie. Trennlinie in der Formatfarbe. - Hintergrund: Dunkler Radial-Gradient in der jeweiligen Formatfarbe (Orange/Grün/Blau/Lila) für alle 4 Formate. - Gesamter iOS-Widget-Code mit `#if os(iOS)` gesichert — watchOS-Complications unverändert.
improvedJune 26, 2026

Version 1.1.0

**Build 1** · 26 June 2026 Deep-Audit Bugfixes (5 Bugs behoben) clearSessionCompletionState — fehlende Session-Felder - `clearSessionCompletionState()` hat bisher nur Block/Set-Ebene zurückgesetzt, aber `completedDate`, `completedDurationMinutes` und `readinessScoreAtCompletion` nicht gelöscht. - Das führte dazu, dass eine erneut gestartete Session veraltete Completion-Metadaten enthielt, die beim Abschließen mehrfach ausgelöst werden konnten. - Fix: Alle drei Session-Level-Felder werden jetzt genauso wie in `reopenSession()` zurückgesetzt. TrainingPlanViews — Falscher Button-Label bei übersprungener Session - Button für übersprungene Sessions zeigte „Mark as incomplete", rief aber `markSessionCompleted()` auf — hat die Session also als ABGESCHLOSSEN markiert, nicht als offen. - Fix: Label korrigiert zu „Mark Complete" mit passender SF Symbol-Ikone; `dismiss()` wird nach der Aktion aufgerufen. WorkoutSessionManager — Pedometer läuft nach EMOM-Rest-Abbruch weiter - `finishSession()` hat `stopTimer()` aufgerufen, aber NICHT `stopPedometer()`. - Beim Abbruch während EMOM-Rest-Phase lief der Schrittzähler unbegrenzt weiter. - Fix: `stopPedometer()` direkt nach `stopTimer()` in `finishSession()` ergänzt. StravaManager — Race Condition bei parallelen Upload-Aufrufen - Dedup-Check (`uploadedWorkoutIds.contains(...)`) und ID-Einfügung erfolgten asynchron — zwei gleichzeitige Aufrufe konnten beide die Prüfung passieren und dasselbe Workout doppelt hochladen. - Fix: Guard am Anfang von `uploadWorkout()` für `retryAttempt == 0`: wenn `isUploading` bereits `true`, wird sofort mit Fehler zurückgekehrt. Retry-Aufrufe (intern, `retryAttempt > 0`) umgehen den Guard korrekt. RaceStore — Sample-Rennen wurden in iCloud gespeichert - `syncToiCloud()` hat alle Rennen inklusive Sample-Daten kodiert — das hat die iCloud KV-Quota unnötig verbraucht und konnte echte Rennen durch Truncation verdrängen. - Fix: Nur echte Rennen (`!isSampleRace`) werden an iCloud übertragen. Truncation-Logik arbeitet ebenfalls nur noch auf echten Rennen.
improvedJune 26, 2026

Version 1.0.9

**Build 8** · 26 June 2026 Watch ↔ iPhone — Workout-Sync Bugfixes (5 Bugs behoben) Rest-Timer Synchronisation - `LiveWorkoutUpdate` trägt jetzt `restTimerEndDate: Date?` — den absoluten Endzeitpunkt des Rest-Timers auf der Uhr. - iPhone verankert seinen Countdown an diesem Datum statt an der übertragenen Restzeit, sodass Übertragungsverzögerungen (1–3 s) den Timer nicht mehr verschieben. - `RestTimerOverlay` wird bei jeder eingehenden Uhr-Aktualisierung neu synchronisiert (`.onChange(of: watchEndDate)`). - Wenn `restTimerDuration` in der ersten `LiveWorkoutUpdate` fehlt, wird `remaining` als Totalwert gesetzt — Fortschrittsbalken beginnt jetzt korrekt bei 0 % statt 100 %. Phantom-Completion / Ungewolltes Block-Vorspulen - `completeSet()` hat neuen Parameter `isRemote: Bool = false`. - Remote-Abschlüsse (iPhone → Uhr via `handleRemoteSetCompletion`) rufen `finishBlock()` und Superset-Rotations-Advance **nicht** mehr auf — die Uhr springt nicht mehr automatisch in den nächsten Block. - `confirmSet()` auf der Uhr prüft, ob der aktuelle Block bereits remote abgeschlossen wurde; in diesem Fall wird `finishBlock()` direkt ausgelöst, ohne ein zusätzliches Set-Ergebnis anzuhängen. Remote Set-Completion Dedup — Fehlende Block-ID im Hash - `handleRemoteSetCompletion` hash enthielt bisher nur Weight/Reps/Duration/SetIndex — ohne Block-Identifier. - Wenn zwei aufeinanderfolgende Blöcke dieselben Werte hatten, wurde der zweite Remote-Abschluss als Duplikat verworfen. - Fix: `currentBlockIndex` ist jetzt Teil des Hashes. Zurück-Navigation vom iPhone - Neuer `RemoteWorkoutCommand.goBack` — iPhone kann die Uhr anweisen, einen Satz zurückzugehen. - `ConnectivityManager.sendGoBackToWatch()` sendet den Befehl via HealthKit-Mirroring. - Neuer **← Go Back**-Button im Live-Begleiter (wird nur bei Übungsblöcken mit abgeschlossenen Sätzen angezeigt). DD-Analyse — Rennerkennung repariert - DD-Rennen wurden in der Analyse nie angezeigt, weil der Filter `formatCategoryId` prüfte — dieses Feld wird bei DD-Rennen nie gesetzt. - DD-Rennen speichern nur `ddCategory` (Solo/Pairs/Relay). Neues `ddCategory`-Property auf `DDDivision` bildet Detailkategorie auf Grobkategorie ab. - `completedRaces()`, `ddDivisionsWithRaces` und `autoSelectDivisions` nutzen jetzt `ddCategory` als Fallback. Schnellstart — Abgehakte Übungen beim Laden gespeicherter Workouts - `loadSavedWorkout()` wies Blöcke direkt zu — inklusive `isCompleted = true` vom letzten Training. - Fix: Completion-Flags und Actual-Werte werden beim Laden zurückgesetzt. **Build 7** · 23 June 2026 Renntempo — Korrekte Berechnung - `averageRunPace` berechnet jetzt Gesamtlaufzeit / Nennstrecke (400 m DD, 1000 m HYROX, 500 m DEKA). Bisher wurde die letzte GPS-Momentangeschwindigkeit bei Segmentende gespeichert, was bei Verlangsamung vor der Station zu einem zu langsamen Wert führte. - Betrifft alle Anzeigen: Renntempo im Trainingsplan-Wizard, Ø PACE in der Rennzusammenfassung, Analyse-Charts und wöchentlicher Digest. - `endCurrentSegment()` (watchOS): Schreibt jetzt ebenfalls die echte Durchschnittspace aus GPS-Segmentdistanz für neue Live-Rennen. Watch — Vorheriges Gewicht/Reps inline - Die Labels „Last: X kg" und „Previous: X kg" wurden aus der Übungsdetailansicht als separate Zeilen entfernt — sie haben den ✓-Button aus dem Bildschirm gedrückt. - Referenzwerte erscheinen jetzt kompakt (9 pt) direkt innerhalb der Kacheln, sodass keine zusätzliche Höhe entsteht. Körperdaten — HealthKit-Sync - Gewichts- und Körperfetthistorie wird jetzt automatisch aus der Health-App importiert. - Beim Öffnen des Körperdaten-Screens wird automatisch synchronisiert; roter Herz-Button für manuellen Re-Sync. - De-Duplizierung auf einen Eintrag pro Kalendertag; manuelle Einträge werden nie überschrieben. Trainingsplan — Kein doppelter Goblet Squat mehr - `buildStrengthSession` schließt jetzt Compound-Übungen aus Part 1 aus, wenn dieselbe Übung bereits als Focus-Station in Part 2 vorkommt (z. B. „Goblet Squat" + „dd_kb_goblet_squat"). AI-Importer — WorkoutRef Supersets korrekt importiert - Gespeicherte Workouts werden beim Import nicht mehr über DSL-Round-Trip verlustbehaftet konvertiert. Stattdessen werden Sentinel-Blöcke in einem Post-Parse-Schritt direkt durch die echten WorkoutBlock-Objekte ersetzt — Supersets bleiben erhalten. Trainingsplan — Rennfokussiert Icon - Icon für „Rennfokussiert" geändert von `flag.checkered` zu `figure.run`. Workout-Analyse — Intensitätsdiagramm - Liniendiagramm → Balkendiagramm mit Farbverlauf. - Pause-Blöcke werden ausgeblendet; nur aktive Übungsblöcke erscheinen. - X-Achsen-Labels um 45° gedreht für bessere Lesbarkeit. - Station-Prefixe (dd_kb_, hyrox_, …) werden aus den Labels entfernt. **Build 6** · 21 June 2026 Watch — Elapsed Time + Heart Rate on Exercise Screen - Elapsed total workout time and live heart rate now shown directly in the exercise tracking screen (weight/reps input view) so the user never has to leave that screen to see them. - Standard workout tracking screen made more compact so all content fits without scrolling. Analyse — DD Division Selection Fixed - DD division picker now appears with just 1 race (was incorrectly requiring 2+). - Division filter re-evaluates when race data loads from iCloud, so the correct division is selected on first open. - Always shows the picker and always filters by selected division, regardless of total race count. Skip Rest — iPhone → Watch Sync - Tapping "Überspringen" / Skip in the iPhone companion rest timer now sends a skipRest command to the Watch via HealthKit mirroring, stopping the Watch rest timer immediately. AI Import — Reps-Only Format Recognised - Lines formatted as "Exercise Name 10x" (number + x, no sets or distance) now correctly parse as an exercise block. Previously these lines were silently dropped, causing incomplete imports. Plan — Extends to Race Day - Off-by-one fix: training plan now always generates weeks up to and including race day. A race scheduled exactly on a Monday (7-day multiple from plan start) no longer falls one week beyond the generated plan. Plan Calendar — Planned Races Shown - WeekStripView now checks both targetRaces (multi-race plans) and the legacy raceDate field. Planned races are highlighted in the calendar strip on their correct day. Today Tab — No Horizontal Scroll - The main ScrollView is now explicitly vertical and its content has a fixed max-width, preventing any child card from making the whole screen horizontally scrollable. Performance — Faster Workout Opening - Opening a session detail sheet now triggers at most 1 disk write for block initialisation instead of one write per block (a 10-exercise workout: 10 writes → 1 write). - Plan history snapshots moved to a background thread so main-thread saves no longer block on directory listing and file reads. - Live-block list in SessionDetailSheet and LiveBlockEditorSheet cached as @State and refreshed only on plan mutations, not on every SwiftUI render pass. **Build 4** · 21 June 2026 Exercise Images — Full Display - All exercise thumbnails and detail images changed from scaledToFill to scaledToFit so the complete exercise/person is always visible instead of being center-cropped. - Applies to: card header thumbnails (32×32), library row thumbnails (44×44), exercise detail sheet header, workout search results, Plan Analyse view, Dashboard widgets. Watch — Race Day Auto-Load - On race day, the Watch app detects today's planned race and pre-loads it automatically so the user only needs to tap START. - Orange "RACE TODAY" banner shown instead of the regular green plan banner. - Auto-load is suppressed after the user manually clears the plan to prevent re-loading. Strava — Activity Type Fix - Strength workouts no longer uploaded as "Run". Root cause: Strava's async JSON upload was defaulting to Run type. Removed the JSON upload path; now uses the reliable manual activity creation with sport_type "WeightTraining". - Recovery sessions with no run blocks now correctly saved to HealthKit as traditionalStrengthTraining instead of running (which could trigger Strava's HealthKit auto-sync as a Run). - "Tracked with GritRace" in activity descriptions is now a tappable link: gritrace.app. Battery — Live Workout Optimisation (iPhone) - TimelineView update intervals reduced from 0.5s to 1s for the block and session elapsed timers (halves timer-driven renders). - Watch background data send rate reduced from 1 Hz to 0.5 Hz; block transitions, set completions and pause/resume still send immediately via the unthrottled path. - HK mirroring throttle and WCSession fallback throttle both updated to 2s to match. AI Plan Import — WorkoutRef Token System - Prompt now gives each fixed workout a UUID token. AI outputs one line (WorkoutRef <uuid>) instead of re-writing the full exercise block — prevents truncation and altered rep counts. - Parser expands tokens at import time; session title is set to the full workout name automatically. - If a UUID is not found (e.g. workout deleted), a visible inline comment is added instead of silently dropping the session. Plan Accuracy Fixes - All upcoming-race filters now compare at day granularity (startOfDay) instead of exact timestamp, preventing races scheduled for today from being treated as past. - buildMultiRaceWeeks uses upcomingTargetRaces (skips past target races) so multi-race plans no longer schedule around already-run races. - daysUntilRace never returns a negative value. - .onChange on RaceStore now detects in-place date edits, not just count changes.
improvedJune 19, 2026

Version 1.0.8

**Build 9** · 19 June 2026 Quick Workout — Watch Sync Overhaul - Auto-connect: when the first exercise is added and a Watch is paired, the plan is automatically sent to the Watch (no manual "Send to Watch" tap required). - Live block sync: adding exercises after the Watch has already started the workout pushes the updated block list to the Watch immediately via HK mirroring + WCSession fallback. - Watch-ended detection: when the Watch finishes the workout, the iPhone shows a "Save & Finish / Continue on iPhone" dialog instead of silently staying open. - Bug fix: `trainingPlanSessionId` is now stamped on Quick Workout plans, fixing a silent session-ID mismatch that caused all live block updates from iPhone → Watch to be rejected. Today Tab — Lighter Layout - Removed four cards that added clutter without value: stats row (completion %, sessions this week), Training Share button, Earned Badges card, and Fitness Trend card. - DashboardCard enum reduced from 12 to 7 cases; stale card IDs in UserDefaults are cleaned up on next launch. Race Sync Dialog — False Positive Fixed - The "Rennplan hat sich geändert" alert no longer appears incorrectly. Root cause: `checkTargetRaceSync` was triggering on planned races not yet in the plan (treated as "new races available"). Removed that check; dialog now only fires for actual date changes or deletions of target races. - Moved check to `.task` (after RaceStore has loaded) and re-runs on `store.races.count` change, so timing is reliable. AI Workout Builder — Custom Workouts Verbatim - Previously the prompt said "integrate them where they fit best", giving the AI freedom to ignore or rewrite selected workouts. - Prompt now says "COPY THESE VERBATIM — DO NOT change, combine, reorder, or replace any exercise." Exercises are formatted in DSL notation so the AI echoes them back parseable. - UI subtitle updated to reflect the actual behavior: "Selected workouts are copied into the plan exactly as you built them." **Build 8** · 18 June 2026 Station Time in Field Distribution — Gate-to-Gate Fix - `bestStationTime` now uses `race.totalStationTime` instead of summing `.stationSegments` (which only covers station-mode recording, not gate-to-gate). - Gate-to-gate races store stations as `.genericStationArea` segments; `totalStationTime` handles both modes. - Fixes the case where a gate-to-gate race showed 18:35 instead of the actual 23:33. - `bestRunTime` similarly simplified to use `race.totalRunTime`. AI Plan Import — Completed Race No Longer Listed - When re-opening the AI plan wizard, target races from the previous plan config are now filtered against `RaceStore.plannedRaces` (which excludes completed races). - Fixes the bug where a race you already ran (e.g. "DD Stuttgart") kept appearing in the race selection list. Dashboard Minimize Button — Badge Position Fix - The minus icon no longer overlaps the card's own expand/collapse chevron (top-right) or any header text (top-left). - Button now floats as a small badge just above the card's top-left corner (offset x:+6, y:−16), sitting in the 16pt gap between cards. **Build 7** · 18 June 2026 InfoTips — Simplified & Localized - Rewrote all InfoTip descriptions across RacePlannerView, RaceDetailView, RaceHistoryView, WorkoutBuilderView to be shorter and clearer. - Roxzone tip no longer uses the word "corridor" — uses "Roxzone" or format-specific name. - All new strings have EN + DE translations in Localizable.xcstrings. - InfoTip now uses `Text(LocalizedStringKey(text))` so all strings auto-localize. Watch Race Start — TransitionSetupView Refactor - Replaced two-step confirmationDialog + sheet flow with a single `TransitionSetupView` screen containing two clearly distinct toggles. - Toggle 1: Include Roxzone/Track Zone/Transition (time counted in race data vs. go straight to station). Default based on format's `hasTransitionZone`. - Toggle 2: Transition timer (live overlay timer at each transition). Default off. - Zone name adapts per format: HYROX → "Roxzone", DD → "Track Zone", DEKA → "Transition", ATHX → "Recovery". RaceSim Navigation Fix - Race simulations scheduled for today now always navigate to the Race Planner, never show workout start/stop buttons. - `TodaysWorkoutCard` gains an `onRaceSimTap` callback; the card shows "Open Race Planner" button for raceSim sessions. - Removed the separate raceSimButton from TodayTabView. Manual Segment Edit — Analysis Refresh Fix - Editing a segment's duration in RaceDetailView now cascades the time delta to all subsequent segments. - This keeps wall-clock `totalTime` (and all analysis cards) accurate after manual edits. Race Completion — Home Screen Card + Confetti - Completed real races on today's date now appear as a gold "RACE COMPLETE" card at the top of the home screen. - Confetti animation plays for 3 seconds on appear. - Tapping the card opens RaceDetailView directly. Race Name from Plan - After tracking a race started from a planned entry, the result now inherits the plan's name as `customName` instead of showing just the date. Strava Auto-Upload for Races - `StravaManager` gains `uploadRace(_ race: HyroxRace)`: generates TCX via `TCXExporter`, uploads to Strava's file endpoint. - Fallback to manual activity creation if TCX upload fails (retries once after 60 s). - Called automatically from `RaceManager.finishRace()` for real races (not training/sim) when Strava is connected. Field Distribution — Data-Driven Running & Station Curves - All three tabs (Overall, Running, Stations) now use real benchmark percentile data instead of fixed time fractions. - HYROX Solo (Open/Pro Men/Women): uses `HyroxSegmentProfile.runTotal` / `.workTotal` / `.total`. - Deadly Dozen: runs = sum of per-journey percentiles; stations = sum of per-labour percentiles. - HYROX Doubles: fraction-based fallback (no segment data available). - Previous DD running mean was ~16:30 for Pairs Men (wrong: 0.31 × total). Correct value is ~26:35 (sum of 12 journey p50 values). - DD division picker switched from `.segmented` to `.menu` style — full labels now visible, no more truncation. **Build 6** · 11 June 2026 iPhone Race — Crash Recovery Alert - After an app crash during a live race, the app now detects the saved recovery snapshot on next launch and shows an alert offering Resume or Discard. - Watch: Discard button now calls `RaceManager.discardRecoverySnapshot()` instead of manually removing two UserDefaults keys. Live Workout Companion — Local Timers & Staleness Detection - Block- and session-elapsed timers on iPhone now tick locally using time anchors sent by the Watch (`blockStartDate`, `sessionStartDate`), so the display stays smooth even when Watch updates are infrequent. - Staleness indicator: companion header turns gray after 15 s without a Watch update; turns yellow when the Watch reports paused. - `isPaused` flag added to `LiveWorkoutUpdate` — elapsed timers freeze when the Watch is paused. Live Workout Signals — Delayed-Delivery Guard - `sendWorkoutStartedSignal`: includes `sentAt` timestamp; receiver ignores signals older than 10 minutes, preventing a queued transferUserInfo from spuriously triggering a "workout started" state after reconnect. - `sendWorkoutEndSignal`: now carries the plan ID; receiver only clears live state if the ID matches the currently-active plan. Feedback Prompt — Snooze Fix - "Maybe Later" now stores a snooze timestamp; the prompt is suppressed for 14 days after snoozing. Previously it reappeared on every launch. - Feedback button correctly marks the prompt as seen AND opens the URL in one tap (was: only opened URL but didn't suppress future prompts). iPhone Race Finish — Pro Upgrade Prompt & Undo Cleanup - Pro upgrade sheet now appears 1.2 s after finishing a race (if `shouldShowProUpgradePrompt` is true). - Undo timer, button, and snapshot are now cleared immediately when the race transitions to `.finished`, preventing a stale undo button from appearing on the results screen. AI Plan Prompt — DSL Grammar Cheat-Sheet - A 27-line DSL grammar reference is now prepended to every AI prompt, giving the model a precise spec for block syntax, indentation, weights, paces, and run intervals. - Run pattern robustness rule updated to cover the `Run <n>x<dist>` interval form. DSL Parser — Tolerant Import - Invalid block lines no longer abort the entire import; they are now collected as warnings and skipped. The plan is returned with a `warnings` array. - If more than 10 lines are skipped, the parser suggests switching to Phase-by-Phase mode. - `importFromJSON` / `TrainingPlanImporter` return type updated to `Result<(plan:, warnings:), ImportError>`. UI Timers — RunLoop.common - All UI-driving timers now run on `RunLoop.main` in `.common` mode so they keep ticking while the user scrolls a list or drags a scroll view. - Affected timers: `undoTimer`, `cadenceSamplingTimer`, `autoDetectTimer`, `durationTimer` (RaceManager) and `displayTimer` (WorkoutSessionManager). Watch Entitlement — iCloud Key-Value Store - Added `com.apple.developer.ubiquity-kvstore-identifier` to the Watch App entitlements, enabling `NSUbiquitousKeyValueStore` for standalone Watch race persistence. Strava API — Breaking Changes & Enhancements - `disconnect()`: endpoint updated from `oauth/deauthorize` to `oauth/revoke`; token now sent in the JSON body instead of the Authorization header. - `uploadTCXFile()` / `createManualActivity()`: `type` / `activity_type` fields replaced with `sport_type` (Strava v3 breaking change); values updated to PascalCase (`Run`, `HIIT`, `WeightTraining`). - Mixed HYROX workouts (runs + stations) are now uploaded as `HIIT`; pure strength workouts upload as `WeightTraining`. - New `uploadWeightTrainingJSON()`: uploads strength-only workouts via Strava's structured JSON format with full set/rep/weight/duration data and `exercise_type` constants (e.g. `BARBELL_BENCH_PRESS`, `SLED_PUSH`, `WALL_BALL_SHOT`). Watch Settings — Division/Category Not Persisting - Fixed bug where changing the Solo/Pair division on the Watch did not update `RaceManager`. The Done button now writes the selected division to the format-specific UserDefaults key (`gritrace.ddCategory`, `gritrace.dekaCategory`, `gritrace.athxCategory`) in addition to the generic `gritrace.watchDivision` key. Training Load — TSB Expandable Chart - TSB (Form) metric tile is now tappable and expands to show a full TSB chart. - Chart shows positive TSB (green area, "Fresh") and negative TSB (red area, "Fatigued") with gradient line, zero rule, and today marker. - Legend explains Fresh / Fatigued zones; subtitle shows today's TSB value in green/red. Race Planner — Simulation Navigation Fix - "Zum Race Planner wechseln" from a training session that was saved as a simulation now reopens the existing simulation instead of creating a new blank plan. - A pre-generated `simulationRaceId` UUID links `TrainingSession.linkedRaceId` to `HyroxRace.id` at save time, so the link is consistent regardless of save order. - `TrainingSession` model: added `linkedRaceId: UUID?` field. Plan Progression Chart — Smaller, More Frequent Dots - Actual readiness dots reduced: normal `56 → 16`, current week `100 → 28`. - New small dots (`symbolSize: 9`, green) added to the planned readiness curve across all weeks (past and future), giving a denser dot pattern along the plan line. - Legend "Actual" circle updated to match the new smaller size. **Build 4** · 05 June 2026 Watch App Launch Hang Fix - Fixed Watch app silently failing to open after several days of use (no crash, just won't launch). Root cause: synchronous file I/O + JSON decoding during `RaceStore.init()` and `WorkoutSessionManager.init()` ran on the main thread during the first frame render. As race history and workout plans accumulated, the launch eventually exceeded the watchOS watchdog limit (~5 s) and the system silently killed the app. - `RaceStore.init()` now defers `migrateIfNeeded()` and `load()` to a Task. Races appear via @Published once decoded. - `WorkoutSessionManager.init()` now defers `loadPlans()` and `restoreFromCheckpointIfAvailable()` to a Task. Plans appear via @Published once decoded. Home Tab — Today's Workout Card Redesign - Exercise list is now always visible directly on the card (previously hidden behind a chevron that overlapped the card-minimize button). - Tapping the card header now opens the full session detail sheet. - Skip and Complete action buttons now show labels next to their icons for clarity. Workout Builder — Rep & Pause Fixes - Exercises added from the library now always seed a numeric rep suggestion parsed from the exercise default (handles "10", "8-12", "12 each side" etc.), so the card never shows an exercise with empty reps. - Consecutive pause blocks in builder-generated workouts are merged into one. The same merge is now applied when the block editor saves session changes (`updateSessionBlocks`) and when a session is updated manually, so duplicate rest blocks no longer appear after edits. Plan Import — Compact AI-Friendly DSL - New text format ("DSL") for importing AI-generated training plans, ~5× shorter than the equivalent JSON. Example: `== Week 1 (base) — Foundations` `Mon strength 60min moderate` ` Bench Press 4x8-10 @60kg rest90 — control descent` ` Run 5km @5:30 easy` `Thu rest` - Supports exercises (sets×reps or sets×seconds), runs (distance + pace + intensity), pauses, race-format stations, and supersets via `Superset:` block. - The existing JSON import still works — the importer auto-detects whether the input is JSON or DSL and routes to the right parser. Triple-backtick code fences from ChatGPT/Claude are stripped automatically. - Backed by 21 unit tests covering all block types, weeks, supersets, error cases, comments, and code-fence handling. AI Prompt Builder — Output Format Toggle - In the AI plan import flow, the prompt-copy step now has a "Compact format" toggle (default ON) with an inline explanation. ON = the prompt asks the AI to output in the new DSL; OFF = classic JSON. - The toggle changes both the human-readable instructions and the schema example appended to the prompt, so ChatGPT/Claude know exactly which format to produce. - Both formats remain fully supported by the importer — switching the toggle never breaks already-pasted plans. AI Prompt Builder — Stricter Guidance - Multi-race section now explicitly states the A/B/C priority is the athlete's user-assigned label and must NOT be reinterpreted by the AI (fixes feedback where Claude/ChatGPT downgraded a near-term user-tagged A-Race to "B/C" based on its own judgment). - "Generate the plan until the A-Race date" clarified to "until the LATEST listed race date" so the AI covers the whole multi-race window. - New DSL robustness checklist embedded in the prompt (one-line rule, indentation, weight/pace patterns, self-scan before returning). - For plans estimated over 8 weeks, the prompt now warns about typo risk and suggests pointing the user toward the "Phase-by-Phase" import mode before generating the DSL block. AI Plan Import — Configure & Paste Step Polish - The "Training Days / Week" number picker is replaced by a clickable weekday picker (Mon–Sun pills). Pre-fills from your existing plan's weekly schedule. The prompt now sends "Training days: Mon, Wed, Fri, Sat (4/week)" with an explicit rule that the AI must place sessions only on the chosen weekdays. - Paste step copy/labels are now format-neutral: "Paste the AI's response", "Validate Plan", "I have the plan →". The body text mentions both DSL and JSON, since the importer auto-detects either format. Phase-by-Phase paste copy was updated too. DSL Parser — Robustness for Real AI Output - Real ChatGPT/Claude plans surface notation we hadn't covered. Fixed so the following common forms now parse correctly: - `Pause 2min`, `Pause 45sec` — long-form duration suffixes - `Run 4x100m @4:30 hard`, `Run 5x1km @4:55 hard`, `Run 4x1.5km @4:55 hard` — interval syntax expands into N separate Run blocks - `Walking Lunges 3x16 @2x24kg` — paired-dumbbell weight now parses as per-hand kg (24, not misread as sets×reps) - `Farmers Carry 100m @2x24kg` — distance-based movements without sets×reps now parse as exercises with distance + weight - `Run 1km @4:50 hard` — `hard` intensity keyword is now recognized (treated as vo2max) - The exercise parser switched to token-based matching so weight tokens like `@2x24kg` can no longer be misinterpreted as a sets×reps token. - Error messages now say "Plan format error: …" instead of "Invalid JSON: …" — the previous wording confused users when a DSL plan failed to parse. - 8 new unit tests cover all the new patterns.
improvedMay 20, 2026

Version 1.0.7

**Build 12** · 20 May 2026 ATHX Zone-Specific Planning & Tracking - Race Planner: ATHX plans now include zone-specific targets (Strength weight progressions, Endurance distance goals, MetCon X time caps) with AI-generated periodisation. - Phone Race Tracker: ATHX live tracking shows zone-specific UI — weight inputs for Strength, distance tracking for Endurance, timer with cap for MetCon X. Zone scores saved automatically. - Plan Analysis: ATHX training plans show zone breakdown charts (Strength volume, Endurance distance trends, MetCon X time progression) instead of run/station analysis. - Today Tab: ATHX sessions display zone-specific detail (target weights, distances, time caps) instead of generic exercise lists. Granular Muscle Groups in Heatmap & Recovery - Muscle heatmap and recovery tracking now use fine-grained muscle groups (e.g. Front Delts, Rear Delts, Lateral Delts instead of just "Shoulders") for more accurate load distribution. - Training load calculations use granular muscle mapping for all exercise types. Watch Workout Controls Redesign - PAUSE button enlarged to equal width as DONE button — much easier to hit during workout. - DONE button now requires a 1-second long press to prevent accidental block skips. - Back and Overview buttons moved to a secondary row below. Cooldown Jump-Back Bug Fix - Fixed bug where Watch would jump from cooldown view back to the last exercise when Phone sends a block structure update during workout. - Cooldown block is now preserved when remote block updates arrive, preventing the view from resetting. - Fixed: `isCooldownBlock` flag now resets when a remote update replaces the current block, preventing session from ending prematurely. Backup v2 — Workouts & Performance Log - Backup now includes all saved workout results and performance log entries (previously only races and training plans). - Restoring a backup re-imports workouts, performance log, and triggers training load recalculation. - Fixed: Restoring a backup twice no longer duplicates workouts or performance log entries — merges by ID. - Fixed: Training load re-bootstrap now starts immediately after restore instead of waiting for user navigation. Exercise Defaults Always Saved - Exercise weight/reps values are now saved per exercise after every workout completion — regardless of whether tracked on Watch or iPhone, from a plan or quick workout. - Values are synced between Watch and iPhone so exercise defaults are consistent across devices. Plan Import — Date-Based Modification & Flexible JSON - "Modify Specific Weeks" replaced with "Modify Specific Days" — now uses two DatePickers (From/To) instead of week number pickers, so users can modify even just 2-3 days without replacing entire weeks. - "Validate & Replace" button: when modifying specific days, validation and import happen in one step — no extra "Import Plan" confirmation needed. - Day-level merge: `replaceDaysInActivePlan` only touches days within the selected date range. Days outside the range are preserved even within the same week. - JSON parser now accepts three formats: `{"weeks": [...]}` wrapper, a single week object `{"weekNumber": 1, "days": [...]}`, or a bare array of week objects. No more "Invalid JSON" when pasting a single week from AI output. - Fixed: Markdown code fences (`` ```json ... ``` ``) are now automatically stripped before parsing. AI output often includes these despite being told not to. - Paste step shows a merge mode indicator with the selected date range. - AI prompt for partial modification now includes: date range, current content of the days being replaced, surrounding week context, and a compact output example. - Manual JSON import sheet also gains a "Replace Weeks Only" toggle for the same workflow. ATHX HR Chart Fix - Fixed: Race detail HR chart used `max(1, runCount)` which created a fake run iteration for ATHX (0 runs). - Now uses the actual number of stations/zones for loop bounds. Translation Fix - Fixed "Back" button not translating to German in certain views. **Build 11** · 19 May 2026 Auto-Merge Overlapping HealthKit Workouts - When tracking a workout on iPhone while Apple Watch (or Garmin/Polar/COROS) simultaneously records a separate workout, the overlapping external workout is now automatically linked and hidden. - Detection: after a GritRace workout finishes, the app scans HealthKit for external workouts with >50% time overlap and links them to the GritRace result. - Workout History: linked external workouts are hidden from the list to avoid duplicates. GritRace workouts with merged HR data show a pink "HR Merged" badge. - Training Load: linked external workouts are excluded from TRIMP/strain calculations to prevent double-counting. - Data model: new `linkedHKWorkoutUUIDs` field on WorkoutSessionResult (backwards-compatible with existing saved data). **Build 7** · 19 May 2026 Watch App Crash Fix - Fixed Watch app crashing on launch due to `Task.detached` violating `@MainActor` isolation (`SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor` is enabled). - `refreshTodayWorkout()` in GridRaceWatchApp and `refreshCompletedSessionIds()` in TrainingViews now use `Task {}` instead of `Task.detached`, inheriting the MainActor context correctly. **Build 7** · 19 May 2026 ATHX UI Polish & Zero-Run Guards (continued) Race Planner — ATHX Display Fixes - Plan Summary hides the "Runs" row for ATHX (runCount=0); station label shows "Zones" instead of "Stations". - Forward Calculator (Pace → Time): run pace input card hidden for ATHX; breakdown shows "Zones" label. - Reverse Calculator (Time → Pace): shows "ESTIMATED ZONE TIME" header instead of "REQUIRED RUN PACE" for ATHX; run-related rows hidden. - AI Coach prompt now describes ATHX as "3 zones (no runs)" instead of "0×0m runs". Watch — ATHX Format Display - Format picker subtitle shows "3 Zones" instead of "0× runs · 0m" for ATHX. - Active format badge shows "3 Zones" instead of "0× 0m" for ATHX. Goal Time Estimation - ATHX goal time estimation no longer requires running pace data — estimates from zone time defaults alone, so users without run history still get auto-estimated goals. **Build 7** · 19 May 2026 ATHX UI Polish & Zero-Run Guards ATHX Race Comparison - New "Zone Scores" section in race comparison view: side-by-side Strength (kg), Endurance (m), and MetCon X (time) with bar charts and best-value highlighting. - New ATHXZoneCompareRow component for non-time value comparisons with accessibility labels. ATHX Race History Cards - Race cards for ATHX races now show Strength (kg), Endurance (m), and MetCon X (time) instead of run/roxzone/station stats. ATHX Share Cards - Share cards for ATHX races show zone scores (Strength kg, Endurance m, MetCon X time/DNF) instead of run/station stats. - Footer text shows "3 Zones · Strength · Endurance · MetCon X" instead of run/station counts. ATHX Log Past Race - New zone score input fields when logging ATHX races: 1RM Strict Press, 3RM Back Squat, 5RM Deadlift (Strength), Total Distance (Endurance), Completion Time + completed toggle (MetCon X). - Zone scores saved to race as ATHXZoneScores. ATHX Dashboard Benchmark - Performance Benchmark card now shows ATHX zone-level percentile tiers (Elite/Advanced/Intermediate/Beginner) based on average zone percentile, instead of returning nil. ATHX Race Planner - Goal pill label shows "Zones" instead of "Stations" for ATHX format. Zero-Run Guards (ATHX crash fixes) - AnalyseTabView: RunDriftPreview and RunDriftCard guard `runCount > 0` to prevent range crashes for ATHX (0 runs). - RaceComparisonView: run total row, run header section, and run detail rows all wrapped in `if fmt.runCount > 0`. TCX Export - ATHX distance now uses actual endurance meters from zone scores instead of format-level totalRunDistanceKm. - Run distance per lap returns 0 instead of 1000 for formats with no runs. Translations - New DE translations: "Endurance" → "Ausdauer", "MetCon X" → "MetCon X", "Zones" → "Zonen". - New localized strings: STRENGTH, ENDURANCE, METCON X, DNF, Strength Zone, Endurance Zone, MetCon X Zone, Total Distance, Completed (under time cap), 3 Zones description. **Build 7** · 19 May 2026 Full ATHX Integration & Phone Tracking Fixes Phone Workout Tracker Performance - Fixed significant lag when tracking workouts on phone only (no Watch): timer no longer re-renders the full exercise list every second. - Timer isolated into its own view (PhoneWorkoutTimerBar); blocks cached and only refreshed on planVersion changes. Rest Timer Fixes - Rest timer now continues counting in background: uses absolute end-date instead of countdown, syncs remaining time on foreground re-entry. - Rest timer no longer triggers between exercises within a superset — only fires after the last exercise in the group. TextField Reps Deletion Fix - Fixed bug where deleting reps (e.g. "11" → "6") was impossible because onChange triggered an update loop on every keystroke. Now only fires when the value parses to a valid number. ATHX-Specific Training Plan Generation - New periodized strength builder: Base (4×8 hypertrophy) → Build (5×5) → Peak (1RM/3RM/5RM race specificity) → Taper (3×5 maintenance). Includes Push Press + RDL accessories. - New MetCon X conditioning builder: full 6-movement circuit at category-specific loads (Ski-Erg, GTOH, Sandbag, BJO, Lunges, Burpee BJ) with phase-scaled training fractions (40–70%). - New endurance interval builder: alternating Run/Row at category swap distances (500m/750m/1000m) with phase-scaled interval counts. ATHX AI Coach Integration - AI prompt builder includes full ATHX-specific training guidance section (3 zones, periodization, zone transitions). - Coach system prompt updated to "elite functional fitness training coach specializing in HYROX, ATHX, DEKA, and Deadly Dozen." ATHX Race Detail View - New ATHX Zone Scores card: shows Strength (kg), Endurance (m), MetCon X (time) with color-coded percentile badges ("Top X%"). - AI analysis prompt includes zone scores with percentile breakdowns instead of empty run splits. - On-device race briefing (FoundationModels) uses zone-balance focus for ATHX instead of pacing consistency. - CSV export includes ATHX zone scores in meta header. - Summary cards hide "RUN" card for ATHX (runCount = 0). ATHX Achievement System - Elite Finish: all 3 zone percentiles ≥ 75%. - Advanced Finish: all 3 zone percentiles ≥ 50% (but not all ≥ 75%). - Clean Sheet: no zone below median (p50). Translations - New DE translations: "ATHX ZONE SCORES" → "ATHX ZONEN-ERGEBNISSE", "Endurance" → "Ausdauer", "MetCon X" → "MetCon X". **Build 7** · 18 May 2026 ATHX Games Format Integration - New race format: ATHX Games — a 2.5-hour structured fitness competition with 3 scored zones (Strength, Endurance, MetCon X). - Full ATHXFormat implementation with 15 categories: Lite / ATHX / Pro × Individual M/F + Pairs M/F/Mixed. - Category-specific weights, reps, and distances for all MetCon X movements (2026 season data). - Onboarding: ATHX category picker (Lite / ATHX / Pro) with descriptions in the setup wizard. - Race Calendar: 9 ATHX events for 2026 season (Dublin, Glasgow, Copenhagen, Birmingham, Barcelona, Marseille, Liverpool, Amsterdam, World Finals Lisbon). - Race Planner: zone-based time distribution using default zone durations (Strength 20min, Endurance 22min, MetCon X ~12min). - Log Past Race: ATHX category selection when logging ATHX races. - Training Plan: ATHX-aware run fraction (30%), division-by-zero protection for formats with no runs. - Station Trainer: ATHX zones available as trainable stations. - Watch App: ATHX category display and settings. - Widgets: ATHX format filter, color (purple), and recommendations. - Dashboard: graceful handling of ATHX in benchmark/weakness/recommendation cards (no benchmarks yet). ATHX Race Tracking (Zone-to-Zone Flow) - Race state machine now supports zone-only formats (no runs): starts directly in Zone 1 instead of Run 1. - Zone-to-zone transitions with official rest times: Feed Zone (10 min after Strength), Recovery Zone (30 min after Endurance). - Skip rest button works for ATHX: advances to next zone immediately. - Watch transition overlay shows "FEED ZONE" / "RECOVERY ZONE" labels and previews the next zone name. - iPhone transition overlay shows ATHX zone labels and next zone preview instead of "RUN N". - Predicted finish time calculation handles zone-only formats (no division by zero on runCount=0). - Race Sim in training plans: station count capped to actual format stations, run blocks skipped for ATHX. - Brick Run removed from ATHX peak phase training pattern. - Session description uses "aerobic base for race-day endurance" instead of "full 0 m of race-day running" for ATHX. ATHX Live Zone Scoring - New ATHXZoneScores model: tracks Strength (1RM Press + 3RM Squat + 5RM Deadlift → Total kg), Endurance (total meters), and MetCon X (completion time). - Zone score input during transition rest (Feed Zone / Recovery Zone): enter your scores while resting between zones. - Watch: compact +/- stepper input for lift weights (5 kg steps) and endurance meters (100 m steps). - iPhone: larger input with labeled rows per lift (2.5 kg / 5 kg steps), endurance meters, and real-time percentile badges. - Estimated percentile ranking: "~Top X%" badge based on benchmark data per category (Lite/ATHX/Pro × M/F). - Benchmark data for 6 category/gender combinations across all 3 zones (estimated from 2025/2026 season structure, easily updatable). - Zone score summary pill on Watch station area view (zones 2+) showing completed zone scores at a glance. - iPhone station area shows completed zone scores with percentile badges during zones 2 and 3. - MetCon X time auto-captured from zone duration on race finish. - Full score card on race finish screen: all 3 zone scores with individual lift breakdown, distance, time, and percentile estimates. - Zone scores persisted on HyroxRace (athxZoneScores field) — saved with race results and synced to paired devices. - ATHX-specific race day checklist: venue hints (indoor, lifting shoes, chalk) and zone recovery tips. - Share card hides roxzone stat for ATHX races. - ATHX category validation on onboarding Next button. **Build 7** · 17 May 2026 Auto-Station Detection Fix (Race Tracking) - Auto-station detection is now OFF by default — users must opt-in via Watch Settings. - Raised minimum run duration before auto-detect can trigger from 24s to 45s for DD/DEKA short runs. - Short-distance formats (<800m) now require 18s of continuous low cadence (was 12s) to reduce false triggers. - Added diagnostic logging when auto-detect fires (run number, duration, cadence) for future debugging. **Build 2** · 17 May 2026 Exercise Images Everywhere - Added 107 exercise images to the asset catalog (101 accessory exercises + 6 station-specific: Rowing, SkiErg, Devil Press, Overhead Carry, Front Carry, Sled Push/Pull). - Exercise thumbnails now appear in all views: workout cards, training plan sessions, quick workouts, workout builder, dashboard widgets, and the muscle group detail sheet. - Tapping an exercise name or image opens the exercise detail sheet with instructions, muscles, and equipment. Station Name Alias System - Centralized exercise resolution via `resolveExercise(named:)` and `exerciseImageId(for:)` in TrainingPlanManager. - 22 station name aliases map DD/HYROX/DEKA station names (e.g. "KB Farmers Carry", "Wall Balls", "Sled Push") to their matching accessory exercise for image display and detail sheets. - 8 station image overrides provide images for station-only exercises without accessory counterparts. Muscle Group Detail Sheet Rework - Tapping a muscle group now shows time-series progression charts instead of static bars. - Segmented time range picker (4W / 3M / 6M / 1Y / All). - Stacked bar chart showing weekly volume per detail muscle. - Individual trend lines per detail muscle with area charts and trend arrows. - Exercise list with thumbnails and tap-to-detail. Analyse Tab - Training history card moved to the top of the training stats section. Granular Muscle Group Heatmap - Added 17 detail muscles (e.g. Quadriceps, Hamstrings, Lats, Biceps, Triceps) that map to the 7 high-level muscle groups. - Exercise Library and Training Plan views show detail muscle tags (primary + secondary) on each exercise. - Fixed "Back" translation — was "Zurück" (navigation back), now correctly "Rücken" (body part). Run Flash Overlay on Watch - During run workouts on Apple Watch, a fullscreen flash overlay now appears on pace status changes (Good Pace / Too Slow / Too Fast) showing the current pace. - Kilometer milestones trigger a flash showing the km number, split pace, and current heart rate. - Flash auto-dismisses after 2.5 seconds with a smooth scale+opacity transition. Exercise Instructions - Every exercise in the library (101 total) now has a detailed step-by-step execution instruction. **Build 1** · 13 May 2026 Exercise Detail on Tap - Tapping an exercise name in a workout now opens the exercise detail sheet with description, instructions, target muscles, equipment, and an image placeholder. - Users can upload their own exercise photos for any exercise (not just custom ones) — images are stored locally. Strava Export Fix: HR, Cadence & Power - Fixed: Heart rate and other HealthKit data were missing from Strava uploads because the workout start time was mismatched (search used completion time instead of start time). - TCX exports now include running cadence (steps/min) and running power (watts) from HealthKit. - Calories and distance are now taken from the actual HealthKit workout instead of estimates. - Search window for matching HealthKit workouts expanded from ±5 to ±10 minutes. Muscle-Group Specific Recovery - Recovery estimation is now per muscle group instead of a single whole-body timer. - Exercises in completed sessions are resolved to their target muscles (primary = full recovery, secondary = 50%). - Running → legs + posterior chain + core; Race sim → all muscle groups. - If a muscle group is trained again before full recovery, remaining time is partially carried over. - Today tab shows individual recovery bars per muscle group, sorted by least recovered first. Z2 Pace Filter Applied Globally - The 8:00/km pace floor for Z2 classification is now enforced everywhere: classification, charts, averagePace(), bestPace(), progression cards, race predictor, and plan estimation. - Walking with elevated heart rate is always classified as warmup, never Z2.
improvedMay 11, 2026

Version 1.0.6

**Build 1** · 11 May 2026 Z2 Pace Now Based on Heart Rate Zones - Run intensity classification (Z2/Tempo/VO2max) is now based on actual heart rate data from HealthKit when available, using the user's configured HR zones. - Pace-based classification remains as fallback when no HR data exists. - Walking workouts are no longer classified as "Run" sessions — mapped to "Recovery" instead. - Z2 pace calculations cap at 8:00/km (480s) as additional safety net. - Fitness Trend chart filters tightened to exclude walking-speed data. Recovery Timer Merged into Tip Card - The recovery timer is now integrated into the recommendation ("Tip") card on the Today tab instead of being a separate card. - Shows a compact progress bar with remaining time below the daily training recommendation. - Removes visual clutter — one card for both training guidance and recovery status. Intensity Indicator (Read-Only) - The intensity picker in the session detail and editor views is now a non-interactive display instead of a changeable control. - Shows the planned intensity (Locker/Moderat/Schwer/Max) as a colored indicator for reference. Apply Workout Replacement to Entire Plan - When replacing a session with a saved or generated workout, the app now asks whether to apply the same replacement to all similar future sessions (same title/type). - Uses the same multi-select dialog as the exercise editor — pick which sessions to update, skip, or apply to all. No Consecutive Pause Blocks in Workouts - Fixed: strength/accessory sessions could have two pause blocks in a row (e.g. warmup activation + light set, or cooldown stretch + foam rolling). - Consecutive pause blocks are now merged into a single block with combined duration and joined descriptions. AI Import Day-of-Week Fix - Fixed: AI-imported plans had mismatched day labels when the import happened on a non-Monday (e.g. importing on Thursday caused "Monday" to show Thursday's date). - All three AI import paths (single JSON, phased JSON, multi-phase merge) now align the start date to Monday before assigning dates. - Existing broken plans are auto-corrected on load — `dayOfWeek` is now derived from the actual date during decoding, so no re-import is needed. Plan Week Alignment Fix - Training plan weeks now always start on Monday (Mon–Sun), regardless of the day the plan was created. - Existing plans are automatically migrated on launch — no need to recreate. - Race day is now correctly placed on the actual weekday in the plan after migration. - If the race date falls past the plan's last day after alignment, an extra week is appended automatically. Onboarding: Getting Started Flow - After the app tour, new users now see a "Getting Started" sheet with three guided actions: Create Training Plan, Log a Past Race, or Browse Upcoming Races. - "Create Training Plan" navigates to the Plan tab and opens the setup wizard; "Log a Past Race" opens the race logging view; "Browse Upcoming Races" navigates to the Races tab. - Updated final tour step text to transition smoothly into the Getting Started sheet. Smarter Plan Creation Defaults - Focus station pre-selection no longer picks stations when fewer than 2 races are logged — avoids misleading suggestions. - Weekly running km is now pre-filled from HealthKit data (4-week average, rounded to nearest 5 km) when creating a new plan. Recovery Timer - After completing a workout, a recovery estimate is calculated based on session type, intensity, duration, and readiness score. - A recovery timer card appears on the Today tab showing a circular progress ring (red → yellow → green), remaining time countdown, and session details. - Recovery state persists across app launches and auto-dismisses when fully recovered.
improvedMay 8, 2026

Version 1.0.5

**Build 8** · 08 May 2026 Progression Chart Redesign (Plan Tab) - Replaced the text-only progression card with a tabbed chart card: Pace / Strength / Load. - Pace tab: line + area chart of Zone 2 pace across the last 12 sessions, Y axis inverted (faster = higher), trend stat showing delta vs. early sessions (e.g. "+15s/km"), Latest / Trend / Best stats row. - Strength tab: scrollable exercise chip picker (exercises with ≥3 logged sessions auto-populated), weight progression line chart per session, Current / Gain / Sessions stats. - Load tab: bar chart of planned/completed training hours per week from the active plan, bars phase-coloured (Base / Build / Peak / Taper / Race Week), legend + average weekly hours stat. - Each tab shows a contextual empty state when no data is available yet. Field Distribution Chart Fixes (Analyse Tab) - Fixed chart drawing in a single flat colour despite the coloured legend — zones now fill correctly (elite / top-30% / mid / back) with opacity based on the athlete's position. - topPercent formula corrected: now properly gives small values (e.g. 3%) to elite athletes and large values to slower athletes. - Division picker is now selectable for HYROX (Open Men/Women, Pro Men/Women, Doubles, Mixed Doubles) in addition to Deadly Dozen. - Auto-detects the user's division from their most recent HYROX race on load; falls back to UserDefaults if no race is logged. Readiness Adaptation Improvements - Watch build failure fixed: GRNotificationCenter references in TrainingPlanManager.swift guarded with #if os(iOS). - Banner no longer re-appears multiple times per day (added shown-today gate via UserDefaults). - "Apply Suggestion" now pre-computes the adapted session before showing the banner — tapping the button commits it reliably; previously could fail silently for certain intensity combinations. - Banner now shows a plain-text description of the proposed change (e.g. "'Interval Run': 2 blocks removed, intensity → Moderate. ~45 min") so you can review before confirming. - Readiness thresholds updated to science-backed values: Green ≥70 (no adaptation needed), Yellow 55–69, Orange 40–54, Red <40. **Build 8** · 06 May 2026 In-App Notification System & Readiness Adaptation - New GRNotificationCenter: slide-in banner from top of screen (swipeable, auto-dismisses after 8s, supports action buttons). - Readiness Adaptation re-enabled: when HRV/sleep readiness is Yellow/Orange/Red, the app posts a banner asking "Adapt Training?" — you confirm before anything changes. No auto-adaptation. - Yellow (60-79): reduces volume ~20% and lowers intensity. Orange (40-59): replaces session with active recovery. Red (<40): rest day recommended. - After adapting, a second banner shows with a "Revert to Original" button so you can always go back to the planned workout. - Toggle in Plan Settings → Readiness Adaptation (off = no prompts, ever). - WorkoutBuilderView now shows a readiness refresh button (waveform icon) when a score is available. Bug Fixes & Stability - Fixed potential crash in performance chart date calculation (Calendar.date(byAdding:) now properly guarded instead of force-unwrapped). - Hevy CSV import now supports 9 date formats: ISO, US (MM/dd/yyyy), EU (dd.MM.yyyy), Asian (yyyy/MM/dd), and more — previously only 2 formats were tried, causing silent import failure for non-German/non-ISO exports. - Hevy CSV: when dates can't be parsed, shows a specific error message with the supported formats instead of a generic "empty plan" error. - AI plan prompt now correctly handles "no equipment" (bodyweight-only) and "full gym" cases — previously only the partial selection case was described to the AI. Race Day Placement Fix - Fixed race day not being blocked correctly for B-Race and C-Race weeks during plan generation. - B-Race weeks now properly block the race day and day before (previously only worked for A-Race weeks with `.raceWeek` phase). - C-Race (training race) weeks now block the race day itself but allow training the day before. - Fixes race appearing on wrong day after plan update/regeneration. AI Plan Import — Equipment & Priority Selection - Added equipment picker to the AI plan import configuration step. - Lets users select which equipment they have access to before generating the prompt. - Equipment info is included in the AI prompt so the generated plan only uses available equipment. - Added Training Focus picker (Race Focused / Hybrid / Muscle Building) to the AI import. - Priority is now described with clear instructions in the prompt (e.g., "Primary goal is muscle hypertrophy. Include dedicated strength/hypertrophy sessions..."). - Both settings prefill from existing plan config when available. Workout Import (Hevy CSV) - New CSV import for workouts from Hevy, Strong, FitNotes, and similar apps. - Flexible column detection — parses exercises, sets, weights, reps, RPE, and dates. - Creates completed sessions on the correct dates, grouped by workout name. - Accessible via Plan Detail → Menu → Import → "Import Workouts (CSV)". AI Plan Import — 3 Modes - New mode selector in the AI plan import flow: Full Plan, Phase by Phase, and Template + Progression. - Full Plan: existing one-prompt approach for capable models like Claude Opus. - Phase by Phase: separate prompts per training phase, paste each separately, app merges automatically. - Template + Progression: AI creates one template week per phase with progression rules, app auto-generates the full plan with weight/rep/pace increases. - Phase labels auto-calculated from plan duration. Run Performance Analysis - Added detailed running metrics to workout stats: Power (W), Cadence (spm), Stride Length (m), Ground Contact Time (ms), Vertical Oscillation (cm), VO₂max, HR Recovery. - New combined Run Performance Chart with HR, Pace, and Elevation layers — individually toggleable via pill buttons, normalized Y-axis with area marks for elevation. - On-device AI analysis (Apple Intelligence / FoundationModels) for running workouts: generates 8-section coaching report covering pace strategy, HR zones, elevation impact, running mechanics, and actionable recommendations. - New HealthKit methods: fetchElevationProfile, fetchPaceProfile, fetchRunningMetrics, fetchLatestVO2Max, fetchHRRecovery — all derived from workout route and statistics data. App Icon Fix - Fixed app icon for App Store: separate Light, Dark, and Tinted variants with correct backgrounds and no alpha channel. - Resolves App Store Connect rejection errors for missing icon, alpha channel, and missing dark mode variant. Workout Tracking Audit Improvements - Added dedup to handleRemoteSetCompletion: prevents double-processing if HK command replays (hash of weight/reps/duration/setIndex). - addExtraSet() now appends a matching ExerciseSet to the exerciseSets array, keeping it in sync with type.sets count. - jumpToBlock() resets superset progress and locked totals when jumping to a different superset group. - JSON encoding for live updates throttled to 1Hz (timer still ticks 4Hz for smooth UI counters). - LiveWorkoutUpdate now includes restTimerRemaining and restTimerDuration; iPhone companion view shows Watch rest timer overlay. - Session checkpoint for crash recovery: saves plan, block results, set data, and progress on every block transition and set completion. Auto-restores to paused state on next launch. - EMOM overall duration check: session auto-finishes when totalDurationMinutes is exceeded, preventing infinite EMOM loops. - HR samples collected during rest/transition phases are carried over into the next block's average (no longer lost between blocks). iPhone Exercise Defaults Display - When opening a session on iPhone, last-used weights/reps from ExerciseDefaultsStore are now applied to all exercise blocks. - New TrainingPlanManager.applyExerciseDefaults(sessionId:) mirrors the Watch-side toWorkoutPlan() logic: updates block-level weightKg/reps and per-set targetWeightKg/targetReps from stored defaults. - Only applies to planned/adapted sessions; already-completed sets (with actualWeightKg) are preserved. - Fixed: applyExerciseDefaults now only bumps planVersion when values actually change, preventing infinite re-render loops. Fix Superset Extra-Round Bug During iPhone Sync - Added superSetLockedTotalSets dictionary that records each superset block's total set count when first encountered during a workout. - Remote block updates (from iPhone edits or planVersion bumps) no longer change the set count mid-superset. - Locked totals are used in round-robin candidate checks, beginBlock() restoration, and handleRemoteBlockUpdate() reconciliation. - Enhanced: all blocks in a superset group are now locked on first encounter (not just the current block), preventing inflated set counts for not-yet-visited blocks. - addExtraSet() now updates superSetLockedTotalSets to keep the lock in sync. - Added lastRemoteBlockUpdateHash deduplication to prevent triple-processing of block updates from 3 delivery channels. - Fixes: doing a superset on Watch required extra rounds (e.g. 3 instead of 2) after any change on iPhone. Fix App Crash When Viewing Workouts - Removed initializeSetsIfNeeded() call from view body that caused infinite re-render loops when combined with applyExerciseDefaults. - Initialization and defaults application now runs once in .onAppear only. Strava Upload Improvements - "Tracked with GritRace" now appears at the top of the Strava description next to the workout name. - Added retry mechanism for TCX uploads: if TCX data is unavailable (HK hasn't synced from Watch yet), retries once after 90 seconds. - Fixes: auto-uploaded workouts were missing HR graphs because TCX upload failed before HealthKit data synced. Watch Exercise Defaults Fix - toWorkoutPlan() now applies ExerciseDefaultsStore values before sending to Watch. - Ensures last-used weight/reps from completed sessions appear as target values on the Watch, not the original plan values. - Per-set level: maps completed set data by set number for accurate per-set targets. - Fixes: Watch showed original plan weights (e.g. 8kg) instead of last-used weights (e.g. 24kg) for all exercises. Save Session as Workout - Long-press any session in the plan overview (both compact and detail view) → "Save as Workout" option. - Session detail sheet now has a "Save as Workout" button near the top. - Saves the session (with all exercises, sets, weights) to My Workouts for standalone reuse. - Name can be customized before saving; defaults to the session title. Alternative Training: My Workouts Integration - "Alternative Training" sheet now has a segmented picker: "Generate" (auto-generate) or "My Workouts" (pick a saved workout). - Saved workouts show Replace / Add Extra buttons inline for quick action. - Unified UX with the "Add Session" sheet which already had this pattern. AI Plan Import: Previous Plan Summary - When requesting a new AI plan, the prompt now includes a comprehensive summary of the previous/current plan. - Includes: plan duration, completion rate, session type breakdown, phase adherence, skipped session patterns. - Includes: run pace progression (zone2 + racePace), top 8 exercise weight progressions. - Instructs the AI to design a progressive follow-up plan based on strengths and weak areas. Fix Race Week Ghost Markers When Moving Races - markRaceWeeks() now clears all existing race week AND taper markers before re-marking, preventing ghost race weeks when a race date is moved. - Taper weeks now move with the race: A-Races get full taper + recovery, B-Races get mini taper + recovery, C-Races get marker only. - RacePlannerView alert now offers both "Update Races Only" (preserves progress, moves markers) and "Generate New Plan" (full re-periodization), matching PlanTabView and RacePlannerTabView. Exercise Weight/Reps Carry-Over Fix - Completing a training session now correctly propagates last-used weights and reps to all future sessions with the same exercises. - Fixed: adapted sessions were skipped during propagation — now included alongside planned sessions. - Fixed: UI did not refresh after propagation — planVersion is now bumped after both markSessionCompleted and updateSessionWithResults. - Added ExerciseDefaultsStore fallback in exercise card — "Last: X kg × Y" badge now appears even when PerformanceLog has no data, using the globally stored exercise defaults.
improvedMay 4, 2026

Version 1.0.4

**Build 11** · 04 May 2026 What's New View (1.0.4) - Added 6-item What's New screen for version 1.0.4: Strava Integration, Hardware Pause, Fitness Trends, Blank Training Plans, AI Plan Review, CSV Export & Share Cards. Fitness Trend — Rolling Windows & Timeframe Fix - Replaced ISO calendar-week grouping with rolling 7-day windows anchored to today. - Fixes the "weekly reset to zero" problem — every data point now covers exactly 7 days, the last window always ends today. - Charts now respond correctly to timeframe switching (4W/3M/6M/1Y) via explicit chartXScale(domain:). - Removed unit: .weekOfYear from Line/Area/Point marks so data plots at actual dates, not ISO-snapped positions. - HealthKit external workouts now fetched for 365 days (was 90) so 6M/1Y charts have data. - Readiness score history limit raised from 90 to 365 (ReadinessEngine + Models.swift saveHistory). - Breakdown table uses monthly aggregation for 6M/1Y (averaged per month, date format "Apr 26"). Badge Detail View - Tapping the Earned Badges card on the Today tab now opens a full detail sheet. - Shows all 7 badge types with icon, name, description, earned count, and which races earned each badge. - Unearned badges shown dimmed with locked styling. Badge Calculation Fixes - All 6 performance badges (Station King, Consistent Pacer, Elite Finish, Advanced Finish, Clean Sheet, Iron Streak) now require raceType == .race. - Training races with reduced volume can no longer trivially earn performance badges. - Iron Streak now verifies the race is actually part of the last-3 consecutive streak. **Build 6** · 03 May 2026 Hardware Pause (Crown + Side Button) - Pressing Crown + Side Button on Apple Watch now pauses/resumes workouts and races, matching native Workout app behavior. - Workouts: HKWorkoutSession state changes forwarded to WorkoutSessionManager via notifications. - Races: New RaceState.paused with full pause/resume support — timer stops, segment duration excludes paused time, total race duration adjusted. - Watch shows dedicated Paused screen (Resume + Stop) during race pause. - iPhone race tracker shows pause overlay with Resume button. - Live Activity shows "PAUSED" indicator. - Stopping a race while paused works correctly (restores state before ending segment). Per-Workout CSV Export - Each workout in history now has an "Export CSV" button for individual workout export. - Complements the existing bulk CSV export in the Workout History section header. Strava Auto-Upload Fix - Auto-upload from Watch now waits 60 seconds for HealthKit workout data to sync before uploading. - Previously auto-uploads had no time-series data because they fired before HKWorkout arrived on iPhone. Pace Alert Oscillation Fix - Pace coaching now uses hysteresis (wider threshold to enter off-target state, narrower to return on-target). - 15-second minimum cooldown between state changes prevents rapid toggling. - Applied to Watch (WorkoutSessionManager), iPhone run tracker (PhoneRunTrackerView), and iPhone phone run tracker (PhoneRunTracker). Elevation-Adjusted Training Load - TRIMP and strain calculations now factor in elevation gain (Minetti et al., 2002). - Elevation extracted from HKWorkout metadata (HKMetadataKeyElevationAscended) or flights climbed. - HR-based TRIMP: +3% per 100m gain (eccentric/neuromuscular load). No-HR estimation: +8% per 100m. - Power-based TRIMP unchanged (running power already captures elevation cost). Hiking vs Walking Separation - Hiking and walking now have distinct training load multipliers. - Hiking: TRIMP factor 1.0 / strain 0.8 (was 0.6 / 0.5). Walking: 0.5 / 0.4. Watch Elevation Tracking Fix - flightsClimbed now explicitly enabled on HKLiveWorkoutDataSource during workouts. - Previously the data source never collected elevation data — the handler existed but received no data. Strava TCX File Upload - Workouts now upload as TCX files with full time-series data (HR samples, GPS route, elevation, distance). - Strava displays HR graph, pace graph, elevation profile, and map instead of just a text summary. - Falls back to manual activity creation if no HealthKit workout data is available. - Workout route authorization (HKSeriesType.workoutRoute) added to HealthKit permissions. CSV Export - Export all completed workouts as CSV from the Workout History section header. - One row per set (or per block if no sets), with session/block/set-level columns. - Share via iOS share sheet. **Build 5** · 02 May 2026 Fitness Trend Card Redesign - New "Stat Stack" layout: three scannable rows (Run Pace · Z2, Readiness, Volume) each with sparkline, value, and delta. - Tap to open detailed Fitness Trend view with full charts, weekly breakdowns, and contextual insights per metric. - 4W/12W timeframe toggle. Fitness Trend Detail View (New) - Full charts per metric: LineMark for pace, AreaMark for readiness, BarMark for volume. - Weekly breakdown tables with per-week deltas. - Auto-generated insight cards (e.g. "Pace improving steadily", "Volume spike detected"). Share Card Redesign - Share cards now show the complete workout with all run + station segments, pace, time, HR, and best/worst indicators. - Auto-generated highlights: negative split, pace drop, even pacing, peak HR, station spread, roxzone percentage. AI Plan Review (New) - Export all training data (completed sessions, readiness, performance metrics, upcoming plan) as a structured prompt for external AI review. - Generate, copy, or share the prompt. AI is instructed that "no changes needed" is a valid answer. TodaysWorkoutCard Design Fix - Left accent bar now rounds its left corners to match the card's 20pt corner radius. Audio Coaching — State-Based Pace Alerts - Pace announcements now trigger only on state change (too slow → on target → too fast) instead of repeating every 60 seconds. - New "Back on target" announcement when returning to pace zone. - Applied to both Watch and iPhone run trackers. Audio Coaching — Music Ducking - Music is now automatically dimmed (ducked) during voice announcements instead of being interrupted. - Music returns to full volume after each announcement via AVSpeechSynthesizerDelegate. HR Zones — Age from HealthKit - HR zone calculation now reads date of birth from HealthKit when no saved config exists, instead of defaulting to age 30 (maxHR 190). - MaxHR computed as 220 − actual age. Strava Auto-Upload (Watch Workouts) - Watch workouts are now automatically uploaded to Strava when they arrive on iPhone (if Strava is connected). - Previously only iPhone-tracked workouts had a manual upload button. Strava Retroactive Upload - Any past workout can now be uploaded to Strava from the workout result view (Workout History, Plan Detail, Dashboard). - Shows upload status: pending, uploading, or already uploaded. Localization - Wrapped all new UI strings in String(localized:) for German/English support across FitnessTrendCard, FitnessTrendDetailView, ShareCardView, and AIPlanReviewSheet. **Build 3** · 02 May 2026 Watch: Distance & Time Tracking - Watch exercise view now supports all three tracking modes (Reps, Distance, Time) instead of only Reps. - Distance mode: Crown-adjustable meters input (5m steps), stepper buttons, long-press for manual entry. - Time mode: Crown-adjustable duration input (5s steps) showing MM:SS, stepper buttons, manual entry. - Previous set display adapts to show distance or time instead of reps when appropriate. - Tracking mode auto-detected from block's effectiveTrackingMode (explicit override, duration target, or name-based). Auto-Sync Today's Workout to Watch - When you edit today's workout in the Session Detail Sheet and tap Done, changes are automatically sent to the Watch (if paired). - Delivery-status toast: green "Synced to Watch" when Watch is reachable, orange "Changes queued" when offline. Strava Integration - New Strava section in Settings: Connect/Disconnect with OAuth2 authorization flow. - "Share to Strava" button in workout summary (WorkoutSessionDetailSheet) after completing a workout. - Uploads workout as a Strava activity with name, type, duration, distance, and detailed exercise breakdown. - Duplicate protection: tracks uploaded workout IDs to prevent double uploads. - Only GritRace-tracked workouts are uploaded — runs from Apple Watch's native app go through Strava's own Health sync. - URL scheme (gritrace://) registered for OAuth callback handling. **Build 2** · 01 May 2026 Custom Exercise Photos - Custom exercises now support photos: add an image when creating a new exercise or edit it later from the detail view. - Images shown as thumbnails in the exercise list and as a banner in the detail sheet. - Change or remove photos anytime via the detail sheet (custom exercises only). - Images stored in Documents directory and persist across iCloud backups / device transfers. Blank Plan — Custom Training from Scratch - New "Blank Plan" option when creating a training plan: start with an empty plan and fill it with your own workouts. - Mini setup wizard: pick format, duration (weeks), training days per week, and optional race date. - Empty days show a dashed "+" circle — tap any day to add a workout directly. - Guided onboarding card explains the 4-step flow (tap day → pick type → add exercises → copy to other days). - Copy Workout: long-press any workout in the plan to copy it to other days (available in both week strip and full plan view). - All empty/rest days are now tappable across all plan types (not just blank plans) to quickly add sessions. **Build 1** · 01 May 2026 Running Pace — Apple Sensor-Fused Speed - Replaced manual GPS delta pace calculation with Apple's `runningSpeed` (HKQuantityTypeIdentifier). This is the same smoothed, sensor-fused pace Apple's own Workout app uses — no more jumping pace display. - Distance tracking simplified: only reads cumulative distance from HealthKit, no longer computes pace from distance deltas. Pace Coaching — Reduced False Alerts - Pace coaching minimum distance increased from 200m to 400m (wait for stable signal). - Pace coaching throttle increased from 30s to 60s between alerts. - Pace deviation threshold increased from 15 to 25 sec/km (avoids false "too fast"/"too slow" announcements from minor fluctuations). Watch Run Block — Overview Button - Added missing Overview (list.bullet) button to run blocks on Watch. Previously only exercise blocks had this button, making it impossible to see the workout overview during a run. Readiness Charts — Improved Readability - Fixed: Y-axis now zooms to actual data range instead of starting at 0 (e.g., HRV ~60ms no longer shows 0-400ms range). - Fixed: Y-axis and X-axis labels enlarged (8→9pt) and color changed from tertiary to secondary for better readability. - Added subtle grid lines for easier value reading. - Fixed: Baseline reference line position now matches the zoomed Y-axis. - AreaMark fill corrected to start from chart bottom instead of 0. Elevation Tracking — New - Added live elevation gain tracking using HealthKit `flightsClimbed` (1 flight ≈ 3m). - Elevation gain displayed on Watch during run blocks (teal tile, only appears when > 0m). - Elevation gain shown in Workout History detail view and analysis stats grid. - Elevation data saved per-session in `WorkoutSessionResult` and per-split in `RunSplitResult`. - Properly reset on session start and block transitions. App Tour — German Translation - All tour step titles, descriptions, and buttons (Next, Back, Done, Skip Tour) are now fully localized in German. Training Load — Running Power Integration - Added `runningPower` (watts) to HealthKit read types for Training Load calculations. - TrainingLoadEngine now prefers Apple's running power over HR-based TRIMP for running/cross-training workouts. Power-based load (avgWatts × minutes / 100) is more accurate than HR-based TRIMP as it captures terrain, speed changes, and doesn't lag like heart rate. - Live `runningPowerWatts` property exposed on HealthKitManager for potential future Watch UI display. - Running power cleared on workout end/discard.
improvedApril 30, 2026

Version 1.0.3

**Build 12** · 30 April 2026 Live Workout Sync — Deep Fix (14 bugs across sync chain) Session Resolution: - Fixed: LiveWorkoutUpdate now includes trainingPlanSessionId so iPhone can reliably identify which training session the Watch is running, instead of relying on fragile block-ID matching. - Fixed: Watch startSession() now propagates trainingPlanSessionId from the plan, ensuring every live update carries the session ID from the very first tick. - Fixed: PhoneWorkoutCompanionView.resolveSession() uses trainingPlanSessionId from live update as primary method; block-ID matching is now fallback only. - Fixed: clearSessionCompletionState on companion appear now uses sessionId from live update. Completion Tracking: - Fixed: Pause blocks no longer included in completedBlockIds sent to iPhone. Previously, pause block IDs polluted completion tracking and could cause wrong exercises to appear checked off. - Fixed: completedBlockIds reconciled when blocks are replaced via remote update — stale IDs from removed blocks are cleaned up. - Fixed: allBlockSetResults and superSetProgress cleaned up for removed blocks during block replacement. Superset Handling: - Fixed: superSetProgress clamped to valid set count after remote block update (add/remove set on iPhone no longer causes out-of-bounds set index on Watch). - Fixed: superSetProgress clamped in beginBlock() to handle cases where set count decreased. - Fixed: currentBlockSetResults cleared when current block is removed/replaced during active workout. Exercise Swap: - Fixed: swapExercise() now calls initializeExerciseSets() after clearing, so Watch receives blocks with proper set arrays instead of empty ones. Logging & Diagnostics: - Added: Logging for HK mirroring decode failures on both Watch and iPhone sides. - Added: Logging for handleRemoteBlockUpdate failures (decode error, session mismatch, stale block cleanup). - Added: Logging for resolveSession() failures with diagnostic context. Previous (Build 11→12): - Block updates sent via 3 channels: HK mirroring + sendMessage + transferUserInfo. - RemoteWorkoutCommand.blockUpdate for HK mirroring channel. - Stale transfers cancelled before new ones. Sync toast accuracy improved. - Removed fragile block-count session matching fallback. **Build 11** · 29 April 2026 German Localization - Added German (de) as the first localized language with 2302 translated UI strings. - Created Localizable.xcstrings String Catalog in Shared/ for iOS + watchOS targets. - Converted 300+ enum/computed properties to `String(localized:)` for runtime localization. - Comprehensive pass across all views: Analyse tab, Dashboard, Race Detail/Comparison, Training Load, Plan Analyse, Race Planner, Today tab, Weekly Digest, Watch app, and all helper views. - Section headers, legend labels, placeholder messages, achievement descriptions, and interpolated strings all translated. - Technical terms (HR, HRV, CTL, ATL, etc.) and brand names kept in English. Session Detail — Fix False Change Detection - Fixed: "Apply changes to similar sessions?" dialog no longer appears when no edits were made. - Root cause: JSON encoding comparison was unreliable for block change detection; replaced with direct Equatable comparison. Readiness Score — Deep Insights & Fixes - New: Tap the readiness card to open a detailed Readiness view with history charts, factor breakdown, and "Why this score?" explanation. - New: 30-day/90-day readiness trend chart with zone bands. - New: Per-factor history charts (HRV, Sleep, Resting HR, Strain) with baseline reference lines. - New: Natural language explanation of each factor's contribution to today's score. - Fixed: Score showed 100 in simulator/no-data scenarios (only strain was calculated). Now returns neutral 70 when no biometric signals are available. - Fixed: Text inside the readiness ring was overflowing into the circular border. Ring enlarged to 110px, content constrained. - Improved: HRV baseline extended from 30 to 60 days per latest research (Plews et al., HRV4Training, Kubios methodology). - Fixed: "Apply to similar sessions?" dialog no longer appears when dismissing completed/skipped session details. - Fixed: Block snapshot timing in session detail to prevent false change detection from auto-initialized exercise sets. Training Plan Detail — Day Cards Show Workout Type - Completed day cards now show the workout type icon (run, strength, etc.) instead of a generic checkmark. - Green ring + checkmark badge replaces solid green fill, keeping the workout type visible. - Session type label (Run, Strength, Cond., etc.) shown below each day card instead of just duration. Paywall Restructuring — Generous Free Tier - Today Tab now fully free: readiness score, training load, daily workout, quick actions all accessible without Pro. - Training Plans now free: create, follow, and complete workout plans without Pro. Advanced features (AI Import, Deload, Pace Adaptation) remain Pro. - Analyse Tab partially free: Performance Level, Best Times, Progress Charts, and Race Splits are free. Deep analysis (Weakness, Pacing, HR Insights, Benchmarks, etc.) gated with elegant inline Pro teasers. - AI Analysis Prompt now free: share/copy race data prompt to any AI app without Pro. - New UI components: ProBadgeDot (tiny orange PRO indicator) and ProSectionGate (inline teaser cards for locked sections). - Removed full-tab paywall locks in favor of feature-level gating for better user engagement. **Build 11** · 29 April 2026 Discard Workout — No HealthKit Save - Cancelling a workout and tapping "Discard" now discards the HealthKit workout data instead of saving it. - Fixed `resetToReady()` also incorrectly saving workout data to Health. Watch — Back to Workout - Added "Back to Workout" button in the Watch workout overview screen. iPhone → Watch Live Sync - Changes made on iPhone companion view (removing sets, swapping exercises) now sync to Watch via `.onChange(of: planVersion)`. Watch Set Navigation - Left/right arrows in the exercise detail view now navigate between sets (back/forward) instead of between exercises. Watch Design System Migration - Transferred the iPhone glass design system (glassCard, glassInset, GRSpacing, GRRadius) to the entire Watch app. - watchOS 26: Native Liquid Glass support via `.glassEffect()`. External Workout → Training Plan - "Extra workout" button now adds the external workout as a completed session to today's training day (previously only dismissed). Week Overview — Improved Info Density - Completed sessions now show the session type icon (run, strength, etc.) instead of a generic checkmark. - Green ring + checkmark badge replaces solid green fill, keeping the workout type visible. - Short type label shown below each day circle (Run, Strength, Cond., etc.). **Build 10** · 29 April 2026 Performance & Visual Refinements - Massively improved scroll performance: reduced GlassBackground from 5 compositing layers to 1 (dark) or 2 (light). - Dark mode cards are now opaque dark surfaces — no blur, no shadow, subtle 0.5pt border. - Light mode cards use translucent material with soft shadow. - iOS 26+: Cards automatically use Apple's native Liquid Glass (`.glassEffect()`). - Stopped MeshGradient background animation — static gradient remains, no more continuous 60fps GPU redraws. Race Planner — Stations Only Autofill - "Calculate Race Plan" now asks whether to adjust stations only (keeping your pace) or recalculate everything. - Fixed critical bug: "Stations Only" was producing wrong totals (e.g. 1:16 instead of 1:10) because roxzone was counted once instead of twice per station. - If the target time is impossible with the current pace, a clear warning explains why and offers to adjust everything instead. Feedback Prompt - One-time feedback prompt appears after 7 days of app usage, linking to the feedback portal. Training Plan Reactivity - Added `planVersion` counter to `TrainingPlanManager` — views now react instantly to structural changes without expensive deep-equality checks. Watch Sync - Stale workout plan transfers are now cancelled before sending a new one, so the Watch always receives the latest version. **Build 6** · 28 April 2026 Glass Design System - Introduced Liquid Glass visual design across the entire app: material blur + tint overlay + specular highlight + hairline border + shadow on all cards. - Three glass tiers: `.glassCard()` (standard cards), `.glassInset()` (smaller elements), `.glassHero()` (hero cards). - Added MeshGradient animated background (`GRScreen`) on all main views (iOS 18+, radial fallback for older). - Replaced ~80+ `Color.surfaceCard` references with glass materials across 25+ files. - Transparent navigation bars on all views with `.toolbarBackground(.hidden)`. - Monospaced digit formatting (`.monospacedDigit()`) on all numeric displays (~40 instances). - Accent glow effect on primary action buttons. - Glass-styled "Start on iPhone" / "Start on Watch" buttons with gradient + border. - Glass-styled secondary action buttons (Move, Complete, Skip) with ultra-thin material. - Glass-styled share mode pills and Share Progress button. Minimizable Dashboard Cards - Cards on the Today tab can now be minimized via a subtle minus button in the top-right corner. - Minimized cards appear as compact 3-column mini tiles at the bottom of the page. - Tap a mini tile to restore the card to its original position. - "Restore All" button to bring back all minimized cards at once. - 11 Today cards are minimizable: Races, Readiness, Recommendation, Training Load, Weekly Digest, Activity, Workout, Stats, Share, Badges, Fitness Trend. - 29 Analyse sections are minimizable (19 Race Analysis + 10 Training). - Minimized state persists across app restarts via UserDefaults. Bug Fix - Fixed scrolling on Today tab: removed `PressableScale` gesture from `CardStyle` that was blocking ScrollView interaction on all cards. Localization - Replaced all remaining German UI strings with English translations across TrainingPlanViews, LogPastRaceView, and Watch TrainingViews. **Build 1** · 27 April 2026 Live Companion View (iPhone) - Instant set removal: removing a set in the live workout view on iPhone now updates the UI immediately. Previously it could take until the next Watch update to reflect the change. - Instant exercise swap display: swapping an exercise during a live workout now refreshes the card instantly on iPhone. - Exercise swap synced to Watch: swapping an exercise on iPhone during a live workout now sends the updated blocks to the Watch immediately. Previously the Watch kept the old exercise. - Block removal synced to Watch: removing a block on iPhone during a live workout now sends the change to the Watch. - Live sync uses fresh data: the sync-to-Watch function now reads the latest blocks from the plan instead of using stale cached/annotated blocks. Send to Watch (Workout Plan Transfer) - Fresh blocks on send: "Track on Watch" and "Send to Watch" in the session detail now always send the current version of the workout (with all edits). Previously it sent the version from when the detail sheet was first opened, so adding/removing blocks after opening the sheet was ignored. - Today section preserved: sending a workout to the Watch no longer removes it from the Watch's "TODAY" section. The scheduledDate is now always included when building the WorkoutPlan. - Stale transfer cancellation: when sending the same workout to Watch multiple times, old queued transfers are cancelled so the Watch always receives the latest version.
newApril 26, 2026

1.0.2

GPS Pace Smoothing (iPhone Run Tracker) - Smoothed pace display: current pace now uses a rolling median over the last 15 seconds instead of raw GPS speed values, eliminating erratic jumps (e.g. 4:20 → 12:00) during steady runs. - Speed accuracy filter: only GPS samples with speedAccuracy 3.0 m/s are used. Unreliable readings are discarded. - Pace clamping: values outside 2:00–15:00/km are filtered out to prevent nonsensical display values. Pace Coaching Improvements - Wider tolerance: pace alerts now use 5% of target pace (was fixed 15s). At 5:30/km target, alerts trigger outside ~5:13–5:47/km instead of 5:15–5:45/km. - Less frequent alerts: cooldown between alerts increased from 30s to 60s. - Later start: pace coaching only starts after 400m (was 200m) to allow average pace to stabilize. Apple Watch Native Workout Export - Tighter pace range: SpeedRangeAlert sent to the native Apple Watch Workout app now uses ±5% (was ±10%). At 5:30/km target, the alert zone is ~5:14–5:47/km instead of 5:00–6:06/km. Purchase & Trial - 3-day free trial: the Pro Monthly subscription now includes a 3-day free trial for new users (introductory offer). - Trial badge in paywall: eligible users see a 3-DAY FREE TRIAL label on the monthly subscription button. - Trial eligibility check: the app automatically detects whether the user qualifies for the introductory offer.
improvedApril 26, 2026

Version 1.0.2

**26 April 2026** GPS Pace Smoothing (iPhone Run Tracker) - Smoothed pace display: current pace now uses a rolling median over the last 15 seconds instead of raw GPS speed values, eliminating erratic jumps (e.g. 4:20 → 12:00) during steady runs. - Speed accuracy filter: only GPS samples with speedAccuracy < 3.0 m/s are used. Unreliable readings are discarded. - Pace clamping: values outside 2:00–15:00/km are filtered out to prevent nonsensical display values. Pace Coaching Improvements - Wider tolerance: pace alerts now use ±5% of target pace (was fixed ±15s). At 5:30/km target, alerts trigger outside ~5:13–5:47/km instead of 5:15–5:45/km. - Less frequent alerts: cooldown between alerts increased from 30s to 60s. - Later start: pace coaching only starts after 400m (was 200m) to allow average pace to stabilize. Apple Watch Native Workout Export - Tighter pace range: SpeedRangeAlert sent to the native Apple Watch Workout app now uses ±5% (was ±10%). At 5:30/km target, the alert zone is ~5:14–5:47/km instead of 5:00–6:06/km. Purchase & Trial - 3-day free trial: the Pro Monthly subscription now includes a 3-day free trial for new users (introductory offer). - Trial badge in paywall: eligible users see a "3-DAY FREE TRIAL" label on the monthly subscription button. - Trial eligibility check: the app automatically detects whether the user qualifies for the introductory offer.