<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Liftosaur: Weight Lifting Tracking App - Blog</title>
  <subtitle>Various blogposts about weightlifting, programming or Liftosaur</subtitle>
  <link href="https://www.liftosaur.com/blog/feed.xml" rel="self"/>
  <link href="https://www.liftosaur.com/blog"/>
  <updated>2026-10-09T02:50:33Z</updated>
  <id>https://www.liftosaur.com/blog</id>
  <author>
    <name>Anton Astashov</name>
    <email>info@liftosaur.com</email>
  </author>
  
  <entry>
    <title>How to start weightlifting</title>
    <link href="https://www.liftosaur.com/blog/posts/how-to-start-weightlifting/"/>
    <updated>2021-01-17T00:00:00Z</updated>
    <id>https://www.liftosaur.com/blog/posts/how-to-start-weightlifting/</id>
    <content type="html">&lt;p&gt;&lt;img src=&quot;https://www.liftosaur.com/blog/images/how-to-start-weightlifting-intro.jpg&quot; alt=&quot;Man adding weights to barbell&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Super short and simple starting guide for the folks who want to start weightlifting.&lt;/p&gt;
&lt;p&gt;To get stronger and look better, you essentially need 3 things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lift heavy weights&lt;/li&gt;
&lt;li&gt;Eat well&lt;/li&gt;
&lt;li&gt;Sleep enough&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To get started:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;a href=&quot;https://www.thefitness.wiki/&quot;&gt;thefitness.wiki&lt;/a&gt;, read articles about &lt;a href=&quot;https://thefitness.wiki/muscle-building-101/&quot;&gt;muscle gain&lt;/a&gt; in general and about &lt;a href=&quot;https://thefitness.wiki/improving-your-diet/&quot;&gt;diet&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then, if you have access to gym with &lt;strong&gt;a barbell and a power rack&lt;/strong&gt; (recommended!)&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Read about &lt;a href=&quot;https://thefitness.wiki/routines/r-fitness-basic-beginner-routine/&quot;&gt;Fitness Basic Beginner Routine&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Google for videos how to make the exercises from there with a proper form. Like &lt;a href=&quot;https://www.google.com/search?q=how+to+bench+press+proper+form&quot;&gt;&amp;quot;How to bench press with proper form&amp;quot;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Go to &lt;a href=&quot;https://www.liftosaur.com/&quot;&gt;Liftosaur&lt;/a&gt;, select &amp;quot;Basic Beginner Routine&amp;quot; program, and start training.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you don&#39;t have access to gym with barbell, but at least have &lt;strong&gt;dumbbells of various weight and some sort of bench&lt;/strong&gt;:&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Read about &lt;a href=&quot;https://old.reddit.com/r/Fitness/comments/2e79y4/dumbbell_ppl_proposed_alternative_to_dumbbell/&quot;&gt;Dumbbell PPL&lt;/a&gt; program.&lt;/li&gt;
&lt;li&gt;Google for videos how to make the exercises from there with a proper form. Like &lt;a href=&quot;https://www.google.com/search?q=How+to+dumbbell+chest+press+proper+form&quot;&gt;&amp;quot;How to dumbbell chest press proper form&amp;quot;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Go to &lt;a href=&quot;https://www.liftosaur.com/&quot;&gt;Liftosaur&lt;/a&gt;, select &amp;quot;Dumbbell PPL&amp;quot; program, and start training.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&#39;s it. It&#39;s that simple. Whole initial reading and videos watching would probably take like 1 hour.
And then you&#39;ll spend around 40-60 minutes in gym about 3 times a week.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>How I came to weightlifting</title>
    <link href="https://www.liftosaur.com/blog/posts/how-i-came-to-weightlifting/"/>
    <updated>2021-01-30T00:00:00Z</updated>
    <id>https://www.liftosaur.com/blog/posts/how-i-came-to-weightlifting/</id>
    <content type="html">&lt;div&gt;&lt;img src=&quot;https://www.liftosaur.com/blog/images/how-i-came-to-weightlifting-intro.jpg&quot; width=&quot;100%&quot; alt=&quot;Boots and a plate&quot; /&gt;&lt;/div&gt;
