<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>hiignite Dev log</title>
    <link>https://hiignite.com/blog.html</link>
    <description>Notes from building a coaching platform for trainers: stack decisions, trade-offs, and what actually held up.</description>
    <language>en</language>
    <copyright>© 2026 hiignite.com</copyright>
    <lastBuildDate>Sat, 07 Nov 2026 18:00:00 +0000</lastBuildDate>
    <atom:link href="https://hiignite.com/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Resources and self-serve programs, in every hiignite plan</title>
      <link>https://hiignite.com/blog/resources-and-self-serve-programs/</link>
      <guid isPermaLink="true">https://hiignite.com/blog/resources-and-self-serve-programs/</guid>
      <pubDate>Sat, 07 Nov 2026 18:00:00 +0000</pubDate>
      <dc:creator>Kenan Balija</dc:creator>
      <description>Live in the hiignite coach dashboard today, in every plan: share videos, PDFs, folders and links from your library with clients, and offer programs they can start on their own. On the phone with app 1.2.</description>
      <content:encoded><![CDATA[<p>Two things went live in the coach dashboard today. <strong>Resources</strong> is a shelf of your own videos, PDFs and links that clients open from their app. <strong>Self-serve programs</strong> are library programs that clients can start on their own. Like <a href="https://hiignite.com/blog/automations-in-every-plan/">automations</a>, both are in every plan, Pro and Unlimited, with no add-on.</p>

<p>You probably already have a library of technique videos and handouts, and your clients already ask what they should do when their block ends. This puts both a tap away, without a message from you each time, and without a second app or a Drive link.</p>

<p>The dashboard side is live now at trainer.hiignite.com. The phone side comes with app 1.2, which I submitted to the App Store and Google Play today. Your clients see the new screens once their app updates, in the coming days.</p>

<h2>Share from your library</h2>

<p>It all starts in <strong>Library</strong>, where your uploads already are. You can share a file (a video, an image or a PDF), a whole folder, or a link. A shared folder stays shared: files you add to it later go out with it.</p>

<p>A share reaches every client you have, now and in future, or one client you pick. Give it a title and, if you like, a short note, such as "Do one before every session. Five minutes is enough." The note shows under the item on their phone.</p>

<p>PDFs can now be uploaded to the library too, so your handouts sit next to your videos. Only your own uploads can be shared: form-check videos, progress photos and clients' private folders never can.</p>

<p><img src="https://hiignite.com/assets/dashboard-screens/library-shared.webp" width="720" height="283" alt="The hiignite Library in the coach dashboard. Three folders, Client Handouts, Technique Library and Warm-Up Routines, carry a green Shared tag. The folder tree on the left ends with Shared with clients, 6." /></p>

<h2>What clients see</h2>

<p>In app 1.2, Home has a <strong>From your coach</strong> card with the newest items. The whole shelf is under <strong>Resources</strong> in their Profile, grouped by your folders, with your note under each item.</p>

<p>Everything opens inside the app. A video plays in the app, an image opens in a viewer, a PDF opens in a reader with a share button, and a link opens in the in-app browser. Opening an item is recorded, so you see "Opened twice, last today" or "Not opened yet" against it.</p>

<p><img src="https://hiignite.com/assets/screens/resources.webp" width="264" height="541" alt="hiignite app, Resources: three items from the coach (a squat walkthrough link, a hip flexor video and a mobility routine link), then the Warm-Up Routines folder with three five-minute warm-up videos, each with the coach's note under it." /> <img src="https://hiignite.com/assets/screens/resource-pdf.webp" width="264" height="541" alt="hiignite app, a PDF handout called Nutrition basics open in the in-app reader, with a close button and a share button at the top." /> <img src="https://hiignite.com/assets/screens/browse-programs.webp" width="264" height="541" alt="hiignite app, Browse programs: a note that the coach has this client on Foundation Strength, then four programs the coach offers, each with its weeks, days per week, sessions, equipment and description." /></p>

<h2>Programs clients can start themselves</h2>

<p>On <strong>Programs</strong>, mark a published library program <strong>Clients can start this</strong>. Clients find it under <strong>Browse programs</strong>: in their Profile, and on Home when their block has ended or they have no program.</p>

<p>Each card shows the weeks, days per week and sessions, the equipment it needs, and your description. They pick one and tap <strong>Start today</strong>.</p>

<p>Starting gives the client their own copy, just like when you assign a program, so your library program never changes. They can switch to another program you offer, and a sheet tells them what stops and that their logged sessions are kept. Or they can stop it.</p>

<p>While you have a client on a block, Browse programs tells them so and nothing starts. That stays your call.</p>

<p><img src="https://hiignite.com/assets/dashboard-screens/programs-self-serve.webp" width="720" height="342" alt="The hiignite Programs page. A Self-serve tile at the top counts 4 programs and 3 clients started. Bodyweight Base is tagged Self-serve and Clients can start this, with 6 weeks, 3 days a week and 18 sessions, and By clients: 1 started, 0 finished, 0 stopped." /></p>

<h2>What you see</h2>

<p><strong>Shared with clients</strong>, at the bottom of the library's folder list, lists every share: who it reaches, how often it was opened and when. Each one has Unshare, and Add a link sits at the top. In the library itself, shared tiles carry a Shared tag and their open counts.</p>

<p>Each self-serve card on Programs counts how many clients started, finished and stopped it. A client who started on their own shows as <strong>Self-started</strong> on the roster and on their page, and their page has a Resources card with what they opened.</p>

<p><img src="https://hiignite.com/assets/dashboard-screens/shared-with-clients.webp" width="720" height="360" alt="Shared with clients in the hiignite Library: six shares, from links and folders sent to all 14 clients to one video for a single client, each with the coach's note. Each row says whether it was opened (Not opened yet, Opened 3 times and last today, or Opened once on Monday) and has an Unshare button. Add a link sits at the top." /></p>

<p>Already coaching on hiignite? <a href="https://trainer.hiignite.com/library">Open your Library</a> and share one handout with every client. New here? <a href="https://trainer.hiignite.com/register">Start free</a>, upload a video, and share it with your first client.</p>]]></content:encoded>
    </item>
    <item>
      <title>Automations are in every hiignite plan</title>
      <link>https://hiignite.com/blog/automations-in-every-plan/</link>
      <guid isPermaLink="true">https://hiignite.com/blog/automations-in-every-plan/</guid>
      <pubDate>Thu, 05 Nov 2026 09:00:00 +0000</pubDate>
      <dc:creator>Kenan Balija</dc:creator>
      <description>Automations are live in the hiignite coach dashboard, in every plan: onboarding, messages and forms on program days, simple rules, a Monday email and broadcasts. Everything sends as you, in your normal chat.</description>
      <content:encoded><![CDATA[<p>Automations went live in the coach dashboard today. Most coaching platforms either don't have them or sell them as an add-on, priced by how many clients you automate. I decided they belong in every plan, so they're in Pro and Unlimited at no extra cost.</p>

<p>Everything sends as you. An automatic message is an ordinary chat message in your thread with that client, so they just see their coach writing, and their app needs no update. It all lives under <strong>Automations</strong>, the lightning bolt in the dashboard's left rail.</p>

<h2>When a client joins</h2>

<p>Set it once: a starter program, an intake form, a welcome message (it can fill in <code>{first_name}</code> and <code>{program}</code>) and a few starter habits. When a client accepts your invite, all of it is applied straight away, so their first look at the app already has their program and your welcome. The program starts the day they join or the next Monday.</p>

<p><img src="https://hiignite.com/assets/dashboard-screens/automations.webp" width="720" height="588" alt="The hiignite Automations page. At the top, rules run as Email me first or Automatically, as me. Below it, When a client joins: the starter program Foundation Strength, starting the day they join or next Monday, a six-question intake form, and a welcome message that uses {first_name} and {program}." /></p>

<h2>Day items in the program builder</h2>

<p>A program day can now carry more than the workout: a message, a form (a one-off check-in), a habit that runs for as many weeks as you set, or a note (for now, a note arrives as a chat message too). So "Day 1: welcome", "Day 3: how did the first sessions feel?" and "Week 4: progress form" are written once in the library program, and every client gets them on their own day, at the time you set, on their own clock.</p>

<p>Programs are copied when you assign them, so editing the library never changes a client who is mid-block. If a client is paused, or you switched automations off for them, nothing piles up: an item that's too late is skipped and logged, not sent in a burst later.</p>

<p><img src="https://hiignite.com/assets/dashboard-screens/builder-day-items.webp" width="720" height="477" alt="The hiignite program builder on week 3. Day 5 lists three items under Also on this day: a message at 10:30 their time, a two-week habit, Stretch 10 min before bed, and a mid-block check-in form. The message is open on the right with its send time and a preview as one client will read it." /></p>

<h2>Rules for what clients don't do</h2>

<p>A rule is one trigger, one wait and one action. There's no flowchart, on purpose: you can read a rule six months later and still know what it does.</p>

<ul>
  <li><strong>Triggers:</strong> gone quiet (no session for a number of days), block ending soon, block ended, check-in overdue, habit streak reached, and joined.</li>
  <li><strong>Actions:</strong> send a message, give a form, add a habit, or start the next program, so "block ended" can queue the next block.</li>
</ul>

<p>A few presets are there when you first open the page. A rule stays quiet if you wrote to that client in the last few days, and it never acts in their evening or night: only from its send time until 21:00, their time.</p>

<p>Rules start in <strong>Email me first</strong>: they only report what they would do, in your Monday email, and nothing is sent. When you trust them, switch the page to <strong>Automatically, as me</strong>. Each rule's "Would run today" preview shows which clients it would reach, and when.</p>

<p><img src="https://hiignite.com/assets/dashboard-screens/automations-rule.webp" width="720" height="436" alt="A hiignite rule open in the editor. Quiet client: when a client has no session for 5 days, send a message as you, at 09:00 their time, skipped if you wrote in the last 4 days, at most once every 14 days. Beside it: Would run today: nobody. Listed in Monday's email. Nothing is sent." /></p>

<h2>The Monday email</h2>

<p>One email on Monday morning, from 07:00 your time, with your roster in one read: who is slipping, who has gone quiet, whose block ends, and what your rules would do or did. It goes to coaches with a live plan and at least one active client.</p>

<h2>Broadcasts, cohorts and pauses</h2>

<p><strong>Broadcasts</strong> send one message to many clients: all of them, a roster group, or everyone on one program. Each client gets it as a normal one-to-one message in their own thread, never a group chat, at the local time you picked in their own zone.</p>

<p><strong>Cohorts</strong> are the group block where everyone starts together. Pick several clients on the roster, assign one program, and give them a shared start: the same day, next Monday, or a day you choose.</p>

<p><strong>Pausing</strong> now shifts the plan. When you resume a paused client, their training and every day item move by the days they were paused, so nothing is lost or doubled. After a pause of 14 days or more, you're asked whether to continue where they left off or restart the block.</p>

<h2>A log and a switch on every client</h2>

<p>Each client's page has an Automations card: a log of what went out, when, and from which day item or rule, and a switch for On, Snooze 7 days or Off.</p>

<p>Already coaching on hiignite? <a href="https://trainer.hiignite.com/automations">Open Automations</a> and start with "When a client joins". New here? <a href="https://trainer.hiignite.com/register">Start free</a>, invite one client, and see what they get on day one.</p>]]></content:encoded>
    </item>
    <item>
      <title>hiignite is now in ChatGPT's plugin directory</title>
      <link>https://hiignite.com/blog/hiignite-is-in-chatgpts-plugin-directory/</link>
      <guid isPermaLink="true">https://hiignite.com/blog/hiignite-is-in-chatgpts-plugin-directory/</guid>
      <pubDate>Sun, 25 Oct 2026 09:00:00 +0000</pubDate>
      <dc:creator>Kenan Balija</dc:creator>
      <description>hiignite is now in ChatGPT's plugin directory. You no longer add it by hand with a server address: search for it in ChatGPT and install it, on a computer or on your phone.</description>
      <content:encoded><![CDATA[<p>Before this you had to add hiignite by hand, with a server address. Now you open Plugins in ChatGPT, search Hiignite, or any other keyword like coaching, and click +. <a href="https://chatgpt.com/plugins?search=Hiignite" target="_blank" rel="noopener">This link goes straight there</a>.</p>

<p>That also removes the catch from <a href="https://hiignite.com/blog/build-hiignite-programs-inside-chatgpt/">my last post</a>: you can do it in the phone app now, not only on a computer. A free ChatGPT account is enough, and you don't need a hiignite account until the first time you save a program.</p>

<p>What it can do hasn't changed. It saves programs and adds exercises that are not in your library yet. It still cannot see your clients, messages, form checks or billing, and nothing reaches a client until you assign it yourself.</p>

<p>Claude isn't part of this. There you still add it by hand, as a custom connector. <a href="https://hiignite.com/chatgpt/">The steps for both are here</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Build hiignite programs inside ChatGPT</title>
      <link>https://hiignite.com/blog/build-hiignite-programs-inside-chatgpt/</link>
      <guid isPermaLink="true">https://hiignite.com/blog/build-hiignite-programs-inside-chatgpt/</guid>
      <pubDate>Sun, 18 Oct 2026 09:00:00 +0000</pubDate>
      <dc:creator>Kenan Balija</dc:creator>
      <description>A lot of coaches draft programs in ChatGPT, then retype them into their coaching platform exercise by exercise. So I built a connector that skips the retyping. Here is what it can do, and what it can't.</description>
      <content:encoded><![CDATA[<p><strong>Update, Oct 25:</strong> hiignite is now in ChatGPT's plugin directory, so you no longer add it by hand, and you can do it on your phone. <a href="https://hiignite.com/blog/hiignite-is-in-chatgpts-plugin-directory/">The short version is here</a>. The rest of this post is from before that.</p>

<p>A lot of coaches already draft programs in ChatGPT. Then they retype the whole thing into their coaching platform, exercise by exercise.</p>

<p>So I built a connector that skips that part. Write the program in chat the way you already do, say "save it to Hiignite", and it lands in your library, ready to edit and assign. Each exercise gets matched to the catalogue, so your client gets its demo video, and anything it can't match comes through as your own custom exercise. It works with Claude too.</p>

<h2>Setting it up</h2>

<p>Setup is about two minutes, once, on a computer. Then you describe the client like you would to a colleague and end with "draft it in Hiignite".</p>

<h2>What it can and can't do</h2>

<p>In your account it can do two things: save programs, and add exercises that are not in the library yet. That is the whole list. It cannot see your clients, messages, form checks or billing, and it cannot assign anything.</p>

<p>A program from ChatGPT doesn't reach a client until you read it and assign it, same as one you wrote by hand.</p>

<h2>Two honest notes</h2>

<p>hiignite is not in ChatGPT's plugin directory yet, so you add it by hand. And it has to be added on a computer, not the phone app.</p>

<p>No idea yet if it will work. But watching a six week program go from one sentence to something sitting in the builder is the first time <a href="https://hiignite.com/blog/the-migration-problem-isnt-the-data/">the migration problem</a> felt solvable.</p>

<p><a href="https://hiignite.com/chatgpt/">The steps with screenshots are here</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>hiignite is live on both stores</title>
      <link>https://hiignite.com/blog/hiignite-is-live-on-both-stores/</link>
      <guid isPermaLink="true">https://hiignite.com/blog/hiignite-is-live-on-both-stores/</guid>
      <pubDate>Fri, 02 Oct 2026 09:00:00 +0000</pubDate>
      <dc:creator>Kenan Balija</dc:creator>
      <description>The hiignite client app is now on the App Store and Google Play. What slowed the launch down: Apple's review feedback, and the EU trader verification under the Digital Services Act that decides whether your app is visible at all.</description>
      <content:encoded><![CDATA[<p>It took longer than I thought it would. Two things slowed it down.</p>

<h2>Apple's review</h2>

<p>First, Apple came back with a few things they wanted changed before approval.</p>

<h2>Trader status</h2>

<p>Second, trader status. Under the EU's Digital Services Act you have to verify yourself as a trader before your app is visible in EU storefronts at all. Not a soft warning either: the app just does not show up. Since I am based in the EU and most of my early coaches will be too, this was not something I could leave for later. If you are shipping anywhere in the EU, do this before you submit, not after.</p>

<h2>What clients get</h2>

<p>Today's session, one tap to mark sets complete, form check video recording, and chat with their coach. What coaches get stays on the web dashboard: programming, review queue, roster.</p>

<p><img src="https://hiignite.com/assets/screens/05-schedule.webp" width="264" height="541" alt="hiignite schedule screen: a week of programmed sessions with completed, next up, and rest days" /> <img src="https://hiignite.com/assets/screens/03-active-workout.webp" width="264" height="541" alt="hiignite mobile workout player: logging reps and weight for Barbell Back Squat with a rest timer running" /> <img src="https://hiignite.com/assets/screens/06-chat.webp" width="264" height="541" alt="hiignite chat screen: a client and coach discussing squat tempo alongside form-check video feedback" /></p>

<h2>Get the app</h2>

<p>Clients join by their coach's invite, then download it like any other app.</p>

<p><strong>App Store:</strong> <a href="https://apps.apple.com/app/hiignite/id6814269056">hiignite on the App Store</a><br />
<strong>Google Play:</strong> <a href="https://play.google.com/store/apps/details?id=com.hiignite.app">hiignite on Google Play</a></p>

<p>If you coach, the app is for your clients, not you. You program and review from the web dashboard. <a href="https://trainer.hiignite.com/register">Start free</a>, build a session, and send your first invite.</p>]]></content:encoded>
    </item>
    <item>
      <title>The migration problem isn't the data</title>
      <link>https://hiignite.com/blog/the-migration-problem-isnt-the-data/</link>
      <guid isPermaLink="true">https://hiignite.com/blog/the-migration-problem-isnt-the-data/</guid>
      <pubDate>Wed, 26 Aug 2026 09:00:00 +0000</pubDate>
      <dc:creator>Kenan Balija</dc:creator>
      <description>A coach explains that the real barrier to switching online coaching platforms isn't exporting client data. It's rebuilding a library of exercise videos for uncommon movements. Plus: building an AI import wizard.</description>
      <content:encoded><![CDATA[<p>I talked to a coach this morning about what would actually stop him switching online coaching platforms.</p>

<p>I expected the answer to be data. Exporting his client records and programmes out of a system that would rather he didn't. That's a real problem, and some platforms do make leaving deliberately awkward.</p>

<p>That wasn't his answer.</p>

<h2>It's the videos</h2>

<p>His biggest hurdle is finding exercise videos. Not bench press: every stock library has bench press. It's the uncommon movements he actually programmes: handstand plate walks, handstand shoulder taps, the things that make his coaching <em>his</em> rather than generic.</p>

<p>Those aren't in the standard libraries. Rebuilding that collection on a new platform takes hours he doesn't have, and until it's rebuilt, the new platform is worse than the old one at precisely the thing he's known for.</p>

<p>Which reframes the whole migration problem for me. I'd been thinking about switching costs as a data problem. For him it's a content problem, and content is a lot harder to export than a spreadsheet of client records.</p>

<h2>So I have a decision to make</h2>

<p>I could film them myself. Unique, premium, mine. And slow. I'm also not confident I'd get the demonstration quality right for every movement, and a badly demonstrated handstand progression is worse than no video at all. I'll probably go this route once the app is making revenue and I can work with real professionals on it.</p>

<p>Or I could use free and open resources. Much faster, but it comes with real risks I'd need to work through first: licensing, attribution, inconsistent quality, and what happens when a source disappears and takes part of the library with it.</p>

<p>I'm still thinking on this one. If you've solved exercise video libraries before, I'd genuinely like to hear how.</p>

<h2>Meanwhile: the import wizard</h2>

<p>This week I'm building the part I <em>can</em> solve. Coaches screenshot their programmes from their old platform, and AI reconstructs them inside hiignite, so migration stops being a weekend of manual retyping.</p>

<p>It doesn't fix the video library. But it does mean the rest of the move is no longer a reason to stay put.</p>]]></content:encoded>
    </item>
    <item>
      <title>Getting Google out of the page load</title>
      <link>https://hiignite.com/blog/getting-google-out-of-the-page-load/</link>
      <guid isPermaLink="true">https://hiignite.com/blog/getting-google-out-of-the-page-load/</guid>
      <pubDate>Wed, 19 Aug 2026 09:00:00 +0000</pubDate>
      <dc:creator>Kenan Balija</dc:creator>
      <description>Why hiignite.com runs Altcha, self-hosted Umami and self-hosted webfonts instead of the Google equivalents, including why the font swap is the one people miss, and why it is now faster too.</description>
      <content:encoded><![CDATA[<p>Small stuff, but it adds up. <a href="https://hiignite.com/">hiignite.com</a> runs <a href="https://altcha.org/">Altcha</a> instead of Google reCAPTCHA, and self-hosted <a href="https://umami.is/">Umami</a> instead of Google Analytics. This week I moved the fonts off Google's CDN too.</p>

<p>All three do the same job as the thing they replaced. None of them send visitor data to Google.</p>

<h2>Three swaps</h2>

<p>Altcha runs its challenge locally: proof-of-work in the visitor's own browser, rather than a call out to a third party to grade whether someone looks human enough.</p>

<p>Umami runs on my own infrastructure. It still tells me which pages get read and roughly where readers are, without setting a cookie and without an analytics company sitting in the middle of it.</p>

<p>Fonts are now served from my own domain, so there's no request to <code>fonts.googleapis.com</code> handing over a visitor's IP on every page load.</p>

<h2>The font one is the easiest to miss</h2>

<p>That last one slips past people, and I think I know why: it arrives through a CSS import, not a script tag.</p>

<p>When you audit what a page loads, you look at scripts. Scripts are where tracking lives, so scripts are what you check. A stylesheet link doesn't read as a third party. It reads as styling. But a font request is still a request, with an IP attached, made by every visitor on every page, to a server that isn't yours.</p>

<h2>The performance argument flipped</h2>

<p>The old defence of Google Fonts was the shared cache: enough sites use the same CDN copy that visitors turn up with it already cached, so you get the font for free.</p>

<p>That argument is dead. Browser cache partitioning gives each site its own cache entry, so nobody arrives at your site with your font pre-cached from somebody else's. What's left of the CDN is a DNS lookup and a connection to a third-party origin that has to complete before the font can even begin downloading.</p>

<p>Self-hosting removes both. It's usually the faster option now. The tradeoff people were accepting on privacy grounds doesn't really exist any more.</p>

<h2>The actual reason</h2>

<p>Same pattern I've been following elsewhere: self-hosted mail, self-hosted infrastructure. Not because Google's tools are bad. They're mostly excellent, mostly free, and better engineered than what I'd build. It's that I'd rather not hand more of the stack to companies whose incentives aren't mine.</p>

<p>And it might turn out to be a real selling point. Trainers handle client health and fitness data: weights, measurements, progress photos, injury history. My guess is they care about where that ends up rather more than they tend to say out loud.</p>]]></content:encoded>
    </item>
    <item>
      <title>Why I built the API in Rust when it wasn't the optimal choice</title>
      <link>https://hiignite.com/blog/why-rust-for-the-api/</link>
      <guid isPermaLink="true">https://hiignite.com/blog/why-rust-for-the-api/</guid>
      <pubDate>Wed, 05 Aug 2026 09:00:00 +0000</pubDate>
      <dc:creator>Kenan Balija</dc:creator>
      <description>An honest account of choosing Rust for hiignite's API as a solo developer: slower development, a compiler that refuses shortcuts, and why it was still the right call for learning.</description>
      <content:encoded><![CDATA[<p>I built <a href="https://hiignite.com/">hiignite</a>'s API in Rust, and I'll say up front that it wasn't the optimal choice for a new project. I want to write the actual reasoning down, because "why Rust" deserves a more honest answer than the one it usually gets.</p>

<h2>The honest reason</h2>

<p>I kept seeing it everywhere. It read as a next-generation language, the one that keeps turning up in the places I pay attention to, and I wanted to build something real with it rather than work through another tutorial. That's the origin of the decision. Not benchmarks, not a requirements matrix. Curiosity, plus a project I actually wanted to exist.</p>

<p>I think that's a more common reason than people admit, and I'd rather own it than reverse-engineer a business case after the fact.</p>

<h2>What it costs</h2>

<p>Development is a lot slower than in any language I already knew. Not slightly slower, a lot. Things I could have written on autopilot elsewhere became genuine problems to sit with.</p>

<p>And the compiler does not let you cut corners. There's no "I'll handle that case later," no leaving a value in a shape you haven't thought through. It refuses, and it refuses in detail, and you don't move until you've answered it. I have a love-hate relationship with it: it's the most irritating part of the day and the reason I trust the result.</p>

<p>On a project built in free time, that cost is real. Slower progress on a side project isn't an abstraction. It's the difference between shipping something and losing interest.</p>

<h2>The bet</h2>

<p>The thought was that it would mould me into a better programmer. Not "better at Rust", better generally. A language that forces you to be explicit about ownership, about what can fail, about what might be absent, is a language that makes you notice how much you'd been leaving to luck everywhere else.</p>

<p>That part I already believe has paid off, and I don't think you need the project to launch to see it.</p>

<h2>Fewer production surprises? Ask me later</h2>

<p>The other thing you read constantly is that Rust means fewer surprises in production. I'd like it to be true. I genuinely don't know yet, because <strong>we're not live</strong>.</p>

<p>So I'm not going to claim it. Anyone telling you about their production Rust experience before they have production is describing a hope, not a result. Ask me again after launch and I'll have an actual answer, including if the answer is that it made no difference.</p>

<h2>Would I recommend it?</h2>

<p>For every project? No. If you're on a deadline, or validating an idea that might not survive the month, use what you know. Rust will slow you down, and being slow to find out an idea was wrong is the expensive kind of slow.</p>

<p>But if you want to learn something that will matter, and you've got a project you're already turning over in the back of your mind anyway, then yes. A big yes. Learning a language in the abstract teaches you syntax. Learning it while building something you care about teaches you the language.</p>

<p>That combination is the only reason this was a good decision. On its own, "Rust for a new solo project" isn't one.</p>]]></content:encoded>
    </item>
    <item>
      <title>Why our TypeScript types come from Rust</title>
      <link>https://hiignite.com/blog/specta-rust-typescript-types/</link>
      <guid isPermaLink="true">https://hiignite.com/blog/specta-rust-typescript-types/</guid>
      <pubDate>Sun, 26 Jul 2026 09:00:00 +0000</pubDate>
      <dc:creator>Kenan Balija</dc:creator>
      <description>Specta generates hiignite's TypeScript types directly from the Rust structs, so the mobile app and web dashboard can't drift from what the API actually returns. And what that costs in workflow.</description>
      <content:encoded><![CDATA[<p>One of the better decisions in building <a href="https://hiignite.com/">hiignite</a> was letting the TypeScript types come from the Rust: not written alongside it, generated from it, by <a href="https://github.com/specta-rs/specta">Specta</a>.</p>

<p>I touched on this in the <a href="https://hiignite.com/blog/building-hiignite-rust-expo-monorepo/">stack overview</a>, but it deserves its own post, because it's the piece I'd keep if I had to throw the rest away.</p>

<h2>The problem it solves: drift</h2>

<p>In past projects, the mobile app and the web client would slowly drift out of sync with the API. Someone renames a field on the backend. The clients carry on referencing the old name. Nothing complains: TypeScript is perfectly happy, because the type it's checking against is one <em>a person wrote by hand</em>, not the one the API actually returns.</p>

<p>So you find out at runtime. Usually on a device. Usually after it shipped.</p>

<p>The insidious part is that hand-written client types <em>look</em> like type safety. You get the autocomplete, the red squiggles, the whole experience. What you don't get is any guarantee that the description still matches the thing being described.</p>

<h2>What Specta changes</h2>

<p>Specta generates the TypeScript straight from the Rust structs, so there's one source of truth and it's the one the API actually serialises:</p>

<pre><code>#[derive(Serialize, Type)]
pub struct Client {
    pub id: Uuid,
    pub name: String,
    pub active: bool,
}</code></pre>

<p>…becomes:</p>

<pre><code>export type Client = { id: string; name: string; active: boolean }</code></pre>

<p>The mobile app and the web dashboard both consume that generated file. Neither can drift from what the API returns, because neither holds an independent opinion about what the API returns.</p>

<p>Rename <code>name</code> to <code>full_name</code> in the Rust, regenerate, and both clients stop compiling, in exactly the places that reference it. The bug that used to surface as a blank field on somebody's phone now surfaces as a build error on my laptop, before I've pushed anything.</p>

<h2>The tradeoff: no free lunch</h2>

<p>Every time the Rust structs change, I have to rebind and regenerate. That's a real step in the workflow, and it's a step I can forget. Forget it and the generated types are stale, which puts you back in drift territory, just with a shorter fuse, because the next regeneration surfaces everything at once.</p>

<p>I won't pretend that's free. It's friction, it's mine to remember, and anyone who tells you code generation costs nothing hasn't run it on a Friday evening.</p>

<h2>Still worth it, every time</h2>

<p>Catching a type mismatch at compile time instead of in production is worth an extra step in the workflow. Every time.</p>

<p>The asymmetry is what makes it easy. Forgetting to regenerate costs me a confusing minute and a rebuild. <em>Not</em> having the generated contract costs a runtime bug on someone else's device, found by them, reported through a channel that may not exist yet, debugged without the stack trace I'd want. One of those is an inconvenience. The other is the kind of bug that erodes trust in the whole app.</p>

<p>For a solo build in evenings and weekends, that trade is not close.</p>]]></content:encoded>
    </item>
    <item>
      <title>Six months building hiignite: Rust, Expo, and one typed contract</title>
      <link>https://hiignite.com/blog/building-hiignite-rust-expo-monorepo/</link>
      <guid isPermaLink="true">https://hiignite.com/blog/building-hiignite-rust-expo-monorepo/</guid>
      <pubDate>Wed, 24 Jun 2026 09:00:00 +0000</pubDate>
      <dc:creator>Kenan Balija</dc:creator>
      <description>The stack behind hiignite: a mobile app, web dashboard and API in one monorepo. Expo, Rust with Axum, SeaORM, and the Specta-generated type contract that killed mobile/web mismatch bugs before they happened.</description>
      <content:encoded><![CDATA[<p>Six months ago I started building <a href="https://hiignite.com/">hiignite</a>, a coaching platform for personal trainers. Mobile app, web dashboard, and API, built as one connected monorepo system instead of three separate stacks. I'm still building it in my free time, so every decision had to survive a specific test: <em>will this still make sense to me after two weeks away from the code?</em></p>

<p>Here's what I picked, and why.</p>

<h2>One system, not three stacks</h2>

<p>The obvious way to build this is three repositories: an app, a dashboard, and a backend. Three CI setups, three dependency trees, three places where the definition of "a client" can quietly drift apart.</p>

<p>I went with a monorepo instead. Not for the tooling fashion, for the seam. When the app, the dashboard and the API live in one tree, a change to a data shape is one commit that touches every consumer of it, and the thing either builds or it doesn't. Working solo, in evenings, that feedback is worth more than any amount of separation-of-concerns purity. The failure mode I was designing against isn't "the code is messy." It's "I shipped a field rename on Tuesday and found out on Sunday that the app never got the memo."</p>

<h2>Expo for mobile <em>and</em> the web dashboard</h2>

<p>Coaches work from a laptop. Their clients work from a phone. That's two products with a lot of shared vocabulary (the same programs, the same sessions, the same clients) and I didn't want to write that vocabulary twice.</p>

<p>Expo covers both from one codebase. iOS, Android and the web dashboard come out of the same components, the same navigation, the same state. Where the platforms genuinely diverge they diverge deliberately, in a file I had to open on purpose, rather than by accident across two codebases that slowly stop agreeing with each other.</p>

<h2>Rust and Axum for the API</h2>

<p>This one I'll be honest about: I was learning Rust and I wanted to use it on something real. Toy projects teach you syntax. They don't teach you what the borrow checker feels like at 11pm when you're four layers into a request handler and something doesn't live long enough.</p>

<p><a href="https://github.com/tokio-rs/axum">Axum</a> turned out to be a good fit for exactly that reason: handlers are plain async functions, extractors are types, and most of what would be framework magic elsewhere is just the type system doing its job. The learning curve was real. What I got in return is an API where a whole category of "what if this is null" questions is answered at compile time rather than in production.</p>

<p>Would a solo builder ship faster in something they already know? Yes, and not by a little. Development in Rust is slower than in any language I'd already learned, and I took that trade deliberately. The typed contract further down is a big part of what I got back for it.</p>

<h2>SeaORM, because I came from Laravel</h2>

<p>Most of my background is Laravel, and the thing I missed most when I moved to Rust wasn't the syntax. It was migrations. Having a versioned, ordered, reversible history of the schema is not a nice-to-have; it's how you stay sane when the schema changes weekly.</p>

<p><a href="https://www.sea-ql.org/SeaORM/">SeaORM</a> is the option that felt most familiar coming from that world. Entities, relations, and a migration story that maps closely onto how I already thought about the database. It's not a one-to-one Eloquent port and it doesn't pretend to be, but the mental model transferred, which meant I spent my learning budget on Rust itself instead of on re-learning how to add a column.</p>

<h2>Specta: the decision that paid off most</h2>

<p>Here's the seam that would have hurt: the API is Rust, and both clients are TypeScript. Every endpoint is a place where a Rust struct becomes JSON and a TypeScript type has to agree with it. Hand-write those types on the client and you've built a bug generator, one that only fires at runtime, on a device, usually after you've shipped.</p>

<p><a href="https://github.com/specta-rs/specta">Specta</a> generates the TypeScript from the Rust. The struct is the source of truth:</p>

<pre><code>#[derive(Serialize, Type)]
pub struct Client {
    pub id: Uuid,
    pub name: String,
    pub active: bool,
}</code></pre>

<p>…and the client-side type falls out of it:</p>

<pre><code>export type Client = { id: string; name: string; active: boolean }</code></pre>

<p>Which sounds like a small convenience and isn't. Rename a field in Rust, regenerate, and the app and the dashboard both stop compiling in exactly the places that need attention. The classic mobile/web type-mismatch bug, where one client was updated and the other wasn't, stops being a bug you find and starts being a build you can't finish. It got caught before it happened, which is the only kind of catching that's actually cheap.</p>

<p>If I had to keep one decision from this whole list, it would be this one.</p>

<h2>Bruno, so the API explains itself</h2>

<p>API collections in <a href="https://www.usebruno.com/">Bruno</a>, checked into the repo alongside the code. The point is that the API is explorable without digging through handler source to work out what a request is supposed to look like.</p>

<p>Right now the person that helps most is me: future me, coming back to an endpoint I wrote in March. But it's also the difference between "here's the API" and "here's the API, and here's a folder of working requests you can fire at it." Bruno keeps those requests as plain files in git rather than trapped in someone's account, which is the part that made me pick it.</p>

<h2>And Docker, because why not</h2>

<p>Docker for the environment. Postgres and the API come up with one command, the same way on any machine I sit down at. It costs almost nothing to set up early and it's genuinely painful to retrofit later, so it went in early.</p>

<h2>Six months in</h2>

<p>The stack, in one line: <strong>Expo</strong> for mobile and the web dashboard, <strong>Rust + Axum</strong> for the API, <strong>SeaORM</strong> for the schema, <strong>Specta</strong> for the type contract between them, <strong>Bruno</strong> for exploring the API, and <strong>Docker</strong> for the environment, all in one monorepo.</p>

<p>None of it is exotic. What's held up is the theme running through it: put the seams where a machine can check them. The monorepo makes a cross-cutting change one commit. Expo makes the app and the dashboard one vocabulary. Specta makes the Rust/TypeScript boundary a compile error instead of a support ticket. For someone building in their free time, the compiler is the only teammate reviewing my work, so I've tried to give it as much to review as possible.</p>

<p>Still building. More notes as things break.</p>]]></content:encoded>
    </item>
  </channel>
</rss>