&lt;div class=&quot;subscription&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@victorfreitas?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Victor Freitas&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/fitness?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;The idea of starting to workout was in my head for a while. Everybody says - once you hit your 30s mark, it&#39;s better to start taking care of your body. Otherwise, you may not get to the moment when you could finally enjoy your 401k.&lt;/p&gt;
&lt;p&gt;I also wanted to look better, more fit (and who wouldn&#39;t?). It&#39;s common knowledge that the best way to achieve that is, well, gym!&lt;/p&gt;
&lt;p&gt;The problem was - how to start? Gym seemed intimidating - there&#39;re various machines, there&#39;re dumbbells, kettlebells, barbells - how do I know which to use, how do I know I use them properly?&lt;/p&gt;
&lt;h3&gt;Bodyweight&lt;/h3&gt;
&lt;p&gt;So, I put aside the gym idea and decided to start with bodyweight exercises. I really liked the idea of bodyweight exercises. There was something in the approach you only use your own body to get stronger. Elegance and simplicity of it attracted me.&lt;/p&gt;
&lt;p&gt;I googled a bit and found the book of Mark Lauren - &lt;a href=&quot;https://www.amazon.com/You-Are-Your-Own-Gym/dp/0345528581&quot;&gt;&amp;quot;You Are Your Own Gym&amp;quot;&lt;/a&gt;. It had good reviews on Amazon, and there was a nice phone app that would tell you how to do exercises and what they should be. There were 10-week programs, it all looked fun, so I started working out three times a week, using the phone app that accompanies the book.&lt;/p&gt;
&lt;p&gt;In about a month, my motivation started to go down. I began to skip workouts, and in about one more month I finally quit. I only went til the 8th week.&lt;/p&gt;
&lt;p&gt;The workouts in that app were pretty challenging for me, but the biggest issue was - I wasn&#39;t feeling I was getting stronger. Bodyweight exercises are pretty hard to progress on. The idea is that you progress by switching to more challenging versions of exercises (like you start with incline push-ups, then horizontal push-ups, then decline push-ups, then one-arm push-ups, etc.), but the difficulty bump when you go from one version to another is so big, it&#39;s hard to actually make it.&lt;/p&gt;
&lt;p&gt;At the end of the 8th week, I was pretty disappointed - the workouts didn&#39;t get any easier, I was exhausted after each workout, and I didn&#39;t feel I got stronger. My motivation finally died, and I quit.&lt;/p&gt;
&lt;p&gt;Now I realize two months are not really enough to see any progress, but back then, I was expecting magical transformations in my body, and when I didn&#39;t see them, I got quite frustrated.&lt;/p&gt;
&lt;div class=&quot;highlight-block&quot;&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/how-i-came-to-weightlifting-david.jpg&quot; width=&quot;300&quot; alt=&quot;David Statue&quot; /&gt;
  &lt;div class=&quot;highlight-block-sum&quot;&gt;I thought I&#39;d look at least like this after 2 months&lt;/div&gt;
&lt;/div&gt;
&lt;h3&gt;Weightlifting&lt;/h3&gt;
&lt;p&gt;One year passed. I write software for a living, and I was reading Michael Snoyman&#39;s blog - he mostly writes about the programming language Haskell, but he also had a set of blog posts about &lt;a href=&quot;https://www.snoyman.com/blog/2017/06/why-i-lift&quot;&gt;lifting and health&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I find those kinds of articles very trustworthy - fitness is not what he sells, it&#39;s just his hobby, and he&#39;s also a software engineer, so I find his experience very relatable. After reading those articles, I was very pumped and decided to start weight lifting as well. He recommended &lt;a href=&quot;https://stronglifts.com/5x5/&quot;&gt;Stronglifts 5x5&lt;/a&gt; as a simple weightlifting program to begin with, and I decided to give it a try!&lt;/p&gt;
&lt;p&gt;I read the site, read the exercise descriptions. It all made sense, and it was quite simple! The idea is that you do the so-called 5 &amp;quot;compound&amp;quot; exercises (they work a wide range of muscles of your body) three times a week, increasing weights each day. The exercise descriptions were veeeeery long and very repetitive though, he was repeating the same things and the same principles over and over and over again - no wonder there&#39;re like 20 thousand words per each exercise. But if you google something like &amp;quot;How to do deadlifts&amp;quot; you&#39;ll get some good videos at the top of search results explaining how to do those with proper form within 3-5 minutes.&lt;/p&gt;
&lt;p&gt;Then, I had to choose a gym. I had Anytime Fitness, LA Fitness, and Planet Fitness in 15-minute drive distance around my house, and Planet Fitness was an obvious winner because it was cheap as peanuts - $10 per month! I was going to visit it first, and if it doesn&#39;t work for some reason - visit Anytime Fitness and LA Fitness.&lt;/p&gt;
&lt;p&gt;Planet Fitness surprised me quite a lot. The gym itself was huge, it got all those fancy machines, but there was no single barbell! No power racks, no squat racks. Even no dumbbells. But rows and rows of empty treadmills, I think five smith machines, some other weird-looking equipment... Stronglifts program required a power rack and a barbell, so I had to pass on it.&lt;/p&gt;
&lt;div class=&quot;highlight-block&quot;&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/how-i-came-to-weightlifting-planetfitness.jpg&quot; width=&quot;600&quot; alt=&quot;Photo of Planet Fitness&quot; /&gt;
  &lt;div class=&quot;highlight-block-sum&quot;&gt;Seriously, why so many treadmills?!&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;LA Fitness was even bigger. It was a massive 2-story building with a pool, basketball court, tens of treadmills again, and two power racks with barbells, with a line of 3 people for each... The chance of staying in line for bench presses like it was in 2010 for iPhone 4 was not super exciting, so that was a pass too.&lt;/p&gt;
&lt;p&gt;Anytime Fitness was a cozy small gym with not-so-cozy pricing. There was one squat rack and one power rack though, two barbells, a rich set of dumbbells. And the main thing - there were no people. I was alone. The whole gym was empty. At 5pm. On Tuesday. That means it&#39;s empty pretty much all the time. That was a big deal for me - to ensure I won&#39;t lose any time staying in line and waiting while somebody is hard working on their curls staying in a rack. I got my wallet and subscribed for a year.&lt;/p&gt;
&lt;p&gt;I started to visit the gym three times a week, strictly following the Stronglifts program. It was definitely way more fun than &amp;quot;You are your own gym&amp;quot;. It was maybe too easy first (since you literally start with an empty barbell), but that would give me time to adjust, get into the habit of visiting the gym, work on proper form. But the most fun part was that I felt progress. At first - every day, then maybe every week, or even every couple of weeks, but my numbers went up and up. I saw that upwards graph in the Stronglifts phone app. Each time the weight I lift goes up, it feels like a little win, and it gives me that endorphin shot. This is addictive. I find it very hard to keep myself motivated for long-term goals - like I will look great if I keep doing this for the next 2 years regularly. I mean - yes, probably, but if not - I&#39;ll have hundreds of hours spent on nothing. And that suspicion eats you. But here - you successfully completed your 5x5 of bench press - done, you got your weight bump for the next workout - you feel a win. And it&#39;s awesome.&lt;/p&gt;
&lt;p&gt;In about three months, I hit my first deload (when you have to reduce the weight on a barbell because you couldn&#39;t accomplish 5 sets of 5 reps for an exercise). But still, I was able to progress. In about three months more, I hit a plateau and just kept deloading and getting to the same level, not being able to increase weight anymore.&lt;/p&gt;
&lt;p&gt;At that time, I was at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;185lb squat 5x5&lt;/li&gt;
&lt;li&gt;150lb bench press 5x5&lt;/li&gt;
&lt;li&gt;105lb overhead press 5x5&lt;/li&gt;
&lt;li&gt;130lb barbell row 5x5&lt;/li&gt;
&lt;li&gt;260lb deadlift 1x5&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By that time, I was already reading a lot of articles related to weightlifting, was hanging out on &lt;a href=&quot;https://www.reddit.com/r/fitness&quot;&gt;/r/fitness&lt;/a&gt;, and found probably the best introduction into weightlifting - &lt;a href=&quot;https://www.thefitness.wiki/&quot;&gt;thefitness.wiki&lt;/a&gt;. I really like how short and to-the-point is all the material there. On Reddit, people mostly criticized Stronglifts 5x5 for being strongly skewed towards squats, and at the same time - praised 5/3/1 programs by Jim Wendler for being a balanced program. thefitness.wiki recommends 5/3/1 for Beginners as a starter, so I picked it up.&lt;/p&gt;
&lt;p&gt;It worked great! I liked that the pace is slower, but you steadily keep increasing weights every three weeks instead. I got used to Stronglifts 5x5 app though, it was simple yet very convenient - it increased or decreased the weights when appropriate, had all the necessary functionality. But it was designed specifically for Stronglifts 5x5, and you can&#39;t adjust it to any other program.&lt;/p&gt;
&lt;h3&gt;Liftosaur&lt;/h3&gt;
&lt;p&gt;I wanted to find an app I could use for a long time, even if I change programs. I wanted to be able to tweak programs if necessary and couldn&#39;t find anything like that. Other apps were either designed for one specific routine (GZCLP, 5/3/1, etc.) or had a pack of popular routines, but none gave full flexibility in program editing. And it makes sense - to make it possible, you have to have full-grown, Turing-complete programming language built-in. I could do that. I&#39;m a software engineer, so I can write an app with a built-in specialized programming language that allows you to express any possible weightlifting program. It will be shipped with popular weightlifting programs and will provide UI to make simple modifications in the routines. But, if you want to create something entirely new and nothing like before, you can always get to the lowest level and use the scripting language - Liftoscript!&lt;/p&gt;
&lt;p&gt;After about six months of building it during my spare time, it resulted in &lt;a href=&quot;https://www.liftosaur.com/about&quot;&gt;Liftosaur&lt;/a&gt;! I use it for tracking my lifts since April 2020, and it goes great (though I&#39;m obviously biased). It also motivates me to keep working out since I could think of some feature, then implement it in the app, and then try it out during my own workouts.&lt;/p&gt;
&lt;p&gt;I now have a way better picture of what it takes to start weightlifting, and if I started today, I&#39;d probably start with the &lt;a href=&quot;https://thefitness.wiki/routines/r-fitness-basic-beginner-routine/&quot;&gt;Basic Beginner Routine&lt;/a&gt; and would follow the &lt;a href=&quot;https://thefitness.wiki/&quot;&gt;thefitness.wiki&lt;/a&gt; advice to switch to 5/3/1 for Beginners or GZCLP after three months (I wrote about it in my &lt;a href=&quot;https://www.liftosaur.com/blog/posts/how-to-start-weightlifting/&quot;&gt;last blogpost&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Lifting weights is super fun. You feel powerful when you lift heavy weights, and you get your little wins pretty often. It&#39;s fun to chase numbers (the weight you lift, the reps you could do, etc.). There&#39;re many health benefits too, but it&#39;s hard to quantify them, and they look more like long-term goals, which I have a hard time to really care about. And you&#39;re looking better and better in the process (though that transformation is very slow, it takes years to really see the difference, so again - more like a long-term thing. If you see somebody sells you a three weeks program promising you&#39;ll look better at the end - it&#39;s a scam, you won&#39;t).&lt;/p&gt;
&lt;p&gt;So, here I am, 1.5 years in for now, and hopefully, it&#39;s just a start.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Quirks of building a PWA app</title>
    <link href="https://www.liftosaur.com/blog/posts/quirks-of-building-a-pwa-app/"/>
    <updated>2021-02-05T00:00:00Z</updated>
    <id>https://www.liftosaur.com/blog/posts/quirks-of-building-a-pwa-app/</id>
    <content type="html">&lt;p&gt;&lt;img src=&quot;https://www.liftosaur.com/blog/images/quirks-of-building-a-pwa-app-pwa.png&quot; alt=&quot;PWA Logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I wanted to build a weightlifting tracker app that would be like Stronglifts 5x5, but flexible, so you could build any program in there or modify any program for your needs. And it&#39;d still be a user-friendly, pleasant to use app. And it&#39;d have all the features you expect from that kind of app - progress view, graphs, rest timer, plates calculator, etc.&lt;/p&gt;
&lt;p&gt;I write web apps for a living. I&#39;m not a mobile developer. I knew that you could write web apps for mobile phones (they&#39;re called PWA - Progressive Web Apps), and then you can install them without App Store or Google Play, and theoretically, they&#39;ll be exactly like native apps.&lt;/p&gt;
&lt;p&gt;There&#39;s also something cool, from idealistic point of view, in writing web apps and running them on phones (vs native apps). Web a is free and decentralized platform, I don&#39;t need to rely on Apple Store or Google Play rules, web apps exist kinda in parallel with the Apple/Google gate-keeped stores, and don&#39;t have to play by its rules. Also, the update cycles are way faster - I can just push the new version out, and it will be immediately delivered to users&#39; devices, I don&#39;t need to wait for a week until my new app version is reviewed by Apple or Google. It&#39;s not like the main reason why I wrote PWA, though - the main reason is that I&#39;m just very comfortable with building web apps, I know how to do it. I&#39;m way less comfortable with building native apps.&lt;/p&gt;
&lt;p&gt;But I also heard that you would never reach the same user experience as with native apps, though I wasn&#39;t sure why. What exactly would prevent that? I was curious to know, I thought - I&#39;m going to write this app as a PWA specifically targeted for mobile devices and experience the development process, see what road bumps I could hit, and how far I could go before I hit a wall.&lt;/p&gt;
&lt;p&gt;And here I am, nine months later, with a working PWA app, which is already pretty feature-rich, and I think at this point I can reflect on the development process and share my thoughts about PWAs and what it takes to develop them.&lt;/p&gt;
&lt;h2&gt;What the heck is PWA?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.liftosaur.com/about&quot;&gt;Liftosaur&lt;/a&gt; is a PWA. What that really means is that it has a &lt;a href=&quot;https://www.liftosaur.com/manifest.webmanifest&quot;&gt;manifest file&lt;/a&gt; and &lt;a href=&quot;https://www.liftosaur.com/webpushr-sw.js&quot;&gt;a script&lt;/a&gt;, that runs as a Service Worker - special JavaScript code, that is run by a browser separately from a web page, in a background. We add a link to our manifest file in &lt;code&gt;index.html&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;manifest&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/manifest.webmanifest&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and then we load that service worker script like this:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/webpushr-sw.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and voila! Our web page just became a Progressive Web App!&lt;/p&gt;
&lt;p&gt;The manifest file describes how your app will look like mostly on phones - what icons would be used, whether it&#39;s full screen or with a URL bar, title/description, what splash screen looks like, etc.&lt;/p&gt;
&lt;p&gt;The service worker script currently mostly solves two problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Caches all the scripts, styles, and some API calls so that the app would work offline.&lt;/li&gt;
&lt;li&gt;Sets up Web Push mechanism, so you&#39;d get pushes when the timer is triggered, and it&#39;s time to start another exercise set.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And that&#39;s mostly it! After that, you can add that web page to your phone home screen, and it&#39;ll like a native web app.&lt;/p&gt;
&lt;div class=&quot;highlight-block highlight-block-flex&quot; style=&quot;padding-left: 3rem; padding-right: 3rem&quot;&gt;
  &lt;div class=&quot;highlight-img-flex&quot;&gt;
    &lt;img src=&quot;https://www.liftosaur.com/blog/images/quirks-of-building-a-pwa-app-home-screen.jpg&quot; width=&quot;100%&quot; alt=&quot;iOS Home Screen with PWA icon&quot; /&gt;
    &lt;div class=&quot;highlight-block-sum&quot;&gt;This is what it looks like on the home screen (last icon)&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;highlight-img-flex&quot;&gt;
    &lt;img src=&quot;https://www.liftosaur.com/blog/images/quirks-of-building-a-pwa-app-liftosaur.jpg&quot; width=&quot;100%&quot; alt=&quot;Liftosaur Screen on iOS&quot; /&gt;
    &lt;div class=&quot;highlight-block-sum&quot;&gt;This is what PWA app looks like on iOS&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;But there are various problems associated with PWAs, related mainly to iOS Safari quirks and users&#39; knowledge about PWAs.&lt;/p&gt;
&lt;h2&gt;Pain points&lt;/h2&gt;
&lt;p&gt;Mostly, my experience with building PWA - you can build decent PWA apps, though Mobile Safari definitely will try to make your life harder. I didn&#39;t really have any problems with Google Chrome on Android; the app works smoothly even on a cheap Android device. The major pain points were:&lt;/p&gt;
&lt;h3&gt;Cookies are weird on iOS when you add your app to the home screen&lt;/h3&gt;
&lt;p&gt;I initially set the app so that &lt;code&gt;index.html&lt;/code&gt; and all other assets were loaded from &lt;code&gt;www.liftosaur.com&lt;/code&gt; and served via Cloudflare CDN. API lived in Cloudflare workers on a separate domain, which used to be &lt;code&gt;liftosaur.workers.dev&lt;/code&gt;. Some API expects the user to be logged in, so I had &lt;code&gt;liftosaur.workers.dev/api/signin/google&lt;/code&gt;, which sets up a JWT auth cookie on the &lt;code&gt;liftosaur.workers.dev&lt;/code&gt; domain. I don&#39;t need those cookies for the primary domain &lt;code&gt;www.liftosaur.com&lt;/code&gt; (they&#39;re HttpOnly anyway, and I use them for API calls only). I thought it&#39;d be fine - the cookies would live only on &lt;code&gt;liftosaur.workers.dev&lt;/code&gt;, the browser would send them to all API endpoints (since they&#39;re also on &lt;code&gt;liftosaur.workers.dev&lt;/code&gt;), and everything would just work. And it worked, almost everywhere - it worked in Mobile Safari browser, it worked in Chrome on Android. But if you add the app to the home screen on iOS, then for some reason, those cookies wouldn&#39;t be stored. Like, at all. I finally found a workaround - I stored the cookies with the &lt;code&gt;.liftosaur.com&lt;/code&gt; domain and moved the API from &lt;code&gt;liftosaur.workers.dev&lt;/code&gt; to &lt;code&gt;api.liftosaur.com&lt;/code&gt;, so they&#39;d share the same cookies with &lt;code&gt;www.liftosaur.com&lt;/code&gt;. But it took me quite a while to figure that out.&lt;/p&gt;
&lt;h3&gt;No web push notifications for iOS&lt;/h3&gt;
&lt;p&gt;There&#39;s a feature in the app - after you&#39;ve finished a set, you press a button, and it starts a timer. Once timer hits some threshold, it sends a push notification to your phone, so you know it&#39;s time to start another set. Apparently, there&#39;s no way to do that on iOS. I couldn&#39;t find a way to send a push notification in iOS, at all. It seems like a possible solution would be to install a &amp;quot;helper&amp;quot; native app that would proxy the push notification, but that seems excessive - I really doubt any user would want to do that!&lt;/p&gt;
&lt;h3&gt;You can trigger a sound only on user action&lt;/h3&gt;
&lt;p&gt;When the timer mentioned above hits a threshold, it plays a sound, so you know - it&#39;s time to start another set. I couldn&#39;t figure out how to do that on iOS. Safari on iOS only allows you to play a sound in e.g. an event click handler. If you try to play it on some random event happening (like for me, it was a Redux action triggering it), Safari complains about audio autoplay being forbidden. There&#39;re some solutions on the Internet/Stack Overflow, but none of them works. Apparently, if a user is done with the set and hits a button, you can start &lt;code&gt;setTimeout&lt;/code&gt; and try to play the audio inside that &lt;code&gt;setTimeout&lt;/code&gt;. It works, but only for small enough timeouts (like several seconds). Anything over a minute - and you get that audio autoplay forbidden error again.&lt;/p&gt;
&lt;p&gt;I couldn&#39;t figure out that too for iOS. Liftosaur on iOS doesn&#39;t send push notifications when timers hit a threshold, and also it doesn&#39;t make a sound when it happens :(&lt;/p&gt;
&lt;h3&gt;iOS could be pretty aggressive unloading from memory the opened PWA apps that are added to the home screen&lt;/h3&gt;
&lt;p&gt;Imagine you just finished a set, and you switch to Twitter to read some stuff there while resting. A couple of minutes later, you switch back to Liftosaur - and iOS already unloaded it from memory, so Liftosaur loads again. So you need to make sure your app launches fast, and also you store as much internal state as possible in IndexedDB. Otherwise, users may have a horrible experience losing all their progress.&lt;/p&gt;
&lt;p&gt;To be fair, it got better after iOS 13, and it&#39;s not as aggressive with unloading PWAs from memory anymore, but it still happens sometimes.&lt;/p&gt;
&lt;h3&gt;Missing some default gestures&lt;/h3&gt;
&lt;p&gt;There&#39;re some gestures built-in into any native app. Like swipe left to right across the screen gets you back to the previous page. Obviously you don&#39;t get them for PWAs, since it&#39;s just a web page.&lt;/p&gt;
&lt;p&gt;It&#39;s not that big of a deal to implement those, but you have to implement those, and on a native app, you get them for free.&lt;/p&gt;
&lt;p&gt;I personally don&#39;t use gestures much on my phone apps, but I can see how it could be a UX issue for some users.&lt;/p&gt;
&lt;h3&gt;Choose your app icon wisely because it will stay forever&lt;/h3&gt;
&lt;p&gt;If you add a PWA app to your home screen, whatever the app icon was, it will be there forever. Even if you update the manifest with the new icon, the icon won&#39;t change on the home screen. Not that big of a deal, but it definitely was an unpleasant surprise.&lt;/p&gt;
&lt;h3&gt;Users have no idea what the heck PWA is&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;And here&#39;s our winner!!!&lt;/strong&gt; The major pain point of PWA is the users&#39; knowledge. They don&#39;t know that a web page they&#39;re on is a web app they can install to their home screen. They don&#39;t know how to do that (and frankly, iOS Safari doesn&#39;t even try to make it obvious). They often even never heard of PWAs at all. They don&#39;t know PWAs exist and they can add a web app to their home screen.&lt;/p&gt;
&lt;div class=&quot;highlight-block highlight-block-flex&quot; style=&quot;padding-left: 3rem; padding-right: 3rem&quot;&gt;
  &lt;div class=&quot;highlight-img-flex&quot;&gt;
    &lt;img src=&quot;https://www.liftosaur.com/blog/images/quirks-of-building-a-pwa-app-ios-share.png&quot; width=&quot;100%&quot; alt=&quot;Liftosaur Screen with share icon&quot; /&gt;
    &lt;div class=&quot;highlight-block-sum&quot;&gt;To add to home screen on iOS, you need to Share (!) first&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;highlight-img-flex&quot;&gt;
    &lt;img src=&quot;https://www.liftosaur.com/blog/images/quirks-of-building-a-pwa-app-ios-add-to-home.png&quot; width=&quot;100%&quot; alt=&quot;Share screen with &#39;Add to Home Screen&#39; option highlighted&quot; /&gt;
    &lt;div class=&quot;highlight-block-sum&quot;&gt;And there find the option &quot;Add to Home Screen&quot;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;So, you need to deliver the installation instructions to your users somehow. Since there&#39;s a good chance they&#39;ve never installed a PWA previously in their life, there&#39;re high chances that this PWA will be their first one, and they are going to figure out how to do that.&lt;/p&gt;
&lt;p&gt;It is already hard to convince somebody to install an app. Convince somebody to install a web app is ten times harder if they never did that, and they don&#39;t know it&#39;s possible.&lt;/p&gt;
&lt;p&gt;To be fair, if you use a web app several times on Android Chrome, and it&#39;s a PWA, Chrome will suggest adding it to the home screen once it notices you use the app often. iOS Safari will not.&lt;/p&gt;
&lt;p&gt;In my opinion, this is the worst pain point of PWA. Technology-wise, and especially on Android - you can make very decent phone apps, and get the user experience very close to native apps. But the people aspect - educating users about PWAs and how to install them - is way harder.&lt;/p&gt;
&lt;p&gt;Here&#39;s a bright side, though - Google Play allows you to publish your PWAs right in the store. It&#39;s called &lt;a href=&quot;https://developers.google.com/web/android/trusted-web-activity&quot;&gt;TWA (Trusted Web Activity)&lt;/a&gt;, and that makes the PWA installation process look exactly like a native app - you can find it in the Play Store, click Install, and it will get installed, just like a regular app. Obviously - nothing like that exists on iOS.&lt;/p&gt;
&lt;div class=&quot;highlight-block&quot;&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/quirks-of-building-a-pwa-app-google-play.png&quot; width=&quot;100%&quot; alt=&quot;Liftosaur in Google Play&quot; /&gt;
  &lt;div class=&quot;highlight-block-sum&quot;&gt;PWA in Google Play Store&lt;/div&gt;
&lt;/div&gt;To build TWA, you&#39;ll need to ensure you get maximum PWA points in Lighthouse, and then you can use &lt;a href=&quot;https://www.pwabuilder.com/&quot;&gt;PWABuilder&lt;/a&gt;, that will generate Android APK, which you can upload to Google Play Store.&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;You definitely can build quality PWAs. You usually won&#39;t have any problems on Android, and you&#39;ll need to jump through some hoops for iOS. Some things are impossible to do on iOS at all. And be prepared that users don&#39;t know anything about PWAs, so you&#39;d need to educate them about it somehow - that means your marketing will be harder.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Offline mode in Liftosaur</title>
    <link href="https://www.liftosaur.com/blog/posts/offline-mode-in-liftosaur/"/>
    <updated>2021-03-04T00:00:00Z</updated>
    <id>https://www.liftosaur.com/blog/posts/offline-mode-in-liftosaur/</id>
    <content type="html">&lt;p&gt;&lt;img src=&quot;https://www.liftosaur.com/blog/images/offline-mode-in-liftosaur-no-internet.png&quot; alt=&quot;No Internet in Chrome&quot; /&gt;&lt;/p&gt;
&lt;p&gt;One of the most significant benefits of PWA apps is enabling offline mode for your apps. You can set up caching in a service worker, and then if there&#39;s no Internet, the service worker returns the bundles and maybe even API responses from the cache. There&#39;re tons of tutorials on how to set it up, but caching is just the tip of an iceberg. You have to structure your whole app around offline mode if you really want to get a seamless experience without the Internet. That&#39;s hard.&lt;/p&gt;
&lt;p&gt;Here I&#39;d like to share how I did offline mode for Liftosaur. It&#39;s not perfect, and it still has its flaws. But it mostly works fine, only occasionally maybe possibly breaking user experience.&lt;/p&gt;
&lt;h2&gt;What do we need to do to make it work offline&lt;/h2&gt;
&lt;p&gt;Basically two things.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We need to cache bundles (HTML, CSS, and JS), so if there&#39;s no Internet, we could fetch them from the cache.&lt;/li&gt;
&lt;li&gt;We need to sync our in-memory part of the state we want to preserve to some local storage and optionally sync it to the cloud when the Internet is available.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&#39;s about it. But there&#39;re caveats.&lt;/p&gt;
&lt;h2&gt;Caching bundles&lt;/h2&gt;
&lt;p&gt;We can use a service worker for caching bundles, intercept all the network calls from there, and cache the bundles and maybe the API calls we care about. There&#39;re a million tutorials on the Internet on how to do that. It&#39;s probably the most popular usage of service workers.&lt;/p&gt;
&lt;p&gt;For initial load, Liftosaur loads its bundles from the Internet and caches them. They are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;index.html&lt;/strong&gt; - the main page, where we render our whole app.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;main.js/css&lt;/strong&gt; - the main bundle with all the logic included and styles for it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;vendor.js/css&lt;/strong&gt; - all third-party libraries: CodeMirror, Preact, io-ts, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those are the files that are absolutely necessary for the working app. There&#39;re also others - Webpushr for push notifications, images of muscles and exercises, Rollbar for error collecting, and so on. If they don&#39;t load - the app works without them with some degraded functionality, some of them we cache anyway, but they&#39;re not that important.&lt;/p&gt;
&lt;p&gt;It&#39;s a good idea to versionize your bundles, so you could cache them forever, and not be afraid of accidentally serving stale versions of bundles. I.e. you load the bundles from your &lt;code&gt;index.html&lt;/code&gt; like:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;main.css?version=123&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text/css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;main.js?version=123&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we store our bundles on Amazon S3, it ignores the querystring params when returning the stored objects, so &lt;code&gt;?version=123&lt;/code&gt; is ignored. But it&#39;s not ignored by CDN, browser cache, and the service worker cache. When we bump the version to &lt;code&gt;124&lt;/code&gt;, it misses the cache both on CDN, browser, and the service worker and returns the actual version of the bundle.&lt;/p&gt;
&lt;p&gt;For a version value we simply use a Git commit hash. In Liftosaur we do that via Webpack&#39;s &lt;code&gt;DefinePlugin&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// webpack.config.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; DefinePlugin &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;webpack&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; commitHash &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;child_process&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;execSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;git rev-parse --short HEAD&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// ... various webpack settings&lt;/span&gt;&lt;br /&gt;  plugins&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DefinePlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      __COMMIT_HASH__&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;commitHash&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CopyPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;src/index.html&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        to&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;index.html&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function-variable function&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;\?version=xxxxxxxx&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;?version=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;commitHash&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, this way, we&#39;ll add the &lt;code&gt;__COMMIT_HASH__&lt;/code&gt; variable to the bundle. When we copy &lt;code&gt;index.html&lt;/code&gt; to the &lt;code&gt;dist&lt;/code&gt; directory, we also replace &lt;code&gt;?version=xxxxxxxx&lt;/code&gt; with proper git commit hash.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;index.html&lt;/code&gt;, we import the bundles accordingly:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;main.css?version=xxxxxxxx&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text/css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;main.js?version=xxxxxxxx&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the service worker, we need to set up fetching the bundles, index.html, and handle caching. The logic for caching the bundles and index.html is different:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For bundles, since they&#39;re versioned, we can cache them forever. I&#39;ll just need to remove the old versions from the cache from time to time&lt;/li&gt;
&lt;li&gt;For index.html, we don&#39;t need to return the cached version of it. We need to ensure the user gets the freshest version of it all the time. Unless the user is offline. In that case - return the cached version of index.html.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, the service worker looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// sw.ts&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// __COMMIT_HASH__ will be filled in by a webpack bundler&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; __COMMIT_HASH__&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cacheName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;liftosaur-sw-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;__COMMIT_HASH__&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; urlsToCache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/main.css?version=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;__COMMIT_HASH__&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/main.js?version=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;__COMMIT_HASH__&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/vendors.css?vendor=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;__COMMIT_HASH__&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/vendors.js?vendor=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;__COMMIT_HASH__&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;/index.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cacheRequestAndResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; caches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cacheName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestAndCacheAllUrls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; caches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cacheName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;urlsToCache&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Fetches all the time, caches it, but never returns&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// the cached version if user is online&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleFetchingIndexHtml&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Response&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cachedResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; caches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cacheRequestAndResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cachedResponse &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cachedResponse&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Checks cache first, if there&#39;s no cached response -&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// fetches the request and caches it&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleFetchingEverythingElse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Response&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cachedResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; caches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cachedResponse&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cachedResponse&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// If the url is one of the urls to cache -&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// cache request and response&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isCacheableRequest &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; urlsToCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;search&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GET&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; isCacheableRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cacheRequestAndResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// When the browser installs the service worker for the&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// first time, we&#39;ll request all the bundles and cache them&lt;/span&gt;&lt;br /&gt;service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;install&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;waitUntil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAndCacheAllUrls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// We intercept all fetch requests from a browser,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// and apply our logic to handling those&lt;/span&gt;&lt;br /&gt;service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fetch&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isIndexHtml &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;index.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GET&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; isIndexHtml&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;respondWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleFetchingIndexHtml&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;respondWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleFetchingEverythingElse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Every times service workers activates -&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// check for old caches and remove them&lt;/span&gt;&lt;br /&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;activate&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; allKeys &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; caches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; keysToRemove &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; allKeys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;k&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; k &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; cacheName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; key &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; keys&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; caches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is a rather long code listing, but it basically implements the logic we described above for bundles and for &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then we need to make sure to set proper &lt;code&gt;Cache-Control&lt;/code&gt; headers. Since the versioned bundles are immutable, we can safely set the expiration time to a year. So, all the versioned bundles (&lt;code&gt;main.js&lt;/code&gt;, &lt;code&gt;main.css&lt;/code&gt;, &lt;code&gt;vendors.js&lt;/code&gt;, etc) are served with &lt;code&gt;Cache-Control: public, max-age=31536000, immutable&lt;/code&gt;. For &lt;code&gt;index.html&lt;/code&gt; though, we need to ensure it&#39;s always fresh, so we set &lt;code&gt;Cache-Control: max-age=0&lt;/code&gt; for it.&lt;/p&gt;
&lt;p&gt;&lt;object data=&quot;https://www.liftosaur.com/blog/images/offline-mode-in-liftosaur-assets.svg&quot; style=&quot;width: 100%&quot; type=&quot;image/svg+xml&quot;&gt;&lt;/object&gt;&lt;/p&gt;
&lt;p&gt;This covers the part when we serve the app&#39;s code, even when the user is offline. But we also need to handle the local state part.&lt;/p&gt;
&lt;h2&gt;Adding local state&lt;/h2&gt;
&lt;p&gt;When a user interacts with the app (for example, progresses through the workout day), we update the global state in memory and sync the changes to the server. When the user refreshes the page, we fetch the state from the server. This way, the user&#39;s data won&#39;t be lost.&lt;/p&gt;
&lt;p&gt;In case there&#39;s no Internet, there&#39;s a chance user could lose the data because we can&#39;t sync it to the server. It makes sense to sync it to some local storage too, so that if there&#39;s no Internet, the user won&#39;t lose the data. It also helps with data privacy - if the user is not logged in, no data is stored in the cloud, everything lives just on their device.&lt;/p&gt;
&lt;p&gt;But now we have two problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The code may change, and it&#39;s expectations of the state shape also may change. That can lead to runtime exceptions when we try to load local or cloud data.&lt;/li&gt;
&lt;li&gt;Local storage data and cloud storage data may change independently. For example, user may use the app on the phone without the Internet. Then they come home, open the app on the laptop, and use it there, then open the app on the phone again. At that moment, we&#39;ll load the data from the server, and if we blindly rewrite the local storage with cloud storage, the user may lose data. We practically have distributed system here now and should figure out how to resolve merge conflicts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Database migrations&lt;/h3&gt;
&lt;p&gt;Let&#39;s talk about the solution to the first problem - changing code expectations of the state shape.&lt;/p&gt;
&lt;p&gt;The codebase and the state are not synced. We can often get an older local state with the newer codebase. For example when there&#39;s a new feature released, the codebase may start to expect a different state shape. Like, we used to specify weight as a regular number. Then we added a new feature for switching between kilograms and pounds, so now we store the weight as &lt;code&gt;{value: number, units: &#39;kg&#39; | &#39;lb&#39;}&lt;/code&gt;. But the local state and the cloud state of our users still have the weights stored as numbers! The user gets a new bundle, it expects the weight to be stored as an object, and that mismatch causes an exception. That easily can crash our app during the boot time, causing a white screen, a completely broken app, and frustrated users.&lt;/p&gt;
&lt;p&gt;Humanity figured a solution for this problem - the database migrations.&lt;/p&gt;
&lt;p&gt;I learned it when I was working with Ruby on Rails, and it&#39;s an excellent concept. Sometimes when you make modifications in the codebase, the expectations of the data store changes. Like, in some database table, you had a field &lt;code&gt;name&lt;/code&gt;, which was a string, but then you decide that you should have &lt;code&gt;first_name&lt;/code&gt; and &lt;code&gt;last_name&lt;/code&gt; stored separately instead and updated the codebase for that. After that, you create a migration that has a version and the executable code to convert your table to the new state (with two separate name fields), and also you define how to rollback (i.e., switch back from two separate name fields to one). It could look like this:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;migration&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;ALTER TABLE users ADD first_name varchar(255)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;ALTER TABLE users ADD last_name varchar(255)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;UPDATE users SET first_name = name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;ALTER TABLE users DROP COLUMN name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;rollback&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;ALTER TABLE users ADD name varchar(255)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;UPDATE users SET name = first_name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;ALTER TABLE users DROP COLUMN first_name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;ALTER TABLE users DROP COLUMN last_name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Version is a monotonically increasing number.&lt;/p&gt;
&lt;p&gt;The database stores its current version. When you try to run migrations, it will run them in the order defined by its versions. For example, the current version is 2, and you defined new migrations with versions 3 and 4. So, when you run migrations, it applies migrations 3 and 4.&lt;/p&gt;
&lt;p&gt;You can also rollback the migrations and run rollbacks in reverse order. Usually, it&#39;s necessary when you published code that broke some functionality, and you want to revert changes. Then, you deploy the previous version of the code and run rollbacks in reverse order - first 4, then 3.&lt;/p&gt;
&lt;p&gt;In a Ruby on Rails app, the process is usually the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You change the codebase&lt;/li&gt;
&lt;li&gt;If the codebase&#39;s state shape expectations changed, you add migrations&lt;/li&gt;
&lt;li&gt;You deploy the app&lt;/li&gt;
&lt;li&gt;You run migrations&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Local state migrations&lt;/h3&gt;
&lt;p&gt;In our case, things are somewhat more complicated. We do have continuous access to the databases, but we don&#39;t have access to the user&#39;s local storage state. So, we can&#39;t run migrations on the user&#39;s local storage state at an arbitrary point in time.&lt;/p&gt;
&lt;p&gt;As a solution, we can move the migrations to the client code - to the JS bundles. And then, when a user opens an app, we will:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get the local storage state, and run migrations on it.&lt;/li&gt;
&lt;li&gt;Fetch the storage state from the cloud, and run migrations on it.&lt;/li&gt;
&lt;li&gt;Merge the cloud state into the local storage state.&lt;/li&gt;
&lt;li&gt;Upload the updated, merged state back to the cloud and to local storage.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;object data=&quot;https://www.liftosaur.com/blog/images/offline-mode-in-liftosaur-migration-diagram.svg&quot; style=&quot;width: 100%&quot; type=&quot;image/svg+xml&quot;&gt;&lt;/object&gt;&lt;/p&gt;
&lt;p&gt;For the server-side generated pages (for example, the shared workout results or the public profile page), the algorithm would be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fetch the data from the database&lt;/li&gt;
&lt;li&gt;Apply migrations&lt;/li&gt;
&lt;li&gt;Render the page, and for hydration - send the migrated data to the client inlined as JSON in the HTML.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;object data=&quot;https://www.liftosaur.com/blog/images/offline-mode-in-liftosaur-migration-server-diagram.svg&quot; style=&quot;width: 100%&quot; type=&quot;image/svg+xml&quot;&gt;&lt;/object&gt;&lt;/p&gt;
&lt;p&gt;I like to handle the local storage as a main source of truth and don&#39;t try to migrate the data in the database before I try to run migrations on the local storage. The reason - in case something goes wrong during running migrations on the local storage state or during merge, and it throws an exception - I want a chance to fix it. If I separately apply the same migration in the database, and it goes well there, but it fails locally - reverting the database migration could be hard.&lt;/p&gt;
&lt;p&gt;I don&#39;t have rollbacks in my migrations (yet?), but they could be implemented. Though it&#39;s way harder than with the regular database migrations. When running migrations, they may fail in different places for different users since users maybe have different versions of the states.&lt;/p&gt;
&lt;p&gt;For now, if some user has issues with the migrations, I just try to fix it asap by adjusting migrations or creating new ones - i.e., always going forward.&lt;/p&gt;
&lt;h3&gt;Implementation details&lt;/h3&gt;
&lt;p&gt;So, this is how it looks like in code. Liftosaur has a global state in memory, and the part of the state we want to store locally and in the cloud lives in the &lt;code&gt;state.storage&lt;/code&gt; field.&lt;/p&gt;
&lt;p&gt;There&#39;s the &lt;code&gt;state.storage.version&lt;/code&gt; field, that stores the current version of the state. For simplicity, for a version, we use the current datetime in &lt;code&gt;yyyymmddhhmmss&lt;/code&gt; format, like &lt;code&gt;20200929231430&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For the local storage we use &lt;code&gt;IndexedDB&lt;/code&gt;, which has horrible API, but it&#39;s asynchronous and not limited by 5MB like &lt;code&gt;window.localStorage&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Initially, we check if the local state is presented - by querying IndexedDB. If it&#39;s there, we fetch it and pass &lt;code&gt;state.storage&lt;/code&gt; field into our migration function. That looks like:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IStorage&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  version&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// ... other fields&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// List of all migrations&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; migrations &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Each migration key is version and some descriptive name.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;20200929231430_split_name_into_first_and_last_name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;storage&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;IStorage&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firstName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt; storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; storage&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;runMigrations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;storage&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; IStorage&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;IStorage&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Ordering migrations by version&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; keys &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;migrations&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  keys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Figuring out what versions we should run for&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newVersions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; keys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;versionStr&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; version &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;versionStr&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; version &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;version&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Run the migrations&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; storage&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; version &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; newVersions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; storageCopy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; migrations&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;version&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;storageCopy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Storing the last version as the new storage version&lt;/span&gt;&lt;br /&gt;  result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;version &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;keys&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;keys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As I said above, it only can run forward; it doesn&#39;t support rollbacks.&lt;/p&gt;
&lt;p&gt;Then, we fetch the storage from the cloud and also run it through migrations. Now we have local storage and cloud storage with the same version. We can merge them now.&lt;/p&gt;
&lt;p&gt;For merging local and cloud states, it&#39;s tricky to do automatically. The logic is different, in some places it makes more sense to combine two arrays; in other cases - prefer one or another.&lt;/p&gt;
&lt;p&gt;For example, it makes sense to combine workouts made both on the cloud and locally. But if, for example, user changed the available plates both locally and in the cloud, it doesn&#39;t make sense to combine both. We may end up having duplicate plates.&lt;/p&gt;
&lt;p&gt;Ideally, it could be solved with special data structures called CRDT, where log changes separately on cloud and locally, and then replay them for merging. But that would complicate things significantly.&lt;/p&gt;
&lt;p&gt;We&#39;re assuming that most of the time, users will use Liftosaur locally while offline, so usually, the local state is newer and more actual than the cloud state. With this assumption, we can combine workouts and some other parts, and for other stuff prefer local state as a source of truth.&lt;/p&gt;
&lt;p&gt;So, like this:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mergeStates&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;localStorage&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; IStorage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cloudStorage&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; IStorage&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; IStorage &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    version&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; localStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;version&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    settings&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; localStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;settings&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Concatenates two arrays, using date as unique key.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// I.e. if both oldStorage.history and newStorage.history&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// has record with date == &#39;2020-08-03&#39;, then the newStorage&#39;s&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// record with that date won&#39;t be added&lt;/span&gt;&lt;br /&gt;    workoutsHistory&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CollectionUtils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;concatBy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;      oldStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;history&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      newStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;history&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;record&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; record&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;date&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// ... etc&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obviously, there&#39;re cases where this approach will work poorly, and users may lose some changes they did, for example, on the laptop while the phone was offline. For Liftosaur (since it&#39;s mostly a phone app), this should be pretty rare, so we sacrifice correctness in favor of simplicity here.&lt;/p&gt;
&lt;p&gt;After the states are merged, we save the merged state in &lt;code&gt;IndexedDB&lt;/code&gt;, and send the new state to the cloud.
And from now on, we keep syncing the state into &lt;code&gt;IndexedDB&lt;/code&gt; and cloud on each &lt;code&gt;state.storage&lt;/code&gt; change.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That&#39;s about it! Implementing offline mode made me scratch my head a bit while doing all those migrations and configuring service workers. But once all the groundwork is done, I don&#39;t really have to think about it anymore while adding new features to Liftosaur. I just add new migrations from time to time and update the merging function.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Tailwind CSS colors in Chrome Devtools</title>
    <link href="https://www.liftosaur.com/blog/posts/tailwind-css-colors-in-devtools/"/>
    <updated>2021-03-06T00:00:00Z</updated>
    <id>https://www.liftosaur.com/blog/posts/tailwind-css-colors-in-devtools/</id>
    <content type="html">&lt;img style=&quot;box-shadow: 0 0 18px 4px rgba(0, 0, 0, 0.1); margin: 15px 0 60px&quot; src=&quot;https://www.liftosaur.com/blog/images/tailwind-css-colors-in-devtools-intro.png&quot; width=&quot;100%&quot; alt=&quot;Tailwind colors in devtools&quot; /&gt;
&lt;p&gt;I like to prototype designs or experiment with UI right in the browser.
For Liftosaur, I use Tailwind CSS to style the app. What I was really missing is the ability to specify the colors from my Tailwind CSS theme in devtools.&lt;/p&gt;
&lt;p&gt;Apparently, it&#39;s pretty easy to add them though. You probably have a &lt;code&gt;tailwind.config.js&lt;/code&gt; file in your project directory (if not, just create it with &lt;code&gt;module.exports = {};&lt;/code&gt;). Then, you could use a script like this:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; resolveConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;tailwindcss/resolveConfig&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; tailwindConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./tailwind.config.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fullConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resolveConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tailwindConfig&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; output &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;:root {\n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; key &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fullConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;colors&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fullConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;theme&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;colors&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    output &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;key&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;value&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;;\n&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; colorKey &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; colorValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;colorKey&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      output &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;key&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;colorKey&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;colorValue&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;;\n&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;output &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;}\n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And run it like:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node tw.js &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; colors.css&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will generate a file with a bunch of CSS variables with colors from your theme. Copy the contents of &lt;code&gt;colors.css&lt;/code&gt; to your app&#39;s CSS file. And you&#39;ll be able to use those in devtools:&lt;/p&gt;
&lt;p&gt;&lt;video style=&quot;width: 100%&quot; playsinline=&quot;&quot; muted=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; src=&quot;https://www.liftosaur.com/blog/images/tailwind-css-colors-in-devtools-video.mp4&quot;&gt;&lt;/video&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>How I moved Liftosaur from Cloudflare Workers to AWS Lambda</title>
    <link href="https://www.liftosaur.com/blog/posts/how-i-moved-liftosaur-from-cloudflare-workers-to-lambda/"/>
    <updated>2021-06-22T00:00:00Z</updated>
    <id>https://www.liftosaur.com/blog/posts/how-i-moved-liftosaur-from-cloudflare-workers-to-lambda/</id>
    <content type="html">&lt;div&gt;&lt;img src=&quot;https://www.liftosaur.com/blog/images/how-i-moved-liftosaur-from-cloudflare-workers-to-lambda-intro.png&quot; width=&quot;100%&quot; alt=&quot;From Cloudflare Workers to AWS Lambda&quot; /&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;TLDR; - I moved Liftosaur from Cloudflare Workers to AWS Lambda + Dynamo. It wasn&#39;t easy, especially it was bad because of poor local development environment. I built a skeleton to simplify that process for other folks who&#39;d need to start a project in AWS - &lt;a href=&quot;https://github.com/astashov/aws-cdk-lambda-typescript-starter&quot;&gt;https://github.com/astashov/aws-cdk-lambda-typescript-starter&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Everything started with Cloudflare Workers&lt;/h2&gt;
&lt;p&gt;When I initially was choosing a hosting for server-side part of Liftosaur, I decided to go with Cloudflare
Workers. I liked the idea - you write a service worker, and then run it in a cloud. And there was a bunch of
other benefits too:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There&#39;s a KV (key-value) store as a database, which should be good enough for start&lt;/li&gt;
&lt;li&gt;It integrates well with Cloudflare CDN&lt;/li&gt;
&lt;li&gt;It has a way to store secrets (like API tokens, keys, etc)&lt;/li&gt;
&lt;li&gt;There&#39;s a local dev server, that emulates the production environment&lt;/li&gt;
&lt;li&gt;And it&#39;s &lt;strong&gt;$5 per month&lt;/strong&gt; flat, which seems quite affordable&lt;/li&gt;
&lt;li&gt;Built-in cache mechanisms, which you can access via Service Worker&#39;s Cache API&lt;/li&gt;
&lt;li&gt;Separate production and development environments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But once I began to use Cloudflare Workers for realz, and deploy stuff into production, I quickly started to hit roadblocks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;That KV store is very limiting. It is what it is - a key-value store. So, no secondary indexes, no range queries. No easy way to even get all records in a collection - you can get collect all the keys, and then fetch values by each key one by one, but that&#39;s very slow.&lt;/li&gt;
&lt;li&gt;Because there&#39;re no secondary indexes, you have to create separate collections in KV store that works as indexes, and manually maintain the consistency between those manual indexes and the source of truth. It quickly becomes very complicated and hard to maintain.&lt;/li&gt;
&lt;li&gt;There&#39;re no good ways to do backups for the KV store. I tried to create a cron task in Cloudflare Dashboard, that&#39;d call a specific endpoint each day, which would try to fetch all the records from the KV stores and save them on S3, but that quickly became incredibly slow.&lt;/li&gt;
&lt;li&gt;Since the app is essentially a Service Worker running in a cloud, you cannot use any Node libraries. That was a big blocker. For example, I needed to create dynamically generated images with workout results when you share your workout in social media. I couldn&#39;t find a non-Node image manipulation library for that. I eventually created an AWS Lambda that does just that, using a Node library &lt;code&gt;jimp&lt;/code&gt;, then exposed it via public REST API, and called that API from a Cloudflare Worker.&lt;/li&gt;
&lt;li&gt;There&#39;s a limit of 1MB for the script you can run in a Cloudflare Worker. There&#39;s a built-in CLI, that bundles all your code with dependencies into one bundle and uploads it to the Cloudflare Worker. At the time when I migrated to AWS, my Cloudflare Workers bundle was 900kb, and it was starting to concern me - I could hit the limit very soon.&lt;/li&gt;
&lt;li&gt;Local dev server is pretty nice, but it crashes &lt;strong&gt;constantly&lt;/strong&gt;! You have to restart it all the time. It&#39;s pretty annoying.&lt;/li&gt;
&lt;li&gt;You have almost no monitoring, and logging is weird - you can only tail logs, but cannot really access old logs. That makes debugging pretty challenging.&lt;/li&gt;
&lt;li&gt;It enforces Webpack for bundling, it uses it internally. It&#39;s maybe not a big deal, but I wanted to switch to &lt;code&gt;esbuild&lt;/code&gt; to make my builds faster, and couldn&#39;t do it for the server code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eventually, all that became unbearable. I was planning a big new feature (add Friends, Likes and Comments), and building all of that on such limited KV store was pretty difficult. Also, backups worked poorly, and that was super concerning - I definitely didn&#39;t want to lose users&#39; data and not be able to restore.&lt;/p&gt;
&lt;h2&gt;Moving to AWS&lt;/h2&gt;
&lt;p&gt;I began to think where I could migrate Liftosaur to, and still pay no more than $5 a month. Would be nice to have a richer set of features, and especially decent database, backups, logging and monitoring. I already had some parts in AWS (e.g. lambdas for the image generators for social media), so decided to check if AWS would address some of the aforementioned issues.&lt;/p&gt;
&lt;p&gt;To stay under $5 a month, in AWS I had to use on-demand payment policies, since Liftosaur traffic is pretty small. Any relational database costs way more than that, but e.g. document database DynamoDB could be within that price range. Moreover, it supports queries, secondary indexes, backups each second (sic!), there&#39;s built-in monitoring and logging. To run the server code, EC2 instances were a bit too expensive, but on-demand Lambdas are pretty cheap. Another benefit is that they&#39;re pretty similar to Cloudflare Workers (except they&#39;re regular Node apps, not Service Workers apps) - you don&#39;t have to manage the actual instances. Also, they come with logging/monitoring via Cloudwatch out-of-the-box.&lt;/p&gt;
&lt;p&gt;AWS Lambdas has a concept of layers (something similar to Docker ones, I guess?), so you can move all the &lt;code&gt;node_modules&lt;/code&gt; into a layer, and the actual size of your server code would be significantly smaller. And for caches, I could just use S3 for now.&lt;/p&gt;
&lt;p&gt;So the setup I ended up with is something like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Still use Netlify for serving statics (CSS and JS bundles). Though that&#39;s probably me being lazy, there&#39;s nothing preventing me to just move it to S3.&lt;/li&gt;
&lt;li&gt;All API is moved to AWS Lambda, and all data is stored in AWS Dynamo, and various caches are in S3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I use &lt;a href=&quot;https://aws.amazon.com/cdk/&quot;&gt;AWS CDK&lt;/a&gt; to describe all the infrastructure I have in AWS. That was the first time I learned about CDK, and wow, it&#39;s really amazing! It is somewhat similar to old AWS Cloudformation. But if you ever tried to write Cloudformation YAML files, you&#39;re unlikely had very good time. Likely, it was very BAD time. Those are multi kloc YAML files, that are very verbose and very hard to write. It becomes even worse when you have to describe business logic in those YAML files, so you write variables and if/else branches in YAML - it&#39;s just as bad as it sounds.&lt;/p&gt;
&lt;p&gt;That new CDK tool still generates those YAML files, but allows you to describe the infrastructure in TypeScript instead. So, you&#39;ll get autocompletion, typechecking, easier API discovery and all the good stuff that a typesystem gives us. Also, using an actual programming language for describing business logic is way nicer. I highly recommend to give it a try, there&#39;s a &lt;a href=&quot;https://cdkworkshop.com/&quot;&gt;pretty cool workshop&lt;/a&gt; to get some taste of it.&lt;/p&gt;
&lt;p&gt;One thing that Lambdas do poorly though is a &lt;strong&gt;local development environment&lt;/strong&gt;. There&#39;s a product in AWS called SAM (Serverless Application Model). It has CLI to run the lambdas locally, &lt;code&gt;sam local start-api&lt;/code&gt;. So, you can generate a Cloudformation YAML from the CDK file. Then, that command - &lt;code&gt;sam local start-api&lt;/code&gt; - will read the YAML file, and based on it will start a local server, that will run your Lambdas in a container. You&#39;ll be able to make API calls to it, and it will execute them inside a container.&lt;/p&gt;
&lt;p&gt;The biggest caveat is that the SAM local dev server is slow. No, this is not enough. It&#39;s &lt;em&gt;SLOOOOOOOOWWWWW&lt;/em&gt;. It seems like it restarts a container on every request, and then does I-dont-know-what, but for any call that really should be like 300ms, you end up with 10 seconds request time.&lt;/p&gt;
&lt;p&gt;Also, there&#39;re other issues - e.g. you cannot use &amp;quot;nameless&amp;quot; Dynamo tables, because if you omit the Dynamo table name in the CDK template, then the local lambda and the lambda in AWS assume different names for some reason. Also, by some reason the binary outputs don&#39;t work (e.g. if I want to return &lt;code&gt;image/png&lt;/code&gt; from a lambda call). It ignores &lt;code&gt;isBase64Encoded&lt;/code&gt; flag by some reason, and local lambda just returns Base64 output, instead of binary.&lt;/p&gt;
&lt;p&gt;Luckily, you don&#39;t really need to run everything locally via &lt;code&gt;sam local start-api&lt;/code&gt;. Lambdas have their own format of requests and responses, but it&#39;s pretty easy to simulate it and just convert e.g. regular requests/responses of built-in Node &lt;code&gt;http&lt;/code&gt; module into Lambdas&#39; specific ones. That&#39;s what I did - wrote a simple Node server and that request/response transformer, and it works perfectly - it&#39;s fast, it reloads on any code changes. And it&#39;s very simple, so easy to add or modify it if necessary. And it solves the SAM local lambda&#39;s issues, like unability to use nameless DynamoDB tables, or returning binary responses.&lt;/p&gt;
&lt;p&gt;I created 2 environments via CDK - prod and dev, each with their own lambda, dynamo tables, S3 buckets, etc. Dev environment also has its own subdomain. When you run the server locally, it connects to the dynamo tables/S3 buckets from Dev environment, so when developing locally you interact with those, and don&#39;t mess with production databases.&lt;/p&gt;
&lt;p&gt;Another nice thing that I&#39;m paying only &lt;strong&gt;~$2.5/month&lt;/strong&gt; for all of this now, so seems like a pretty good deal.&lt;/p&gt;
&lt;h2&gt;Starter skeleton for TypeScript/React projects on AWS Lambda&lt;/h2&gt;
&lt;p&gt;Migration to AWS wasn&#39;t that easy. I had some experience in AWS before, but mostly around EC2s, not Lambdas. It took me a while to figure out how to properly setup the infrastructure, how to describe it all in CDK, how to make it as cheap as possible for a low-traffic site like Liftosaur, how to set up local development environment properly, and multi-environment development in general.&lt;/p&gt;
&lt;p&gt;To help other folks with that, and provide some template for cheap hosting on AWS Lambdas, I created a starter skeleton for the TypeScript/React projects on AWS Lambda and Dynamo. Here it is:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/astashov/aws-cdk-lambda-typescript-starter&quot;&gt;https://github.com/astashov/aws-cdk-lambda-typescript-starter&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There, you can find:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Minimal CDK template for the AWS infrastructure, optimized for simplest and cheapest (but still very performant) setup for low traffic websites.&lt;/li&gt;
&lt;li&gt;Fast and reliable local development environment - a server that restarts automatically and instantly on code changes. It regular Node&#39;s built-in &lt;code&gt;http&lt;/code&gt; server for handling requests, and uses &lt;code&gt;esbuild&lt;/code&gt; to blazingly fast compile/recompile the project - in less than a second.&lt;/li&gt;
&lt;li&gt;When you run the CDK script, it also deploys the app itself, and uploads CSS/JS bundles to S3 and CDN.&lt;/li&gt;
&lt;li&gt;It&#39;s a skeleton for NodeJS + TypeScript + React + server-side rendering support (SSR)&lt;/li&gt;
&lt;li&gt;Has multiple environments - so you can test the changes in development environment first, before deploying it fully to production. Development and production have separate S3 buckets, lambdas, databases, a separate domain, etc.&lt;/li&gt;
&lt;li&gt;Super easy to attach a public domain purchased via AWS Route53&lt;/li&gt;
&lt;li&gt;Super easy to attach a certificate created via AWS Certificate Manager.&lt;/li&gt;
&lt;li&gt;Serve the CSS, JS bundles and HTML from the same domain, to not worry about cross-domain issues. But still serve CSS and JS from CDN, to not sacrifice app performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;IMHO it&#39;s pretty convenient for various small-traffic websites, pet projects, etc - you can start from it, and modify the CDK template if necessary to add more services if necessary (like email services, queues, etc).&lt;/p&gt;
&lt;p&gt;So, try it out, hopefully you&#39;ll find it useful!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Workout planner inspired by the software development tools</title>
    <link href="https://www.liftosaur.com/blog/posts/workout-planner/"/>
    <updated>2023-07-09T00:00:00Z</updated>
    <id>https://www.liftosaur.com/blog/posts/workout-planner/</id>
    <content type="html">&lt;p&gt;&lt;img src=&quot;https://www.liftosaur.com/blog/images/workout-planner-hero.png&quot; alt=&quot;Workout Planner Hero image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I recently finished a bunch of books on weightlifting programming, such as &lt;a href=&quot;https://www.strongerbyscience.com/art-and-science/&quot;&gt;Art of Lifting and Science of Lifting&lt;/a&gt; by Greg Nuckols, and &lt;a href=&quot;https://muscleandstrengthpyramids.com/&quot;&gt;The Muscle and Strength Training Pyramid&lt;/a&gt; by Eric Helms, Andy Morgan, and Andrea Valdez. These are excellent books that introduced me to the world of weightlifting programming. They delve into the details of how to approach creating your own weightlifting programs, tailoring them to your needs and goals.&lt;/p&gt;
&lt;p&gt;However, building a program is a complex task. You need to balance numerous variables, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ensuring you have enough sets per muscle group.&lt;/li&gt;
&lt;li&gt;Ideally spreading out work on muscle groups throughout the week.&lt;/li&gt;
&lt;li&gt;Ensuring you have enough volume, but not too much volume (based on your training age).&lt;/li&gt;
&lt;li&gt;If you&#39;re building an intermediate or advanced program, you probably want to include week over week periodization, undulating volume and intensity, and splitting the block into mesocycles, etc.&lt;/li&gt;
&lt;li&gt;Things would be different if you&#39;re training for strength or for hypertrophy.&lt;/li&gt;
&lt;li&gt;And there are a lot of other parameters to keep in mind as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&#39;re just starting to get into weightlifting programming, it&#39;s a lot to keep in mind. Surprisingly, I couldn&#39;t find any tool or app to assist with this (although I could be bad at googling).&lt;/p&gt;
&lt;p&gt;It&#39;s not an easy problem to solve though. Need to think through UI and UX of such tool, so that it&#39;d be easy to use, and you&#39;d have all the information in front of you, and you could easily tweak it. Would be also cool to be able store the programs you build too, and adjust them when you progress or when your goals change.&lt;/p&gt;
&lt;h2&gt;Getting inspiration from software development tools&lt;/h2&gt;
&lt;p&gt;The software development industry has decades of experience in developing tools for writing programs. A weighlifting program shares some similarities with a software program. For example, we have pretty formalized hierarchy - a weightlifting program consists of weeks, a week consists of workouts, a workout consists of exercises. Each exercise consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sets x reps (or reps range)&lt;/li&gt;
&lt;li&gt;RPE (Rate of Perceived Exertion) value - basically how close you should be to failure&lt;/li&gt;
&lt;li&gt;Weight (although it may not be necessary in program planning, and could be replaced by RPE)&lt;/li&gt;
&lt;li&gt;Rest time&lt;/li&gt;
&lt;li&gt;Additional properties (like tempo, if it&#39;s a superset, etc)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can come up with formalized syntax to describe a workout. For example, each line would be an exercise in a workout, and various properties (sets x reps, RPE, etc) would be separated by slashes. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Bench Press / 3x5 / @9 / 180s
Shoulder Press / 8+ @9 180s, 3x5 @7 60s
Bicep Curl / 3x8-12 / @8 / 90s
... etc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, we start with the exercise, then after slash we have sets and reps. We can group them together like &lt;code&gt;3x8&lt;/code&gt;, or we can list them separately by comma, like &lt;code&gt;2x8, 6, 4&lt;/code&gt;. AMRAP sets would be suffixed by &lt;code&gt;+&lt;/code&gt;. We can add RPE (via &lt;code&gt;@&lt;/code&gt; symbol) and rest time (by suffixing a number with &lt;code&gt;s&lt;/code&gt;) - either to each set, or globally apply to the whole exercise if we add them to separate section separated by slashes &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The order of sections within exercise doesn&#39;t really matter, could be &lt;code&gt;3x5 / @9 / 180s&lt;/code&gt; or &lt;code&gt;@9 / 3x5 / 180s&lt;/code&gt; or any other order.&lt;/p&gt;
&lt;p&gt;Having the whole workout as a plain text has a lot of benefits - we can freely copy/paste it, quickly change it in any text editor, it&#39;s easy to read and it&#39;s condensed and brief.&lt;/p&gt;
&lt;p&gt;In software development, there&#39;re a lot of tools that help with writing code - code editors that would colorize the syntax so it&#39;s easier to read it, auto-format the code, show various hints and information when you place a cursor on specific words. I think we could get inspired from those tools, and build something similar for weightlifting programming.&lt;/p&gt;
&lt;h2&gt;Designing a tool for weightlifting programming&lt;/h2&gt;
&lt;p&gt;So, I opened Figma, and tried to come up with a design for such tool. I think it could look something like this:&lt;/p&gt;
&lt;a href=&quot;https://www.liftosaur.com/blog/images/workout-planner-collapsed.png&quot; target=&quot;_blank&quot;&gt;
&lt;img src=&quot;https://www.liftosaur.com/blog/images/workout-planner-collapsed.png&quot; alt=&quot;Workout Planner Collapsed design&quot; /&gt;
&lt;/a&gt;
&lt;p&gt;And this is description of various sections:&lt;/p&gt;
&lt;a href=&quot;https://www.liftosaur.com/blog/images/workout-planner-collapsed-explained.png&quot; target=&quot;_blank&quot;&gt;
&lt;img src=&quot;https://www.liftosaur.com/blog/images/workout-planner-collapsed-explained.png&quot; alt=&quot;Workout Planner Collapsed design explained&quot; /&gt;
&lt;/a&gt;
&lt;p&gt;So, the main part is the workout text field. We type the exercises, sets x reps, RPE, etc there, according to the syntax described above. The editor highlights different sections/parts in different colors to simplify reading.&lt;/p&gt;
&lt;p&gt;When you type there, it&#39;ll show up the autocompletion with the exercises. You can autocomplete by exercise name, but also by target/synergetic muscles and muscle groups.&lt;/p&gt;
&lt;p&gt;At the top, we can switch between different weeks, name a week and name each workout. It shows approximate time
the workout would take (based on rest times, number of sets x reps, if there&#39;re any supersets), and also the approximate number of calories it&#39;ll burn.&lt;/p&gt;
&lt;p&gt;On the right of the workout text field it shows the &lt;strong&gt;Week stats&lt;/strong&gt;. There, you could see the total number of sets, and also distribution between strength and hypertrophy sets (based on number of reps). Also, you see the number of sets per muscle group, and the distribution between push, pull, upper and lower exercises.&lt;/p&gt;
&lt;p&gt;In the set distributions, the format is: &lt;code&gt;4 (2s, 2h), 2 days&lt;/code&gt;, meaning it&#39;s 4 sets total, 2 sets are strength, 2 sets are hypertrophy. &lt;code&gt;2 days&lt;/code&gt; is frequency - meaning how many times per week you train that muscle group. Muscle groups are clickable, so you can add exercises targeting specific muscle groups to the workout by clicking on them.&lt;/p&gt;
&lt;p&gt;While scrolling all the workouts of the week, the week stats are sticky, so you can always see them.&lt;/p&gt;
&lt;p&gt;The set numbers are colored in red, yellow or green, based on whether you&#39;re in the recommended range. You can define the range in Settings, with some presets, like 10-12 sets per muscle group for novice, 13-15 for intermediate, etc, and that will define the color of the set numbers.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Settings&lt;/strong&gt; are accessible by clicking on the cog icon. There, you can define whether you train for strength or hypertrophy (defining percentage of strenght or hypertrophy sets), the recommended set ranges per muscle group, recommended frequency per muscle group, default Rest timer, and also define the custom exercises.&lt;/p&gt;
&lt;p&gt;When you click on the workout text field, and place an input cursor to some line with some exercise, 2 new sections appear.&lt;/p&gt;
&lt;p&gt;First is &lt;strong&gt;Workout stats&lt;/strong&gt;, it shows up between the &lt;strong&gt;Week stats&lt;/strong&gt; and the text field for the workout. It has the same data as the &lt;strong&gt;Week stats&lt;/strong&gt; except the frequency.&lt;/p&gt;
&lt;p&gt;Second is the &lt;strong&gt;Exercise stats&lt;/strong&gt;, that will be displayed under the workout text field, showing the details for that exercise. There you&#39;d see the muscles and muscle groups this exercise targets, and the number of sets this week.&lt;/p&gt;
&lt;a href=&quot;https://www.liftosaur.com/blog/images/workout-planner-expanded.png&quot; target=&quot;_blank&quot;&gt;
&lt;img src=&quot;https://www.liftosaur.com/blog/images/workout-planner-expanded.png&quot; alt=&quot;Workout Planner Expanded design&quot; /&gt;
&lt;/a&gt;
&lt;p&gt;The main purpose of &lt;strong&gt;Exercise stats&lt;/strong&gt; though is to show the week over week change of sets x reps, as well as the week over week graphs of volume and intensity. This is especially useful for planning mesocycles and periodization for intermediate/advanced programs, where you could define the &amp;quot;accumulation&amp;quot; blocks, &amp;quot;intensification&amp;quot; blocks, etc. The graphs would provide a visual clue of how the volume and intensity changes in those blocks.&lt;/p&gt;
&lt;a href=&quot;https://www.liftosaur.com/blog/images/workout-planner-expanded-explained.png&quot; target=&quot;_blank&quot;&gt;
&lt;img src=&quot;https://www.liftosaur.com/blog/images/workout-planner-expanded-explained.png&quot; alt=&quot;Workout Planner Expanded design explained&quot; /&gt;
&lt;/a&gt;
&lt;h2&gt;Progressive overload&lt;/h2&gt;
&lt;p&gt;The workout planner wouldn&#39;t help with the progressive overload though. Its purpose is to define exercises, sets and reps for the workouts for all the weeks. Then, you would convert it into the Liftosaur&#39;s program, and open in the &lt;a href=&quot;https://www.liftosaur.com/program&quot;&gt;Liftosaur&#39;s Web Editor (Program Buidler)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There, you&#39;d add some kind of progressive overload to the exercises, and do final tweaks for your program. E.g. linear progression for novice program, or just adding weight at the end of mesocycle/macrocycle for intermediate/advanced program, adding double progression for isolates, tweaking warmups, etc.&lt;/p&gt;
&lt;p&gt;Then, you generate the link for the program, import it into Liftosaur app - and voila! You can now follow your program in the Liftosaur app!&lt;/p&gt;
&lt;p&gt;Each program in the workout planner has a unique URL (same as in Liftosaur programs), so you can bookmark it, and then open it later on again. This way, you can get back to your program, tweak it, maybe increase/decrease volume, maybe change exercises. You can also share it with other people if you want.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This IMHO could be a really useful tool to build your own weightlifting programs. What do you think?&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Launched Workout planner!</title>
    <link href="https://www.liftosaur.com/blog/posts/launched-workout-planner/"/>
    <updated>2023-09-13T00:00:00Z</updated>
    <id>https://www.liftosaur.com/blog/posts/launched-workout-planner/</id>
    <content type="html">&lt;p&gt;&lt;img src=&quot;https://www.liftosaur.com/blog/images/workout-planner-hero-2.png&quot; alt=&quot;Workout Planner Hero image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A couple months ago &lt;a href=&quot;https://www.liftosaur.com/blog/posts/workout-planner&quot;&gt;I wrote&lt;/a&gt; about the idea of building a workout planner, that would help you design weightlifting programs. I&#39;ve been working on it since then, and now it&#39;s ready!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You can check it out here: &lt;a href=&quot;https://liftosaur.com/planner&quot;&gt;https://liftosaur.com/planner&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I believe it&#39;s an amazing tool for building weightlifting programs. It&#39;s a great way to visualize your programs, and see if you have enough volume per muscle group, and if you&#39;re hitting them with enough frequency. Hopefully it&#39;ll become a starting point for people to build their programs, especially for those who don&#39;t want to mess with coding.&lt;/p&gt;
&lt;p&gt;It allows you to plan programs, make sure you have enough weekly volume per muscle group, and can balance it with workout time. You can build multiweek programs, and it&#39;ll show weekly undulating of volume and intensity per exercise on a graph. And you can export the program to the Liftosaur app, making it a way faster way to build Liftosaur programs.&lt;/p&gt;
&lt;p&gt;It&#39;s not as flexible as the Liftosaur&#39;s built-in program editor, but it&#39;s a lot faster to build programs with it, and it probably be enough for like 80% of use cases. And you can always do the final tweaks in Liftosaur if needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you would prefer to watch the video instead, here&#39;s how I use Planner to build a program&lt;/strong&gt;&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/CCFKG1s0u1o?si=MSkcM1mZ_c9EnfxH&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2&gt;How to use it&lt;/h2&gt;
&lt;p&gt;It&#39;s inspired by rich text editors called IDEs (Interactive Development Environment), that are pretty popular in the software engineering world. Those are the text editors that will autocomplete your code, suggest what you should write, check the code you wrote for errors, and generally allow you to write code faster and more efficiently.&lt;/p&gt;
&lt;p&gt;The workout planner is similar - it will autocomplete the exercises, equipment, show available progressive overloads, colorize the syntax, etc. It uses a special syntax, so there&#39;s some learning curve, but it&#39;s very similar to how people generally describe their workout programs, and once you learn it - you can build programs really fast.&lt;/p&gt;
&lt;p&gt;Each exercise should go on a separate line. It consists of sections separated by a slash (&lt;code&gt;/&lt;/code&gt;). The simplest exercise is written like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can do rep ranges too:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8-12&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can list multiple sets, separated by commas, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That would be 8 sets total - first 5 reps, then 3 reps, 1 rep, and then 5 sets of 5 reps.&lt;/p&gt;
&lt;p&gt;If you don&#39;t specify the weight, it&#39;ll use the weight calculated from
&lt;abbr title=&quot;RPE - Rate of Perceived Exertion - is a way to measure how close you are to failure. It&#39;s a scale from 1 to 10, where 1 is very easy, and 10 is failure.&quot;&gt;RPE&lt;/abbr&gt;
tables, like &lt;a href=&quot;https://articles.reactivetrainingsystems.com/wp-content/uploads/2015/11/E1RM-TABLE.png&quot;&gt;this one&lt;/a&gt;. By default it&#39;ll assume you want to do exercises til failure (@10 RPE), so e.g. if you write:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x12&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It&#39;ll check in the RPE table that for if you want to do 12 reps til failure (@10 RPE), you probably should use 65% of 1RM, so it&#39;ll set the weight to 65% of 1RM under the hood. If you don&#39;t want to go to full failure, you can specify desired RPE:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x12&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Then the weight would be lower - 60%. You can specify the weight explicitly, as a percentage of 1RM, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x12&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Or you can specify the weight in kg/lb, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x12&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60kg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If you specify the weight in kgs though, it&#39;d be static, and not tied to the &lt;code&gt;state.weight&lt;/code&gt; state variable when you convert it into Liftosaur program.&lt;/p&gt;
&lt;p&gt;RPE, percentage and weight can be specified for each set or range of sets individually, so you can mix and match:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5 &lt;/span&gt;&lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3 &lt;/span&gt;&lt;span class=&quot;tok-number&quot;&gt;@9&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1 &lt;/span&gt;&lt;span class=&quot;tok-number&quot;&gt;@10&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;50%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can specify the rest time. E.g. this is how you could do &lt;strong&gt;myo-reps&lt;/strong&gt; - i.e. doing heavy 12, and then doing 5x5 with short rest times and same weight:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;12 &lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;20s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;20s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can also specify the rest time, weight, 1RM percentage and RPE also, for all sets, so you don&#39;t have to repeat yourself. Do it in a separate section like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;12&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;20s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;So, an example workout may look something like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;
Incline Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8-12&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;90s&lt;/span&gt;
Skullcrusher &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x15&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt;
Lateral Raise &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x15&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;By default, it&#39;ll use default equipment - e.g. for &lt;strong&gt;Bench Press&lt;/strong&gt; it&#39;ll use &lt;strong&gt;Barbell&lt;/strong&gt;. If you want to specify different equipment, add it after the exercise name, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press, dumbbell &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2&gt;How to read stats&lt;/h2&gt;
&lt;p&gt;The planner shows you daily and weekly number of sets per muscle group, per movement type and split between &amp;quot;strength&amp;quot; and &amp;quot;hypertrophy&amp;quot; sets. As an example, this is &lt;a href=&quot;https://www.liftosaur.com/n/2dbed2bc&quot;&gt;Workout Planner for the GZCLP program&lt;/a&gt;.
Let&#39;s take a look at the &lt;strong&gt;Week Stats&lt;/strong&gt; section. It looks like this:&lt;/p&gt;
&lt;p class=&quot;markdown-body-pre&quot;&gt;
Shoulders: &lt;span style=&quot;color: #8c8e00&quot;&gt;15↓&lt;/span&gt; (7.5s, 7.5h), &lt;span style=&quot;color: #38a169&quot;&gt;3d&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;That describes the volume (number of sets) you have weekly per muscle group. E.g. in the example above, we have
15 sets for shoulders. If you hover your mouse over the number &#39;15&#39;, you&#39;ll see what exercises contribute to that
number:&lt;/p&gt;
&lt;div class=&quot;markdown-body-pre&quot; style=&quot;font-weight: bold&quot;&gt;
&lt;div&gt;Bent Over Row - 3 (0s, 3h)&lt;/div&gt;
&lt;div&gt;Overhead Press - 5 (5s, 0h)&lt;/div&gt;
&lt;div style=&quot;color: #607284&quot;&gt;Bench Press - 4 (2.5s, 1.5h)&lt;/div&gt;
&lt;div style=&quot;color: #607284&quot;&gt;Lat Pulldown - 3 (0s, 3h)&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can see that &lt;strong&gt;Bent Over Row&lt;/strong&gt; and &lt;strong&gt;Overhead Press&lt;/strong&gt; are black (meaning they have target muscles in the Shoulder muscle group), and &lt;strong&gt;Bench Press&lt;/strong&gt; and &lt;strong&gt;Lat Pulldown&lt;/strong&gt; are gray (meaning they have synergist muscles in the Shoulder muscle group).&lt;/p&gt;
&lt;p&gt;The number of sets is colored in &lt;span style=&quot;color: #e53e3e&quot;&gt;red&lt;/span&gt;, &lt;span style=&quot;color: #8c8e00&quot;&gt;yellow&lt;/span&gt; or &lt;span style=&quot;color: #38a169&quot;&gt;green&lt;/span&gt;, based on whether you&#39;re in the recommended range. You can define the range in &lt;strong&gt;Settings&lt;/strong&gt;, with some presets, like 10-12 sets per muscle group for novice, 13-15 for intermediate, etc, and that will define the color of the set numbers. &lt;span style=&quot;color: #e53e3e&quot;&gt;Red&lt;/span&gt; - you&#39;re completely outside of the range, &lt;span style=&quot;color: #8c8e00&quot;&gt;yellow&lt;/span&gt; - you&#39;re close to the range, &lt;span style=&quot;color: #38a169&quot;&gt;green&lt;/span&gt; - you&#39;re in the range. The arrow ↓ means you need to decrease the number of sets to get into the range.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;(2.5s, 1.5h)&lt;/code&gt; means it has 2.5 sets in strength rep range (1-8 reps) and 1.5 sets of hypertrophy (8+ reps). We count synergist muscles by default as a half set, hence there&#39;s .5 - i.e. the number is not round.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;3d&lt;/code&gt; is frequency - means we hit this muscle group 3 days per week.&lt;/p&gt;
&lt;p&gt;There&#39;re also Day Stats, that show up if you click on the workout text field. Muscle groups are links there, so you can click it and it&#39;ll filter the Exercises to only show ones that have that muscle group.&lt;/p&gt;
&lt;h2&gt;Specifying progressive overload&lt;/h2&gt;
&lt;p&gt;You can specify the progressive overload for each exercise. It won&#39;t affect the weekly volume stats, but it&#39;ll be added when you export a program to Liftosaur. For now, there&#39;re 3 types of progressive overload:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linear Progression (&lt;strong&gt;lp&lt;/strong&gt;) - increase or decrease the weight by a fixed amount or percentage after N attempts.&lt;/li&gt;
&lt;li&gt;Double Progression (&lt;strong&gt;dp&lt;/strong&gt;) - increase the reps within a range, then reset the reps and increase the weight.&lt;/li&gt;
&lt;li&gt;Reps Sum (&lt;strong&gt;sum&lt;/strong&gt;) - increase the weight if the total sum of reps of all sets more than the threshold.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can add a progression like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That will increase the weight by 5lb after 3 successful attempts. Or like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;dp&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;4&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It will keep increasing reps from 8 to 12 (8 + 4), and then reset the reps to 8 and increase the weight by 5lb.&lt;/p&gt;
&lt;p&gt;The autocomplete for those progressive overloads contains more details.&lt;/p&gt;
&lt;h2&gt;Examples&lt;/h2&gt;
&lt;p&gt;Here&#39;re some examples of workouts you can build with the workout planner:&lt;/p&gt;
&lt;h3&gt;Stronglifts 5x5&lt;/h3&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-comment&quot;&gt;// Workout A
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;)
Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;)
Bent Over Row &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;)
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;// Workout B
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;)
Overhead Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;)
Deadlift &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5 &lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;)&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Myo reps&lt;/h3&gt;
&lt;p&gt;Do &amp;quot;activating&amp;quot; set, and then additional sets of the same weight with less reps and short rest:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;12&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;20s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;Drop sets&lt;/h3&gt;
&lt;p&gt;Do a set with 100%, then 85%, then 75%, then 60% of the weight, with short rests:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5 &lt;/span&gt;&lt;span class=&quot;tok-number&quot;&gt;100%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;8 &lt;/span&gt;&lt;span class=&quot;tok-number&quot;&gt;85%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;10 &lt;/span&gt;&lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;12 &lt;/span&gt;&lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;40s&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;5/3/1 Basic Beginner main lift&lt;/h3&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5 &lt;/span&gt;&lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3 &lt;/span&gt;&lt;span class=&quot;tok-number&quot;&gt;85%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1+ &lt;/span&gt;&lt;span class=&quot;tok-number&quot;&gt;95%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;Bullmastiff Week 1 Day 1&lt;/h3&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x6&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;65%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;6+ &lt;/span&gt;&lt;span class=&quot;tok-number&quot;&gt;65%&lt;/span&gt;
Romanian Deadlift, barbell &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x12&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@6&lt;/span&gt;
Bent Over Row &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;2x12-15&lt;/span&gt;
Leg Curl &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;2x12-15&lt;/span&gt;
Leg Extension &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;2x12-15&lt;/span&gt;
Cable Crunch &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;2x12-15&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2&gt;Exporting to Liftosaur&lt;/h2&gt;
&lt;p&gt;When you&#39;re done and would like to export this program to Liftosaur, press that big purple button &lt;strong&gt;&amp;quot;Convert to Liftosaur program&amp;quot;&lt;/strong&gt;. That will generate a link and copy it to the clipboard. You then can go to Liftosaur, and paste that link into the &amp;quot;Import link&amp;quot; field on the Choose Program screen. And that&#39;s it!&lt;/p&gt;
&lt;p&gt;Check it out, and hopefully you&#39;ll find this new Workout Planner useful!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Combine Workout Planner and Liftoscript</title>
    <link href="https://www.liftosaur.com/blog/posts/combine-workout-planner-and-liftoscript/"/>
    <updated>2024-01-04T00:00:00Z</updated>
    <id>https://www.liftosaur.com/blog/posts/combine-workout-planner-and-liftoscript/</id>
    <content type="html">&lt;div&gt;&lt;img src=&quot;https://www.liftosaur.com/blog/images/combine-workout-planner-and-liftoscript-intro.jpg&quot; width=&quot;100%&quot; alt=&quot;Midjourney generated book and some barbell plates&quot; /&gt;&lt;/div&gt;
&lt;div class=&quot;subscription&quot;&gt;This is what Midjourney thinks a good illustration for the post. Who am I to argue with AI overlords?&lt;/div&gt;
&lt;p&gt;I recently added &amp;quot;Full Program&amp;quot; mode to the &lt;a href=&quot;https://www.liftosaur.com/planner&quot;&gt;Workout Planner&lt;/a&gt;. Now you can edit a whole
weightlifting program as just one text file.&lt;/p&gt;
&lt;p&gt;And this is so cool! Making weightlifting programs was never easier and faster. But I still have to fall back to a regular
Web Editor from time to time for some custom progression logic. And that sucks, there&#39;s way too much clicking around, I
really would want to build whatever I want in the Workout Planner instead end to end.&lt;/p&gt;
&lt;p&gt;So I couldn&#39;t stop thinking - could I somehow bring the full power of the regular Web Editor to Workout Planner?
Having Liftoscript there, I could implement any possible progression logic in it as well. And after some brainstorming, I think I have something in mind.&lt;/p&gt;
&lt;p&gt;The idea is to use Liftoscript syntax right inside the Workout Planner program. But first, we&#39;d need to extend both Liftoscript and Workout Planner with the new features.&lt;/p&gt;
&lt;p&gt;Currently, in Liftoscript, you can access required/completed reps, weight, rpe, etc via &lt;code&gt;reps[1]&lt;/code&gt;, &lt;code&gt;completedReps[1]&lt;/code&gt;, &lt;code&gt;weights[1]&lt;/code&gt;, etc.
We need to add a way to directly change required reps, weight, rpe and timer in the program in the Finish Day Script. I.e. you&#39;d be able to do this:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;reps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Set all the reps of all the sets to 5&lt;/span&gt;&lt;br /&gt;reps&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Set all the reps of the 1st set variation to 8&lt;/span&gt;&lt;br /&gt;reps&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// Set the 2nd set reps of the 1st set variation to 3&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;weight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;lb &lt;span class=&quot;token comment&quot;&gt;// Set all the weights of all the sets to 5lb&lt;/span&gt;&lt;br /&gt;weight&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Set all the weights of the 1st set variation to 50% of 1RM&lt;/span&gt;&lt;br /&gt;weight&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rpeMultiplier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Set the 2nd set&#39;s weight of the 1st set variation to that RPE multiplier&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Same thing for timer and RPE&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, if we have that, we can introduce new progress function in the Workout Planner - &lt;code&gt;custom()&lt;/code&gt;. In that function, in the curly braces, we can add our Liftoscript code!&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;Bench Press &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;x5 &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedReps &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; reps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;weight &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;lb&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Between the parentheses in the &lt;code&gt;custom&lt;/code&gt; function we can define the state variables. Some state variables would be available by default (like &lt;code&gt;weight&lt;/code&gt;). E.g. if we want to increase weight after 3 successful attempts, and reduce weight after 2 failures, it&#39;d be:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;Bench Press &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;x5 &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;success&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; failures&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedReps &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; reps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;success &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;success &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;weight &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;lb&lt;br /&gt;      state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;success &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;      state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;failures &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;failures &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;failures &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;weight &lt;span class=&quot;token operator&quot;&gt;-=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;lb&lt;br /&gt;      state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;success &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;      state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;failures &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unlike regular Liftoscript and Web Editor, you cannot use state variables in reps/weight/timer/etc. But you can directly change them for the next workout in the script using our new Liftoscript syntax we introduced above. Like for the double progression:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;Bench Press &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;x8 &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;success&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; failures&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedReps &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; reps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    reps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; reps&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reps&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ns&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    reps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;weight &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;lb&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can also introduce set variations in the Workout Planner, just by listing them one after another, like:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;Bench Press &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;x3 &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;x2 &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;x1 &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedReps &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; reps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;setVariation &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, we&#39;ll have 3 set variations - &#39;5x3&#39;, &#39;6x2&#39; and &#39;10x1&#39;. And you can switch between them by assigning a value to &lt;code&gt;state.setVariation&lt;/code&gt; (also a default built-in state variable).
Similar way you can specify multiple descriptions and switch between them via &lt;code&gt;state.description&lt;/code&gt; state var:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// First description&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Second description&lt;/span&gt;&lt;br /&gt;Bench Press &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;x3 &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;x2 &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;x1 &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedReps &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; reps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also will introduce new Workout Planner syntax to reuse logic of exercises, which will look like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt;
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Bench Press&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This way Squat will reuse whatever sets/reps, progression, etc are defined for the Bench Press.&lt;/p&gt;
&lt;p&gt;Using all of that, we can e.g. implement GZCLP:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;# Week &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;## Day &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;t1&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Squat &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;x3 &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;x2 &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;x1 &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;increase&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;lb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedReps &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; reps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;weight &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;increase&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sets &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;setVariation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;setVariation &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;t2&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Bench Press &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;x10 &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;x8 &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;x6 &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;state1weight&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; increase&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state3increase&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;lb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedReps &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; reps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;weight &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;lb&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stage &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stage1weight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;weight&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stage &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stage &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stage &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stage &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;weight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stage1weight &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stage3increase&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;t3&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Lat Pulldown &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;x15 &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedReps&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ns&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;weight &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;lb&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;## Day &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;br /&gt;t1&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Overhead Press &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;t1&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Squat &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;increase&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;t2&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Deadlift &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;t2&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Bench Press &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state1weight&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; increase&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state3increase&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;t3&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Bent Over Row &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;t3&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Lat Pulldown&lt;br /&gt;&lt;br /&gt;## Day &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;br /&gt;t1&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Bench Press &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;t1&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Squat &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;increase&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;t2&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Squat &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;t2&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Bench Press &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state1weight&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; increase&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state3increase&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;t3&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Lat Pulldown &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;## Day &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;br /&gt;t1&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Deadlift &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;t1&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Squat &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;increase&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;t2&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Overhead Press &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;t2&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Bench Press &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state1weight&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; increase&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state3increase&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;t3&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Bent Over Row &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that reusing exercises can define their values for the state variables via the same &lt;code&gt;custom(foo: 3)&lt;/code&gt; syntax, just without the block withing the curly braces - it&#39;ll be reused from the original exercise.&lt;/p&gt;
&lt;p&gt;And that&#39;s it! That pretty much would be the whole GZCLP program, with all the autoincrements, changing stages on failures, and all the proper logic for T1s, T2s and T3s.&lt;/p&gt;
&lt;p&gt;All that stuff makes the Workout Planner pretty much as powerful as the regular Liftosaur&#39;s program editor. And a big advantage is that you don&#39;t really need to know/use all of that, you still can do simple programs like:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt;
Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;But if you really want something very custom and unusual for you progression, you can use this new &lt;code&gt;custom() { }&lt;/code&gt; syntax
to do whatever you want realy.&lt;/p&gt;
&lt;p&gt;One caveat here is that if I integrate Workout Planner into the app fully and make it the default program editor, then
when people open e.g. built-in GZCLP program to tweak it, and see that blob of code for T1/T2 exercise, that could be quite intimidating. Currently it&#39;s kinda buried in UI and doesn&#39;t look as scary.&lt;/p&gt;
&lt;p&gt;But IMHO that&#39;s a step in the right direction, and would unlock a lot of potential.&lt;/p&gt;
&lt;p&gt;What do you think? Would love to hear your thoughts on that! Please comment on Reddit what do you think about it!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>New Experimental Program Editor</title>
    <link href="https://www.liftosaur.com/blog/posts/new-experimental-program-editor/"/>
    <updated>2024-01-25T00:00:00Z</updated>
    <id>https://www.liftosaur.com/blog/posts/new-experimental-program-editor/</id>
    <content type="html">&lt;div&gt;&lt;img src=&quot;https://www.liftosaur.com/blog/images/new-experimental-program-editor-intro.jpg&quot; width=&quot;100%&quot; alt=&quot;Buff guy writing notes&quot; /&gt;&lt;/div&gt;
&lt;p&gt;In the latest post I tried to &lt;a href=&quot;https://www.liftosaur.com/blog/posts/combine-workout-planner-and-liftoscript/&quot;&gt;envision&lt;/a&gt; what would it take to combine Planner syntax with Liftoscript, and write Liftosaur programs using that new syntax. And it&#39;s finally here!&lt;/p&gt;
&lt;p&gt;IMHO it worked out amazingly! I really like the new syntax, IMHO it makes it WAAAY simpler to write programs, they&#39;re easier to comprehend. Each program is essentially just a blob of text, it&#39;s easy to share it, edit it, etc. I can&#39;t wait to see what y&#39;all going to create in it! :)&lt;/p&gt;
&lt;p&gt;So, there&#39;s now a way to create &amp;quot;experimental&amp;quot; Liftosaur program - if you select &amp;quot;Create experimental program&amp;quot; in the
program creation modal in the app. There, you use Workout Planner syntax to create those new programs - you just type
exercises and sets x reps you want to have.&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;https://www.liftosaur.com/blog/images/new-experimental-program-editor-create.png&quot; width=&quot;300&quot; alt=&quot;Liftosaur screen to create new experimental program&quot; /&gt;&lt;/div&gt;
&lt;p&gt;I described it in one of the previous posts, but will quickly remind you here.&lt;/p&gt;
&lt;h2&gt;Reminder on the existing Workout Planner syntax&lt;/h2&gt;
&lt;h3&gt;Basics&lt;/h3&gt;
&lt;p&gt;Each exercise goes on a separate line. It consists of sections separated by a slash (&lt;code&gt;/&lt;/code&gt;). First should go the exercise name, and then in any order you can put the sections. The simplest exercise is written like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can do rep ranges too:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8-12&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can list multiple sets, separated by commas, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x5&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x3&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x1&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That would be 8 sets total - first 5 reps, then 3 reps, 1 rep, and then 5 sets of 5 reps.&lt;/p&gt;
&lt;p&gt;If you don&#39;t specify the weight, it&#39;ll use the weight calculated from
&lt;abbr title=&quot;RPE - Rate of Perceived Exertion - is a way to measure how close you are to failure. It&#39;s a scale from 1 to 10, where 1 is very easy, and 10 is failure.&quot;&gt;RPE&lt;/abbr&gt;
tables, like &lt;a href=&quot;https://articles.reactivetrainingsystems.com/wp-content/uploads/2015/11/E1RM-TABLE.png&quot;&gt;this one&lt;/a&gt;. By default it&#39;ll assume you want to do exercises til failure (@10 RPE), so e.g. if you write:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x12&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It&#39;ll check in the RPE table that for if you want to do 12 reps til failure (@10 RPE), you probably should use 65% of 1RM, so it&#39;ll set the weight to 65% of 1RM under the hood. If you don&#39;t want to go to full failure, you can specify desired RPE:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x12&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Then the weight would be lower - 60%. You can specify the weight explicitly, as a percentage of 1RM, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x12&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Or you can specify the weight in kg/lb, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x12&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60kg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;RPE, percentage and weight can be specified for each set or range of sets individually, so you can mix and match:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x3&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@9&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x1&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@10&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;50%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can specify the rest time. E.g. this is how you could do &lt;strong&gt;myo-reps&lt;/strong&gt; - i.e. doing heavy 12, and then doing 5x5 with short rest times and same weight:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x12&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;20s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;20s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can also specify the rest time, weight, 1RM percentage and RPE also, for all sets, so you don&#39;t have to repeat yourself. Do it in a separate section like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x12&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;20s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;To add AMRAP sets, add &lt;code&gt;+&lt;/code&gt; after the reps number. And to log RPE, add &lt;code&gt;+&lt;/code&gt; after the RPE number. Like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;4x5&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x5+&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If you want to enable &amp;quot;Quick add sets&amp;quot; feature (where you may have more sets than you planned), add &lt;code&gt;+&lt;/code&gt; after the set number:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3+x5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;So, an example workout may look something like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;
Incline Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8-12&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;90s&lt;/span&gt;
Skullcrusher &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x15&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt;
Lateral Raise &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x15&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;By default, it&#39;ll use default equipment - e.g. for &lt;strong&gt;Bench Press&lt;/strong&gt; it&#39;ll use &lt;strong&gt;Barbell&lt;/strong&gt;. If you want to specify different equipment, add it after the exercise name, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press, Dumbbell &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;Progressions&lt;/h3&gt;
&lt;p&gt;You can also specify the progressive overload for each exercise. There&#39;re 3 built-in types of progressive overload:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linear Progression (&lt;strong&gt;lp&lt;/strong&gt;) - increase or decrease the weight by a fixed amount or percentage after N attempts.&lt;/li&gt;
&lt;li&gt;Double Progression (&lt;strong&gt;dp&lt;/strong&gt;) - increase the reps within a range, then reset the reps and increase the weight.&lt;/li&gt;
&lt;li&gt;Reps Sum (&lt;strong&gt;sum&lt;/strong&gt;) - increase the weight if the total sum of reps of all sets more than the threshold.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can add a progression like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That will increase the weight by 5lb after 3 successful attempts. Or like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;dp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;8&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;12&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It will keep increasing reps from 8 to 12, and then reset the reps to 8 and increase the weight by 5lb.&lt;/p&gt;
&lt;h3&gt;Full mode, weeks and days&lt;/h3&gt;
&lt;p&gt;There&#39;s a way to switch to the &amp;quot;Full Mode&amp;quot;, where your whole program is just one blob of text. There, to specify different weeks/days, you use this syntax:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 2
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x4&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;I.e. prefixing week names with &lt;code&gt;#&lt;/code&gt;, and day names with &lt;code&gt;##&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Descriptions&lt;/h3&gt;
&lt;p&gt;You can add descriptions to exercises via &lt;code&gt;//&lt;/code&gt; comments, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-comment&quot;&gt;// Pause **2 seconds** at the bottom
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can use Markdown syntax there, and also the descriptions would be reused in the following weeks/days, until you overwrite them. E.g. if you have description on Week 1 and Week 3, Week 2 would reuse Week 1 description, and Week 4+ would reuse Week 3 description. You can stop that by adding empty &lt;code&gt;//&lt;/code&gt; comment, like this, then this week and following weeks won&#39;t reuse the description. Like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;// Pause **2 seconds** at the bottom
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 3
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;//
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 4
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;In this case, Week 1 and Week 2 will have the description about the pause, and Week 3 and Week 4 won&#39;t.&lt;/p&gt;
&lt;h3&gt;Exercise Labels&lt;/h3&gt;
&lt;p&gt;If you want the same exercises to be separate (e.g. you have Squat as a main lift, and Squat as an accessory lift, and
you want to apply different progressions for them), you can mark exercises with different labels. Like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;main: Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;accessory: Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;dp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;8&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;12&lt;/span&gt;)&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Set Labels&lt;/h3&gt;
&lt;p&gt;If you can add labels to the sets (8 characters max!), just putting them in parentheses &lt;code&gt;()&lt;/code&gt; when you specify the sets x reps:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;4x5&lt;/span&gt; (Main), &lt;span class=&quot;tok-atom&quot;&gt;1x5+&lt;/span&gt; (AMRAP) &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;Warmups&lt;/h3&gt;
&lt;p&gt;By default, it will add some warmups, but if you want to change that, you can use &lt;code&gt;warmup&lt;/code&gt; section, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;warmup&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;1x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;45lb&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;135lb&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x3&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Syntax is very similar to the regular sets x reps, but you cannot specify timer and RPE there. Also, percentages are not
1RM percentages, but percentages of the weight of the first set.&lt;/p&gt;
&lt;p&gt;If you don&#39;t want warmups at all, you can specify &lt;code&gt;warmup: none&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;warmup&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;none&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2&gt;New syntax&lt;/h2&gt;
&lt;p&gt;The new experimental programs work somewhat different from the former Liftosaur programs.&lt;/p&gt;
&lt;p&gt;Previously, it was all about state variables. You can define them, and use them in weights, reps, timer, etc. And by changing them in the Finish Day Script, you can program progressive overload.&lt;/p&gt;
&lt;p&gt;It&#39;s quite different in the new programs. You still can use state variables, but mostly to remember some state through the workouts - like number of successful workouts in a row, or some weight you had at the beginning of the mesocycle, or something like that. Most of the time you won&#39;t need them.&lt;/p&gt;
&lt;p&gt;Instead, you&#39;ll change the weights, reps, timer and RPE directly in the exercises. And for that, you&#39;ll use the Liftoscript
together with the planner syntax. There&#39;s a new &lt;code&gt;progress&lt;/code&gt; property that you can apply to the exercise - &lt;code&gt;custom()&lt;/code&gt;. It looks like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;100lb&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt;
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note that we include Liftoscript inside &lt;code&gt;{~ ~}&lt;/code&gt; - curly braces with tildas! This is so that we can distinguish Liftoscript syntax from the regular Workout Planner syntax.&lt;/p&gt;
&lt;p&gt;So, instead of changing the state variable &lt;code&gt;state.weight&lt;/code&gt;, you just directly change the built-in variable &lt;code&gt;weights&lt;/code&gt;. And when you finish the workout and open the program again, you&#39;ll see that weight went up and the program now looks like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;105lb&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt;
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Instead of &lt;code&gt;100lb&lt;/code&gt; now it says &lt;code&gt;105lb&lt;/code&gt;! And that&#39;s another important feature of the new programs - they will update based on the progressive overload. If you update reps, weight, timer, etc in your &lt;code&gt;custom() {~ ~}&lt;/code&gt; block, it will be reflected in the program text.&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;105lb&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;30s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/// Update weights of all sets&lt;/span&gt;
    &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/// Set reps of all sets&lt;/span&gt;
    &lt;span class=&quot;tok-keyword&quot;&gt;timer&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;90&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/// Set timers between all sets&lt;/span&gt;
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;After completion, it&#39;ll look like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x7&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;110lb&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;90s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/// Update weights of all sets&lt;/span&gt;
    &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/// Set reps of all sets&lt;/span&gt;
    &lt;span class=&quot;tok-keyword&quot;&gt;timer&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;90&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/// Set timers between all sets&lt;/span&gt;
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Similar to old Liftosaur programs, you can specify set variations. You don&#39;t need to use them for various weeks/days like in the old editor, it&#39;s more for falling back to other set schemes e.g. if you failed to finish sets.&lt;/p&gt;
&lt;p&gt;To add set variations, just list sets x reps in another section, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;2x7&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (!(&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;)) {
    &lt;span class=&quot;tok-keyword&quot;&gt;setVariationIndex&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Here you have 2 set variations - &lt;code&gt;3x5&lt;/code&gt; and &lt;code&gt;2x7&lt;/code&gt;. You can switch between set variations via a special &lt;code&gt;setVariationIndex&lt;/code&gt; variable. By default, first set variation is selected, and if you want to use another one, you add exclamation mark (&lt;code&gt;!&lt;/code&gt;) in front of it, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ! &lt;span class=&quot;tok-atom&quot;&gt;2x7&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (!(&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;)) {
    &lt;span class=&quot;tok-keyword&quot;&gt;setVariationIndex&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now, &lt;code&gt;2x7&lt;/code&gt; would be selected. After finishing a workout, when it runs the finish day script, it&#39;ll automatically add exclamation mark to the newly selected set variation if it&#39;s not the first one.&lt;/p&gt;
&lt;p&gt;When you update weights, reps, etc - you can target specific sets, set variations, days or weeks. Like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x7&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;110lb&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;90s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;] += &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/// Only update weight of 1st set&lt;/span&gt;
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;:&lt;span class=&quot;tok-number&quot;&gt;2&lt;/span&gt;] += &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/// Only update 2st set of 1st set variation&lt;/span&gt;
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;:*:&lt;span class=&quot;tok-number&quot;&gt;2&lt;/span&gt;] += &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/// Only update 2st set of ALL set variations on the first day&lt;/span&gt;
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;3&lt;/span&gt;:&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;:*:&lt;span class=&quot;tok-number&quot;&gt;2&lt;/span&gt;] += &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/// Only update 2st set of ALL set variations on the first day of 3rd week&lt;/span&gt;
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;So, the syntax is &lt;code&gt;reps[week:day:setvariation:set]&lt;/code&gt;. You can use &lt;code&gt;*&lt;/code&gt; instead of a number to target all weeks/days/sets.
If you omit week, day, set variation or set, it&#39;ll assume it&#39;s &lt;code&gt;*&lt;/code&gt; for them. I.e. &lt;code&gt;weights[1:2]&lt;/code&gt; is the same as &lt;code&gt;weights[*:*:1:2]&lt;/code&gt;, &lt;code&gt;weights[1]&lt;/code&gt; is the same as &lt;code&gt;weights[*:*:*:1]&lt;/code&gt;, and &lt;code&gt;weights&lt;/code&gt; is the same as &lt;code&gt;weights[*:*:*:*]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you want to use state variables, you need to specify them inside &lt;code&gt;custom()&lt;/code&gt; parentheses, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x7&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;110lb&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;90s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;increment: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;) {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; += state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;increment&lt;/span&gt;
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It will create a state variable &lt;code&gt;increment&lt;/code&gt;, which you can use in your Finish Day Script.&lt;/p&gt;
&lt;p&gt;You can also reuse Finish Day Scripts, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;increment: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;) {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; += state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;increment&lt;/span&gt;
  }
~}
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x4&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;increment: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;) { ...Bench Press }&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Here, we reuse the same finish day script for both Bench Press and Squat, but for Squat we&#39;ll increment the weight by 10lb. I.e. we also can use state variables to parameterize our Finish Day Scripts. Note that in this case it&#39;s just curly braces &lt;code&gt;{ }&lt;/code&gt;, without tildas - because it&#39;s still Workout Planner syntax.&lt;/p&gt;
&lt;h2&gt;In-workout Updates&lt;/h2&gt;
&lt;p&gt;Similar to &lt;code&gt;progress: custom()&lt;/code&gt;, you can use another property: &lt;code&gt;update: custom()&lt;/code&gt;. It won&#39;t change the program,
but it will change &lt;strong&gt;currently ongoing workout&lt;/strong&gt;. You can use it to update sets based on your performance.
E.g. adjust weights if RPE was higher or lower than expected, or if you did more or less reps than expected during AMRAP.&lt;/p&gt;
&lt;p&gt;It&#39;s almost the same script as in &lt;code&gt;progress: custom()&lt;/code&gt;, just you cannot use state variables, and you cannot change the program - you can change only the current workout. You can access any other variables, and a special variable &lt;code&gt;setIndex&lt;/code&gt;, that is set to the index of the set you just finished.&lt;/p&gt;
&lt;p&gt;Those scripts are run after completing any set, or changing completed reps on any set. You can change:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;reps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;minReps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;weights&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RPE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can either target all sets, or specific sets. To target all sets, you could do:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;reps = 5
weights&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;*] = 100lb&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;and to target specific set, you could do:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;reps&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;2&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; = 5
weights&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;1&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; = 100lb&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can only change non-completed sets. You cannot change already completed sets, both successfully or unsuccessfully.&lt;/p&gt;
&lt;p&gt;For example, if you want to adjust half-sets after the first AMRAP set, you can do:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x6+&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3x3&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;update&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;setIndex&lt;/span&gt; == &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt; = &lt;span class=&quot;tok-keyword&quot;&gt;floor&lt;/span&gt;(&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;] / &lt;span class=&quot;tok-number&quot;&gt;2&lt;/span&gt;)
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That will change all the rest sets after finishing the first AMRAP set, and will set reps == half of the reps of the 1st set.&lt;/p&gt;
&lt;h2&gt;Percentages&lt;/h2&gt;
&lt;p&gt;I added new numeric type to Liftoscript - percentage (both in old and new programs!). You can set percentages in state variables, use them in weight expressions, etc. Percentages mean % of 1RM. So, you can e.g. write in your weight expression field &lt;code&gt;50%&lt;/code&gt;, and it&#39;ll be 50% of 1RM for that exercise.&lt;/p&gt;
&lt;h2&gt;Some features were removed&lt;/h2&gt;
&lt;p&gt;Two features were removed from the Liftoscript syntax:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reusing of previous week sets x reps via &lt;code&gt;...&lt;/code&gt;, like &lt;code&gt;Bench Press / ...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Omitting set number when there&#39;s just one set. I.e. previously you could write &lt;code&gt;5 50lb&lt;/code&gt; for 5 reps and 50lb. Now, you have to specify sets too, i.e. &lt;code&gt;1x5 50lb&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I had to kill reusing sets x reps because over time weeks can go out of sync, e.g. if you increased reps of the exercise on week 2 or something like that. And for the set numbers - it&#39;s to make it more future-proof, when I add e.g. time-based exercises.&lt;/p&gt;
&lt;h2&gt;Things that are not yet implemented (but will be soon!)&lt;/h2&gt;
&lt;p&gt;Comments via triple slash &lt;code&gt;///&lt;/code&gt; are not supported yet. I mean you can add them, but they will be wiped out after finishing a workout.&lt;/p&gt;
&lt;h2&gt;Examples&lt;/h2&gt;
&lt;p&gt;So, using all of that, here how we could implement various programs:&lt;/p&gt;
&lt;h3&gt;Stronglifts 5x5:&lt;/h3&gt;
&lt;p&gt;That one is pretty simple:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Workout A
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;)
Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;)
Bent Over Row &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;)
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Workout B
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;)
Overhead Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;)
Deadlift &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;)&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Greyskull LP&lt;/h3&gt;
&lt;p&gt;Also pretty trivial :)&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Workout A
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;2x5&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x5+&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;warmup&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;45lb&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;55%&lt;/span&gt; , &lt;span class=&quot;tok-atom&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;70%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;85%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;2.5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;)
Bent Over Row &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;2x5&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x5+&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;2.5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;)
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;2x5&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x5+&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;warmup&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;45lb&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;55%&lt;/span&gt; , &lt;span class=&quot;tok-atom&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;70%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;85%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;2.5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;)
Triceps Extension &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;2x12&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x12+&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;2.5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;)
Ab Wheel &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;2x12&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x12+&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;0lb&lt;/span&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Workout B
&lt;/span&gt;Overhead Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;2x5&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x5+&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;warmup&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;45lb&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;55%&lt;/span&gt; , &lt;span class=&quot;tok-atom&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;70%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;85%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;2.5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;)
Chin Up &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;2x5&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x5+&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;2.5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;)
Deadlift &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;2x5&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x5+&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;warmup&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;45lb&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;55%&lt;/span&gt; , &lt;span class=&quot;tok-atom&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;70%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;85%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;)
Bicep Curl &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;2x12&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x12+&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;2.5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;)
Shrug &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;2x12&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x12+&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;0&lt;/span&gt;)&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;GZCLP&lt;/h3&gt;
&lt;p&gt;That one really challenges the approach :) There&#39;s a lot of logic, several set variations, custom progressions, etc.
But we still can do it! It&#39;d look like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;/// Note that we specify 4 set variations. The last one is a testing week,
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;/// to get the new weight before going back to the first set.
&lt;/span&gt;t1: Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x3&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;6x2&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;10x1&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x5&lt;/span&gt; (5RM Test) &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;increase: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;) {~
  &lt;span class=&quot;tok-comment&quot;&gt;/// Fourth set is 5RM retesting week. So, we&#39;ll use 85% of the new 5RM for the weights,&lt;/span&gt;
  &lt;span class=&quot;tok-comment&quot;&gt;/// and also we&#39;ll update our 1RM for the exercise based on that - and that value would be transferrable&lt;/span&gt;
  &lt;span class=&quot;tok-comment&quot;&gt;/// to other programs too!&lt;/span&gt;
  if (&lt;span class=&quot;tok-keyword&quot;&gt;setVariationIndex&lt;/span&gt; == &lt;span class=&quot;tok-number&quot;&gt;4&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;setVariationIndex&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; = &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;] * &lt;span class=&quot;tok-number&quot;&gt;0.85&lt;/span&gt;
    &lt;span class=&quot;tok-keyword&quot;&gt;rm1&lt;/span&gt; = &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;] / &lt;span class=&quot;tok-keyword&quot;&gt;rpeMultiplier&lt;/span&gt;(&lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;, &lt;span class=&quot;tok-number&quot;&gt;10&lt;/span&gt;)
  } else if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; = &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-keyword&quot;&gt;ns&lt;/span&gt;] + state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;increase&lt;/span&gt;
  } else {
    &lt;span class=&quot;tok-keyword&quot;&gt;setVariationIndex&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;
  }
~}
&lt;span class=&quot;tok-comment&quot;&gt;/// Here we have 3 set variations, and we use a state variable to remember stage 1 weight.
&lt;/span&gt;t2: Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x10&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;35%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;35%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x6&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;35%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;stage1weight: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;0lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;increase: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;stage3increase: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;) {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; = &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-keyword&quot;&gt;ns&lt;/span&gt;] + &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt;
  } else if (&lt;span class=&quot;tok-keyword&quot;&gt;setVariationIndex&lt;/span&gt; == &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;) {
    state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;stage1weight&lt;/span&gt; = &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-keyword&quot;&gt;ns&lt;/span&gt;]
    &lt;span class=&quot;tok-keyword&quot;&gt;setVariationIndex&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;
  } else if (&lt;span class=&quot;tok-keyword&quot;&gt;setVariationIndex&lt;/span&gt; == &lt;span class=&quot;tok-number&quot;&gt;2&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;setVariationIndex&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;
  } else {
    &lt;span class=&quot;tok-keyword&quot;&gt;setVariationIndex&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; = state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;stage1weight&lt;/span&gt; + state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;stage3increase&lt;/span&gt;
  }
~}
&lt;span class=&quot;tok-comment&quot;&gt;/// That one is simple - we just increase the weight by 5lb if we did 25 reps on the last set.
&lt;/span&gt;t3: Lat Pulldown &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x15&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x15+&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt;[&lt;span class=&quot;tok-keyword&quot;&gt;ns&lt;/span&gt;] &gt;= &lt;span class=&quot;tok-number&quot;&gt;25&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; = &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-keyword&quot;&gt;ns&lt;/span&gt;] + &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt;
  }
~}
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;/// All the logic is the same for all T1s, T2s and T3s, so we just reuse the Finish Day Scripts
&lt;/span&gt;t1: Overhead Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x3&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;6x2&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;10x1&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;increase: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;) { ...t1: Squat }
t2: Deadlift &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x10&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;35%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;35%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x6&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;35%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;stage1weight: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;0lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;increase: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;stage3increase: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;15lb&lt;/span&gt;) { ...t2: Bench Press }
t3: Bent Over Row &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x15&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x15+&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() { ...t3: Lat Pulldown }&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 3
&lt;/span&gt;t1: Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x3&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;6x2&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;10x1&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;increase: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;) { ...t1: Squat }
t2: Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x10&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;35%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;35%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x6&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;35%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;stage1weight: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;0lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;increase: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;stage3increase: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;15lb&lt;/span&gt;) { ...t2: Bench Press }
t3: Lat Pulldown &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x15&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x15+&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 4
&lt;/span&gt;t1: Deadlift &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x3&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;6x2&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;10x1&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;increase: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;) { ...t1: Squat }
t2: Overhead Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x10&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;35%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;35%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x6&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;35%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;stage1weight: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;0lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;increase: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;stage3increase: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;) { ...t2: Bench Press }
t3: Bent Over Row &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x15&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x15+&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Once you get familiar with the syntax, it&#39;s IMHO pretty easy to figure out how it works, just by reading the text.&lt;/p&gt;
&lt;h2&gt;App Changes&lt;/h2&gt;
&lt;p&gt;The edit program screen now looks very different from regular Liftosaur editor, and very similar to the Workout Planner web page. Similarly to the Workout Planner, you can see weekly/daily volume per muscle group, exercise intensity/volume undulation graphs week over week. You can also test your changes in program preview. It allows you to quickly simulate various scenarios, and make sure the logic you defined works as expected.&lt;/p&gt;
&lt;p&gt;You can also switch to Full Program Mode there, and there&#39;s new UI for rearranging weeks and days.&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;https://www.liftosaur.com/blog/images/new-experimental-program-editor-edit-program.png&quot; width=&quot;300&quot; alt=&quot;Liftosaur screen to edit experimental programs&quot; /&gt;&lt;/div&gt;
&lt;p&gt;Daily Stats and Exercise Graphs get enabled when you focus a cursor on specific day or exercise.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So, give it a try, and let me know what you think! I&#39;m really excited about this new syntax and new capabilities it unlocks. That was a huge rewrite of the Liftosaur engine, so let me know if something got broken.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Make new Liftosaur programs less repetitive</title>
    <link href="https://www.liftosaur.com/blog/posts/make-new-liftosaur-programs-less-repetetive/"/>
    <updated>2024-02-27T00:00:00Z</updated>
    <id>https://www.liftosaur.com/blog/posts/make-new-liftosaur-programs-less-repetetive/</id>
    <content type="html">&lt;div&gt;&lt;img src=&quot;https://www.liftosaur.com/blog/images/make-new-liftosaur-programs-less-repetetive-intro.jpg&quot; width=&quot;100%&quot; alt=&quot;Bart Simpson writing weightlifting program&quot; /&gt;&lt;/div&gt;
&lt;p&gt;I&#39;m currently working on porting over the built-in programs to the new syntax, and I&#39;ve noticed that it&#39;s quite repetitive. I expected that, and thought that with Find &amp;amp; Replace (since it&#39;s just one text file) it&#39;d be easy to edit the exercises in bulk. But in reality it&#39;s not that easy. It&#39;s more or less okay when you only create a new program, but if you need to make changes in it, you sometimes need to change it in like 30 places, and it&#39;s very easy to make a mistake.&lt;/p&gt;
&lt;p&gt;That&#39;s especially obvious with the long multi-week programs e.g. GZCL: Rippler or UHF. Like, I was writing those, and then noticed that I forgot to add last set as AMRAP. And I was already on the 7th week. Going back and adding that &lt;code&gt;+&lt;/code&gt; sign to every single exercise across all days and weeks is really brutal.&lt;/p&gt;
&lt;p&gt;One of the killer features of the old programs was &amp;quot;Reuse Logic&amp;quot;. You can define the exercise once, and then you can make other exercises reuse the logic of the original one. That was very convenient.&lt;/p&gt;
&lt;p&gt;I initially didn&#39;t want to add reusing syntax to the new programs, because it&#39;s very complex technically - because of the progressions. For example, you can mark some exercise as reusing another exercise&#39;s sets. But then, the original exercise progresses the reps, and the reused exercise - does not. So, we need to break reusing at this point, and change the program text.&lt;/p&gt;
&lt;p&gt;After writing some programs though, I realized that it&#39;s very important to add reusing logic. Otherwise, it&#39;s PITA to create programs in the new syntax.&lt;/p&gt;
&lt;p&gt;So, for that, I&#39;m planning to add 3 new features.&lt;/p&gt;
&lt;h2&gt;Reusing sets&lt;/h2&gt;
&lt;p&gt;We already have syntax for reusing progressions - like &lt;code&gt;progress: custom() { ...Bench Press }&lt;/code&gt;. Let&#39;s also add similar syntax for reusing sets. It will look like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;60s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Bench Press&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This will make the Squat reuse the sets of the Bench Press. So, if the Bench Press has 5x5 with 8 RPE and 60s rest timer, the Squat will also have that.&lt;/p&gt;
&lt;p&gt;If the Bench Press updates the weight though, after finishing the workout the program will look like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;60s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;105lb&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;60s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I.e. the app broke the reusing at this point, because Bench Press changed its weight, and they&#39;re not the same with Squat anymore.&lt;/p&gt;
&lt;p&gt;By default, it&#39;ll try to reuse the exercise from the same week at any day. But if there&#39;re 2 same exercises at the same week, it&#39;ll show an error. You can make it more specific like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;60s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Bench Press&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;2&lt;span class=&quot;tok-atom&quot;&gt;:&lt;/span&gt;4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This will make the Squat reuse the Bench Press from the 4th day of the 2nd week. I.e. it&#39;s &lt;code&gt;[week:day]&lt;/code&gt;. You can omit &lt;code&gt;week&lt;/code&gt;, and just specify &lt;code&gt;[day]&lt;/code&gt;, it&#39;ll try to find the exercise at the same week then.&lt;/p&gt;
&lt;p&gt;You can add overrides on top of the reused stuff, e.g. override weight, RPE or timer, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;60s&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;100lb&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;150lb&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This will reuse everything in Squat except the weight - it&#39;ll be &lt;code&gt;150lb&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Reusing exercises across weeks&lt;/h2&gt;
&lt;p&gt;Usually, weightlifting programs have pretty much the same exercises across all weeks, just changing weights/reps/RPE/etc week over week. Or sometimes not changing anything at all. In that case, it&#39;d be convenient to not write the same thing over and over across all weeks.&lt;/p&gt;
&lt;p&gt;For that, you&#39;ll be able to specify the week ranges for exercises. It&#39;ll look like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;1&lt;span class=&quot;tok-atom&quot;&gt;, &lt;/span&gt;2&lt;span class=&quot;tok-atom&quot;&gt;, &lt;/span&gt;4-8&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;90s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;100lb&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In the square brackets after the exercise name you can specify what weeks or week ranges you want to apply this exercise to. In this case, it&#39;ll apply to weeks 1, 2, and from 4 to 8. It&#39;ll show up at the same days within the weeks, after explicitly specified exercises.&lt;/p&gt;
&lt;p&gt;Combining with the &lt;strong&gt;reusing sets&lt;/strong&gt; feature, you can only define the exercise once at the first week, and then just reuse it across all weeks:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;60s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
Squat&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;1-8&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Bench Press&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Bench Press defines different set schemes across like 8 weeks. But Squat is only added at the first week, like &lt;code&gt;Squat[1-8]&lt;/code&gt;, and it&#39;ll reuse the Bench Press exercise across all 8 weeks.&lt;/p&gt;
&lt;p&gt;That should give you a quick way of defining exercises across all weeks. E.g. you want to add another T3 to the GZCL: Rippler. You just type something like this at the first week:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bicep Curl&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;1-8&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...t3: Lat Pulldown&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;and that&#39;s it! It&#39;ll show up across all weeks.&lt;/p&gt;
&lt;h2&gt;Template exercises&lt;/h2&gt;
&lt;p&gt;One popular approach people had in the old programs is to create &amp;quot;template&amp;quot; exercises. They exist in a program, and not added to any day/week, but other exercises reuse the logic of those &amp;quot;template&amp;quot; exercises.&lt;/p&gt;
&lt;p&gt;I think it&#39;s even more important to allow such &amp;quot;template&amp;quot; exercises in the new syntax. Sometimes, we may want to have an exercise we&#39;ll take the sets and progression from, but we don&#39;t want to add it to any day/week so that it wouldn&#39;t ever change. It&#39;d be just a &amp;quot;template&amp;quot; for other exercises.&lt;/p&gt;
&lt;p&gt;For that, there&#39;s going to be a new syntax &lt;code&gt;used: none&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;60s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;used&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;none&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This way &lt;code&gt;Bench Press&lt;/code&gt; will be a &amp;quot;template&amp;quot; exercise, and it won&#39;t show up at any day/week. But other exercises can reuse its sets and progression:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;60s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;used&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;none&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  &lt;span class=&quot;tok-comment&quot;&gt;// logic here&lt;/span&gt;
~}
&lt;p&gt;Squat&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;1-8&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() { ...Bench Press }&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Those 3 features IMHO should simplify the process of creating new programs A LOT. Let me know what you think - in the comments to the Reddit post!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Implementing localized pricing for your mobile app</title>
    <link href="https://www.liftosaur.com/blog/posts/implementing-localized-pricing-for-your-app/"/>
    <updated>2024-08-26T00:00:00Z</updated>
    <id>https://www.liftosaur.com/blog/posts/implementing-localized-pricing-for-your-app/</id>
    <content type="html">&lt;p&gt;&lt;img src=&quot;https://www.liftosaur.com/blog/images/implementing-localized-pricing.jpg&quot; alt=&quot;Implementing localized pricing for your mobile app&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR; I recently implemented localized pricing for the mobile app, which was suprisingly tricky. So, sharing my experience of fighting with Apple and Google APIs to make it work, and sharing Google Sheets and scripts to calculate and upload the prices.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Coming up with fair prices for various countries&lt;/h2&gt;
&lt;p&gt;I used to have flat pricing for all the users. But it&#39;s kinda unfair to charge the same amount for users from US and e.g. India or Phillipines - the purchase power is so different in those countries! In addition to that, Apple and Google try to take into account the local taxes, so sometimes the price gets even higher than in the US - e.g. if you set the price as $70, in Germany by default Apple / Google would set it to be around €80. And people were not super happy about that.&lt;/p&gt;
&lt;p&gt;So, I decided to do localized prices. But how to make sure I&#39;m setting a fair price for all those 170+ countries? I have no idea what&#39;s the cost of living there for most of them.&lt;/p&gt;
&lt;p&gt;I&#39;d frankly expect that Apple or Google solve that, and provide adjust the prices for various countries, but they don&#39;t really do that. So, we have to do it ourselves!&lt;/p&gt;
&lt;p&gt;To solve that, we could divice that country GDP per capita by the USA GCP per capita, and that&#39;s going to be our multiplier for the prices. It probably makes sense to cap it at the top and the bottom, so e.g. the monthly subscription price doesn&#39;t go higher than $4.99, and lower than $0.99. The top cap is because likely the most advertised price would be the US one, and people from other rich countries like Singapore or Schwitzerland would be disappointed they have to pay higher than US. And the bottom cap is to avoid super small prices like a tiny fraction of a dollar e.g. in Phillipines, where fees would eat the majority of the price.&lt;/p&gt;
&lt;p&gt;I found some data on GDP per capita on the Internet, dumped it into a Google Sheet, and calculated the multiplier. Then, added some columns to specify the base price in USD, then the adjusted price after applying multiplier in USD. Apple and Google require for some countries to provide the prices in local currencies, so I grabbed the exchange rates and created  columns with prices in local currencies.&lt;/p&gt;
&lt;p&gt;Then, to make the prices prettier (like a &amp;quot;marketing price&amp;quot;, with all those $4.99, $19.99) - added another set of columns that would round up the prices to make them look &amp;quot;markety&amp;quot;.&lt;/p&gt;
&lt;p&gt;It&#39;s a bit annoying that Apple and Google use a different set of countries, different country codes (Google uses 2-character ones like US or NL, and Apple uses 3-character like USA or NLD), and also different currencies per country - Google more often prefers local currency, and Apple fallbacks to USD. So, I had to create 2 separate sheets for Apple and Google prices.&lt;/p&gt;
&lt;h2&gt;My Google Sheet with prices&lt;/h2&gt;
&lt;p&gt;At the end, the sheets look like this: &lt;a href=&quot;https://docs.google.com/spreadsheets/d/1BwSqpkqa98nk9Gh7238U7KQbvdvh_tGlXmQKE0Druwk&quot;&gt;https://docs.google.com/spreadsheets/d/1BwSqpkqa98nk9Gh7238U7KQbvdvh_tGlXmQKE0Druwk&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There&#39;re 2 sheets for Apple and Google prices, and 2 auxiliary sheets for GDP multiplier and currency exchange rates.
You could use that to calculate your own localized prices. Just replace the values in &lt;strong&gt;&amp;quot;Monthly Base&amp;quot;&lt;/strong&gt;, &lt;strong&gt;&amp;quot;Yearly Base&amp;quot;&lt;/strong&gt; and &lt;strong&gt;&amp;quot;Lifetime Base&amp;quot;&lt;/strong&gt; with your USD prices, and maybe (if you want) adjust the caps in the formulas for &lt;strong&gt;&amp;quot;Monthly Adj US&amp;quot;&lt;/strong&gt;, &lt;strong&gt;&amp;quot;Yearly Adj US&amp;quot;&lt;/strong&gt; and &lt;strong&gt;&amp;quot;Lifetime Adj US&amp;quot;&lt;/strong&gt;. By default the caps are $1-5 for montly, $8-40 for yearly, and $16-80 for lifetime.&lt;/p&gt;
&lt;p&gt;The final prices we&#39;ll use are in the &lt;strong&gt;Mkt Monthly/Yearly/Lifetime&lt;/strong&gt; columns - marketing prices in proper currencies.&lt;/p&gt;
&lt;p&gt;So, we have prices for Apple and Google, now need to upload them. I expected I&#39;d go to App Connect console or Google Play console, edit the subscription, and upload the prices as some CSV file. Ha, not that fast!&lt;/p&gt;
&lt;p&gt;Apparently in both Apple/Google I can only edit one price for one country at a time. So, I&#39;d need to manually enter 170 countries * 3 price plans * 2 platforms = ~1000 times! This is ridiculous. Given their UI is not blazing fast, it&#39;d take days to do so!&lt;/p&gt;
&lt;p&gt;Luckily, they have APIs, so we could use the APIs to upload the prices. Unfortunately, those APIs look like they were written by an unpaid intern without any supervision. They&#39;re confusing, buggy, return weird cryptic error messages, and even ChatGPT doesn&#39;t know how to use them properly.&lt;/p&gt;
&lt;p&gt;It took me a couple days to figure out how to upload the prices, but finally I&#39;m done, and I&#39;m happy to share the final scripts with you. The scripts are in JavaScript, so you&#39;d need NodeJS to run them.&lt;/p&gt;
&lt;h2&gt;Uploading prices to Google&lt;/h2&gt;
&lt;p&gt;The API and the way how prices for subscriptions and for in-app purchases work internally is VERY different in Apple and Google.&lt;/p&gt;
&lt;p&gt;In Google, we send ALL the prices for a subscription as one request. The API is very sensitive to the request body,
but the documentation is sparse, so it&#39;s hard to figure out what it expects. If you send something that Google doesn&#39;t expect, it just responds with generic &amp;quot;Internal error.&amp;quot;, and go figure what it doesn&#39;t like.&lt;/p&gt;
&lt;p&gt;Luckily, the response of listing all subscription info from API and the request body for updating a subscription is pretty much the same JSON, so we can use that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, we get all our subscriptions info via &lt;code&gt;androidpublisher.monetization.subscriptions.list&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Then, in the response, we modify &lt;code&gt;subscription.basePlans.regionalConfigs.price&lt;/code&gt; values.&lt;/li&gt;
&lt;li&gt;We use the modified response as a POST body for &lt;code&gt;androidpublisher.monetization.subscriptions.patch&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Similar for in-app purchases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, we get all our subscriptions info via &lt;code&gt;androidpublisher.inappproducts.list&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Then, in the response, we modify &lt;code&gt;product.prices&lt;/code&gt; values.&lt;/li&gt;
&lt;li&gt;We use the modified response as a POST body for &lt;code&gt;androidpublisher.inappproducts.patch&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For some reason, the number of countries supporting in-app billing is smaller that the subscription billing, so need
to filter out some countries as well.&lt;/p&gt;
&lt;p&gt;At the end, the script looks like this: &lt;a href=&quot;https://gist.github.com/astashov/705c45e5f8f6c89cc5f968ba128b1800&quot;&gt;https://gist.github.com/astashov/705c45e5f8f6c89cc5f968ba128b1800&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To run it, install &lt;code&gt;googleapis&lt;/code&gt; (&lt;code&gt;npm install googleapis&lt;/code&gt;), modify the script with your service account key, package name,
subscription/product ids, then make sure to download the CSV file from the sheet and name it &lt;code&gt;google_prices.csv&lt;/code&gt;, and run it as &lt;code&gt;node update_google_prices.js&lt;/code&gt;. Hopefully it&#39;ll work! :)&lt;/p&gt;
&lt;h2&gt;Uploading prices to Apple&lt;/h2&gt;
&lt;p&gt;For Google, the API was a bit tricky, but at the end we could just update all prices for each subscription with just one API call, and provide the prices directly.&lt;/p&gt;
&lt;p&gt;This is not the case at all for Apple, it would be too simple. You cannot just set the price for your subscription!
Instead, it has a list of &amp;quot;price points&amp;quot;, and you should assign that price point to your subscription and territory. I.e. if Apple only has price points $0.99 and $0.79 for Afganistan, you cannot set the price e.g. $0.85. You have to use the price points.&lt;/p&gt;
&lt;p&gt;Each country has a few hundreds of price points, and each price point has an id. To know the ids, you need to fetch the price points for each territory from their API. The API seems to have some rate limit, and also pagination with the page size limit, so make sure to not do it in parallel, and need to sometimes retry it too when it fails. Seems like sweet spot is to run 2-3 requests in parallel.
Then, you probably want to cache all those price points and their ids on disk to avoid slow repeated network calls when you restart the script.&lt;/p&gt;
&lt;p&gt;After you download all the price points, you need to find the closest price point to a price from the spreadsheet/CSV file, get id of that price point, and update the subscription and territory with that price point id. You can only update one price point per subscription and territory, but for some reason you can do it in bulk for in-app purchases and send all price points for all territories for an in-app purchase in one request.&lt;/p&gt;
&lt;p&gt;So, that&#39;s how it basically works:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fetch all the price points for the territories&lt;/li&gt;
&lt;li&gt;Find closest price point&lt;/li&gt;
&lt;li&gt;Update that price point for the subscription / territory&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And similarly for in-app purchases.&lt;/p&gt;
&lt;p&gt;The script looks like this: &lt;a href=&quot;https://gist.github.com/astashov/79dd4ef4e91ea012710145623bfe0984&quot;&gt;https://gist.github.com/astashov/79dd4ef4e91ea012710145623bfe0984&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To run it, install &lt;code&gt;jsonwebtoken&lt;/code&gt; and &lt;code&gt;node-fetch&lt;/code&gt; (&lt;code&gt;npm install jsonwebtoken node-fetch&lt;/code&gt;), modify the script with your keys, app id, subscription/product ids, etc, then make sure to download the CSV file from the sheet and name it &lt;code&gt;apple_prices.csv&lt;/code&gt;, and run it as &lt;code&gt;node update_apple_prices.js&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Changing the prices&lt;/h2&gt;
&lt;p&gt;After all of that, updating the prices for your app becomes a pretty trivial routine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open the spreadsheet, and update the monthly/yearly/lifetime prices in the &lt;strong&gt;Base&lt;/strong&gt; columns.&lt;/li&gt;
&lt;li&gt;Export the updated spreadsheets into &lt;code&gt;google_prices.csv&lt;/code&gt; and &lt;code&gt;apple_prices.csv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;node update_apple_prices.js&lt;/code&gt; and &lt;code&gt;node update_google_prices.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And that&#39;s it!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Overview of Liftosaur - scriptable weightlifting planner and tracker app</title>
    <link href="https://www.liftosaur.com/blog/posts/liftosaur-overview/"/>
    <updated>2025-09-01T00:00:00Z</updated>
    <id>https://www.liftosaur.com/blog/posts/liftosaur-overview/</id>
    <content type="html">&lt;a href=&quot;https://youtu.be/hr53rHRKsU4&quot; target=&quot;_blank&quot;&gt;
&lt;div&gt;&lt;img src=&quot;https://www.liftosaur.com/blog/images/youtube/20250901-liftosaur-overview.png&quot; width=&quot;100%&quot; alt=&quot;Youtube video preview&quot; /&gt;&lt;/div&gt;
&lt;div class=&quot;subscription&quot;&gt;Check out the video version of this post on Youtube!&lt;/div&gt;
&lt;/a&gt;
&lt;p&gt;In this post I want to go over the main features of Liftosaur - a scriptable workout planner and tracker app.
This is probably the most powerful lifting app on the market, incredibly flexible and customizable. You can
implement any program you want, and change every bit of it. You can specify any progressive overload logic you want, even script it if it&#39;s somewhat unique.&lt;/p&gt;
&lt;p&gt;The app is pretty unique, so hopefully this post may help you decide whether you want to use it as your lifting app.&lt;/p&gt;
&lt;h2&gt;Onboarding&lt;/h2&gt;
&lt;div class=&quot;image-with-text&quot;&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/liftosaur-overview/first.png&quot; alt=&quot;Initial screen&quot; /&gt;
  &lt;div&gt;
    When we first start the app, we&#39;re greeted by the welcome screen, and brief
    overview of featuers in stories-like UI.
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;image-with-text image-with-text-reverse&quot;&gt;
  &lt;div&gt;
    We proceed, and pick the unit here.
    I personally hate long onboarding flows, and I tried to be really considerate,
    and only ask the things that are important to starting using the app.
    E.g. unit here is important because the app will need to convert the weights
    in built-in programs into that unit when you pick them.
  &lt;/div&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/liftosaur-overview/unit.png&quot; alt=&quot;Unit pick screen&quot; /&gt;
&lt;/div&gt;
&lt;div class=&quot;image-with-text&quot;&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/liftosaur-overview/equipment.png&quot; alt=&quot;Equipment screen&quot; /&gt;
  &lt;div&gt;
    Next we can configure the equipment we have, and hide the ones we don&#39;t have. Equipment is used for rounding weights, and for the plates calculator. For each equipment we can specify the bar and the plates we have, which would be used for rounding the weights. Totally optional and we can do it later, so we can skip for now.
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;image-with-text image-with-text-reverse&quot;&gt;
  &lt;div&gt;
    Now we can choose a built-in program, create our own, or just go without a program. Liftosaur really works best if you have the program you follow, but there&#39;re also ways to gradually build it over time as well. There&#39;re filters at the top to help you find the program that fits your goals, your schedule, how much time you can spend in a gym, etc. Let&#39;s choose e.g. GZCLP to start with.
  &lt;/div&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/liftosaur-overview/programs.png&quot; alt=&quot;Programs screen&quot; /&gt;
&lt;/div&gt;
&lt;div class=&quot;image-with-text&quot;&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/liftosaur-overview/rm1.png&quot; alt=&quot;1 Rep Max screen&quot; /&gt;
  &lt;div&gt;
    GZCLP uses 1RM to calculate starting weights, so we can specify them now (or later, we can skip it as well).
    I can enter my 1RM here, and also use 1RM calculator if I don&#39;t know my 1RMs, but know e.g. 5RMs.
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Workouts&lt;/h2&gt;
&lt;div class=&quot;image-with-text&quot;&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/liftosaur-overview/history.png&quot; alt=&quot;History screen&quot; /&gt;
  &lt;div&gt;
    Okay, now we can do workouts. We can just go ahead and use the default GZCLP, and later I can show how we could also change every bit of it.
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;image-with-text image-with-text-reverse&quot;&gt;
  &lt;div&gt;
    &lt;p&gt;
      GZCLP uses 1RM to calculate starting weights, so we can specify them now (or later, we can skip it as well).
      I can enter my 1RM here, and also use 1RM calculator if I don&#39;t know my 1RMs, but know e.g. 5RMs.
    &lt;/p&gt;
    &lt;p&gt;
      On the workout screen, all the weights and reps already prefilled, so for most programs you just tap the check icon when you finish what&#39;s prescribed.
    &lt;/p&gt;
    &lt;p&gt;
      The weights are rounded to your equipment, so make sure you&#39;ve got the right bar weight and plates set up, and it&#39;ll set the right weights you can do with your equipment.
    &lt;/p&gt;
    &lt;p&gt;
      When you finish a set, the rest timer shows up. You can specify the rest time either globally, or per program exercise.
    &lt;/p&gt;
    &lt;p&gt;
      Let&#39;s say we did successfully all the sets, and we can see it will increase the weight next time. We can switch over to the next exercise.
    &lt;/p&gt;
  &lt;/div&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/liftosaur-overview/t1.png&quot; alt=&quot;T1&quot; /&gt;
&lt;/div&gt;
&lt;div class=&quot;image-with-text&quot;&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/liftosaur-overview/t2.png&quot; alt=&quot;T2&quot; /&gt;
  &lt;div&gt;
    &lt;p&gt;
      For the T2 exercise, we can pretend we failed the last set. And we can see the app will switch to a different set scheme (stage 2) next time - instead of 3x10, we&#39;ll do 3x8 next time.
    &lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;image-with-text image-with-text-reverse&quot;&gt;
  &lt;div&gt;
    &lt;p&gt;
      Now we&#39;re done with the workout, and we can optionally share it in social media or privately, sync to Apple Health, and continue.
    &lt;/p&gt;
  &lt;/div&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/liftosaur-overview/finish.png&quot; alt=&quot;Finish Day screen&quot; /&gt;
&lt;/div&gt;
&lt;h2&gt;Programs&lt;/h2&gt;
&lt;p&gt;We can see that workout in the history now. In the app, there&#39;re 2 main entities - workouts and programs. The only thing that the program does - it defines what the next workout will look like, nothing else.
So, you can switch programs at any time, run multiple at the same time, go without program - the only thing that it&#39;ll change - what the next workout will look like.&lt;/p&gt;
&lt;p&gt;When you finish a workout, it changes the current program. The program under the hood is just plain text, so when you finish a workout - it rewrites that text, with the new weights, reps, sets, etc. That&#39;s how it does the progression - it changes the program when workout is finished. All the scripts defined in the program are evaluated, and new program text is generated.&lt;/p&gt;
&lt;div class=&quot;image-with-text&quot;&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/liftosaur-overview/programtext.png&quot; alt=&quot;Program Text Screen&quot; /&gt;
  &lt;div&gt;
    &lt;p&gt;
      If we go to Program screen, we could actually see and edit the program text. Go to Edit, then tap this coding icon. The program is written using special markup/scripting language called &lt;a href=&quot;https://www.liftosaur.com/docs&quot;&gt;Liftoscript&lt;/a&gt;. You can define the weeks, days, exercises, and write the progression logic right there. The syntax is simple, and you don&#39;t have to define scripts, you can just use built-in progressions, like linear or double progression.
    &lt;/p&gt;
    &lt;p&gt;
      Programs being just text is a very powerful concept. You can easily share them, you can store them even in your Notes, you can edit them in any text editor. Liftosaur provides IDE for that, so it&#39;d have autocomplete, error checking, etc - both in the app or on the web, in a browser. Editing a lot of text on a phone could be quite inconvenient, so it&#39;s usually easier to do that from a laptop.
    &lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;image-with-text image-with-text-reverse&quot;&gt;
  &lt;div&gt;
    &lt;p&gt;
      You don&#39;t have to use the text editing though (although it&#39;s IMHO the most convenient for large-scope changes). Everything you can do through the text - you can also do through UI, and it&#39;s sometimes more convenient if you just need to do small tweaks in your program, like adjust the weight or reps of some exercise, tweak the increments of progression, etc.
    &lt;/p&gt;
    &lt;p&gt;
      Like, this is how I could change the weight, or adjust the increment for progression - go to T1, and do that there. I could do that per set, or just across all of the sets. This is especially useful if you do multi-week programs, so you could tweak weights used across multiple weeks/days.
    &lt;/p&gt;
  &lt;/div&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/liftosaur-overview/editexercise.png&quot; alt=&quot;Edit Exercise Screen&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;Now, this is a vanilla GZCLP with just one T3 exercise added - it&#39;s a lat pulldown or bent over row. I want to tweak the program, and maybe add a few more T3 exercises. I personally like to do that on my laptop, just because there&#39;s larger screen and a keyboard to type faster. Let&#39;s check out the web editor for that.&lt;/p&gt;
&lt;h2&gt;Web Editor&lt;/h2&gt;
&lt;p&gt;If you open &lt;a href=&quot;https://www.liftosaur.com/&quot;&gt;liftosaur.com&lt;/a&gt;, you can log in there, go to Your Programs, and edit our GZCLP program.&lt;/p&gt;
&lt;div class=&quot;highlight-block&quot;&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/liftosaur-overview/website.png&quot; width=&quot;600&quot; alt=&quot;Website screenshot&quot; /&gt;
&lt;/div&gt;
&lt;div class=&quot;highlight-block&quot;&gt;
  &lt;img src=&quot;https://www.liftosaur.com/blog/images/liftosaur-overview/webeditor.png&quot; width=&quot;600&quot; alt=&quot;Web Editor screenshot&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;Here, on the right you can see Week Stats. This is rather important info that helps you to ensure you&#39;ve got enough volume for the muscle groups you care about. Let&#39;s walk it over.&lt;/p&gt;
&lt;p&gt;At the top I could see the total sets and split between strength and hypertrophy. It&#39;s simply - if it&#39;s &amp;gt;= 8 reps - it&#39;s hypertrophy. Otherwise - strength. Then, split between core, upper, lower, push and pull sets.&lt;/p&gt;
&lt;p&gt;The most important part is below though. It&#39;s a weekly number of sets per muscle group. Research shows that you need to hit muscles 10-20 sets per week, 2-3 days per week to get good growth. More for advanced lifters, less for beginners.
Here you can see that number per muscle group. If we hover over with the mouse, we&#39;ll see what exercises that comes from. Black means that exercise directly targets that muscle, gray - it&#39;s a synergist muscle. We use multiplier for synergist muscles, 0.5 by default.&lt;/p&gt;
&lt;p&gt;Colors and arrows show whether we&#39;re outside of the range - red means significantly outside, yellow - a bit outside, green - within the range. Arrow shows whether we need to increase or decrease the number of sets to get to the range. The range is defined in Settings, and you can change the range there, and also change the synergist multiplier.&lt;/p&gt;
&lt;p&gt;So, we can see we&#39;re kinda weak on biceps, triceps and forearms, so let&#39;s add some exercises to hit those muscles more.  We can tap on the muscle group there to filter by it, so for triceps we can add Skullcrushers, for biceps - bicep curls, can also add hammer curl for forearms too, and e.g. Triceps Extension.&lt;/p&gt;
&lt;p&gt;Now we can save it, and enjoy our updated GZCLP program. If we open the app now, it will sync the updated program, and we can do workouts with it.&lt;/p&gt;
&lt;p&gt;That&#39;s the main functionality of the app. There&#39;s more stuff - there&#39;re graphs, RPE support, body measurements, etc, etc, and the &lt;a href=&quot;https://www.liftosaur.com/docs&quot;&gt;Liftoscript language&lt;/a&gt; allows you to do a lot of things.&lt;/p&gt;
&lt;p&gt;So, check it out, and hopefully this gives you a good overview of the app!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Documentation | Liftosaur</title>
    <link href="https://www.liftosaur.com/blog/docs/"/>
    <updated>2026-10-09T02:50:33Z</updated>
    <id>https://www.liftosaur.com/blog/docs/</id>
    <content type="html">&lt;h3&gt;Contact Us&lt;/h3&gt;
&lt;p&gt;If you have any questions, don&#39;t hesitate to contact us at &lt;a href=&quot;mailto:info@liftosaur.com&quot;&gt;info@liftosaur.com&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Liftosaur is an app that combines both weightlifting programming part (that people sometimes use Google Sheets for) and workouts tracking part. I.e. it consists of 2 steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You choose and clone a built-in program &lt;strong&gt;or&lt;/strong&gt; create your own program. Program will prescribe what exercises to do over weeks/days, with what sets x reps x weight, and how to progress over time (i.e. applying progressive overload).&lt;/li&gt;
&lt;li&gt;You run that program and track your workouts and your progress.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any built-in program is built using existing Liftosaur features, so those programs are completely customizable - you can change every bit of them.&lt;/p&gt;
&lt;p&gt;Instead of clicking around to add exercises like in other apps, Liftosaur takes a different approach - you create the programs by typing your exercises, sets, reps, etc in a text field, using special syntax called &lt;strong&gt;Liftoscript&lt;/strong&gt;. There&#39;s some learning curve because of that, but the syntax is very simple and autocomplete helps, and once you get familiar with it - you can create and edit the programs very quickly and efficiently.&lt;/p&gt;
&lt;p&gt;The syntax is pretty similar to how people already often describe their programs. Let&#39;s take a look!&lt;/p&gt;
&lt;h2&gt;Basics&lt;/h2&gt;
&lt;p&gt;Each exercise goes on a separate line. It consists of sections separated by a slash (&lt;code&gt;/&lt;/code&gt;). First goes the exercise name, and then in any order - the sections. The simplest exercise is written like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can do rep ranges too:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8-12&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can list multiple sets, separated by commas, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x5&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x3&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x1&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That would be 8 sets total - first 5 reps, then 3 reps, 1 rep, and then 5 sets of 5 reps.&lt;/p&gt;
&lt;p&gt;If you specify neither the weight nor the
&lt;abbr title=&quot;RPE - Rate of Perceived Exertion - is a way to measure how close you are to failure. It&#39;s a scale from 1 to 10, where 1 is very easy, and 10 is failure.&quot;&gt;RPE&lt;/abbr&gt;,
like here:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x12&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;then the weight field will be empty during the workout, and you&#39;ll have to enter the weight yourself to finish a set.&lt;/p&gt;
&lt;p&gt;But if you specify the RPE, the weight will be calculated from RPE tables, like &lt;a href=&quot;https://www.liftosaur.com/blog/images/rpetable.png&quot;&gt;this one&lt;/a&gt;. So, if you write:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x12&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;it&#39;ll check in the RPE table that for 12 reps at @8 RPE you probably should use 60% of 1RM, so it&#39;ll set the weight to 60% of 1RM under the hood. @10 RPE means going til failure - for 12 reps that&#39;d be 65% of 1RM.&lt;/p&gt;
&lt;p&gt;You can specify the weight explicitly, as a percentage of 1RM, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x12&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Or you can specify the weight in kg/lb, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x12&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60kg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;RPE, percentage and weight can be specified for each set or range of sets individually, so you can mix and match:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x3&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@9&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x1&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@10&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;50%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can specify the rest time. E.g. this is how you could do &lt;strong&gt;myo-reps&lt;/strong&gt; - i.e. doing heavy 12, and then doing 5x5 with short rest times and same weight:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x12&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;20s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;20s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can also specify the rest time, weight, 1RM percentage and RPE also, for all sets, so you don&#39;t have to repeat yourself. Do it in a separate section like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x12&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;20s&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;To add AMRAP sets, add &lt;code&gt;+&lt;/code&gt; after the reps number. And to log RPE, add &lt;code&gt;+&lt;/code&gt; after the RPE number. Like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;4x5&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x5+&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If you want to enable &amp;quot;Quick add sets&amp;quot; feature (where you may have more sets than you planned), add &lt;code&gt;+&lt;/code&gt; after the set number:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3+x5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And if you want the app to ask you what was the weight you did (similar to AMRAP reps), you can add &lt;code&gt;+&lt;/code&gt; after the weight:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;100lb&lt;/span&gt;+&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x6&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;70%&lt;/span&gt;+, &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;50%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;An example workout may look something like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;
Incline Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8-12&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;90s&lt;/span&gt;
Skullcrusher &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x15&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt;
Lateral Raise &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x15&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;By default, it&#39;ll use the default equipment - e.g. for &lt;strong&gt;Bench Press&lt;/strong&gt; it&#39;ll use &lt;strong&gt;Barbell&lt;/strong&gt;. If you want to specify a different equipment, then add it after the exercise name, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press, Dumbbell &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;Full mode, weeks and days&lt;/h3&gt;
&lt;p&gt;There&#39;s a way to switch to the &amp;quot;Full Mode&amp;quot;, where your whole program is just one blob of text. There, to specify different weeks/days, you use this syntax:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 2
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x4&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;I.e. prefixing week names with &lt;code&gt;#&lt;/code&gt;, and day names with &lt;code&gt;##&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Exercise Descriptions&lt;/h3&gt;
&lt;p&gt;You can add descriptions to exercises via &lt;code&gt;//&lt;/code&gt; comments, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-comment&quot;&gt;// Pause **2 seconds** at the bottom
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can use &lt;a href=&quot;https://www.markdownguide.org/cheat-sheet/&quot;&gt;Markdown&lt;/a&gt; syntax there, and also the descriptions would be reused in the following weeks/days, until you overwrite them. E.g. if you have description on Week 1 and Week 3, Week 2 would reuse Week 1 description, and Week 4+ would reuse Week 3 description. You can stop that by adding empty &lt;code&gt;//&lt;/code&gt; comment, like this, then this week and following weeks won&#39;t reuse the description. Like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;// Pause **2 seconds** at the bottom
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 3
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;//
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 4
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;In this case, Week 1 and Week 2 will have the description about the pause, and Week 3 and Week 4 won&#39;t.&lt;/p&gt;
&lt;h3&gt;Week and day descriptions&lt;/h3&gt;
&lt;p&gt;You can also add descriptions to weeks and days. You can also use &lt;a href=&quot;https://www.markdownguide.org/cheat-sheet/&quot;&gt;Markdown&lt;/a&gt; syntax there, and the day descriptions would be reused in the following days, until you overwrite them - same logic as for Exercise Descriptions.&lt;/p&gt;
&lt;p&gt;You can add them through UI, or in the full mode, you also can add them as comments to &lt;code&gt;# Week&lt;/code&gt; and &lt;code&gt;## Day&lt;/code&gt; lines, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-comment&quot;&gt;// This is a description for week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;// * Do this
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;// * Then do that
&lt;/span&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;
&lt;span class=&quot;tok-comment&quot;&gt;// This is a description for day 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;// **Very important to do this:**
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;Exercise Labels&lt;/h3&gt;
&lt;p&gt;If you want the same exercises to be separate (e.g. you have Squat as a main lift, and Squat as an accessory lift, and
you want to apply different progressions for them), you can mark exercises with different labels. Like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;main: Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;accessory: Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;dp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;8&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;12&lt;/span&gt;)&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Set Labels&lt;/h3&gt;
&lt;p&gt;If you can add labels to the sets (8 characters max!), just putting them in parentheses &lt;code&gt;()&lt;/code&gt; when you specify the sets x reps:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;4x5&lt;/span&gt; (Main), &lt;span class=&quot;tok-atom&quot;&gt;1x5+&lt;/span&gt; (AMRAP) &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;Warmups&lt;/h3&gt;
&lt;p&gt;By default, it will add some warmups, but if you want to change that, you can use &lt;code&gt;warmup&lt;/code&gt; section, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;warmup&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;1x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;45lb&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;135lb&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x3&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Syntax is very similar to the regular sets x reps, but you cannot specify timer and RPE there. Also, percentages are not
1RM percentages, but percentages of the weight of the first set.&lt;/p&gt;
&lt;p&gt;If you don&#39;t want warmups at all, you can specify &lt;code&gt;warmup: none&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;warmup&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;none&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;Supersets&lt;/h3&gt;
&lt;p&gt;You can group exercises into supersets via &lt;code&gt;superset&lt;/code&gt; section, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;superset&lt;/span&gt;: A
Deadlift &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;superset&lt;/span&gt;: A
Bent Over Row &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;superset&lt;/span&gt;: A
Bicep Curl &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That would group Squat, Deadlift and Bent Over Row into the &lt;strong&gt;Superset A&lt;/strong&gt;. When you run the workout, after you complete a set of Squat, it&#39;ll switch to Deadlift, then to Bent Over Row, and then back to Squat for the second round, and so on.&lt;/p&gt;
&lt;p&gt;The syntax looks like &lt;code&gt;superset: GROUP_NAME&lt;/code&gt;, where &lt;code&gt;GROUP_NAME&lt;/code&gt; could be any string.
By default they use superset default rest timer (which you can set up in Me -&amp;gt; Timers), but you can override it
with per-set timers. For example:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;superset&lt;/span&gt;: A
Deadlift &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;superset&lt;/span&gt;: A
Bent Over Row &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;180s&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;superset&lt;/span&gt;: A&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If you have superset default rest timer set to 15s, then Squat and Deadlift would use 15s rest between sets, and Bent Over Row would use 180s rest.&lt;/p&gt;
&lt;h3&gt;Progressive overload&lt;/h3&gt;
&lt;p&gt;It&#39;s very important to incorporate progressive overload into your workouts - how you will increase sets, reps, weight, etc over time, to give stimulus for your muscles to grow. In Liftosaur, you can specify progressions for your exercises, and the app will automatically change weights/reps/etc based on the conditions you specify.&lt;/p&gt;
&lt;p&gt;There&#39;re 3 built-in types of progressive overload:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linear Progression (&lt;strong&gt;lp&lt;/strong&gt;) - increase or decrease the weight by a fixed amount or percentage after N attempts.&lt;/li&gt;
&lt;li&gt;Double Progression (&lt;strong&gt;dp&lt;/strong&gt;) - increase the reps within a range, then reset the reps and increase the weight.&lt;/li&gt;
&lt;li&gt;Reps Sum (&lt;strong&gt;sum&lt;/strong&gt;) - increase the weight if the total sum of reps of all sets more than the threshold.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can add a progression like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That would increase the weight after each successful completion of 3 sets x 8 reps by 5lb.
You only need to add it to one of the days in your program per exercise, no need to repeat it week over week or day over day. It will be applied to all such exercises in a program. You may disable progression for specific days or weeks via &lt;code&gt;progress: none&lt;/code&gt; section - for example if that&#39;s a deload week. For example:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;/// We don&#39;t need to specify progress here, but it still would be applied - same lp(5lb)
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 3
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;/// That&#39;s a deload week - we want to disable progression for that day
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;none&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;If you try to specify different progressions for the same exercise in different weeks/days, it&#39;ll give you an error - the progressions are applied for an exercise across whole program. You cannot have e.g. linear progression on day 1, and double progression on day 2.&lt;/p&gt;
&lt;p&gt;There&#39;s a way to have e.g. 2 Bench Press exercises with different progressions though - you can add labels to exercises, and they would be considered different exercises in that case. Label is just some word before an exercise name, with a colon &lt;code&gt;:&lt;/code&gt; after it. For example - &lt;code&gt;aux: Bench Press&lt;/code&gt; or &lt;code&gt;strength: Squat&lt;/code&gt; or anything like that.&lt;/p&gt;
&lt;p&gt;So with labels, e.g. you have low-rep range Bench Press and high-rep range Bench Press in your program, and you want both of them have Double Progression, but in different ranges. You could do it like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;lowrep: Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x3&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;dp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;6&lt;/span&gt;)
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 2
&lt;/span&gt;highrep: Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;dp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;8&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;12&lt;/span&gt;)&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h4&gt;Linear Progression&lt;/h4&gt;
&lt;p&gt;Linear Progression is when you add weight after N (1 or more) successful finishing of all sets and reps, and optionally - also reducing the weight after N (1 or more) unsuccessful finishing of all sets and reps.&lt;/p&gt;
&lt;p&gt;You add Linear Progression to exercises by specifying &lt;code&gt;lp&lt;/code&gt; progress type, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In parenthesis after &lt;code&gt;lp&lt;/code&gt; you specify the weight values and conditions for the Linear Progression. It can take 6 arguments max, separated by commas, and this is their meaning:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;lp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;weight increase&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; increase attempts&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; current increase attempt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; weight decrease&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; decrease attempts&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; current decrease attempt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You don&#39;t have to specify all the arguments, for example if you don&#39;t want decreasing weights after failures - you may skip 3 last arguments.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-comment&quot;&gt;/// increase by 5lb after 1 successful attempt
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;/// increase by 5lb after 2 successful attempts
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;2&lt;/span&gt;)&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;/// increase by 5lb after 2 successful attempts, and already had 1 successful attempt
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;)&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;/// increase by 5lb after 2 successful attempt, and decrease by 10lb after 3 unsuccessful
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;3&lt;/span&gt;)&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;/// You can also use percentages for increase (instead of absolute values)
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5%&lt;/span&gt;)&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h4&gt;Double Progression&lt;/h4&gt;
&lt;p&gt;Double Progression is when you increase reps after successful finishing your sets x reps within some range, and after that - increase the weight. It&#39;s good for exercises when you cannot apply Linear Progression easily (e.g for Bicep Curl, adding 5lb each time would stop working quickly), or when you already past the phase when you can add 5lb each session even for big lifts like Squat or Bench Press.&lt;/p&gt;
&lt;p&gt;It looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;weight increase&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; min reps&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; max reps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-comment&quot;&gt;// Increases the reps from 8 to 12 reps, then adds 5lb to weight and
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;// goes back to 8 reps
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;dp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;8&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;12&lt;/span&gt;)
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;// Increases the reps from 6 to 10 reps, then adds 5% to weight and
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;// goes back to 6 reps
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x6&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;dp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5%&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;6&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;10&lt;/span&gt;)&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h4&gt;Sum Of Reps Progression&lt;/h4&gt;
&lt;p&gt;Simply adds weight if the total sum of all reps across all sets is more than the threshold.
Looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reps threshold&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; weights increase&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-comment&quot;&gt;// Increases the weight if the sum of all reps is more than 30
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x10+&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;sum&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;30&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;Ways to make written programs less repetitive&lt;/h3&gt;
&lt;p&gt;Weightlifting programs are often very repetitive - you usually have multiple exercises with the same set schemes,
using the same waving progressions over weeks. It&#39;s important to have ways to not repeat yourself, so it&#39;d be easier to modify the programs, add exercises, change the set schemes, progressions, etc. E.g. a 12-week program with 4-days per week
and 5 exercises in each day in total may have 240 places where you specify the sets! And modifying such program would be a very tedious task.&lt;/p&gt;
&lt;p&gt;Liftosaur offers a bunch of syntax sugar to make it easier to write and modify the programs.&lt;/p&gt;
&lt;h4&gt;Reusing the exercises&#39;s sets/reps/weight/RPE/timer, warmups and update/progress scripts via &lt;code&gt;...Squat&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;You can reuse the sets/reps/weight/RPE/timer and warmups of another exercise. You can either specify the exact week/day of the exercise to reuse, or by default it&#39;ll look into any day of the current week. The syntax for reusing the sets looks like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Bench Press&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The Squat would reuse 5x5 sets of the Bench Press from the current week. And if you change 5x5 of Bench Press to e.g. 3x8, that would be applied to Squat as well. The progress part &lt;code&gt;progress: lp(5lb)&lt;/code&gt; of the Bench Press also would be reused!&lt;/p&gt;
&lt;p&gt;For multi-week programs it may look like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Bench Press
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x9&lt;/span&gt;
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Bench Press&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 3
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x10&lt;/span&gt;
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Bench Press&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;So, Squat would be &lt;code&gt;3x8&lt;/code&gt; on week 1, &lt;code&gt;3x9&lt;/code&gt; on week 2, and &lt;code&gt;3x10&lt;/code&gt; on week 3, because by default it tries to find the original exercise in the &lt;strong&gt;any&lt;/strong&gt; day of the &lt;strong&gt;same&lt;/strong&gt; week.&lt;/p&gt;
&lt;p&gt;You can also specify the exact week/day to reuse the exercise from, by syntax &lt;code&gt;...Bench Press[day]&lt;/code&gt; (in the current week) or &lt;code&gt;...Bench Press[week:day]&lt;/code&gt;. Like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Bench Press&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;2&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 2
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt;
Deadlift &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x3&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This way Squat would use &lt;code&gt;5x5&lt;/code&gt;, and not &lt;code&gt;3x8&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Bench Press&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;2&lt;span class=&quot;tok-atom&quot;&gt;:&lt;/span&gt;1&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt;
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x5&lt;/span&gt;
Deadlift &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x3&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;And this way Squat also would use &lt;code&gt;5x5&lt;/code&gt; from Bench Press on week 2, day 1.&lt;/p&gt;
&lt;p&gt;You can also override anything in the reusing exercise, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;200lb&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;60s&lt;/span&gt;
Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;150lb&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Bench Press would be &lt;code&gt;3x8 150lb 60s&lt;/code&gt; in this case. Or like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;200lb&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;60s&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;update&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  &lt;span class=&quot;tok-comment&quot;&gt;// some update logic&lt;/span&gt;
~} &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;)
Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;150lb&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In this case, Bench Press would have the same &lt;code&gt;update&lt;/code&gt; logic as Squat, but instead of 10lb Linear Progression, it&#39;d have 5lb.&lt;/p&gt;
&lt;p&gt;One thing to note that if the reused exercise changes their weight, sets, reps, etc - after finishing a workout the app would try to extract the new values into overrides. For example, if you have the following setup:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Bench Press&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And then you finished all sets of Squat successfully. That will change the weight of Squat, and the program now would look like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;185lb&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I.e. the app notices that the weight of the Bench Press and Squat are not the same anymore, so it extracts the weight
of Squat into override.&lt;/p&gt;
&lt;p&gt;And if you progress in Bench Press - then &lt;strong&gt;ALL&lt;/strong&gt; the reused exercises would move the weights into overrides - because it just went unsync with them.&lt;/p&gt;
&lt;h4&gt;Repeating the same exercise over multiple weeks via &lt;code&gt;Squat[1-4]&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Usually in multi-week programs, you have exactly the same exercises on the same days over multiple weeks. So, to avoid typing them over and over, you can specify that the same exercise would be repeated over multiple weeks, by specifying a range of weeks after exercise name.&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;1-5&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The syntax is &lt;code&gt;Squat[fromWeek-toWeek]&lt;/code&gt;. If you do that, you don&#39;t have to type &lt;code&gt;Bench Press / 3x8&lt;/code&gt; on weeks 2-5. In the full day mode, your days would be empty, and in the per-day mode - the exercises would be listed under the text input on the repeated days, but would be uneditable.&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Bench Press&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;1-4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;
&lt;span class=&quot;tok-meta&quot;&gt;# Week 3
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;
&lt;span class=&quot;tok-meta&quot;&gt;# Week 4
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Like this - you don&#39;t need to write it in the weeks 2-4.&lt;/p&gt;
&lt;p&gt;This works especially nice in combination with the previous feature - reusing sets/reps/weights/etc. In multi-week programs, you specify the weekly undulation for one of the exercises, and then other exercises reuse and repeat it, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 2
&lt;/span&gt;Bench Press&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;1-4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Squat&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x9&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 2
&lt;/span&gt;
&lt;span class=&quot;tok-meta&quot;&gt;# Week 3
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x10&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 2
&lt;/span&gt;
&lt;span class=&quot;tok-meta&quot;&gt;# Week 4
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x11&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 2&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;When repeating, it tries to preserve the order of exercises, but things may get ambiguous if you have multiple exercises starting repeating in various days.
In that case, you can specify the order of exercises, within square brackets, like &lt;code&gt;Squat[order,fromWeek-toWeek]&lt;/code&gt; or just &lt;code&gt;Squat[order]&lt;/code&gt; for non-repeating exercises.&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;1&lt;span class=&quot;tok-atom&quot;&gt;,&lt;/span&gt;1-4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;
Bench Press&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;2&lt;span class=&quot;tok-atom&quot;&gt;,&lt;/span&gt;1-4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;
Bicep Curl&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;3&lt;span class=&quot;tok-atom&quot;&gt;,&lt;/span&gt;1-4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h4&gt;Reusing descriptions&lt;/h4&gt;
&lt;p&gt;Exactly the same idea as with reusing sets:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;// T1 exercise. Work up to 3RM, and then do 4 singles.
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x3&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;+, &lt;span class=&quot;tok-atom&quot;&gt;4x1&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;// ...Squat
&lt;/span&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x3&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;+, &lt;span class=&quot;tok-atom&quot;&gt;4x1&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Bench Press would reuse the description of Squat, and it&#39;d be &lt;code&gt;T1 exercise. Work up to 3RM, and then do 4 singles.&lt;/code&gt; too.
You can use the same syntax as for reusing sets, like &lt;code&gt;// ...Squat[3:2]&lt;/code&gt; to reuse the description from week 3, day 2,
or &lt;code&gt;// ...Squat[3]&lt;/code&gt; to reuse the description from the current week&#39;s day 3.&lt;/p&gt;
&lt;h4&gt;Exercise templates (or unused exercises) via &lt;code&gt;/ used: none&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;With the features like above, it&#39;s often pretty convenient to specify a &amp;quot;template&amp;quot; exercise, which wouldn&#39;t be used in a program, but would work as a template for other exercises. To do that, you can specify an exercise and remove it from a program with the &lt;code&gt;/ used: none&lt;/code&gt; section, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x10+&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3x10&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;70%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;used&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;none&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...Squat&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In this case, the Squat would be used as a template for Bench Press, but wouldn&#39;t be used in the program itself.&lt;/p&gt;
&lt;p&gt;It&#39;s not required that the exercise existed if it&#39;s marked as &lt;code&gt;used: none&lt;/code&gt;, so you can give the templates arbitrary names:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;T1 &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;used&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;none&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x10+&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3x10&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;70%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
t1: Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...T1&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Templates also solve the problem of the original exercise changing e.g. their weight and therefore breaking the reusing.
Since templates would never progress, they would never break the reusing. The reused exercises still may break reusing on progression, but at least only for that specific reused exercise, not for all of them.&lt;/p&gt;
&lt;h4&gt;Combining it all together&lt;/h4&gt;
&lt;p&gt;These features work the best when combined together. For example, you can have a template exercise, and then reuse it over multiple weeks, and use repeat syntax to avoid repeating the reused exercises across all weeks. E.g. an example program may look something like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;/// Specifying templates for our exercises
&lt;/span&gt;t1 &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;used&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;none&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x6&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3x3&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;
t2 &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;used&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;none&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x8&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3x4&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;70%&lt;/span&gt;
t3&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;1-4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;used&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;none&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x10+&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;60%&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;sum&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;30&lt;/span&gt;, &lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;/// Now the actual exercises:
&lt;/span&gt;Squat&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;1&lt;span class=&quot;tok-atom&quot;&gt;,&lt;/span&gt;1-4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...t1
Romanian Deadlift&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;2&lt;span class=&quot;tok-atom&quot;&gt;,&lt;/span&gt;1-4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...t2
Bicep Curl&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;3&lt;span class=&quot;tok-atom&quot;&gt;,&lt;/span&gt;1-4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...t3&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 2
&lt;/span&gt;Bench Press&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;1&lt;span class=&quot;tok-atom&quot;&gt;,&lt;/span&gt;1-4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...t1
Overhead Press&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;2&lt;span class=&quot;tok-atom&quot;&gt;,&lt;/span&gt;1-4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...t2
Lat Pulldown&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;3&lt;span class=&quot;tok-atom&quot;&gt;,&lt;/span&gt;1-4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...t3&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 3
&lt;/span&gt;Deadlift&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;1&lt;span class=&quot;tok-atom&quot;&gt;,&lt;/span&gt;1-4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...t1
Front Squat&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;2&lt;span class=&quot;tok-atom&quot;&gt;,&lt;/span&gt;1-4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...t2
Hanging Leg Raise&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;3&lt;span class=&quot;tok-atom&quot;&gt;,&lt;/span&gt;1-4&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ...t3&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-meta&quot;&gt;# Week 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;/// Now we only need to specify undulating sets for main templates exercises
&lt;/span&gt;t1 &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x7&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3x4&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;
t2 &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x9&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;70%&lt;/span&gt;
&lt;span class=&quot;tok-comment&quot;&gt;## Day 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 3
&lt;/span&gt;
&lt;span class=&quot;tok-meta&quot;&gt;# Week 3
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;t1 &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x8&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3x4&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;
t2 &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x10&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;70%&lt;/span&gt;
&lt;span class=&quot;tok-comment&quot;&gt;## Day 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 3
&lt;/span&gt;
&lt;span class=&quot;tok-meta&quot;&gt;# Week 4
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 1
&lt;/span&gt;t1 &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x9&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3x5&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;80%&lt;/span&gt;
t2 &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x11&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3x6&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;70%&lt;/span&gt;
&lt;span class=&quot;tok-comment&quot;&gt;## Day 2
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;## Day 3&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Advanced&lt;/h2&gt;
&lt;p&gt;What&#39;s described above is probably enough to cover 95% of the use-cases. But in case you want some custom progressions, you can do it with a special &lt;code&gt;progress: custom()&lt;/code&gt; Liftoscript syntax.&lt;/p&gt;
&lt;p&gt;There, you can unleash the full power of the scripting in Liftosaur. You can directly change weights, reps, sets, timers, RPE, etc using &lt;code&gt;if/else&lt;/code&gt;s, state variables, math and boolean logic.&lt;/p&gt;
&lt;p&gt;E.g. this is how a variant of Linear Progression - increasing weight by 5lb if first set was successful - would look like:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;] &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;]) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt;
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If you have ever written scripts in JavaScript, Python, or a similar programming language (or even Excel!),
this Liftoscript will look very familiar. If not, no worries, it&#39;s a pretty simple and small language.&lt;/p&gt;
&lt;p&gt;In the example above, we increase the weight of all sets by 5lb if the completed reps of the first set were equal or more than required number of reps of the first set. Let&#39;s take a look at the syntax closer.&lt;/p&gt;
&lt;p&gt;The logic is written in the curly braces &lt;strong&gt;with tildes&lt;/strong&gt; - i.e. between &lt;code&gt;{~&lt;/code&gt; and &lt;code&gt;~}&lt;/code&gt;. Inside those curly braces you can access required reps, completed reps, weights, RPE, etc of all the sets that were finished for that exercise, and you can update weights/reps/etc of the program based on that.&lt;/p&gt;
&lt;p&gt;The conditional logic written with &lt;code&gt;if (...) { ... }&lt;/code&gt; sentence. In the parenthesis you specify the condition when the block of the &lt;code&gt;if&lt;/code&gt; should happen. &lt;code&gt;completedReps[1]&lt;/code&gt; gives you the number of &lt;strong&gt;completed reps&lt;/strong&gt; of the first set (&lt;code&gt;[1]&lt;/code&gt; part - arrays indexes start from 1), and &lt;code&gt;reps[1]&lt;/code&gt; gives you the number of &lt;strong&gt;required reps&lt;/strong&gt; that were defined for that set. So &lt;code&gt;completedReps[1] &amp;gt;= reps[1]&lt;/code&gt; means that completed reps of the first set are equal or more than the required reps of the first set.&lt;/p&gt;
&lt;p&gt;For convenience, you can also check if all completed reps of all sets are equal or more than required reps. You could do: &lt;code&gt;completedReps &amp;gt;= reps&lt;/code&gt; (i.e. omit &lt;code&gt;[1]&lt;/code&gt; part). It works for all the arrays (&lt;code&gt;weights&lt;/code&gt;, &lt;code&gt;reps&lt;/code&gt;, &lt;code&gt;RPE&lt;/code&gt;, etc) - e.g. you can do &lt;code&gt;weights &amp;gt;= 50lb&lt;/code&gt; - it is true if all the weights of all the sets are equal or more than 50lb.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;weights += 5lb&lt;/code&gt; means we should increment the weights of all sets by 5lb. You can be more specific, e.g. you may want to increment the weight of only one set - e.g. second set - &lt;code&gt;weights[2] += 5lb&lt;/code&gt;, or you can increment all weights for the exercise on week 2 day 3, then you could do: &lt;code&gt;weights[2:3:*:*] += 5lb&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of incrementing weights, you can directly assign values to them. E.g. &lt;code&gt;weights = weights[numberOfSets] + 5lb&lt;/code&gt; would
set all the weights equals to the weight of the last finished set and add 5lb to it.&lt;/p&gt;
&lt;p&gt;This is the list of available variables you can get values from in your &lt;code&gt;progress: custom()&lt;/code&gt; scripts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;weights[n]&lt;/code&gt; or &lt;code&gt;w[n]&lt;/code&gt; - initial weight after rounding of an N set. N starts from 1.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;originalWeights[n]&lt;/code&gt; or &lt;code&gt;cw[n]&lt;/code&gt; - initial weight before rounding of an N set. N starts from 1.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completedWeights[n]&lt;/code&gt; or &lt;code&gt;cw[n]&lt;/code&gt; - completed weight of an N set. N starts from 1.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reps[n]&lt;/code&gt; or &lt;code&gt;r[n]&lt;/code&gt; - number of reps for an N set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completedReps[n]&lt;/code&gt; or &lt;code&gt;cr[n]&lt;/code&gt; - number of completed reps for an N set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completedRepsLeft[n]&lt;/code&gt; - number of completed reps for the left side for unilateral exercises for an N set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RPE[n]&lt;/code&gt; - if exercise has RPE - the RPE expression that&#39;s required for an N set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completedRPE[n]&lt;/code&gt; - if exercise has RPE, and the set is marked as Log RPE - RPE that user entered for an N set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timers[n]&lt;/code&gt; - if the exercise sets have explicit timer set up - value of that timer&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rm1&lt;/code&gt; - 1 Rep Max of a current exercise. You can set it in the Exercise Stats section (if you tap on exercise name on the workout screen)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;day&lt;/code&gt; - current day number, starting from 1.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;week&lt;/code&gt; - for multi-week programs - current week number, starting from 1.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dayInWeek&lt;/code&gt; - current index of day in week, starting from 1.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;programNumberOfSets&lt;/code&gt; - how many sets was prescribed by a program.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;numberOfSets&lt;/code&gt; or &lt;code&gt;ns&lt;/code&gt; - how many sets were in the exercise (could be changed by adding/removing sets during workout).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completedNumberOfSets&lt;/code&gt; - how many sets are completed (by a checkmark).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setVariationIndex&lt;/code&gt; - current set variation index (see below about set variations)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;descriptionIndex&lt;/code&gt; - current description index&lt;/li&gt;
&lt;li&gt;&lt;code&gt;amraps[n]&lt;/code&gt; - whether to mark the set AMRAP, &lt;code&gt;1&lt;/code&gt; for AMRAP, &lt;code&gt;0&lt;/code&gt; for not AMRAP&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logrpes[n]&lt;/code&gt; - whether to ask user what was the actual RPE, &lt;code&gt;1&lt;/code&gt; to ask, &lt;code&gt;0&lt;/code&gt; to not ask&lt;/li&gt;
&lt;li&gt;&lt;code&gt;askweights[n]&lt;/code&gt; - whether to ask user what was the actual weight, &lt;code&gt;1&lt;/code&gt; to ask, &lt;code&gt;0&lt;/code&gt; to not ask&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And this is the list of available variables you can change (assign new values):&lt;/p&gt;
&lt;p&gt;The weights/reps/RPE and timers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;weights&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RPE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timers&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;amraps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logrpes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;askweights&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For those, you can specify what set, set variation, day or week you want to change it in. To target specific set, you use the following syntax:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;weights&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;week&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;day&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;setvariation&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;set&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where the values of week/day/setvariation/set is either a number (or expression that calculates to a number), or &lt;code&gt;*&lt;/code&gt; - that&#39;d mean &lt;strong&gt;any&lt;/strong&gt; week/day/setvariation/set.&lt;/p&gt;
&lt;p&gt;If you omit any leading value, it&#39;s assumed it&#39;s a &lt;code&gt;*&lt;/code&gt;. For example:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  &lt;span class=&quot;tok-comment&quot;&gt;// Changes the weights across all sets for the exercise&lt;/span&gt;
  &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;// same as weights[*:*:*:*] = 50lb&lt;/span&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;// Changes the 5th set across all weeks/days/setvariations&lt;/span&gt;
&lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;] = &lt;span class=&quot;tok-number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;// same as weights[&lt;em&gt;:&lt;/em&gt;:*:5] = 50lb&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;// Changes the 5th set in 3rd day across all weeks/setvariations&lt;/span&gt;
&lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;3&lt;/span&gt;:&lt;em&gt;:&lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;] = &lt;span class=&quot;tok-number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;// same as weights[&lt;/span&gt;&lt;/em&gt;:3:*:5] = 50lb
~}&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;You can also change 1RM of an exercise, via assigning to &lt;code&gt;rm1&lt;/code&gt; variable:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  &lt;span class=&quot;tok-keyword&quot;&gt;rm1&lt;/span&gt; = &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;]
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can also set the values of the following variables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;setVariationIndex&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;descriptionIndex&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But more about those below in the section about &amp;quot;Set Variations&amp;quot; and &amp;quot;Advanced Descriptions&amp;quot;&lt;/p&gt;
&lt;h3&gt;State Variables&lt;/h3&gt;
&lt;p&gt;Sometimes you need to remember some values between the workouts. For example, you want to increase the weight after 3 successful attempts, so you need to know what is the current attempt, and how many already happened.&lt;/p&gt;
&lt;p&gt;For that, you can use &lt;strong&gt;state variables&lt;/strong&gt;. Those can hold the value between the workouts. You list them inside the parenthesis of &lt;code&gt;custom()&lt;/code&gt;, and then refer to them within the &lt;code&gt;{~ ... ~}&lt;/code&gt; block. For example:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;attempt: 0&lt;/span&gt;) {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;attempt&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;
    if (state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;attempt&lt;/span&gt; &gt; &lt;span class=&quot;tok-number&quot;&gt;3&lt;/span&gt;) {
      &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt;
      state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;attempt&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;0&lt;/span&gt;
    }
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;attempt&lt;/code&gt; variable will be increased across workouts, and then reset to 0 once you hit 3 successful attempts.&lt;/p&gt;
&lt;p&gt;Another use case for the state variables is in reusing the &lt;code&gt;progress: custom()&lt;/code&gt; logic. The scripts can become pretty large, and usually you want multiple exercises to follow the same logic. So, for that you can reuse it! For that, just specify the exercise you&#39;re reusing the logic from like this: &lt;code&gt;Bench Press / 3x8 / progress: custom() { ...Squat }&lt;/code&gt;. I.e. add it within the curly braces - &lt;code&gt;{&lt;/code&gt; and &lt;code&gt;}&lt;/code&gt; (without tildes! So the app would know it&#39;s not the script itself). For example, if we want to reuse the logic above, it&#39;d look like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;attempt: 0&lt;/span&gt;) {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;attempt&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;
    if (state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;attempt&lt;/span&gt; &gt; &lt;span class=&quot;tok-number&quot;&gt;3&lt;/span&gt;) {
      &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt;
      state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;attempt&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;0&lt;/span&gt;
    }
  }
~}
&lt;p&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;attempt: 0&lt;/span&gt;) { ...Bench Press }&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;But we may want to have higher weight increments for Squat. For that, we can &lt;strong&gt;parameterize&lt;/strong&gt; the script with the state variables! Let&#39;s add another state variable &lt;code&gt;increment&lt;/code&gt;, and use it in the script:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;attempt: 0&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;increment: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;) {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;attempt&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;
    if (state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;attempt&lt;/span&gt; &gt; &lt;span class=&quot;tok-number&quot;&gt;3&lt;/span&gt;) {
      &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; += state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;increment&lt;/span&gt;
      state&lt;span class=&quot;tok-variableName&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;attempt&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;0&lt;/span&gt;
    }
  }
~}
&lt;p&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;attempt: 0&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;increment: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;) { ...Bench Press }&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;You can see that now we use &lt;code&gt;state.increment&lt;/code&gt; in &lt;code&gt;weights += state.increment&lt;/code&gt; expression. And we use different values for &lt;code&gt;increment&lt;/code&gt; - &lt;code&gt;5lb&lt;/code&gt; in Bench Press and &lt;code&gt;10lb&lt;/code&gt; in Squat.&lt;/p&gt;
&lt;p&gt;There&#39;s also a way to define &lt;strong&gt;user-prompted&lt;/strong&gt; state variables. For that, add a &lt;code&gt;+&lt;/code&gt; sign after the variable name, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;shouldBumpWeight+: 0&lt;/span&gt;) {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;shouldBumpWeight&lt;/span&gt; &gt; &lt;span class=&quot;tok-number&quot;&gt;0&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt;
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In this case, after the last set the app will ask the user for the &lt;code&gt;shouldBumpWeight&lt;/code&gt; value. And if user
enters 1, the weight would be increased. Otherwise - it&#39;d stay the same.&lt;/p&gt;
&lt;p&gt;When reusing progress logic, you may omit the variables with the same value. I.e., this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;attempt: 0&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;increment: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;) {~
  &lt;span class=&quot;tok-comment&quot;&gt;// ...some logic&lt;/span&gt;
~}
&lt;p&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;attempt: 0&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;increment: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;) { ...Bench Press }&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;and this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;attempt: 0&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;increment: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;) {~
  &lt;span class=&quot;tok-comment&quot;&gt;// ...some logic&lt;/span&gt;
~}
&lt;p&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;increment: &lt;/span&gt;&lt;span class=&quot;tok-string tok-number&quot;&gt;10lb&lt;/span&gt;) { ...Bench Press }&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;are equivalent (note we skipped &lt;code&gt;attempt: 0&lt;/code&gt; in the second example).&lt;/p&gt;
&lt;h3&gt;Temporary Variables&lt;/h3&gt;
&lt;p&gt;Sometimes you want to store long math expression value in a variable to use it across the script. You can do it with temporary variables. The syntax looks like this - &lt;code&gt;var.foo = 30lb&lt;/code&gt;. I.e. they should be prefixed with &lt;code&gt;var.&lt;/code&gt;. For example:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  var.foo = &lt;span class=&quot;tok-keyword&quot;&gt;floor&lt;/span&gt;(&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;] / &lt;span class=&quot;tok-number&quot;&gt;2&lt;/span&gt;)
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;2&lt;/span&gt;] = var.foo
    &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;3&lt;/span&gt;] = var.foo
    &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;4&lt;/span&gt;] = var.foo
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;Loops&lt;/h3&gt;
&lt;p&gt;There&#39;s also a way to iterate over the sets with &lt;strong&gt;loops&lt;/strong&gt;. It looks like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  for (var.i in &lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[var.i] = &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[var.i] + &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt;
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That would set the weights of the next workout to 5lb more than finished weights of this workout.
The syntax is &lt;code&gt;for (var.i in weights)&lt;/code&gt;, where &lt;code&gt;var.i&lt;/code&gt; should always be a temporary variable (i.e. start with &lt;code&gt;var.&lt;/code&gt;), and
the expression on the right side of &lt;code&gt;in&lt;/code&gt; should return an array.
The &lt;code&gt;var.i&lt;/code&gt; would contain the index of each set, starting from 1.&lt;/p&gt;
&lt;h3&gt;Prints&lt;/h3&gt;
&lt;p&gt;For debugging purposes, there&#39;s &lt;code&gt;print&lt;/code&gt; function. You can use it to debug your scripts. Liftoscript doesn&#39;t support strings, so you can only pass numbers, weights or percentages there. It looks like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;print(1, 20lb, 30%)
print(100, weights&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;1&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt;, completedReps&lt;span class=&quot;tok-atom&quot;&gt;[&lt;/span&gt;2&lt;span class=&quot;tok-atom&quot;&gt;]&lt;/span&gt;)
print(var.max)&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It accepts any number of arguments, and you&#39;ll see all the prints either in playground or when you finish exercise during workout, after all sets are done.&lt;/p&gt;
&lt;h3&gt;Update&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;progress: &lt;/code&gt; logic updates the weights/reps/etc &lt;strong&gt;in the program&lt;/strong&gt;, after you finish a workout. But there&#39;s also a way to update sets while you&#39;re doing a workout! For example, you want to set the number of drop sets or dropset reps based on the first set completed reps, or something like that.&lt;/p&gt;
&lt;p&gt;For that, you can use &lt;code&gt;update: custom()&lt;/code&gt; syntax, which is very similar to &lt;code&gt;progress: custom()&lt;/code&gt;. The difference is that you cannot change program state variables, and you cannot access the program at all - only read the program state, and the currently ongoing workout values. The script would be run &lt;strong&gt;before&lt;/strong&gt; completing any sets (with &lt;code&gt;setIndex&lt;/code&gt; == 0), and then every single time user taps on a set.&lt;/p&gt;
&lt;p&gt;So, the list of variables you can get values from is pretty much the same:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;weights&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;originalWeights&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completedWeights&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completedReps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completedRepsLeft&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RPE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completedRPE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rm1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;day&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;week&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dayInWeek&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;programNumberOfSets&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;numberOfSets&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completedNumberOfSets&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setVariationIndex&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;descriptionIndex&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;amraps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logrpes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;askweights&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setIndex&lt;/code&gt; - index of a set that was tapped (it&#39;s 0 for the initial run - before completing any sets)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But assigning new values is only allowed to the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;weights&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RPE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timers&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;numberOfSets&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, to change the 2nd set, you do &lt;code&gt;weights[2] = 60lb&lt;/code&gt;, and to change all sets, you do &lt;code&gt;weights = 50lb&lt;/code&gt;.
You &lt;strong&gt;cannot&lt;/strong&gt; change the sets that already completed, if you try that, it&#39;d be ignored.&lt;/p&gt;
&lt;p&gt;You cannot change other set variations, weeks or days, so syntax like &lt;code&gt;weights[1:2:3:4]&lt;/code&gt; is not allowed, you can only do &lt;code&gt;weights&lt;/code&gt; or &lt;code&gt;weights[1]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;By changing &lt;code&gt;numberOfSets&lt;/code&gt; you can add or delete sets. E.g. if you had 2 sets, and you do &lt;code&gt;numberOfSets = 5&lt;/code&gt;, you&#39;ll add 3 more sets. You can also delete the sets - if you had 5 sets, and you do &lt;code&gt;numberOfSets = 2&lt;/code&gt;, it&#39;d delete last 3 sets. But again - only if they weren&#39;t already finished.&lt;/p&gt;
&lt;p&gt;By default, it will add the same sets as the last set in that exercise. If you want to tweak new sets (or change existing), you can use &lt;code&gt;sets()&lt;/code&gt; function. It accepts 9 arguments (!), and looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sets&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fromIndex&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; toIndex&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; minReps&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maxReps&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isAmrap&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; weight&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; rpe&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; shouldLogRpe&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;code&gt;minReps&lt;/code&gt; and &lt;code&gt;maxReps&lt;/code&gt; are diffrent, it&#39;ll make it a rep range. &lt;code&gt;isAmrap&lt;/code&gt; and &lt;code&gt;shouldLogRpe&lt;/code&gt; should be &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt; (&lt;code&gt;1&lt;/code&gt; means it&#39;s enabled).&lt;/p&gt;
&lt;p&gt;Using all of that, we can implement the example above - based on how many reps we did - add 3 sets and set the reps to half of the first set reps.&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;update&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;setIndex&lt;/span&gt; == &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt; &amp;&amp; &lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;] &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;]) {
    &lt;span class=&quot;tok-keyword&quot;&gt;numberOfSets&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;4&lt;/span&gt;
    &lt;span class=&quot;tok-keyword&quot;&gt;sets&lt;/span&gt;(&lt;span class=&quot;tok-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;tok-number&quot;&gt;4&lt;/span&gt;, &lt;span class=&quot;tok-keyword&quot;&gt;floor&lt;/span&gt;(&lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;] / &lt;span class=&quot;tok-number&quot;&gt;2&lt;/span&gt;), &lt;span class=&quot;tok-keyword&quot;&gt;floor&lt;/span&gt;(&lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;] / &lt;span class=&quot;tok-number&quot;&gt;2&lt;/span&gt;), &lt;span class=&quot;tok-number&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;], &lt;span class=&quot;tok-number&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-number&quot;&gt;0&lt;/span&gt;)
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;floor(reps[1] / 2)&lt;/code&gt; makes sure that if there&#39;s a reminder when dividing by 2, we round it down. Like 5 / 2 -&amp;gt; 2.&lt;/p&gt;
&lt;p&gt;You usually would want to always add a condition on &lt;code&gt;setIndex&lt;/code&gt;, to ensure you&#39;re running the update script at the
right time, and also to distinguish initial run (wth &lt;code&gt;setIndex&lt;/code&gt; == 0) and runs when you complete sets.&lt;/p&gt;
&lt;p&gt;We can also reuse the update scripts, similarly to the &lt;code&gt;progress: custom()&lt;/code&gt; scripts, with &lt;code&gt;{ ...Squat }&lt;/code&gt; syntax:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;update&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;setIndex&lt;/span&gt; == &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt; &amp;&amp; &lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;] &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;]) {
    &lt;span class=&quot;tok-keyword&quot;&gt;numberOfSets&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;4&lt;/span&gt;
    &lt;span class=&quot;tok-keyword&quot;&gt;sets&lt;/span&gt;(&lt;span class=&quot;tok-number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;tok-number&quot;&gt;4&lt;/span&gt;, &lt;span class=&quot;tok-keyword&quot;&gt;floor&lt;/span&gt;(&lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;] / &lt;span class=&quot;tok-number&quot;&gt;2&lt;/span&gt;), &lt;span class=&quot;tok-keyword&quot;&gt;floor&lt;/span&gt;(&lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;] / &lt;span class=&quot;tok-number&quot;&gt;2&lt;/span&gt;), &lt;span class=&quot;tok-number&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;], &lt;span class=&quot;tok-number&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;tok-number&quot;&gt;0&lt;/span&gt;)
  }
~}
&lt;p&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;update&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() { ...Bench Press }&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;You can specify both &lt;code&gt;update: custom()&lt;/code&gt; and any &lt;code&gt;progress: &lt;/code&gt; within the same exercise.&lt;/p&gt;
&lt;h3&gt;Number of sets&lt;/h3&gt;
&lt;p&gt;To do number-of-sets-based progressions, you can use &lt;code&gt;numberOfSets&lt;/code&gt; variable in your &lt;code&gt;progress&lt;/code&gt; scripts, similar to how you could do it in the &lt;code&gt;update&lt;/code&gt; scripts. E.g. this is how you could setup a set-based double progression (which would increase sets from 3 to 5, and then would increase weight and reset sets back to 3):&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    if (&lt;span class=&quot;tok-keyword&quot;&gt;numberOfSets&lt;/span&gt; &lt; &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;) {
      &lt;span class=&quot;tok-keyword&quot;&gt;numberOfSets&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;
    } else {
      &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt;
      &lt;span class=&quot;tok-keyword&quot;&gt;numberOfSets&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;3&lt;/span&gt;
    }
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can also target specific weeks/days/setvariations if you want to. E.g. if you only want to change the number of sets on week 2, day 3, set variation 1, you can do &lt;code&gt;numberOfSets[2:3:1] += 1&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Set Variations&lt;/h3&gt;
&lt;p&gt;Sometimes you may want to have multiple sets x reps schemes within the same exercise, and switch between them on some condition. For example, in a popular weightlifting program called &amp;quot;GZCLP&amp;quot;, you do &lt;code&gt;5x3&lt;/code&gt; sets, but if you fail, you switch to &lt;code&gt;6x2&lt;/code&gt; sets, and then - to &lt;code&gt;10x1&lt;/code&gt; sets. To program that in Liftosaur, there&#39;s a concept of &amp;quot;set variations&amp;quot; - so that you can specify several set schemes and switch between them. Example above may look like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x3&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;6x2&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;10x1&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; = &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt;[&lt;span class=&quot;tok-keyword&quot;&gt;ns&lt;/span&gt;] + &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt;
  } else {
    &lt;span class=&quot;tok-keyword&quot;&gt;setVariationIndex&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;There, we increase the weight by 5lb if we successfully finished the sets. But if not - we&#39;ll increment &lt;code&gt;setVariationIndex&lt;/code&gt;, which would become &lt;code&gt;2&lt;/code&gt;, and the next time the first line of the exercise will look like:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x3&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; ! &lt;span class=&quot;tok-atom&quot;&gt;6x2&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;10x1&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note the exclamation mark &lt;code&gt;!&lt;/code&gt; at &lt;code&gt;6x2&lt;/code&gt; - that means that this is the current set variation. Putting that exclamation mark in front of a set variation makes it currently selected.&lt;/p&gt;
&lt;p&gt;So, to define the set variations you just list them separated by &lt;code&gt;/&lt;/code&gt;, and then use the &lt;code&gt;setVariationIndex&lt;/code&gt; in the script to define the logic when to choose which set variation. And the app tracks which one is the current one by the &lt;code&gt;!&lt;/code&gt; character.&lt;/p&gt;
&lt;h3&gt;Advanced Descriptions&lt;/h3&gt;
&lt;p&gt;Similarly to set variations, you can specify mutliple descriptions per exercise, and switch between the current one by assigning to the &lt;code&gt;descriptionIndex&lt;/code&gt; variable. The current one is marked the same as in set variations - by &lt;code&gt;!&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To add mutliple descriptions, simply leave an empty line between them, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-comment&quot;&gt;// This is the first description.
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;// We explain here how to do an exercise
&lt;/span&gt;
&lt;span class=&quot;tok-comment&quot;&gt;// This is second description. Users already know what to do.
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x3&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;6x2&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;10x1&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;descriptionIndex&lt;/span&gt; == &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;descriptionIndex&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;2&lt;/span&gt;
  }
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt;
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;After finishing the first workout, you&#39;ll notice the program now marks the currently selected description:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;&lt;span class=&quot;tok-comment&quot;&gt;// This is the first description.
&lt;/span&gt;&lt;span class=&quot;tok-comment&quot;&gt;// We explain here how to do an exercise
&lt;/span&gt;
&lt;span class=&quot;tok-comment&quot;&gt;// ! This is second description. Users already know what to do.
&lt;/span&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;5x3&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;6x2&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;10x1&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  if (&lt;span class=&quot;tok-keyword&quot;&gt;descriptionIndex&lt;/span&gt; == &lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;descriptionIndex&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;2&lt;/span&gt;
  }
  if (&lt;span class=&quot;tok-keyword&quot;&gt;completedReps&lt;/span&gt; &gt;= &lt;span class=&quot;tok-keyword&quot;&gt;reps&lt;/span&gt;) {
    &lt;span class=&quot;tok-keyword&quot;&gt;weights&lt;/span&gt; += &lt;span class=&quot;tok-number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;lb&lt;/span&gt;
  }
~}&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;(note the &lt;code&gt;!&lt;/code&gt; at the beginning of the second description).&lt;/p&gt;
&lt;h3&gt;Split exercise lines&lt;/h3&gt;
&lt;p&gt;If the exercise lines get too long, and it becomes inconvenient to read and modify those, you can split them into multiple lines. Just add &lt;code&gt;\&lt;/code&gt; character at the end of a section, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;1x5&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@8&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;75%&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;120s&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-number&quot;&gt;@9&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;60s&lt;/span&gt; \
  &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;warmup&lt;/span&gt;: &lt;span class=&quot;tok-atom&quot;&gt;1x5&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x3&lt;/span&gt;, &lt;span class=&quot;tok-atom&quot;&gt;1x1&lt;/span&gt; \
  &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;lp&lt;/span&gt;(&lt;span class=&quot;tok-string tok-number&quot;&gt;5lb&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;Tags&lt;/h3&gt;
&lt;p&gt;Sometimes you want to change state variable of one exercise from another exercise. For that, you can use tags.
You can add tags to an exercise using &lt;code&gt;id: tags(123)&lt;/code&gt; syntax, like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;id&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;tags&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;123&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You may add multiple tags to the same exercise, and you may add same tags to multiple exercises, for example:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;id&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;tags&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;100&lt;/span&gt;)
Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;id&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;tags&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;101&lt;/span&gt;)&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;So they both will have the same tag &lt;code&gt;1&lt;/code&gt;, and also Squat will have &lt;code&gt;100&lt;/code&gt;, and Bench Press - &lt;code&gt;101&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And then you can change their state variables from another exercise. Let&#39;s say they specify progression like this:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Squat &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;id&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;tags&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;100&lt;/span&gt;) &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;rating: 0&lt;/span&gt;) {~ ~}
Bench Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;id&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;tags&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;tok-string&quot;&gt;101&lt;/span&gt;) &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;tok-string&quot;&gt;rating: 0&lt;/span&gt;) { ...Squat }&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;So, from another exercise you can change the rating state variable:&lt;/p&gt;
&lt;div class=&quot;plannercode&quot;&gt;&lt;div class=&quot;plannercode-content&quot;&gt;Overhead Press &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-atom&quot;&gt;3x8&lt;/span&gt; &lt;span class=&quot;tok-comment&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;tok-keyword&quot;&gt;progress&lt;/span&gt;: &lt;span class=&quot;tok-propertyName&quot;&gt;custom&lt;/span&gt;() {~
  &lt;span class=&quot;tok-comment&quot;&gt;// Changes the rating state var in both Squat and Bench Press&lt;/span&gt;
  state&lt;span class=&quot;tok-variableName&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;tok-number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;tok-variableName&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;rating&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;10&lt;/span&gt;
&lt;p&gt;&lt;span class=&quot;tok-comment&quot;&gt;// Changes the rating state var only in Bench Press&lt;/span&gt;
state&lt;span class=&quot;tok-variableName&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;tok-number&quot;&gt;101&lt;/span&gt;&lt;span class=&quot;tok-variableName&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;tok-keyword&quot;&gt;rating&lt;/span&gt; = &lt;span class=&quot;tok-number&quot;&gt;10&lt;/span&gt;
~}&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;I.e. the syntax is &lt;code&gt;state[tag].variablename = value&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Script Language Reference&lt;/h2&gt;
&lt;h3&gt;Types&lt;/h3&gt;
&lt;p&gt;Result of an expression could be a number, weight, percentage or boolean.
Number, percentage or weight could be assigned to a state variable.
Only number could be used for number of reps for set expressions.
Number, percentage or weight could be used for set weight expressions.&lt;/p&gt;
&lt;h3&gt;Values&lt;/h3&gt;
&lt;p&gt;The values are numbers (1, 3, 100, 31534532, etc.), and also you can add kg or lb suffix to the number to indicate that it is a weight (like 1lb, 3kg, 100lb, 31534532kg). You can also add &#39;%&#39; to indicate this is percentage (of 1 Rep Max)&lt;/p&gt;
&lt;p&gt;Using lb/kg is recommended for when you deal with weights, because they will be converted to kilograms properly if user selects that unit on the settings screen.&lt;/p&gt;
&lt;h3&gt;Operators&lt;/h3&gt;
&lt;p&gt;The following operators are available:&lt;/p&gt;
&lt;p&gt;Math: +, -, /, *, %
Boolean: &amp;gt;, &amp;lt;, &amp;lt;=, &amp;gt;=, ==, &amp;amp;&amp;amp;, ||
Ternary operator: for example &lt;code&gt;3 &amp;gt; 4 ? 1 : 2&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;If/else&lt;/h3&gt;
&lt;p&gt;You can add conditional logic to your scripts using &lt;code&gt;if/else&lt;/code&gt; statements or ternary operators.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedReps&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;someVar &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedReps&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;someVar &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;someVar &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedReps&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;someVar &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedReps&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;someVar &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;someVar &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;someVar &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Assignment&lt;/h3&gt;
&lt;p&gt;You can assign values only to state variables, by &lt;code&gt;=&lt;/code&gt; operator:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;someVar &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&#39;s also syntax to add/subtract/multiply/divide to the current value, like this:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;someVar &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;someVar &lt;span class=&quot;token operator&quot;&gt;-=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;someVar &lt;span class=&quot;token operator&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;someVar &lt;span class=&quot;token operator&quot;&gt;/=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Predefined read-only variables&lt;/h3&gt;
&lt;p&gt;You cannot assign values to them, but you can use their values. They are:&lt;/p&gt;
&lt;h4&gt;For &lt;code&gt;progress: custom()&lt;/code&gt;:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;weights[n]&lt;/code&gt; or &lt;code&gt;w[n]&lt;/code&gt; - initial weight after rounding of an N set. N starts from 1.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;originalWeights[n]&lt;/code&gt; - initial weight before rounding of an N set. N starts from 1.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completedWeights[n]&lt;/code&gt; - completed weight of an N set. N starts from 1.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reps[n]&lt;/code&gt; or &lt;code&gt;r[n]&lt;/code&gt; - number of reps for an N set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completedReps[n]&lt;/code&gt; or &lt;code&gt;cr[n]&lt;/code&gt; - number of completed reps for an N set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completedRepsLeft[n]&lt;/code&gt; - number of completed reps for a left side for unilateral exercises for an N set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RPE[n]&lt;/code&gt; - if exercise has RPE - the RPE expression that&#39;s required for an N set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completedRPE[n]&lt;/code&gt; - if exercise has RPE, and the set is marked as Log RPE - RPE that user entered for an N set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rm1&lt;/code&gt; - 1 Rep Max of a current exercise. You can set it in the Exercise Stats section (if you tap on exercise name on the workout screen)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;day&lt;/code&gt; - current day number, starting from 1.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;week&lt;/code&gt; - for multi-week programs - current week number, starting from 1.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dayInWeek&lt;/code&gt; - current index of day in week, starting from 1.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;programNumberOfSets&lt;/code&gt; - how many sets was prescribed by a program.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;numberOfSets&lt;/code&gt; or &lt;code&gt;ns&lt;/code&gt; - how many sets were in the exercise.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;completedNumberOfSets&lt;/code&gt; - how many sets are completed (by a checkmark).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setVariationIndex&lt;/code&gt; - current set variation index (see below about set variations)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;descriptionIndex&lt;/code&gt; - current description index&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;For &lt;code&gt;update: custom()&lt;/code&gt;:&lt;/h4&gt;
&lt;p&gt;Same as &lt;code&gt;progress: custom()&lt;/code&gt;, but also &lt;code&gt;setIndex&lt;/code&gt; - index of the set user just tapped.&lt;/p&gt;
&lt;h3&gt;Predefined write variables&lt;/h3&gt;
&lt;p&gt;You assign new values to them.&lt;/p&gt;
&lt;h4&gt;For &lt;code&gt;progress: custom()&lt;/code&gt;:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;weights[day:week:setvariation:set]&lt;/code&gt; - weight of a set&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reps[day:week:setvariation:set]&lt;/code&gt; - number of reps for a set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RPE[day:week:setvariation:set]&lt;/code&gt; - RPE that&#39;s required for a set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timers[day:week:setvariation:set]&lt;/code&gt; - timer that&#39;s assigned for a set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;amraps[day:week:setvariation:set]&lt;/code&gt; - whether to mark the set AMRAP, &lt;code&gt;1&lt;/code&gt; for AMRAP, &lt;code&gt;0&lt;/code&gt; for not AMRAP&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logrpes[day:week:setvariation:set]&lt;/code&gt; - whether to ask user what was the actual RPE, &lt;code&gt;1&lt;/code&gt; to ask, &lt;code&gt;0&lt;/code&gt; to not ask&lt;/li&gt;
&lt;li&gt;&lt;code&gt;askweights[day:week:setvariation:set]&lt;/code&gt; - whether to ask user what was the actual weight, &lt;code&gt;1&lt;/code&gt; to ask, &lt;code&gt;0&lt;/code&gt; to not ask&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rm1&lt;/code&gt; - 1 Rep Max of a current exercise.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setVariationIndex&lt;/code&gt; - index of the current set variation&lt;/li&gt;
&lt;li&gt;&lt;code&gt;descriptionIndex&lt;/code&gt; - index of the current description&lt;/li&gt;
&lt;li&gt;&lt;code&gt;numberOfSets[week:day:setvariation]&lt;/code&gt; - number of sets for the exercise in this workout&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;For &lt;code&gt;update: custom()&lt;/code&gt;:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;weights[set]&lt;/code&gt; - weight of a set&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reps[set]&lt;/code&gt; - number of reps for a set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RPE[set]&lt;/code&gt; - RPE that&#39;s required for a set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timers[set]&lt;/code&gt; - timer that&#39;s assigned for a set.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rm1&lt;/code&gt; - 1 Rep Max of a current exercise.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;numberOfSets&lt;/code&gt; - number of sets for the exercise in this workout&lt;/li&gt;
&lt;li&gt;&lt;code&gt;amraps[set]&lt;/code&gt; - whether to mark the set AMRAP, &lt;code&gt;1&lt;/code&gt; for AMRAP, &lt;code&gt;0&lt;/code&gt; for not AMRAP&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logrpes[set]&lt;/code&gt; - whether to ask user what was the actual RPE, &lt;code&gt;1&lt;/code&gt; to ask, &lt;code&gt;0&lt;/code&gt; to not ask&lt;/li&gt;
&lt;li&gt;&lt;code&gt;askweights[set]&lt;/code&gt; - whether to ask user what was the actual weight, &lt;code&gt;1&lt;/code&gt; to ask, &lt;code&gt;0&lt;/code&gt; to not ask&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;State variables&lt;/h3&gt;
&lt;p&gt;You can access the state variables you defined via &lt;code&gt;state.&lt;/code&gt; prefix. E.g. &lt;code&gt;state.myVar&lt;/code&gt;.
You can write values into them via assignment operator (&lt;code&gt;=&lt;/code&gt;)&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;myVar &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Built-in functions&lt;/h3&gt;
&lt;p&gt;There are some built-in functions you can use in the expressions. They are:&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;rpeMultiplier&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Used for calculating the multiplier of 1RM given the reps and RPE. Like for example, for 13 reps at 9 RPE, it&#39;d
return multiplier of 0.6, i.e. you probably would be able to lift 60% of your 1RM for 13 reps at 9 RPE.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;weight &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rpeMultiplier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First argument is the number of reps (1-24), second - the RPE value (1-10).&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;floor&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;It rounds the number down to the nearest integer.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextWeight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;floor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;152.4&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;reps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;floor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2.7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;ceil&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;It rounds the number up to the nearest integer.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextWeight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ceil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;152.4&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;reps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ceil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2.7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;round&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;It rounds the number to the nearest integer.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextWeight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;152.4&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;reps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2.7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;sum&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Sums all values. Accepts any combination of arrays, individual numbers, and weights as arguments.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedReps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;weight &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;total &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;allReps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reps&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completedReps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;adjusted &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completedReps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;min&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Finds the minimum value. Accepts any combination of arrays, individual numbers, and weights as arguments.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;minWeight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;weights&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;smallest &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lowest &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reps&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completedReps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;capped &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completedReps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;max&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Finds the maximum value. Accepts any combination of arrays, individual numbers, and weights as arguments.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxCompletedReps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedReps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;biggest &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;highest &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reps&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completedReps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;floored &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completedReps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;increment&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;It&#39;s a function that accepts weight, and returns incremented weight - minimum possible increment of the weight, based on the equipment settings. I.e. it will take into account the available plates, or what&#39;s the next
fixed weight on the equipment.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;weights&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;increment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedWeights&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;decrement&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;It&#39;s a function that accepts weight, and returns decremented weight - minimum possible decrement of the weight, based on the equipment settings. I.e. it will take into account the available plates, or what&#39;s the previous
fixed weight on the equipment.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;weights&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;decrement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completedWeights&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;sets&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;To setup new sets (or change existing), it works only for &lt;code&gt;update: custom()&lt;/code&gt;. It accepts 9 arguments (!), and looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sets&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fromIndex&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; toIndex&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; minReps&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maxReps&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isAmrap&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; weight&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; rpe&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; shouldLogRpe&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;code&gt;minReps&lt;/code&gt; and &lt;code&gt;maxReps&lt;/code&gt; are diffrent, it&#39;ll make it a rep range. &lt;code&gt;isAmrap&lt;/code&gt; and &lt;code&gt;shouldLogRpe&lt;/code&gt; should be &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt; (&lt;code&gt;1&lt;/code&gt; means it&#39;s enabled).&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sets&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;lb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will set 6 reps, 50lb, non-AMRAP, 50lb, @8 RPE (without logging RPE) to sets from 2 to 4.&lt;/p&gt;
&lt;h2 id=&quot;how-to-delete-your-account&quot;&gt;How to delete your account&lt;/h2&gt;
&lt;p&gt;If you want to delete your account, go to Settings (cog icon at the bottom right corner), then Account - and there&#39;s a button to delete an account.&lt;/p&gt;
&lt;p&gt;You can also email to &lt;code&gt;info@liftosaur.com&lt;/code&gt;, and I can help with that.&lt;/p&gt;
&lt;p&gt;You can delete both local account (all your data stored on a device) and remote account - all your data stored on the servers. All the data there would be deleted permanently.&lt;/p&gt;
&lt;p&gt;There&#39;re 30 days backups on the servers, so the remote account data will be still in the backups for the next 30 days, and after that it&#39;ll be gone completely.&lt;/p&gt;
</content>
  </entry>
</feed>