<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shri</title>
    <description>The latest articles on DEV Community by Shri (@hereticles).</description>
    <link>https://dev.to/hereticles</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3276906%2F8b659278-fadc-4170-801f-2c737785f62d.png</url>
      <title>DEV Community: Shri</title>
      <link>https://dev.to/hereticles</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hereticles"/>
    <language>en</language>
    <item>
      <title>Mobile Strategy Games Don't Have a Monetisation Problem - They Have a Game Problem</title>
      <dc:creator>Shri</dc:creator>
      <pubDate>Thu, 28 May 2026 07:10:56 +0000</pubDate>
      <link>https://dev.to/hereticles/mobile-strategy-games-dont-have-a-monetisation-problem-they-have-a-game-problem-4icj</link>
      <guid>https://dev.to/hereticles/mobile-strategy-games-dont-have-a-monetisation-problem-they-have-a-game-problem-4icj</guid>
      <description>&lt;p&gt;A few months ago, my partner got me playing one of those mobile strategy games - you know the type - build a city, train troops, join an alliance, fight other players etc.. They had already tried two others before - one shut down, and the other didn’t quite stick.&lt;/p&gt;

&lt;p&gt;I avoided playing any of the previous ones, mainly on principle - I didn’t want to play a P2W game. But then they seemed to be having so much fun, so I thought I’d give it a go.&lt;/p&gt;

&lt;p&gt;I’m not proud of it. I keep thinking that I should quit, that I shouldn’t spend money on it.&lt;/p&gt;

&lt;p&gt;What keeps me in it?&lt;/p&gt;

&lt;p&gt;The community is genuinely good. The alliance I’m in has brought together smart, funny, generous people from across the world who I wouldn’t have met any other way. We coordinate across time zones. We celebrate each other’s real-life milestones. We’ve built something that feels, improbably, like a small society.&lt;/p&gt;

&lt;p&gt;The game, though. The game grates at me constantly.&lt;/p&gt;

&lt;p&gt;And the more I’ve thought about why, the more I’ve come to an uncomfortable conclusion: &lt;strong&gt;there is no game there. There never was.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Monetisation as Design
&lt;/h2&gt;

&lt;p&gt;Looking in from the outside, I figured these were games which allowed you to skip time / effort by spending money. I was woefully naive.&lt;/p&gt;

&lt;p&gt;Every single aspect of the game is built to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feel like what you want is &lt;em&gt;just&lt;/em&gt; out of reach&lt;/li&gt;
&lt;li&gt;Train you to spend (gems, speed ups, stacks of other currency types)&lt;/li&gt;
&lt;li&gt;Manufacture social pressure to level up, support your alliance&lt;/li&gt;
&lt;li&gt;Blur the line between in-game currency and real money&lt;/li&gt;
&lt;li&gt;Make ego bruising a de-facto standard, forcing impulsive spend&lt;/li&gt;
&lt;li&gt;Design specific events to funnel actions into spending&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s take each of these in turn&lt;/p&gt;

&lt;h3&gt;
  
  
  Just out of reach
&lt;/h3&gt;

&lt;p&gt;There is the basic timer-gating of everything as you’d expect, but it gets more sophisticated. At first, there is a cap of 30 for the city level.&lt;/p&gt;

&lt;p&gt;Everybody races there - spending money. The F2P players don’t stand a chance. You need to spend hundreds of dollars to get there. Then it extends into fire crystal. The free players are forever falling even further behind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spend, spend, spend
&lt;/h3&gt;

&lt;p&gt;When I first started playing, I hoarded stuff - gems, speed ups, chests etc. Then I realised that just slows me down. The “way to play the game” is to spend everything as soon as you get it. That rewards you with more stuff, which you then spend.&lt;/p&gt;

&lt;p&gt;There are red dots everywhere guiding you through all the stuff you get “for free” and you’ll “earn” by spending other currency.&lt;/p&gt;

&lt;p&gt;The faster you can spend stuff, the faster you get stuff, and the faster you grow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Social Pressure
&lt;/h3&gt;

&lt;p&gt;Most servers end up with a Non-Aggression-Pact or NAP alliances - starting with the 10 most powerful alliances. This list tends to shrink as the wheat is separated from the chaff. No prizes for guessing how you end up a powerful alliance.&lt;/p&gt;

&lt;p&gt;To be able to be competitive with the other alliances, we need to grow. If the other alliances have players that spend a lot of money, we’ll need to match them.&lt;/p&gt;

&lt;p&gt;The top two alliances in our server have decided that they will rotate the “presidency” between them - because the other alliances are not strong enough. They want to encourage the other alliances to grow through competition or merges.&lt;/p&gt;

&lt;p&gt;Why is that fair? Because, otherwise, it’ll demotivate the “whales” from spending money or playing the game. That was the literal justification from the leader of the top alliance.&lt;/p&gt;

&lt;h3&gt;
  
  
  In-game currency vs real money
&lt;/h3&gt;

&lt;p&gt;The game trains you to click buttons - you get rewards. Some of them give you gems, some of them give you speed ups, resources - all useful stuff. It’s easy, and attractive to click the buttons.&lt;/p&gt;

&lt;p&gt;There are paid packs - and they have attractive buttons too - but they look different enough that you wouldn’t accidentally buy something.&lt;/p&gt;

&lt;p&gt;There is always a pack advertised when you open the game. You get into the habit of closing it quickly. Except, occasionally, they’ll offer a pack for free. So you “buy it.” You are being taught that buying a pack is easy, simple and profitable.&lt;/p&gt;

&lt;p&gt;They have another event which is a series of purchases. The first one is free, which unlocks one for £0.99, which unlocks two more free ones, then £1.99 and so on. The more expensive packs are ranked as epic / mythic etc. so it feels aspirational.&lt;/p&gt;

&lt;p&gt;But wait - £0.99 for an extra build queue - that’s hands down the best deal ever - and it’s only a pound. It’s worth it at the start of the game. I’ll get that. I’ve just trained myself that it’s ok to spend money in the game. The floodgates have opened.&lt;/p&gt;

&lt;p&gt;I found myself buying a pack the other day, on autopilot. I knew what was happening, but I couldn’t stop myself. It was only a pound - but it was 4000% of gem value - most of the packs are only 2000% - 3000%. What a good deal!&lt;/p&gt;

&lt;h3&gt;
  
  
  Buying Ego
&lt;/h3&gt;

&lt;p&gt;Every single thing in this game is ranked. Most powerful player, alliance, heroes, troops - you name it - there is a ranking for it somewhere. Events (most of which are purely about spending) are also ranked.&lt;/p&gt;

&lt;p&gt;Who wants to be ranked 523?&lt;/p&gt;

&lt;p&gt;These rankings set up the sharper edge.&lt;/p&gt;

&lt;p&gt;A powerful player from another alliance attacked everyone in our alliance. We were burning.&lt;/p&gt;

&lt;p&gt;The top player in our alliance retaliated by burning almost everyone in their alliance. He burned through hundreds of dollars trying to burn the attacking player - and ultimately had to give up.&lt;/p&gt;

&lt;p&gt;I felt bad for that player. He had spent all this money, and because the game lets your soldiers die, which a lot of his did in the endeavour, he wasn’t that much more powerful than when he started. He might as well have set his money literally on fire.&lt;/p&gt;

&lt;p&gt;Several weeks later, a player in our sister alliance was attacked. The attacker was small enough that a small group of us was able to “exact vengeance.” However, I felt my blood boil - “how dare they?” If they were stronger, would I have burned money for vengeance?&lt;/p&gt;

&lt;h3&gt;
  
  
  Events Funnel To Spend
&lt;/h3&gt;

&lt;p&gt;Of course, all of the above is not enough. Now, everything is wound tight - obstacles to spending have been removed, and the pavlovian behaviour has been well trained.&lt;/p&gt;

&lt;p&gt;The next step is events where you fight against other alliances. The rewards are just enough to make it interesting - but the competitive environment is electric. Squad chat is buzzing, the commanders are directing. You are teleporting, attacking, there are small fires.&lt;/p&gt;

&lt;p&gt;It is still timer-gated. You can teleport every x minutes. Your troops need to be healed immediately, so you’ll need to spend speed ups.&lt;/p&gt;

&lt;p&gt;New areas open up so you have to teleport, then time runs out. Your friends are being attacked and they want you to help them. You don’t have any ports left - so what do you do? You buy them with gems. The next teleport is twice as expensive. You run out of gems. What do you do?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it works
&lt;/h2&gt;

&lt;p&gt;For me, it’s the people. It’s not just that though. The game rewards you for clicking. It gives you red dots to clear, and it doesn’t take any cognitive load. The game has many layers of complexity. That is interesting to peel back and figure out.&lt;/p&gt;

&lt;p&gt;There are regular events you can participate in, and it feels good when you get better at it.&lt;/p&gt;

&lt;p&gt;All of this though is underpinned by the social aspect. You are not just getting better at the game, others in the alliance are watching you get better at the game. The witness aspect is truly rewarding.&lt;/p&gt;

&lt;p&gt;The next aspect is the electricity in the battles - whether it is fighting for a facility, or the events, the adrenaline is pumping, and your heart is pounding.&lt;/p&gt;

&lt;p&gt;I remember, a few weeks ago a couple of us were garrisoned in a building with a couple of minutes left in the timer. The enemy literally surrounded us. There were perhaps a dozen red cities to our two or three blue ones.&lt;/p&gt;

&lt;p&gt;We needed one more person to come and reinforce us. The air sparkled blue, flashes of lightning and someone ported in. They backed us up, and we won!&lt;/p&gt;

&lt;p&gt;Another time, similar situation, we were woefully outnumbered, but we were just about holding it. Then someone teleports in, and starts burning the enemy cities. One by one, they get ported out. We hold the building. We win.&lt;/p&gt;

&lt;p&gt;Of course, sometimes we lose - but that bonds us too.&lt;/p&gt;

&lt;p&gt;The game creates intense relationships with people all across the world - with people we will probably never meet in real life. If we met these people in real life, we wouldn’t be able to talk to each other because most of them speak another language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Well, let’s make it ethical
&lt;/h2&gt;

&lt;p&gt;The obvious response is: build the same game with ethical monetisation. Cosmetics only. No pay-to-win. Keep the city builder, keep the alliance politics, keep the territory wars.&lt;/p&gt;

&lt;p&gt;I don’t think that’ll work.&lt;/p&gt;

&lt;p&gt;The engagement in these games - the reason people stay, the reason alliances form, the reason anyone cares about territory - is almost entirely downstream of the pressure. Remove the monetised pressure and you have to replace it with something else, or the game has no tension.&lt;/p&gt;

&lt;p&gt;What keeps you logging in isn’t love of the game mechanics. It’s social obligation created by artificial scarcity. Fix the artificial scarcity and the logins stop. Which means the alliance coordination stops. Which means the community - the genuinely good thing these games accidentally create - stops.&lt;/p&gt;

&lt;p&gt;Without the whales, there is nothing to rail against - so the free players would all just get along with little conflict.&lt;/p&gt;

&lt;p&gt;There are times when we would turn up to take over a facility and smaller alliances would just leave the territory. They might have taken the facility already, but instead of trying to hold it, they just leave. They don’t want to recoup any potential damage.&lt;/p&gt;

&lt;p&gt;Without the option of paying money to get more powerful - maybe everyone would just leave when someone clearly more powerful came along. Maybe people would focus more on the co-operative aspects. But the co-operative aspects aren’t necessarily fun by themselves - they’re the chores you do to be more powerful and fight in PvP.&lt;/p&gt;

&lt;p&gt;Sure PvP takes skill, some tactics and practice - no question. However, these requirements are there only to legitimise it when people spend money. “It’s not just about spending money” - but if that’s the case, then why do so many people spend so much money?&lt;/p&gt;

&lt;p&gt;This is not a strategy / city building game. It’s a how-quickly-can-I-drain-my-bank-account game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Good Live Service Games
&lt;/h2&gt;

&lt;p&gt;There are three good live service games that I point people to. Path of Exile (1 &amp;amp; 2), Guild Wars (1 &amp;amp; 2) and Eve Online.&lt;/p&gt;

&lt;p&gt;They all focus on paying for convenience rather than pay to win, and of course cosmetics.&lt;/p&gt;

&lt;p&gt;They are all very different games from this though. I have played PoE, and GW, both 1 and 2 for hundreds of hours but I did not build these intense relationships in there. In fact, I built no real friendships in those games.&lt;/p&gt;

&lt;p&gt;But then, I didn’t really play PvP - I stuck to the PvE and never felt pulled into the competitive layer the way this mobile game pulled me in.&lt;/p&gt;

&lt;p&gt;Eve Online would probably be the closer fit - and I really enjoyed it. The main problem I had with it was that it doesn’t force you into alliances (or corporations in their parlance) like these mobile games do.&lt;/p&gt;

&lt;p&gt;The world feels humungous and difficult to navigate. On these mobile games, you quickly get a sense of the whole population on the server. You see the same people over and over again at facilities, or event leaderboards. You are forced into an alliance to survive and there is alliance chat front and centre.&lt;/p&gt;

&lt;p&gt;Without the highly extractive spine of these mobile games, is it possible to create a game capable of high engagement and tight-knit communities?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fundamentals
&lt;/h2&gt;

&lt;p&gt;To be able to make a game that creates the best of these mobile games without the worst of it, the first thing that needs to be set correctly is the incentive&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;not for the players, but for the ones creating the game.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A studio optimising for extraction will build different systems than one optimising for a game worth playing. Everything downstream follows from that choice.&lt;/p&gt;

&lt;p&gt;This part is difficult to get right. It needs trust, and faith.&lt;/p&gt;

&lt;p&gt;Grinding Gear Games, who made Path of Exile, mainly focused on building a really good game and trusted their player base to appreciate it.&lt;/p&gt;

&lt;p&gt;I spent over £100 on Guild Wars 2 buying all the expansions and a bunch of stuff from the store. Content and features were gated behind these purchases, but I was happy to spend the money - this time I was also happy to unlock features I wanted.&lt;/p&gt;

&lt;p&gt;I don’t care about cosmetics - I never have - and yet, I spent money on Path of Exile cosmetic packs. I can’t help it. I feel fantastic when I spend money with GGG buying things I don’t care about. I’ve spent over £100 on their packs.&lt;/p&gt;

&lt;p&gt;I’ve spent maybe £50 on this mobile game - and I felt like a chump after _every_transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Player Value
&lt;/h2&gt;

&lt;p&gt;With community and bonding as the highest items of player value, intense shared player experiences become the central piece of the game.&lt;/p&gt;

&lt;p&gt;The language barrier has to be lifted. Automated translations are a minimum requirement - and ideally better quality than what’s in these games.&lt;/p&gt;

&lt;p&gt;These experiences should not be fully on the player schedule. Being forced into an encounter is part of the intensity of the experience. Without that, it’s easy to just forget about engaging.&lt;/p&gt;

&lt;p&gt;It needs to be people-led - as it is now - the alliances are the core component. Negotiating with other alliances and players for non-aggression pacts, punishing poorly behaved players, are all important aspects of building the sense of community and belonging.&lt;/p&gt;

&lt;p&gt;GGG uses seasonal resets to powerful effect. Having an end state after which the whole server starts again helps avoid constant power creep. It also gives players another chance to start from the beginning, using all the knowledge and experience they’ve gained.&lt;/p&gt;

&lt;p&gt;I think it would still be important to allow people to pay for boosts. There will be people who would rather spend money on the game than time. I am not opposed to that - but it should not be the driving force. Spending money should not let a player overtake one who did not spend money. Pay for convenience, not power.&lt;/p&gt;

&lt;p&gt;Competing to build something together could be a really rewarding ending for a season. Instead of more fighting in a server vs server where you might lose, having a clear and defined victory condition that is collaborative would leave everyone feeling positive. Once completed, they can bask in the joy of the finished task. Perhaps it could be a really difficult battle the server has to band together to win. People who were once enemies now backing you up. You finish the season with even more friends - forged in battle.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’m Left With
&lt;/h2&gt;

&lt;p&gt;I keep wishing that I could make something different, that the world treated its people as better than just a money machine.&lt;/p&gt;

&lt;p&gt;In the meantime, I am left paying for these small hits of community, of friends forged in fire. The cost, to my bank account, at least is small.&lt;/p&gt;

</description>
      <category>design</category>
      <category>discuss</category>
      <category>gamedev</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Sovereignty Over Convenience</title>
      <dc:creator>Shri</dc:creator>
      <pubDate>Wed, 27 May 2026 08:54:20 +0000</pubDate>
      <link>https://dev.to/hereticles/sovereignty-over-convenience-1lkn</link>
      <guid>https://dev.to/hereticles/sovereignty-over-convenience-1lkn</guid>
      <description>&lt;p&gt;I’ve recently been on a journey reclaiming sovereignty over all of my data and infrastructure. I still remember the era before the cloud when you had to do everything yourself.&lt;/p&gt;

&lt;p&gt;Cloud changed all of that, and it was really nice - and convenient. In the back of my mind, though, there was a tiny little scratch.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Security is inversely proportional to convenience&lt;/p&gt;

&lt;p&gt;– Professor Evi Nemeth&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GitHub was so convenient compared to running your own infrastructure. The scratch stayed small. Then Microsoft bought GitHub.&lt;/p&gt;

&lt;p&gt;Over the last few years, things changed. Trust in big tech has eroded so heavily.&lt;/p&gt;

&lt;p&gt;LLM’s have also made things a lot worse - in two ways.&lt;/p&gt;

&lt;p&gt;Every organisation seems to be trusting LLM’s to write and deploy code. I’ve got the LLM’s to write code - and I would not trust that anywhere near security critical code. GitHub is using non-deterministic, hallucination-prone models to make security decisions.&lt;/p&gt;

&lt;p&gt;Secondly, LLM’s are vacuuming up all the data they can get their hands on - no permission sought.&lt;/p&gt;

&lt;p&gt;However, my much bigger issue is that I have no choice in the matter. My consent was not sought - my concerns were not voiced. My only choice is to carry on in their boat or get out.&lt;/p&gt;

&lt;p&gt;Ultimately, though, way down deep, in the very core of my questions, there were two basic questions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is all my stuff on the cloud being used for?&lt;/li&gt;
&lt;li&gt;How safe is it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As each day passed, and my trust in the big tech eroded, the pain of setting up the infrastructure started to pale in comparison to the pain of my data being used without my informed consent.&lt;/p&gt;

&lt;p&gt;I had to act. But first - an inventory!&lt;/p&gt;

&lt;p&gt;My cloud data mainly lived across:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub

&lt;ul&gt;
&lt;li&gt;multiple repos, public &amp;amp; private&lt;/li&gt;
&lt;li&gt;blog site (on github pages with custom domain)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Google

&lt;ul&gt;
&lt;li&gt;Emails&lt;/li&gt;
&lt;li&gt;Documents&lt;/li&gt;
&lt;li&gt;Photos&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Facebook

&lt;ul&gt;
&lt;li&gt;Photos&lt;/li&gt;
&lt;li&gt;Posts&lt;/li&gt;
&lt;li&gt;who knows what else&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a starting point, I wanted to tackle my public infra - mainly GitHub.&lt;/p&gt;

&lt;p&gt;I ultimately wanted to feel as safe as I felt before the trust in the cloud was eroded, with the minimal effort. That meant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Truly private storage for my private data&lt;/li&gt;
&lt;li&gt;Strong GDPR support.&lt;/li&gt;
&lt;li&gt;Hosting the public aspects on trustworthy platforms&lt;/li&gt;
&lt;li&gt;Resilience. I’d need to build that manually, starting with offsite backups.&lt;/li&gt;
&lt;li&gt;High availability. I’d need to get as close to that as I reasonably could - so I also needed monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It would be no small task, and the biggest pain point would be ongoing maintenance and supporting it if something goes wrong.&lt;/p&gt;

&lt;p&gt;I would need to document how every bit ties together, make it easy enough to redeploy services, reinstall servers and to monitor them, all the while also keeping it safe, secure and minimising any attack surface.&lt;/p&gt;

&lt;p&gt;Right… There is a cost to getting all of these “for free” on the cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tooling &amp;amp; Services
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cloud IaC
&lt;/h3&gt;

&lt;p&gt;Documenting all of this would be tricky - unless I use Infrastructure as code. I’d used terraform quite a bit and was comfortable with it - well, opentofu now. However, I knew that I would want to separate things into small units and that I would want to make re-usable components - for static site hosting for example. These features were easier with pulumi - and I can skip the cloud features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bare Metal IaC
&lt;/h3&gt;

&lt;p&gt;That would however, not cover any server configuration. I would have servers both at home and remotely. I could just configure them once and hope for the best - but they will inevitably need a full upgrade, which might need config tweaking again. I might need to reinstall, and then I’d need to figure out all the configuration I did.&lt;/p&gt;

&lt;p&gt;I have been pretty disciplined in the past about noting config changes so I could reproduce it later, but that’s neither an easy nor a pleasant task.&lt;/p&gt;

&lt;p&gt;I wanted something automated - I considered something like&lt;a href="https://etckeeper.branchable.com/" rel="noopener noreferrer"&gt;etckeeper&lt;/a&gt;, but that would store every config change, including from package updates. I wanted to track &lt;em&gt;only&lt;/em&gt; my changes.&lt;/p&gt;

&lt;p&gt;I wondered if &lt;a href="https://docs.kernel.org/filesystems/overlayfs.html" rel="noopener noreferrer"&gt;overlayfs&lt;/a&gt;could be a solution, with my version overlaid over the packaged installed&lt;code&gt;/etc&lt;/code&gt; - but it wasn’t really designed for such a use case. I’d also need to somehow redirect the package installs to a different place than &lt;code&gt;/etc&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Another option was &lt;code&gt;stow&lt;/code&gt;. I was already using it for my dotfiles. I could have&lt;code&gt;root&lt;/code&gt; &lt;code&gt;stow&lt;/code&gt; files into &lt;code&gt;/etc&lt;/code&gt;. This option felt the most straightforward until I had to re-install my home server (omv -&amp;gt; proxmox).&lt;/p&gt;

&lt;p&gt;I wanted to be able to automate any reinstalls further - not just &lt;code&gt;/etc&lt;/code&gt;, but also package installs. The most straightforward tool fit for this was &lt;code&gt;ansible&lt;/code&gt;. I did consider &lt;code&gt;chef&lt;/code&gt;, &lt;code&gt;puppet&lt;/code&gt;, etc. but they offered a lot of features I didn’t need. The only feature that &lt;code&gt;ansible&lt;/code&gt; didn’t give me that I wanted was state management like &lt;code&gt;pulumi&lt;/code&gt;, but from what I could see, the alternatives did not provide that.&lt;/p&gt;

&lt;p&gt;In the end, I just decided to manually clean up after ansible when it leaves any files, config or packages behind. Worst case, I also had the nuclear option of wiping and reinstalling everything to get rid of any cruft since I configure everything through ansible. I’ve already done that once.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud Services
&lt;/h3&gt;

&lt;p&gt;There are specific cloud services I would still need. I need offsite backup - in case something happens to my server. I also need to host my blog and my public sources repos.&lt;/p&gt;

&lt;p&gt;In Europe, I found two strong candidates: &lt;a href="https://www.hetzner.com/" rel="noopener noreferrer"&gt;Hetzner&lt;/a&gt;, immediately felt more enterprise level, which was confirmed with the pricing.&lt;a href="https://www.scaleway.com/en/" rel="noopener noreferrer"&gt;Scaleway&lt;/a&gt; felt friendlier and the pricing was more accessible.&lt;/p&gt;

&lt;p&gt;Ultimately, I picked Scaleway because the pricing was cheaper at lower usage levels, giving me a bit of time to ramp up. The interface was also easier to understand and navigate.&lt;/p&gt;

&lt;p&gt;Scaleway has Object Storage for offsite backup and for static site hosting.&lt;/p&gt;

&lt;p&gt;I considered hosting a &lt;a href="https://forgejo.org/" rel="noopener noreferrer"&gt;forgejo&lt;/a&gt; instance but that meant a VPS, a database server, patches, potential issues around bots / LLM training, higher level of complexity, and cost. It would also add friction to user interaction - they’d have to register to my instance, which would have only my code.&lt;/p&gt;

&lt;p&gt;I instead opted for &lt;a href="https://codeberg.org/" rel="noopener noreferrer"&gt;codeberg&lt;/a&gt;. If it’s good enough for zig, it’ll be good enough for me.&lt;/p&gt;

&lt;p&gt;forgejo might become interesting again after it integrates&lt;a href="https://forgefed.org/" rel="noopener noreferrer"&gt;ForgeFed&lt;/a&gt;, which would make cross-instance collaboration easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backup
&lt;/h2&gt;

&lt;p&gt;For backup, &lt;a href="https://restic.net/" rel="noopener noreferrer"&gt;restic&lt;/a&gt; was best fit compared to borg. It has better S3/Object-Storage backend support natively. More importantly, borg needs to be a running service, and so more maintenance. With restic, I can just sftp to the server for all operations, backup, view or restore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring
&lt;/h2&gt;

&lt;p&gt;The last time I did monitoring and alerting in production, I was using munin / monit / nagios. Everyone has moved on. I’d evaluated datadog, grafana, New Relic etc in a previous job, but of course, I am not opting for a cloud option.&lt;/p&gt;

&lt;p&gt;While there are a few options out there, &lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;prometheus&lt;/a&gt;and &lt;a href="https://grafana.com/" rel="noopener noreferrer"&gt;grafana&lt;/a&gt; came up as fairly standard, and I wanted more experience in them, so they were picked for the stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating
&lt;/h2&gt;

&lt;p&gt;The first step was to back everything up.&lt;/p&gt;

&lt;p&gt;I set up restic on my desktop, backing up to my server (&lt;code&gt;atlas&lt;/code&gt;), which backed everything up to Scaleway’s object storage.&lt;/p&gt;

&lt;p&gt;That took many, many hours to complete.&lt;/p&gt;

&lt;p&gt;The next step was to tidy up my home infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  atlas
&lt;/h3&gt;

&lt;p&gt;The server I have was already running &lt;a href="https://www.openmediavault.org/" rel="noopener noreferrer"&gt;omv&lt;/a&gt; and&lt;a href="https://watch.plex.tv/me" rel="noopener noreferrer"&gt;plex&lt;/a&gt;. While 15+ years old, it is a dual cpu box with 32G of RAM. It had 4 2T magnetic drives on &lt;code&gt;mdadm&lt;/code&gt; using &lt;code&gt;raid-6&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I had three more 2T drives on my desktop that I wanted to move over to the server because it’ll be good to have the space.&lt;/p&gt;

&lt;p&gt;I would also need additional services on the box.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;prometheus&lt;/code&gt; and &lt;code&gt;grafana&lt;/code&gt; and any other related services for monitoring and alerting.&lt;/li&gt;
&lt;li&gt;codeberg actions runner in a contained environment to mitigate security risks&lt;/li&gt;
&lt;li&gt;forgejo instance for my private source repos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I could still run debian with docker and deploy services on there. However, I wanted more isolation for the codeberg actions runner - just in case it managed to escape the container.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.proxmox.com/en/products/proxmox-virtual-environment/overview" rel="noopener noreferrer"&gt;Proxmox Virtual Environment&lt;/a&gt;was a good fit. I’d used it years earlier, but wiped it after it’d fallen behind on updates and it felt like a huge task to upgrade it. This time, I’ll be using ansible so I could even wipe and reinstall if I had to.&lt;/p&gt;

&lt;p&gt;Proxmox would also bring zfs, with raidz2 which provided safer array expansion and scrubbing to catch bitrot early.&lt;/p&gt;

&lt;p&gt;There is a single SSD on atlas, and installing proxmox on there was the easy part. I then had to find temporary storage for around 4T of data.&lt;/p&gt;

&lt;p&gt;I spread them out on my desktop over several drives, I had some space on my M2 SSD and my games drives which had a lot of space but no resilience. I put all of my data which I could recreate if I needed to on there - e.g. my blu ray rips.&lt;/p&gt;

&lt;p&gt;All the more valuable data luckily fit on my desktop’s raid5.&lt;/p&gt;

&lt;p&gt;Worst case, all of it was also backed up using restic to a scaleway storage bucket.&lt;/p&gt;

&lt;h4&gt;
  
  
  Base Config
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;atlas&lt;/code&gt; remains for the most part the core install of proxmox.&lt;/p&gt;

&lt;p&gt;Apart from basics like neovim, it’ll also host the zfs storage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;through sftp for restic&lt;/li&gt;
&lt;li&gt;nfsv4 for the other linux boxes&lt;/li&gt;
&lt;li&gt;samba (when I need shares on my dual boot or vm’s)&lt;/li&gt;
&lt;li&gt;ssh for git&lt;/li&gt;
&lt;li&gt;data directories for all the services running in docker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also hosts the LXC containers for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Codeberg Actions Runner (&lt;code&gt;ussain&lt;/code&gt;)

&lt;ul&gt;
&lt;li&gt;on a separate network so that it can’t access my LAN&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;docker services (&lt;code&gt;loom&lt;/code&gt;)

&lt;ul&gt;
&lt;li&gt;prometheus&lt;/li&gt;
&lt;li&gt;blackbox-exporter (web site monitoring)&lt;/li&gt;
&lt;li&gt;prometheus-pve-exporter (for proxmox stats)&lt;/li&gt;
&lt;li&gt;grafana&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jellyfin.org/" rel="noopener noreferrer"&gt;jellyfin&lt;/a&gt; (privacy respecting alternative to plex)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since all of these are deployed via ansible with the data stored in atlas’ zfs, it’s easy enough to change the configuration and deploy them to another lxc. Only other manual step would be to delete the previous instance since ansible doesn’t clean up after itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaleway
&lt;/h3&gt;

&lt;p&gt;Scaleway will host my blog site. I’d been meaning to rename it anyway, so this was a good opportunity to do that. My website uses &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;hugo&lt;/a&gt;which outputs static html, so I can host it on S3 like Object Store. The problem with that it gives you a long domain name and doesn’t support custom domains directly.&lt;/p&gt;

&lt;p&gt;To be able to route it via a custom domain, the simplest solution is&lt;a href="https://www.scaleway.com/en/edge-services/" rel="noopener noreferrer"&gt;Edge Services&lt;/a&gt; on Scaleway. On AWS, I’d used CloudFront - but it is annoying, and you needed to set up http -&amp;gt; https redirect as well.&lt;/p&gt;

&lt;p&gt;Scaleway also has an additional cost component. You need to pay €0.99/month minimum. That’ll give you one pipeline (i.e. one domain) and it’s €4 for each additional pipeline.&lt;/p&gt;

&lt;p&gt;I wanted to host two domains - so that’d set me back €4.99/month. Doesn’t break the bank, but it’s also much more expensive than AWS. A small price to pay for privacy and sovereignty.&lt;/p&gt;

&lt;p&gt;I found the edge services quite fiddly though. I could delete it from the console, but pulumi didn’t detect the changes correctly. I also had some issues which meant that it burned through generating 50 ssl certificates and required I wait 7 days before trying again.&lt;/p&gt;

&lt;p&gt;At this point, I decided to deploy a VPS instead.&lt;/p&gt;

&lt;p&gt;I knew that I would eventually need a VPS for &lt;a href="https://remark42.com/" rel="noopener noreferrer"&gt;remark42&lt;/a&gt;to support commenting on my blog. A VPS is around €7, only €1 more than the Edge Services pipelines.&lt;/p&gt;

&lt;p&gt;I used pulumi to provision the VPS(hera), including ipv4 and ipv6, and used ansible to configure it.&lt;/p&gt;

&lt;p&gt;I also put together a &lt;code&gt;pulumi&lt;/code&gt; stack for &lt;code&gt;ansible&lt;/code&gt; outputs. It picks up the bucket s3 urls and writes a config file for &lt;code&gt;ansible&lt;/code&gt;. &lt;code&gt;ansible&lt;/code&gt; then uses this to configure &lt;code&gt;caddy&lt;/code&gt; to route the relevant domains to their s3 buckets. This automation helps to keep the cloud state synced with the server configuration without manual intervention.&lt;/p&gt;

&lt;p&gt;What it does have though is a node exporter for prometheus. However, it’s not easy for prometheus to connect to it. The safe option I could come up with was to use wireguard between the docker lxc (loom) and &lt;code&gt;hera&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I also added a firewall rule on loom to prevent any new connections originating from &lt;code&gt;hera&lt;/code&gt;. I want prometheus to be able to access &lt;code&gt;hera&lt;/code&gt;, but not the other way around. That adds an extra layer of protection if &lt;code&gt;hera&lt;/code&gt; is ever compromised.&lt;/p&gt;

&lt;p&gt;Again, all of this is configured through &lt;code&gt;ansible&lt;/code&gt;, so if &lt;code&gt;hera&lt;/code&gt; is ever compromised, I could just wipe it and reconfigure it with one command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying the websites
&lt;/h3&gt;

&lt;p&gt;Now that the websites were configured, I wanted to give the visitors more than an error page.&lt;/p&gt;

&lt;p&gt;This was also the appropriate time to move my repo from GitHub to codeberg. I created a new repo on codeberg and pushed the repo up. I just edited&lt;code&gt;.git/config&lt;/code&gt; instead of removing the origin and adding it back in.&lt;/p&gt;

&lt;p&gt;The site was previously using both GitHub Actions and GitHub Pages. It would now be using forgejo actions and rclone to push to Scaleway. But wait - it needed auth.&lt;/p&gt;

&lt;p&gt;So far, codeberg has been the one bit of infrastructure I couldn’t code. I had to actually go on to the website and click through the UI manually. It has an API, but pulumi does not support it.&lt;/p&gt;

&lt;p&gt;I did however put together a little script which takes a &lt;code&gt;CODEBERG_TOKEN&lt;/code&gt; env var, picks up the secret key from pulumi outputs and sets it as a secret on the specified repo.&lt;/p&gt;

&lt;p&gt;I have a function in pulumi for static sites which also creates and exports this token, which makes it pretty straightforward to add more static sites. This reusable functionality was the reason I wanted pulumi from the start. I’d done reusable components in terraform as well, but they were a lot clunkier.&lt;/p&gt;

&lt;p&gt;I considered collecting web server logs, as I remembered doing back in the day - with apache and analysing them &lt;a href="https://awstats.sourceforge.io/" rel="noopener noreferrer"&gt;awstats&lt;/a&gt;. These days, it would probably be &lt;a href="https://goaccess.io/" rel="noopener noreferrer"&gt;GoAccess&lt;/a&gt;. However the privacy concerns around storing ip addresses needed handling properly, so I put that on the backburner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring &amp;amp; Alerting
&lt;/h2&gt;

&lt;p&gt;Monitoring and Alerting was honestly the biggest reason why I kept putting off bringing everything in-house. Not the work involved in building or running it, but the feeling of being constantly on call - which I was for 13 years.&lt;/p&gt;

&lt;p&gt;That was for multiple high profile, high traffic websites. This is my personal blog - it’s not a problem if the site is offline for a few hours - nobody is losing money. Still, the pavlovian response was one of stress.&lt;/p&gt;

&lt;p&gt;I was able to work through it largely by reminding myself that there are no SLA’s.&lt;/p&gt;

&lt;p&gt;I got prometheus to scrape all the data it can from the server, the lxc’s and my desktop. I pulled in some dashboards from grafana to visualise them. It’s nice to see a historical usage for my desktop and the server, including averages, growth rate etc.&lt;/p&gt;

&lt;p&gt;However, alerting was the main reason for all of this.&lt;a href="https://github.com/prometheus/blackbox_exporter" rel="noopener noreferrer"&gt;blackbox-exporter&lt;/a&gt; monitors my blog and another site I’ve got set up. This is valuable, and nice to see green across the board and that it checks expiry for the ssl certificate. I’d been burned in the past with certificate expiry and it’s nice to not have to worry.&lt;/p&gt;

&lt;p&gt;When I set up the new site, I also noticed that I’d forgotten to set up a redirect from a previous domain. That domain had a lot of SEO juice, which was cut off for a while because I hadn’t tested it.&lt;/p&gt;

&lt;p&gt;This time, I made sure to add all redirects to the &lt;code&gt;blackbox_exporter&lt;/code&gt; tests.&lt;/p&gt;

&lt;p&gt;I configured them to go to a channel in a discord server, the path of least resistance. A better option would probably be &lt;a href="https://fluxer.app/" rel="noopener noreferrer"&gt;fluxer.app&lt;/a&gt;but it doesn’t yet have a mobile app.&lt;/p&gt;

&lt;p&gt;It triggered an alert the other day. My heart skipped a beat before I remembered that it was just my blog. It resolved itself while investigating it. I could find nothing wrong on the server, which was unlikely to be the culprit anyway.&lt;/p&gt;

&lt;p&gt;It was probably the Object Storage Bucket, so I added the website endpoints to the monitoring as well. In the event of a future site failure, I will be able to see at a glance at which point the failure is.&lt;/p&gt;

&lt;p&gt;For a moment, I regretted switching it all to my infrastructure - because it would not have gone down for a few minutes on GitHub. I then realised that I simply don’t know how often my site went down when it was on GitHub or for how long - it was never monitored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Journey So Far
&lt;/h2&gt;

&lt;p&gt;I am thrilled to have my blog live on my own infra instead of on GitHub’s. Having the whole setup documented in pulumi and ansible makes it much easier to maintain and manage. It also makes it much easier to take a look at how I set something up.&lt;/p&gt;

&lt;p&gt;I also appreciate that I can redeploy all or parts of the services with ease if required, and that upgrades should be relatively pain-free.&lt;/p&gt;

&lt;p&gt;The infrastructure that is deployed feels less opaque than when I had done similar things 15+ years ago, thanks to it all being managed via IaC.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;I also have to pull everything down from google and facebook. I already have the file storage ready - I just need to pull everything down, then delete it from the cloud.&lt;/p&gt;

&lt;p&gt;Email will be a bit more work. I am using &lt;a href="https://mailbox.org/en/" rel="noopener noreferrer"&gt;mailbox.org&lt;/a&gt;for my new domain, which feels good enough. I have half a dozen email addresses. I can rationalise them down to three, but I also want to wipe out all the junk email in the process.&lt;/p&gt;

&lt;p&gt;Google Photos will be trickier still, partly because I’ve not looked for a self-hosted solution for that yet. There is also the problem with how to provide access to my internal servers to the internet safely so that photos can be uploaded / downloaded from the phone when I’m out and about. Headscale might be one way to solve this.&lt;/p&gt;

&lt;p&gt;I also want to put together a dashboard on grafana that will show me a comprehensive high level overview of my whole estate on one monitor - that’s an endeavour for another day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So far in my journey, I have migrated key repos from GitHub, both private and public to safer places. I feel substantially safer already. Most of the repos were code, but one was my zettelkasten / second brain knowledge archive. This used to be a private repo on GitHub. It is now stored on &lt;code&gt;atlas&lt;/code&gt; across from me in my room, encrypted and backed up to Scaleway. That was the biggest win of this whole process.&lt;/p&gt;

&lt;p&gt;The rest of the data will come down here in time, and I have no doubt that I’ll feel safer by the end of it.&lt;/p&gt;

&lt;p&gt;Am I actually safer? That is much harder to measure. I know that I have limited the attack surface as much as possible. Plus, my scale is so small that I am unlikely to be targeted.&lt;/p&gt;

&lt;p&gt;On the other hand, GitHub and other cloud platforms have a lot of people worrying about and considering security and safety on a daily basis. They regularly patch the servers and track security vulnerabilities.&lt;/p&gt;

&lt;p&gt;Am I actually safer? I don’t know - but I feel safer.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>infrastructure</category>
      <category>privacy</category>
      <category>security</category>
    </item>
    <item>
      <title>Only One Per Customer</title>
      <dc:creator>Shri</dc:creator>
      <pubDate>Mon, 25 May 2026 13:44:52 +0000</pubDate>
      <link>https://dev.to/hereticles/only-one-per-customer-4no0</link>
      <guid>https://dev.to/hereticles/only-one-per-customer-4no0</guid>
      <description>&lt;p&gt;&lt;a href="https://icle.es/endeavours/henge.md" rel="noopener noreferrer"&gt;henge&lt;/a&gt; pushes config to edge devices. Semantically, each device should connect once - except:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What if a device is misconfigured with the details for another device&lt;/li&gt;
&lt;li&gt;What happens if a device disconnected, but we haven’t picked it up yet&lt;/li&gt;
&lt;li&gt;How about if a device has a zombie connection and decides to reconnect&lt;/li&gt;
&lt;li&gt;Any other cases I’d not thought about.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, we need to figure out how to handle a second connection from the same device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Allow multiple connections
&lt;/h2&gt;

&lt;p&gt;For each device, we track multiple connections. Instead of a &lt;code&gt;map&lt;/code&gt; to each&lt;code&gt;channel&lt;/code&gt;, we’d have one to an array of &lt;code&gt;chan&lt;/code&gt;s (&lt;code&gt;[]chan&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Every operation on channel then becomes a loop.&lt;/p&gt;

&lt;p&gt;For the case where each correct case is most likely to be one device to one connection, this option has a lot of additional complexity, work and defect surface area.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reject additional connections
&lt;/h2&gt;

&lt;p&gt;This option is simpler. If a device is already connected, we reject any additional connections.&lt;/p&gt;

&lt;p&gt;This option is certainly simpler than allowing multiple connections. However, we are dealing with a network and there are many reasons why a connection does not close properly.&lt;/p&gt;

&lt;p&gt;To be able to mitigate some of the issues, we’d have to allow the user to manually clear an open connection so that a device can re-connect.&lt;/p&gt;

&lt;p&gt;The endpoint is easy enough to write, but requiring user intervention is not something I like having as part of a product.&lt;/p&gt;

&lt;p&gt;We could add a heartbeat, and close the connection down if it’s doesn’t receive a heartbeat for a configured amount of time. This reduces the reliance on user intervention by replacing it with wait time. Of course, it also now adds complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Close existing connection on reconnect
&lt;/h2&gt;

&lt;p&gt;How about on reconnect, we close any existing connections and reconnect to the new request?&lt;/p&gt;

&lt;p&gt;Instead of one &lt;code&gt;chan&lt;/code&gt; per connection, we now have a &lt;code&gt;Subscription&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Subscription&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;stream&lt;/span&gt;   &lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;henge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConfigChangedEvent&lt;/span&gt; &lt;span class="c"&gt;// actual stream of events&lt;/span&gt;
    &lt;span class="n"&gt;closeCmd&lt;/span&gt; &lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;                 &lt;span class="c"&gt;// on msg, unsubscribe&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the connection code involves a little more of a dance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ConfigEventBroker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deviceId&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Subscription&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Subscription&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;henge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConfigChangedEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;closeCmd&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// already connected&lt;/span&gt;
        &lt;span class="c"&gt;// close previous connection&lt;/span&gt;
        &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;closeCmd&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the connection itself respects the quit command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;snap&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sendSSEEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;snap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Values&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;snap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error while emitting sse event"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"err"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;closeCmd&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="c"&gt;// client reconnected. We can close this one&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unsubscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetConnected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unable to set connect status to disconnected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"err"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We don't have to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Track multiple connections&lt;/li&gt;
&lt;li&gt;Have Heartbeats&lt;/li&gt;
&lt;li&gt;Require user intervention&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the event of rapid reconnect flurry coinciding with a slow handler, it should&lt;br&gt;
still behave correctly, albeit slower. This rare edge case is accepted. If a&lt;br&gt;
device is reconnecting that quickly to a slow handler, there are probably much&lt;br&gt;
bigger problems at play.&lt;/p&gt;

</description>
      <category>distributedsystems</category>
      <category>iot</category>
      <category>networking</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Always On</title>
      <dc:creator>Shri</dc:creator>
      <pubDate>Tue, 14 Apr 2026 18:51:09 +0000</pubDate>
      <link>https://dev.to/hereticles/always-on-49n3</link>
      <guid>https://dev.to/hereticles/always-on-49n3</guid>
      <description>&lt;p&gt;I knew as soon as my phone rang what it was about.&lt;/p&gt;

&lt;p&gt;It was the same every time. I would drag myself up to answer the phone - my body, my mind screamed at me, but I had gotten good at overriding every instinct through sheer willpower. I could hear the apologetic tone on the other side, and I could recognise some of the voices after a while. I mustered up all of my strength to be and sound as awake as possible. I needed to be professional even if I was still in my underwear.&lt;/p&gt;

&lt;p&gt;I had a glass of water on my bedside table. I’d pick that up and head to my office in the spare room. The computer was always on and always ready to go — like me I guess. I’d log on to the servers, and check the logs. If I can identify which one fell out of the group, I can restart just that one. If I was too late or if the issue had escalated, I’d have to restart the whole cluster — shut them all down, give them a few seconds, then bring each one up, while keeping an eye on them. I could do it half asleep after a while.&lt;/p&gt;

&lt;p&gt;Falling back asleep wasn’t a breeze either - I was tired - exhausted - but I was now also wired. Waking up in the morning was harder - the alarm would go off and my body would be limp. I still remember the sheer power of will to drag myself into the shower, then carry on with the rest of the day.&lt;/p&gt;

&lt;p&gt;Of 266 incidents over about two years, I answered 156.&lt;/p&gt;

&lt;p&gt;I remember one particular night, though I do not remember how many times I’d woken up beforehand. I was already tired.&lt;/p&gt;

&lt;p&gt;megabus.com had gone offline. I got an alert. “But someone else is on call tonight,” I told them. “We already tried them twice,” came the reply. I had to deal with this. I had to deal with this.&lt;/p&gt;

&lt;p&gt;I remember sitting at my desk at home working on fixing it. At some point, something was different, though I don’t remember what. While I was working on fixing it, I remember being overcome with an overwhelming impulse to get up from the chair and walk away — I almost imagined myself walking away. I resisted and shut down that impulse. I fixed megabus as I had always done. In fixing megabus though, something broke inside me, somewhere deep, in the very core of my being. I was never the same again.&lt;/p&gt;

&lt;p&gt;I analysed the system top to bottom, inside and out. I even waded through JVM internals.&lt;/p&gt;

&lt;p&gt;It got incrementally better, more stable. I think I rewrote every component that wasn’t the core ticketing system. In the end, what pushed it over the line were two unexpected changes. Automated nightly restarts of each node in the cluster and a rate limiter.&lt;/p&gt;

&lt;p&gt;On the 10th December 2012, the system had a sale event. We had a bank of screens on a wall with all the key stats for the system. It looked cool, and we felt a bit like we were on a TV show. At peak, nearly 15,000 concurrent sessions — six or seven times the average. Over 30,000 bookings in a single day, three times more than the normal amounts across all systems.&lt;/p&gt;

&lt;p&gt;We watched it closely, all day. Nothing broke. Nothing screamed. Everyone smiled, but there was no celebration.&lt;/p&gt;

</description>
      <category>career</category>
      <category>devjournal</category>
      <category>mentalhealth</category>
      <category>sre</category>
    </item>
    <item>
      <title>Did They Have a Problem That Year?</title>
      <dc:creator>Shri</dc:creator>
      <pubDate>Tue, 14 Apr 2026 09:24:26 +0000</pubDate>
      <link>https://dev.to/hereticles/did-they-have-a-problem-that-year-1ed8</link>
      <guid>https://dev.to/hereticles/did-they-have-a-problem-that-year-1ed8</guid>
      <description>&lt;p&gt;2008 was a heck of a year for kraya, and for me. We were already operating megabus.com in the UK, USA, and Canada, along with Oxford Tube, the sales website for coach usa - all for Stagecoach.&lt;/p&gt;

&lt;p&gt;We were also working on the fringe website. We integrated the website with the brand spanking new ticketing system - which cost nearly £900k.&lt;/p&gt;

&lt;p&gt;We were also hosting websites for Boots, Kellogg’s Food Service and dozens of other clients.&lt;/p&gt;

&lt;p&gt;All of this was held together by three or four developers, two systems administrators and me.&lt;/p&gt;

&lt;p&gt;On the 13 June (incidentally, I got married on the same date years later), as I was just getting ready for a wild night on the town, a call comes through - which John answers.&lt;/p&gt;

&lt;p&gt;I still remember them laughing and then doing a double take “oh, you’re serious? let me get Shri”&lt;/p&gt;

&lt;p&gt;It was the fringe. We’d already known that they were having trouble with their ticketing system. I’d even pitched in, made suggestions - looked at their code to try and help, but none of that was enough. I expected an update.&lt;/p&gt;

&lt;p&gt;They wanted to know if we could put together an interim booking system for them over the weekend. I wasn’t sure. I told them I’d speak to my team and get back to them.&lt;/p&gt;

&lt;p&gt;I wasn’t involved with the work on the fringe up until this point. I knew very little about it. I was focused on megabus.com. The US version of the site had a big marketing campaign happening in a few days and that was what I was meant to be focused on.&lt;/p&gt;

&lt;p&gt;By the time I put the phone down, Chris, who had been the lead on the fringe already had a answer. “We can do it!”&lt;/p&gt;

&lt;p&gt;“But how - it’s got to take more than a weekend - right?”&lt;/p&gt;

&lt;p&gt;The fringe website was already built well and had a clean layer interfacing with the new ticketing system. In fact, that was the bulk of the work that year.&lt;/p&gt;

&lt;p&gt;So.. Chris told me - all we would have to do is to implement the functionality within that thin layer, fattening it up.&lt;/p&gt;

&lt;p&gt;He believed we could do it. I believed him.&lt;/p&gt;

&lt;p&gt;I hopped in a cab, headed over to the fringe to talk it through. I didn’t promise them we’d be able to get something ready by Monday, but I promised we’d do our best.&lt;/p&gt;

&lt;p&gt;We were in the office on the weekend, writing code. I remember working on the basket, sending diffs over email and generally having a good time.&lt;/p&gt;

&lt;p&gt;I even had some megabus US fun to keep me entertained in the form of issues with loading sheets - I was already in the office, so it was one step easier to fix.&lt;/p&gt;

&lt;p&gt;One of the bits of functionality which took a surprising amount of time was the seat allocation. None of us had to worry about that before - it was just capacity management on megabus. For the fringe though, we had to allocate actual seats with seat numbers and everything.&lt;/p&gt;

&lt;p&gt;With the fringe we had multiple tables which all joined together (thanks hibernate) to encode a tremendous amount of detail about the seating plans - including their physical location on a map.&lt;/p&gt;

&lt;p&gt;It was too much detail for us, so we had to simplify it all down to get it working in the timeframe. We kept most of the rest of the structures intact to keep the data migration easier once the ticketing system was fixed.&lt;/p&gt;

&lt;p&gt;By Monday, we had each managed at the most 6 hours of sleep each of the previous three nights. I still have vivid memories of a suit of armour that we put together using packing material while we were waiting for bits of data or details of logic.&lt;/p&gt;

&lt;p&gt;I broke the MySQL replication at 01:24, fixed by 01:32&lt;/p&gt;

&lt;p&gt;I remember the delirium setting in. Email sent to the client with “fun fun fun fun fun fun fun” as the subject&lt;/p&gt;

&lt;p&gt;There were random emails to my brother “I’m still here!”&lt;/p&gt;

&lt;p&gt;I also remember making makeshift beds with bubblewrap to get a wee nap here and there. We were all so exhausted - pumped up on coffee and nicotine.&lt;/p&gt;

&lt;p&gt;Finally at 03:35 on the Tue email to client: “DONE DONE DONE DONE DONE NODE NODE NODE NODE”&lt;/p&gt;

&lt;p&gt;Then at 05:33, requesting a PostgreSQL server rebuild for megabus US for their marketing campaign.&lt;/p&gt;

&lt;p&gt;At 10am on Tuesday, the fringe is finally able to sell tickets. The website promptly fell over from the load, but we nurse it back and it sells 65k+ tickets in the first week.&lt;/p&gt;

&lt;p&gt;It would be at least two more weeks before the ticketing system is fixed and brought back in.&lt;/p&gt;

&lt;p&gt;For the work we did for them that year, and the previous one, we effectively only charged about 30% - because that’s all they could afford. This year, we asked if they could put our name on the website.&lt;/p&gt;

&lt;p&gt;Over the next two weeks(while megabus US was on their marketing campaign), we fought many battles. There were 750 duplicate bookings. Numerous customer complaints (thanks to our name being on the website) - almost all of them blaming us for the failure of the ticketing system. People did not understand that we put in the interim one, not the one that failed.&lt;/p&gt;

&lt;p&gt;Press releases went out from the fringe - only two credited us. Both misspelt the company name. Both called us a web design company — which, we were not, had never been, and had no interest in becoming.&lt;/p&gt;

&lt;p&gt;In truth, I wanted to be a hero - I think we all did. What we really wanted was an acknowledgement of what we had done - which was nowhere to be found. We got paid though - at least for a part of our effort.&lt;/p&gt;

&lt;p&gt;For many years after that, I would tell people with pride - “did you know - I saved the fringe, back in 2008,” which was inevitably met with something like “oh, did they have a problem that year?”&lt;/p&gt;

</description>
      <category>production</category>
      <category>career</category>
      <category>programming</category>
      <category>sysadmin</category>
    </item>
    <item>
      <title>What we Carried</title>
      <dc:creator>Shri</dc:creator>
      <pubDate>Mon, 13 Apr 2026 19:02:04 +0000</pubDate>
      <link>https://dev.to/hereticles/what-we-carried-djf</link>
      <guid>https://dev.to/hereticles/what-we-carried-djf</guid>
      <description>&lt;p&gt;I started my company in 2000. I was 17. I built megabus.com in 2003. I was 21.&lt;/p&gt;

&lt;p&gt;It started off small, and little by little, I carried more and more. I became we, and we carried more and more. Before we realised, we were carrying a great deal. Ultimately, though, if something went seriously wrong, it would be on my shoulders.&lt;/p&gt;

&lt;p&gt;The chart does not capture the scaling of the organisation, or other departments like tech support or hosting, which had dozens of clients.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvhyx1w9stuaq6ylfdo7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvhyx1w9stuaq6ylfdo7.png" alt="gantt chart of the main projects done by kraya through its life" width="800" height="973"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Data mined by Claude from my emails, issue trackers and code repos&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The section at the top is the number of active committers for that quarter. You can see my on my todd at the start and the rise and the fall of the dev team.&lt;/p&gt;

&lt;p&gt;The very peak of it was in 2008. We &lt;a href="https://icle.es/saving-the-fringe.md" rel="noopener noreferrer"&gt;built a booking system over the weekend for the Edinburgh festival fringe because their brand new £800k+ system could only handle one person at a time.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There were a handful of us building it while I was simultaneously prepping and managing the megabus US systems for a sales campaign. At the same time we were operating megabus across the UK, USA, and Canada, Oxford Tube, Coach USA, the Fringe website itself and numerous other smaller hosting clients like Boots, Kelloggs Food Service, and so on.&lt;/p&gt;

&lt;p&gt;We were a total of ~4 developers and two systems administrators holding all of these together.&lt;/p&gt;

&lt;p&gt;I was recently reminded of a story a friend of mine told me. Before he was my friend, he worked with me, and one of the things we did together was one the big megabus deployments when we migrated to a Java EE ticketing system.&lt;/p&gt;

&lt;p&gt;One part of the migration was the data. I had developed a tool to migrate the data and on the evening - everything was prepared, we were off peak, and we had taken the site offline. He ran the script, which went on for a wee while and it failed. It was not meant to do that.&lt;/p&gt;

&lt;p&gt;The way he tells the story, he told me about the failure. I come over, look at the errors, say “hmmm, that’s interesting,” and head off to have a cigarette. A few minutes later I go over to my desk type away furiously, then asked him to run it again.&lt;/p&gt;

&lt;p&gt;It worked, and completed.&lt;/p&gt;

&lt;p&gt;I remember that night. I don’t remember what the problem was or how I fixed it, but I do remember that moment when I went over to see how it had failed. In the short walk from my desk to his, I reiterated in my mind, all the possible backup plans - with the worst case scenario being to call off the migration on that day. We would do it another day. It would cost money, but it would be do-able. I was ok with that.&lt;/p&gt;

&lt;p&gt;I was curious as to where my limits were, so I kept pushing, until I would meet with a wall.&lt;/p&gt;

&lt;p&gt;There were no rails and there were no railings - just a cliff edge, unmarked… I didn’t know that - I expected a brick wall.&lt;/p&gt;

&lt;p&gt;The worst of it would be only a few years later, in 2011. We built a new Java EE ticketing system for a fraction of what it should have cost in about 30% of the time it needed.&lt;/p&gt;

&lt;p&gt;I personally responded to over 250 out of hours emergency tickets over an 18 month period. That was hard!&lt;/p&gt;

&lt;p&gt;I had run off a cliff edge, and like the roadrunner in the cartoons, it took a while before I realised there was no ground beneath me.&lt;/p&gt;

&lt;p&gt;A few years after I ran off the cliff, the company shut down. A few years later, I would start my active recovery journey through therapy. A few years later still, when I felt ready for a leadership role, I was asked to lead a problematic team - a role they struggled to fill for a while.&lt;/p&gt;

&lt;p&gt;The team worked hard and delivered but struggled with the perception of poor delivery. Trust was thin, stress was high and morale was low. The situation was so bad that the week before I was supposed to start, the scrum master who was supposed to be my guide through it all quit.&lt;/p&gt;

&lt;p&gt;I was warned by multiple people that this job was loaded with problems. I took on the job anyway, without a real guide, straight into multiple serious issues.&lt;/p&gt;

&lt;p&gt;I loved it and managed to turn the whole thing around in my first week. Delivered key items, laid the foundations of trust and improved morale. It took a bit longer to bed everything down. Within weeks, I was asked if I would take on leading the entire digital team.&lt;/p&gt;

&lt;p&gt;It was here, many years later, I got a sense of how unusual it was for such a tiny team to do so much.&lt;/p&gt;

&lt;p&gt;It was here, many years later, I got a sense of how it is to have guardrails, to have support, peers to lean on.&lt;/p&gt;

&lt;p&gt;It was here, for the first time I realised that the job didn’t have to be a lonely one.&lt;/p&gt;

</description>
      <category>career</category>
      <category>devjournal</category>
      <category>leadership</category>
      <category>startup</category>
    </item>
    <item>
      <title>Whatcha Thinking?</title>
      <dc:creator>Shri</dc:creator>
      <pubDate>Mon, 13 Apr 2026 11:05:48 +0000</pubDate>
      <link>https://dev.to/hereticles/whatcha-thinking-3pj9</link>
      <guid>https://dev.to/hereticles/whatcha-thinking-3pj9</guid>
      <description>&lt;p&gt;I loved working on megabus. I was in love with it. My girlfriend at the time had a habit of asking what I was thinking about when I looked deep in thought. The answer - every single time, was inevitably megabus. She eventually stopped asking.&lt;/p&gt;

&lt;p&gt;I was 22 years old.&lt;/p&gt;

&lt;p&gt;When I built the original prototype for megabus.com, I built it using PHP + PostgreSQL. I put together a document detailing my reasoning for these choices. I quoted 33 days for it, built it over six weeks and charged £13,200.&lt;/p&gt;

&lt;p&gt;The support contract was £300/month - for one day a month. On the first day, megabus.com sold 200 orders.&lt;/p&gt;

&lt;p&gt;When megabus had its first expansion, I was up overnight bringing new servers online and scaling it live. I loved it - my code was finally being tested.&lt;/p&gt;

&lt;p&gt;Over a week, I’d probably burned through many days of effort. I remember the project manager specifically asking me to invoice for the extra work I put into it. I even said that I would - except I didn’t.&lt;/p&gt;

&lt;p&gt;I’ve had a long time to think about this - why did I not send that invoice? I even had approval.&lt;/p&gt;

&lt;p&gt;The answer, as with most things of this nature is complicated. I loved the work and I didn’t want it to end. I didn’t want a potential conflict trying to figure out what a reasonable amount was to charge. I felt that I should have done a better job in the first place - I felt responsible that I had not told them that scaling of this nature would not have worked without prep work.&lt;/p&gt;

&lt;p&gt;I had not scaled anything before.&lt;/p&gt;

&lt;p&gt;I was 22 years old.&lt;/p&gt;

&lt;p&gt;I was super grateful that someone believed in me. I naively assumed that they saw all the extra effort I was putting in and that they would reward me for it - that they would have my back.&lt;/p&gt;

&lt;p&gt;I remember adding a bunch of different bits of functionality because I wanted it there. I didn’t want to go through the process of quoting for it, and it getting potentially rejected, not to mention the waiting for decisions. One key bit of functionality I remember is adding in a percentage load column for the loading sheets. I built it, showed it - they loved it! It went live. I did not charge for it.&lt;/p&gt;

&lt;p&gt;At this point, the vast majority of my time was spent on megabus - very little of it actually paid for.&lt;/p&gt;

&lt;p&gt;At a glance, based on the emails sent, I probably spent a minimum of 10 days each month supporting megabus when I was charging for one day.&lt;/p&gt;

&lt;p&gt;In Jan 2004 - I proposed &lt;em&gt;doubling&lt;/em&gt; the contract to two days for £4,800/year. It probably kicked in in Feb 2004. By March 2004, the site exceeded that revenue each day.&lt;/p&gt;

&lt;p&gt;In the following months, I probably spent, on average a minimum of at least double the time I was paid for. I should have charged for it.&lt;/p&gt;

&lt;p&gt;I grew the team, and the support contract based on the minimum I needed to maintain the product - not based on the amount of time I was spending.&lt;/p&gt;

&lt;p&gt;For my 28th birthday, my girlfriend at the time organised a cake which was a image representing kraya - which was basically megabus. I felt bad that she thought that kraya was the most important thing in my life - she was right - but it still felt bad. kraya had other clients at the time, but my time wasn’t monopolised by other clients, or indeed by kraya - my heart still belonged to megabus.&lt;/p&gt;

&lt;p&gt;And it would all have been all fine too, except for a grave miscalculation I made.&lt;/p&gt;

&lt;p&gt;In 2010, after trying to rebuild the ticketing system for £500k, and making some mistakes with people I trusted, kraya ended up in £150k in the hole. We needed some money urgently.&lt;/p&gt;

&lt;p&gt;I was desperate and naively, I reached out to stagecoach for help. I thought they were my friend - that they would have my back.&lt;/p&gt;

&lt;p&gt;They understandably lost a great deal of trust in my ability to manage and lead my company. I trusted the wrong person - but that was still my mistake. They were right.&lt;/p&gt;

&lt;p&gt;I thought that I’d built up enough goodwill that they would help me through this. I’d felt I would have way more than that “in the bank” in terms of goodwill. I learned that professional relationships do not work that way that dark afternoon, standing outside my office on the phone, in the rain.&lt;/p&gt;

&lt;p&gt;They didn’t make my life easier. Instead, I’d ended up rattling the cage - they were now panicked - realising their over-reliance on an organisation that could disappear at any point.&lt;/p&gt;

&lt;p&gt;Instead of support, I had further actions, renegotiating the contract and what felt like punitive, and definitely invasive reporting obligations.&lt;/p&gt;

&lt;p&gt;I was hurt and angry. I had poured my heart, my soul - hey, my very life into this product that I loved.&lt;/p&gt;

&lt;p&gt;Suffice it to say - I got no help - no loan, no offer of investment - though they did suggest buying us outright - which I rejected.&lt;/p&gt;

&lt;p&gt;I signed a contract under circumstances I would not wish on anyone.&lt;/p&gt;

&lt;p&gt;The best I got from them was a challenge - if we were really spending more time than we were charging for - prove it. I did! We documented every minute we were spending - I wasted my time on spreadsheets, pointless meetings and work to try and rebuild the broken trust.&lt;/p&gt;

&lt;p&gt;We went from £300k in the hole to £200k profit within a year. We charged for a whole year in support around 20% of what the system made in a day.&lt;/p&gt;

&lt;p&gt;I was 28 years old.&lt;/p&gt;

&lt;p&gt;Around the same time, I was also dealing with the operational aftermath of trying to build a java EE ticketing system over six months for £500k. I thought it would take a year and cost £1m. In hindsight, it needed two years and probably three million pounds.&lt;/p&gt;

&lt;p&gt;Over 18 months, I personally answered over 250 out of hours emergency calls. We had a rota and others on call too - but I took the vast majority of these calls. I felt bad putting others through what I knew was gruelling.&lt;/p&gt;

&lt;p&gt;All of this led me down a narrower and narrower path to a serious breakdown - though I didn’t know enough to name it until many years later. All I knew - all I felt was that something broke in me.&lt;/p&gt;

&lt;p&gt;We managed to resolve all of the issues, but the deployment of that version kept getting pushed.&lt;/p&gt;

&lt;p&gt;Stagecoach cancelled the contract in 2012. They had started building a ticketing system in-house two years prior - the cost of my grave mistake. I wasn’t able to make the meeting - I was in India, and at the same time as the meeting, I was meeting for the first time the one who is now my wife.&lt;/p&gt;

&lt;p&gt;I was 28 years old. I spent the next 15 years putting myself back together.&lt;/p&gt;

&lt;p&gt;How much did it cost them to build it inhouse? If I had charged for my time from the start, would we all have been better off?&lt;/p&gt;

&lt;p&gt;I still feel something deep inside me every time I see a megabus - a sense of pride mixed in with a deep sense of sadness - not for what I lost - but for what could have been.&lt;/p&gt;

&lt;p&gt;I am 44 years old, and I am starting again.&lt;/p&gt;

</description>
      <category>career</category>
      <category>devjournal</category>
      <category>php</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Publishing from hugo to dev.to</title>
      <dc:creator>Shri</dc:creator>
      <pubDate>Tue, 23 Sep 2025 08:30:54 +0000</pubDate>
      <link>https://dev.to/hereticles/publishing-from-hugo-to-devto-2hkd</link>
      <guid>https://dev.to/hereticles/publishing-from-hugo-to-devto-2hkd</guid>
      <description>&lt;p&gt;I have been pondering federating parts of my blog to &lt;a href="https://dev.to/"&gt;dev.to&lt;/a&gt;

for a bit more visibility.&lt;/p&gt;
&lt;p&gt;However, I had a couple of issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;With html (instead of markdown), it would not pick up the code blocks
correctly&lt;/li&gt;
&lt;li&gt;With markdown, it would render the relative links incorrectly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What I really wanted was a way to render the hugo markdown into Jekyll style
(which is what forem wants) but with the links rendered.&lt;/p&gt;
&lt;p&gt;This was a little more complicated than I would have liked.&lt;/p&gt;
&lt;h2 id="goals"&gt;Goals&lt;/h2&gt;
&lt;p&gt;At a minimum, I wanted two main things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Render code blocks correctly&lt;/li&gt;
&lt;li&gt;Relative URLs should be rendered as absolute (because they won’t work on
dev.to)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pre-existing-solutions"&gt;Pre-existing Solutions&lt;/h2&gt;
&lt;p&gt;I found &lt;a href="https://github.com/maelvls/hudevto" rel="noopener noreferrer"&gt;hugodevto&lt;/a&gt;
 which looked promising
except:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Not a fan of having to manually update hundreds of posts with the devto id&lt;/li&gt;
&lt;li&gt;Had a couple of fiddly bits to get it working (my plain text outputs had some
troubles for unknown reasons)&lt;/li&gt;
&lt;li&gt;It rendered image urls,
&lt;a href="https://github.com/maelvls/hudevto/issues/2#issuecomment-3302934120" rel="noopener noreferrer"&gt;but not regular urls.&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ultimately though, it felt a bit bulkier than what I was looking for&lt;/p&gt;
&lt;h2 id="using-hugo"&gt;Using Hugo&lt;/h2&gt;
&lt;p&gt;I had &lt;a href="https://drone-ah.com/tags/hugo/" rel="noopener noreferrer"&gt;done enough work with hugo&lt;/a&gt;
 and
&lt;a href="https://drone-ah.com/tags/inscribe/" rel="noopener noreferrer"&gt;output formats&lt;/a&gt;
 to have a vague idea of how to make it work.&lt;/p&gt;
&lt;h3 id="limitations"&gt;Limitations&lt;/h3&gt;
&lt;p&gt;There are a few limitations to doing it this way though. Hugo
&lt;a href="https://gohugo.io/render-hooks/introduction/" rel="noopener noreferrer"&gt;does not provide render hooks for everything&lt;/a&gt;
.
You will end up with html in the output. However, since Forem (and Jekyll) will
just render them, it fits my use case. It won’t work as well if you try and use
this to generate like for like markdown usable in Jekyll.&lt;/p&gt;
&lt;h3 id="define-a-new-content-type"&gt;Define a new content type&lt;/h3&gt;
&lt;p&gt;We want a new content type which will output markdown&lt;/p&gt;


```toml
[outputFormats.jekyll]
    mediaType = "text/markdown"
    baseName = "index"
    isPlainText = true
    isHTML = false
    notAlternative = true
    path = 'jekyll'        # put the output in `public/jekyll` so it's easier to find

[outputs]
    page = ['html', 'jekyll'] # Output all pages in our jekyll format as well
```


&lt;p&gt;You also need a basic template before hugo will output our markdown files.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/drone-ah/wordsonsand/blob/dc753f20a8d932a4bdb36378efb7bc5f3bf6cb1d/blog/layouts/_default/single.jekyll.md" rel="noopener noreferrer"&gt;&lt;code&gt;layouts/_default/single.jekyll.md&lt;/code&gt;&lt;/a&gt;
&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
title: {{ .Title }}
published: true
date: {{ .Date }}
{{- with .Params.tags }}
tags: [{{ delimit . ", " }}]
{{- end }}
canonical_url: {{ .Permalink }}
---

{{ .Content }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, if you &lt;code&gt;hugo build&lt;/code&gt;, it’ll render the &lt;code&gt;.md&lt;/code&gt; files, but the content
will be html.&lt;/p&gt;
&lt;h3 id="render-code-blocks-as-markdown"&gt;Render code blocks as markdown&lt;/h3&gt;
&lt;p&gt;We can use the
&lt;a href="https://gohugo.io/render-hooks/code-blocks/" rel="noopener noreferrer"&gt;code block render hook&lt;/a&gt;
 to
“convert them” back to markdown.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/drone-ah/wordsonsand/blob/dc753f20a8d932a4bdb36378efb7bc5f3bf6cb1d/blog/layouts/_default/_markup/render-codeblock.jekyll.md" rel="noopener noreferrer"&gt;&lt;code&gt;layouts/_default/_markup/render-codeblock.jekyll.md&lt;/code&gt;&lt;/a&gt;
&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;{{ .Type }}&lt;br&gt;
{{ .Inner }}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3 id="render-absolute-urls"&gt;Render absolute urls&lt;/h3&gt;

&lt;p&gt;I already have a pretty extensive
&lt;a href="https://github.com/drone-ah/wordsonsand/blob/dc753f20a8d932a4bdb36378efb7bc5f3bf6cb1d/blog/layouts/_default/_markup/render-link.html" rel="noopener noreferrer"&gt;&lt;code&gt;render-link&lt;/code&gt;&lt;/a&gt;
 so updating
it was just a case of making a copy of it and replacing relative url references
with absolute ones.&lt;/p&gt;
&lt;p&gt;It’s fine for it to be in html because Forem will still render it correctly.
They could be rendered as markdown and it should work just as well.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/drone-ah/wordsonsand/blob/dc753f20a8d932a4bdb36378efb7bc5f3bf6cb1d/blog/layouts/_default/_markup/render-link.jekyll.md" rel="noopener noreferrer"&gt;&lt;code&gt;layouts/_default/_markup/render-link.jekyll.md&lt;/code&gt;&lt;/a&gt;
&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ /* other content */ }}
  &amp;lt;a href="{{ printf "%s#%s" .PageInner.Permalink $u.Fragment | safeURL }}" {{ with .Title }}title="{{ . }}"{{ end }}&amp;gt;{{ $text }}&amp;lt;/a&amp;gt;
{{ /* other content */ }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3 id="output"&gt;Output&lt;/h3&gt;

&lt;p&gt;With these relatively minor changes, I was able to render markdown files I could
then pop into dev.to and it works for the handful I set up.&lt;/p&gt;

&lt;h2 id="next-steps"&gt;Next Steps&lt;/h2&gt;

&lt;h3 id="images"&gt;Images&lt;/h3&gt;

&lt;p&gt;One big glaring omission is images - it’s not as relevant for me because I
rarely use images.&lt;/p&gt;

&lt;p&gt;I expect it to be easy enough to use the
&lt;a href="https://gohugo.io/render-hooks/images/" rel="noopener noreferrer"&gt;image render hook&lt;/a&gt;
 to achieve this.&lt;/p&gt;

&lt;h3 id="tags"&gt;Tags&lt;/h3&gt;

&lt;p&gt;One of the issues I have is that dev.to has a strict tag limit of four.
Currently I manually edit that when I create the post on dev.to.&lt;/p&gt;

&lt;p&gt;It would be better to have a &lt;code&gt;devto_tags&lt;/code&gt; field because my local content tags
aren’t always relevant for dev.to.&lt;/p&gt;

&lt;p&gt;I could also write a script to automate the setting of the &lt;code&gt;devto_tags&lt;/code&gt; field
automatically based on the first four tags, and mapping from my tags to dev.to
tags if necessary by building a small map data set somewhere.&lt;/p&gt;

&lt;h3 id="automation"&gt;Automation&lt;/h3&gt;

&lt;p&gt;Once the above two are done, it would be good to automate it. I could&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developers.forem.com/api/v0#tag/articles/operation/getUserAllArticles" rel="noopener noreferrer"&gt;get all the posts&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;get all the local posts&lt;/li&gt;
&lt;li&gt;map them based on the canonical url (which is returned by the endpoint)&lt;/li&gt;
&lt;li&gt;upload updated ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not all my posts are technical, so I’d also want to add a field to the
frontmatter (&lt;code&gt;devto_published&lt;/code&gt;) and figure out a way to push updates only if
there are changes.&lt;/p&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;I have a working solution for now - and if dev.to brings enough traffic / value,
then I’ll consider spending a bit more time adding polish.&lt;/p&gt;

&lt;p&gt;For the time being though, seems to work!&lt;/p&gt;

&lt;p&gt;Feel free to use any of this code (such that it is) as you wish.&lt;/p&gt;

</description>
      <category>wordsonsand</category>
      <category>hugo</category>
      <category>devto</category>
    </item>
    <item>
      <title>Using `locateFile` to have js and wasm in different locations with emscripten</title>
      <dc:creator>Shri</dc:creator>
      <pubDate>Thu, 18 Sep 2025 09:15:42 +0000</pubDate>
      <link>https://dev.to/hereticles/using-locatefile-to-have-js-and-wasm-in-different-locations-with-emscripten-2koi</link>
      <guid>https://dev.to/hereticles/using-locatefile-to-have-js-and-wasm-in-different-locations-with-emscripten-2koi</guid>
      <description>&lt;p&gt;Originally published &lt;a href="https://drone-ah.com/2025/09/17/calling-javascript-from-zig-through-webassembly/" rel="noopener noreferrer"&gt;at drone-ah.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As part of building &lt;a href="//../../excursions/shine.md"&gt;shine&lt;/a&gt;, I am using&lt;br&gt;
&lt;a href="https://lume.land" rel="noopener noreferrer"&gt;lume&lt;/a&gt; and webassembly with zig.&lt;/p&gt;

&lt;p&gt;zig, through emscripten generates both a js and wasm file, which, by default are&lt;br&gt;
expected to be in the same directory.&lt;/p&gt;

&lt;p&gt;I wanted to put them in different places, and struggled to get that working with&lt;br&gt;
lume for a bit. I did eventually solve it though:&lt;/p&gt;
&lt;h2&gt;
  
  
  Include emscripten js file
&lt;/h2&gt;

&lt;p&gt;Firstly, the js file output from emscripten should be included in you page&lt;br&gt;
manually. I had used &lt;code&gt;site.add&lt;/code&gt; which meant that it was loaded &lt;em&gt;before&lt;/em&gt; we could&lt;br&gt;
put the override for &lt;code&gt;locateFile&lt;/code&gt; in &lt;code&gt;window.Module&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I am using the&lt;br&gt;
&lt;a href="https://github.com/lumeland/theme-simple-blog" rel="noopener noreferrer"&gt;simple-blog theme&lt;/a&gt;, so I add the&lt;br&gt;
following line to the top of &lt;code&gt;src/index.vto&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/js/shine.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Override &lt;code&gt;Module&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;You can override how emscripten finds the wasm file in the&lt;br&gt;
&lt;a href="https://emscripten.org/docs/api_reference/module.html#Module.locateFile" rel="noopener noreferrer"&gt;Module Object&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This can be done in the html file in a script block, or even better, in a js&lt;br&gt;
file that is included automatically.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;js/main.js&lt;/code&gt; felt like a good place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;locateFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scriptDirectory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shine.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/static/shine/shine.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;scriptDirectory&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;With this setup, I can not only have the js and wasm files in different&lt;br&gt;
locations, it's also easy to modify / augment the &lt;code&gt;Module&lt;/code&gt; object.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tooling</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Calling Javascript from Zig through WebAssembly</title>
      <dc:creator>Shri</dc:creator>
      <pubDate>Wed, 17 Sep 2025 14:52:20 +0000</pubDate>
      <link>https://dev.to/hereticles/calling-javascript-from-zig-through-webassembly-2hdf</link>
      <guid>https://dev.to/hereticles/calling-javascript-from-zig-through-webassembly-2hdf</guid>
      <description>&lt;p&gt;The next step for 
   &lt;span&gt;shine&lt;/span&gt;
   is to build a bridge
   between &lt;a href="/tags/zig/"&gt;zig&lt;/a&gt;
   and &lt;a href="/tags/javascript/"&gt;javascript&lt;/a&gt;
   .
&lt;/p&gt;

&lt;p&gt;I am currently planning to using &lt;a href="https://supabase.com/" rel="noopener noreferrer"&gt;supabase&lt;/a&gt;
   for storage.
   Unsurprisingly, it does not have a zig sdk. It does, however, have a javascript
   sdk.
&lt;/p&gt;

&lt;p&gt;If I can write basic CRUD operations in javascript and call that from zig
   through webassembly, that could make that integration a lot easier.
&lt;/p&gt;

&lt;h2 id="goals"&gt;Goals&lt;/h2&gt;

&lt;p&gt;There are a few ideal restrictions for me - mainly because writing javascript is
   not fun for me.
&lt;/p&gt;

&lt;ul&gt;
   &lt;li&gt;Use the Supabase js/ts library through zig&lt;/li&gt;
   &lt;li&gt;Use TypeScript as much as possible. (I don’t love TypeScript, but at least
      it’s not javascript)
   &lt;/li&gt;
   &lt;li&gt;Keep as much of the supabase related code in the web part so that
      &lt;a href="/tags/deno/"&gt;deno&lt;/a&gt;
      and &lt;a href="/tags/lume/"&gt;lume&lt;/a&gt;
      can handle any heavy lifting.
   &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="options"&gt;Options&lt;/h2&gt;

&lt;p&gt;I can’t use FFI(Foreign Function Interface):&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extern "env" fn jsLog(ptr: [*]const u8, len: usize) void;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"env"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wasm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instantiateStreaming&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prog.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="na"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cm"&gt;/* read from memory and console.log */&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because imgui pulls in emscripten, which means we don’t have the ability to call
   &lt;code&gt;instantiateStreaming&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;With emscripten, declaring an external function is easy enough.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a couple of options to wire them up to the javascript:&lt;/p&gt;

&lt;h3 id="libraryjs--mergeinto"&gt;
&lt;code&gt;library.js&lt;/code&gt; / &lt;code&gt;mergeInto&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This option requires javascript files on the zig side. If you want to start with
   typescript, you’ll need to integrate a transpiler into the build chain as well.
&lt;/p&gt;

&lt;p&gt;First, you want a javascript file - let’s call it &lt;code&gt;libshine.js&lt;/code&gt;, and pop it into
   a &lt;code&gt;js&lt;/code&gt; dir.
&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// js/libshine.js&lt;/span&gt;
&lt;span class="c1"&gt;// Emscripten will provide these globals at link/runtime&lt;/span&gt;
&lt;span class="nx"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;mergeInto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;funcs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;LibraryManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;library&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;UTF8ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;len&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;mergeInto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LibraryManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;library&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="na"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UTF8ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🟢 Zig says:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then need to pass this &lt;code&gt;js&lt;/code&gt; file into the build step&lt;/p&gt;

&lt;p&gt;as part of my sokol build step, I pass it in as &lt;code&gt;.extra_args&lt;/code&gt;.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="c"&gt;// create a build step which invokes the Emscripten linker&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;link_step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;sokol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emLinkStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;lib_main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;mod_main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;resolved_target&lt;/span&gt;&lt;span class="o"&gt;.?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;optimize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;mod_main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;optimize&lt;/span&gt;&lt;span class="o"&gt;.?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;emsdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dep_emsdk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;use_webgl2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;use_emmalloc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;use_filesystem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;shell_file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;dep_sokol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"src/sokol/web/shell.html"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="c"&gt;// set the js file here&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;extra_args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="s"&gt;"--js-library"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"js/libshine.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then call it from zig, with something like:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello from zig"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From my firefox console:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lume live reloading is ready. Listening for changes...     localhost:3000:102:15
🟢 Zig says: hello from zig                                shine.js:3168:11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3 id="em_js--em_asm"&gt;
&lt;code&gt;EM_JS&lt;/code&gt; / &lt;code&gt;EM_ASM&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The other option is to use
   &lt;a href="https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#interacting-with-code-call-javascript-from-native" rel="noopener noreferrer"&gt;&lt;code&gt;EM_JS&lt;/code&gt;&lt;/a&gt;
   which involves writing a wee bit of &lt;code&gt;C&lt;/code&gt;, which can embed the &lt;code&gt;javascript&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;In theory, it’s as simple as:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; 
&lt;span class="cpf"&gt;&amp;lt;emscripten.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;span class="n"&gt;EM_JS_DEPS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bla&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"$UTF8ToString"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;EM_JS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UTF8ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and adding it into the build file:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="c"&gt;// build the main file into a library, this is because the WASM 'exe'&lt;/span&gt;
&lt;span class="c"&gt;// needs to be linked in a separate build step with the Emscripten linker&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;shine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"shine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;root_module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;mod_main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c"&gt;// get the Emscripten SDK dependency from the sokol dependency&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;dep_emsdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;dep_sokol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"emsdk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{});&lt;/span&gt;
&lt;span class="c"&gt;// need to inject the Emscripten system header include path into&lt;/span&gt;
&lt;span class="c"&gt;// the cimgui C library otherwise the C/C++ code won't find&lt;/span&gt;
&lt;span class="c"&gt;// C stdlib headers&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;emsdk_incl_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dep_emsdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"upstream/emscripten/cache/sysroot/include"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;shine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;root_module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addCSourceFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"src/libjs.c"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;.&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="c"&gt;// optional extra emcc flags&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="n"&gt;shine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSystemIncludePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emsdk_incl_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The calling code in &lt;code&gt;main.zig&lt;/code&gt; remains the same:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello from zig"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this didn’t work, and failed with:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error: undefined symbol: jsLog (referenced by root reference (e.g. compiled C/C++ code))
warning: To disable errors for undefined symbols use `-sERROR_ON_UNDEFINED_SYMBOLS=0`
warning: _jsLog may need to be added to EXPORTED_FUNCTIONS if it arrives from a system library
Error: Aborting compilation due to previous errors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks to
   &lt;a href="https://ziggit.dev/t/help-with-getting-a-simple-call-to-js-through-emscripten-working/12090/3" rel="noopener noreferrer"&gt;some help&lt;/a&gt;
   from &lt;a href="https://ziggit.dev/u/floooh" rel="noopener noreferrer"&gt;flooh&lt;/a&gt;
   (who btw put together the
   &lt;a href="https://github.com/floooh/sokol" rel="noopener noreferrer"&gt;sokol&lt;/a&gt;
   and
   &lt;a href="https://github.com/floooh/sokol-zig" rel="noopener noreferrer"&gt;sokol-zig&lt;/a&gt;
   packages as well the
   &lt;a href="https://github.com/floooh/sokol-zig-imgui-sample" rel="noopener noreferrer"&gt;sokol-imgui-sample&lt;/a&gt;
   template
   which I used to kick start this project.), I was able to get it working.
&lt;/p&gt;

&lt;p&gt;Turns out the c file needs to have a function in it that is used in the zig
   file - it doesn’t need to do anything.
&lt;/p&gt;

&lt;p&gt;So, based on the suggestion, &lt;code&gt;libjs.c&lt;/code&gt; changes to:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; 
&lt;span class="cpf"&gt;&amp;lt;emscripten.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;span class="n"&gt;EM_JS_DEPS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bla&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"$UTF8ToString"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;EM_JS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UTF8ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and in &lt;code&gt;main.zig&lt;/code&gt;:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello from zig"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From my firefox console:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lume live reloading is ready. Listening for changes...     localhost:3000:102:15
🟢 Zig says: hello from zig                                shine.js:3168:11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see a working example in [my forked repo](&lt;/p&gt;

&lt;h2 id="em_js-directly-through-zig-unsuccessful"&gt;
&lt;code&gt;EM_JS&lt;/code&gt; directly through &lt;code&gt;zig&lt;/code&gt; [unsuccessful]&lt;/h2&gt;

&lt;p&gt;Looking at the macro for &lt;code&gt;EM_JS&lt;/code&gt; and with my good friend ChatGPT, I attempted
   translating it to zig and made some progress, but ultimately failed to get it
   working. I’ll leave the work here in the hopes it might be helpful.
&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define _EM_JS(ret, c_name, js_name, params, code)                             \
_EM_BEGIN_CDECL                                                              \
ret c_name params EM_IMPORT(js_name);                                        \
__attribute__((visibility("hidden")))                                        \
void* __em_js_ref_##c_name = (void*)&amp;amp;c_name;                                 \
EMSCRIPTEN_KEEPALIVE                                                         \
__attribute__((section("em_js"), aligned(1))) char __em_js__##js_name[] =    \
#params "
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;::&amp;gt;&lt;/span&gt;
&lt;span class="s"&gt;" code;                                                       \
_EM_END_CDECL&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above macro translates to zig roughly (with help from ChatGPT) as:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c"&gt;/// 2. Keep a reference to avoid the linker removing the function.&lt;/span&gt;
&lt;span class="c"&gt;///    Same role as __em_js_ref_* in the C macro.&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="mi"&gt;__&lt;/span&gt;&lt;span class="n"&gt;em_js_ref_jsLog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c"&gt;/// 3. Embed the JS implementation in a special section called "em_js".&lt;/span&gt;
&lt;span class="c"&gt;///    Emscripten will scan this and inject the code into the output JS.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="mi"&gt;__&lt;/span&gt;&lt;span class="n"&gt;em_js__jsLog&lt;/span&gt; &lt;span class="k"&gt;align&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;linksection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"em_js"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="s"&gt;"(const char* s)
&amp;lt;::&amp;gt;
{ console.log(UTF8ToString(s)); }&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added a &lt;code&gt;pub fn&lt;/code&gt; and called it from main:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which gave me the familiar error about not being able to find &lt;code&gt;jsLog&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;comparing the linker sections gave some clues:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ wasm-objdump --section=linking -x &amp;lt;path/to/libjs.o&amp;gt;
libjs.o:        file format wasm 0x1
Section Details:
Custom:
- name: "linking"
- symbol table [count=9]
- 0: F 
&amp;lt;dummy&amp;gt;
func=1 [ binding=global vis=hidden ]
- 1: D 
&amp;lt;__em_js_ref_jsLog&amp;gt;
segment=0 offset=0 size=4 [ binding=global vis=hidden ]
- 2: F 
&amp;lt;jsLog&amp;gt;
func=0 [ undefined explicit_name binding=global vis=default ]
- 3: D 
&amp;lt;__em_js__jsLog&amp;gt;
segment=1 offset=0 size=53 [ exported no_strip binding=global vis=hidden ]
- 4: S &amp;lt;.debug_abbrev&amp;gt; section=7 [ binding=local vis=default ]
- 5: G 
&amp;lt;env.__stack_pointer&amp;gt;
global=0 [ undefined binding=global vis=default ]
- 6: S &amp;lt;.debug_str&amp;gt; section=9 [ binding=local vis=default ]
- 7: T 
&amp;lt;env.__indirect_function_table&amp;gt;
table=0 [ undefined exported no_strip binding=global vis=default ]
- 8: S &amp;lt;.debug_line&amp;gt; section=10 [ binding=local vis=default ]
- segment info [count=2]
- 0: .data.__em_js_ref_jsLog p2align=2 [ ]
- 1: em_js p2align=0 [ RETAIN ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the zig object:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ wasm-objdump --section=linking -x js.o
js.o:   file format wasm 0x1
Section Details:
Custom:
- name: "linking"
- symbol table [count=6]
- 0: F 
&amp;lt;dummy&amp;gt;
func=1 [ binding=global vis=default ]
- 1: D 
&amp;lt;__em_js_ref_jsLog&amp;gt;
segment=0 offset=0 size=4 [ binding=global vis=default ]
- 2: F 
&amp;lt;jsLog&amp;gt;
func=0 [ undefined explicit_name binding=global vis=default ]
- 3: D 
&amp;lt;__em_js__jsLog&amp;gt;
segment=1 offset=0 size=4 [ binding=global vis=default ]
- 4: D 
&amp;lt;__anon_946&amp;gt;
segment=2 offset=0 size=54 [ binding=local vis=default ]
- 5: T 
&amp;lt;env.__indirect_function_table&amp;gt;
table=0 [ undefined exported no_strip binding=global vis=default ]
- segment info [count=3]
- 0: .rodata.__em_js_ref_jsLog p2align=2 [ ]
- 1: em_js p2align=0 [ ]
- 2: .rodata.__anon_946 p2align=0 [ ]`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From what I could understand (which is little), it looks like &lt;code&gt;__em_js__jsLog&lt;/code&gt;
   in the zig obj is a pointer while from C, it’s the full string.
&lt;/p&gt;

&lt;p&gt;hardcoding it as a static array helped:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="mi"&gt;__&lt;/span&gt;&lt;span class="n"&gt;em_js__jsLog&lt;/span&gt; &lt;span class="k"&gt;align&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;linksection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"em_js"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="sc"&gt;'('&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;')'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="sc"&gt;'&amp;lt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;':'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;':'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'{'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="sc"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'('&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="sc"&gt;'U'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'T'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'F'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'T'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'S'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'('&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="sc"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;')'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;')'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;';'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'\x00'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output from this is a little more promising&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ wasm-objdump --section=linking -x js.o
js.o:   file format wasm 0x1
Section Details:
Custom:
- name: "linking"
- symbol table [count=5]
- 0: F 
&amp;lt;dummy&amp;gt;
func=1 [ binding=global vis=default ]
- 1: D 
&amp;lt;__em_js_ref_jsLog&amp;gt;
segment=0 offset=0 size=4 [ binding=global vis=default ]
- 2: F 
&amp;lt;jsLog&amp;gt;
func=0 [ undefined explicit_name binding=global vis=default ]
- 3: D 
&amp;lt;__em_js__jsLog&amp;gt;
segment=1 offset=0 size=53 [ binding=global vis=default ]
- 4: T 
&amp;lt;env.__indirect_function_table&amp;gt;
table=0 [ undefined exported no_strip binding=global vis=default ]
- segment info [count=2]
- 0: .rodata.__em_js_ref_jsLog p2align=2 [ ]
- 1: em_js p2align=0 [ ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s look at the two side by side&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# From C
- 3: D 
&amp;lt;__em_js__jsLog&amp;gt;
segment=1 offset=0 size=53 [ exported no_strip binding=global vis=hidden ]
# From zig
- 3: D 
&amp;lt;__em_js__jsLog&amp;gt;
segment=1 offset=0 size=53 [ binding=global vis=default ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are some clear differences in how the two are output and I am already
   beyond my knowledge level here - so I’ll leave it to someone who knows this
   stuff better (or wait until I do)
&lt;/p&gt;

&lt;p&gt;You can
   &lt;a href="https://github.com/drone-ah/sokol-zig-imgui-sample/tree/zig_em_js" rel="noopener noreferrer"&gt;check out the code in the branch of my forked repo&lt;/a&gt;
&lt;/p&gt;

&lt;h2 id="next-steps"&gt;Next steps&lt;/h2&gt;

&lt;p&gt;My plan is to use &lt;code&gt;EM_JS&lt;/code&gt; through &lt;code&gt;C&lt;/code&gt; to implement glue JavaScript functions -
   something like:
&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;EM_JS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UTF8ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By doing this, I can have one-line js code in the &lt;code&gt;.c&lt;/code&gt; file and all the
   implementation can go into the web side (and can easily be TypeScript too).
&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="na"&gt;jsLog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🟢 Zig says:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>zig</category>
      <category>webassembly</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Auto reload WASM with zig+lume</title>
      <dc:creator>Shri</dc:creator>
      <pubDate>Wed, 17 Sep 2025 11:20:51 +0000</pubDate>
      <link>https://dev.to/hereticles/auto-reload-wasm-with-ziglume-31ag</link>
      <guid>https://dev.to/hereticles/auto-reload-wasm-with-ziglume-31ag</guid>
      <description>&lt;p&gt;I’ve been taking some time off to rest and recover from health issues that made&lt;br&gt;
it hard to focus. To ease back in, I’ve started a small project:&lt;br&gt;
&lt;a href="//../../excursions/shine.md"&gt;shine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project would do well to be multiplatform - mobile and web. The obvious&lt;br&gt;
choice was &lt;a href="https://dev.to/tags/flutter"&gt;flutter&lt;/a&gt; and I have enjoyed working with it before.&lt;/p&gt;

&lt;p&gt;However, as I'm currently in love with &lt;a href="https://dev.to/tags/zig"&gt;zig&lt;/a&gt;, I wanted to work with&lt;br&gt;
that instead.&lt;/p&gt;
&lt;h2&gt;
  
  
  Libraries
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Graphics
&lt;/h3&gt;

&lt;p&gt;I've been playing with &lt;a href="https://dev.to/tags/raylib"&gt;raylib&lt;/a&gt; and that was my initial instinct.&lt;br&gt;
However, raylib&lt;br&gt;
&lt;a href="https://github.com/raysan5/raylib/discussions/2681" rel="noopener noreferrer"&gt;does not support iOs&lt;/a&gt; and&lt;br&gt;
&lt;a href="https://github.com/raysan5/raylib/discussions/3626" rel="noopener noreferrer"&gt;has issues with wasm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I considered a few options, including&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.libsdl.org/" rel="noopener noreferrer"&gt;sdl&lt;/a&gt;, which looked great but was perhaps a little
too low level for me.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Jack-Ji/jok" rel="noopener noreferrer"&gt;jok&lt;/a&gt; - does not support mobile and possibly
has a little more than I needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the end, I decided to go with &lt;a href="https://github.com/floooh/sokol" rel="noopener noreferrer"&gt;sokol&lt;/a&gt; and&lt;br&gt;
&lt;a href="https://github.com/floooh/sokol-zig" rel="noopener noreferrer"&gt;sokol-zig&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While a little more lower level than raylib, it has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a modern clean api&lt;/li&gt;
&lt;li&gt;first class mobile support&lt;/li&gt;
&lt;li&gt;first class wasm support&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  UI
&lt;/h3&gt;

&lt;p&gt;I've been working with &lt;a href="https://dev.to/tags/dvui"&gt;dvui&lt;/a&gt; a lot recently. Unfortunately, it&lt;br&gt;
doesn't support sokol. &lt;a href="https://github.com/SpexGuy/Zig-ImGui" rel="noopener noreferrer"&gt;imgui&lt;/a&gt; is a better&lt;br&gt;
option.&lt;/p&gt;

&lt;p&gt;There is even a&lt;br&gt;
&lt;a href="https://github.com/floooh/sokol-zig-imgui-sample" rel="noopener noreferrer"&gt;template project that I could start from&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  WASM first
&lt;/h2&gt;

&lt;p&gt;To keep things straightforward, I decided to start with wasm. If I make the site&lt;br&gt;
mobile friendly, I could see how it goes and see if it needs a mobile version.&lt;/p&gt;

&lt;p&gt;I will need some shared data storage and have been considering&lt;br&gt;
&lt;a href="https://supabase.com/" rel="noopener noreferrer"&gt;supabase&lt;/a&gt;, which has &lt;a href="https://dev.to/tags/javascript"&gt;javascript&lt;/a&gt;&lt;br&gt;
libs. By using &lt;a href="https://dev.to/tags/wasm"&gt;wasm&lt;/a&gt;, I can effectively shim in js functions to&lt;br&gt;
handle that instead of having to write bare rest calls from zig.&lt;/p&gt;
&lt;h3&gt;
  
  
  Structuring the project
&lt;/h3&gt;

&lt;p&gt;Is this a zig project with a web component, vice versa or indeed two independent&lt;br&gt;
parts that work together.&lt;/p&gt;

&lt;p&gt;I did a fair amount of web searching to see if there was some guidance I could I&lt;br&gt;
find for a good way to structure a relatively straightforward zig+js project.&lt;/p&gt;

&lt;p&gt;I could not find one. In the end, I decided to keep it fairly straightforward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- shine/
  - src/ # zig code
  - web/ # all the web stuff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;p&gt;After a bit of research, and realising that I will probably need a little bit of&lt;br&gt;
supporting content around &lt;a href="//../../excursions/shine.md"&gt;shine&lt;/a&gt;, I decided to go&lt;br&gt;
with &lt;a href="https://lume.land/" rel="noopener noreferrer"&gt;lume&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used the &lt;a href="https://github.com/lumeland/theme-simple-blog" rel="noopener noreferrer"&gt;simple-blog theme&lt;/a&gt; as&lt;br&gt;
a template to start from. I could have just pulled the template in but I wanted&lt;br&gt;
a custom homepage.&lt;/p&gt;

&lt;p&gt;When I tried to add an &lt;code&gt;index.md&lt;/code&gt;, it complained about two files wanting to&lt;br&gt;
write &lt;code&gt;index.html&lt;/code&gt;. From what I could find, the easiest way to override the&lt;br&gt;
homepage was to just pick up the theme and edit it - which was easy enough.&lt;/p&gt;
&lt;h3&gt;
  
  
  WASM =&amp;gt; frontend
&lt;/h3&gt;

&lt;p&gt;I didn't want to copy over the wasm and the js file every time, so I added a&lt;br&gt;
couple of steps to &lt;code&gt;build.zig&lt;/code&gt; right after the &lt;code&gt;link_step&lt;/code&gt; (also included below)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="c"&gt;// build.zig&lt;/span&gt;
&lt;span class="c"&gt;// create a build step which invokes the Emscripten linker&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;link_step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;sokol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emLinkStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;lib_main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;mod_main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;resolved_target&lt;/span&gt;&lt;span class="o"&gt;.?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;optimize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;mod_main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;optimize&lt;/span&gt;&lt;span class="o"&gt;.?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;emsdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dep_emsdk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;use_webgl2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;use_emmalloc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;use_filesystem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;shell_file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;dep_sokol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"src/sokol/web/shell.html"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c"&gt;// attach to default target&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstallStep&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;dependOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;link_step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;step&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c"&gt;// Copy shine.js from default emscripten output&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;js_install&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addInstallFileWithDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"zig-out/web/shine.js"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;custom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"../web/src/static/shine"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;"shine.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;js_install&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dependOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;link_step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;step&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstallStep&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;dependOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;js_install&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;step&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c"&gt;// Copy shine.wasm from default emscripten output&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;wasm_install&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addInstallFileWithDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"zig-out/web/shine.wasm"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;custom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"../web/src/static/shine"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;"shine.wasm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;wasm_install&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dependOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;link_step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;step&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These steps will copy across the wasm and the js file across to &lt;code&gt;static/shine&lt;/code&gt;.&lt;br&gt;
I wanted to put the js in &lt;code&gt;src/js&lt;/code&gt; and the wasm in the static dir. However, the&lt;br&gt;
js file expects the wasm in the same dir. I tried overriding &lt;code&gt;locateFile&lt;/code&gt; but it&lt;br&gt;
didn't work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- index.vto --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;locateFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/static/shine/shine.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/js/shine.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was able to get lume to process the javascript file by &lt;code&gt;add&lt;/code&gt;ing it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// _config.ts&lt;/span&gt;
&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;static/shine/shine.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With all of these set up, I was able to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zig build &lt;span class="nt"&gt;-Dtarget&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;wasm32-emscripten &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in one window. This command will rebuild wasm and provide it to lume whenever&lt;br&gt;
the zig code changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deno task serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this in another window will mean that lume will rebuild on any changes,&lt;br&gt;
including a new wasm file and redeploy. The redeploy will trigger an auto-reload&lt;br&gt;
of the page as well if I have it in a browser.&lt;/p&gt;

&lt;p&gt;I now effectively have automated reload with changes if I make changes in either&lt;br&gt;
zig or the frontend.&lt;/p&gt;

&lt;p&gt;I don't have &lt;em&gt;hot&lt;/em&gt; reload - but this is pretty good for now.&lt;/p&gt;

</description>
      <category>zig</category>
      <category>webassembly</category>
      <category>lume</category>
    </item>
    <item>
      <title>Building Pong in Zig with Raylib – Part 1: Setup, Paddles, and Ball</title>
      <dc:creator>Shri</dc:creator>
      <pubDate>Sat, 28 Jun 2025 19:38:29 +0000</pubDate>
      <link>https://dev.to/hereticles/building-pong-in-zig-with-raylib-part-1-setup-paddles-and-ball-119l</link>
      <guid>https://dev.to/hereticles/building-pong-in-zig-with-raylib-part-1-setup-paddles-and-ball-119l</guid>
      <description>&lt;p&gt;Before continuing development on Triangle - my larger, arcade ARPG, factory
game, I wanted to take a step back and build something small and familiar. Pong
felt like the perfect choice: quick to prototype, easy to understand, and a good
warm-up before diving deeper into raylib again.&lt;/p&gt;

&lt;p&gt;This post goes alongside &lt;a href="https://youtu.be/ICq2D_na6zc" rel="noopener noreferrer"&gt;my video on YouTube&lt;/a&gt;
,
and walks through the early steps of the project: setting up the game, getting
something on screen, and implementing the paddles and the ball.&lt;/p&gt;

&lt;h2 id="why-pong"&gt;Why Pong?&lt;/h2&gt;

&lt;p&gt;Sometimes, before getting deeper into a project, it helps to do something simple
just to get your hands moving again. I hadn’t written Zig in a couple of weeks,
and wanted a fast feedback loop to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get back into using &lt;code&gt;raylib-zig&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Have a bit of fun&lt;/li&gt;
&lt;li&gt;Remind myself why I made certain decisions in the first place&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="project-setup"&gt;Project Setup&lt;/h2&gt;

&lt;p&gt;I’m using &lt;a href="https://github.com/Not-Nik/raylib-zig" rel="noopener noreferrer"&gt;&lt;code&gt;raylib-zig&lt;/code&gt;&lt;/a&gt;
 for this. You
can scaffold a new project with:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./project_setup.sh &amp;lt;project-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could also use &lt;code&gt;zig init&lt;/code&gt; and then add the dependency in manually if you
prefer.&lt;/p&gt;

&lt;h2 id="drawing-the-playground"&gt;Drawing the Playground&lt;/h2&gt;

&lt;p&gt;The Pong “arena” is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two paddles&lt;/li&gt;
&lt;li&gt;A ball (currently static)&lt;/li&gt;
&lt;li&gt;A center dividing line&lt;/li&gt;
&lt;li&gt;Placeholder for scores&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="implementing-paddles"&gt;Implementing Paddles&lt;/h2&gt;

&lt;p&gt;The paddles have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A position (top-left corner)&lt;/li&gt;
&lt;li&gt;A fixed size&lt;/li&gt;
&lt;li&gt;A simple render function&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;@import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"std"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;rl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;@import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"raylib"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Paddle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;@This&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Paddle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Paddle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawRectangleV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;white&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It might have been nice to be able to have the paddle calculate more of its own
values, but the more I think about it, the more it makes sense to keep the logic
in paddle simpler.&lt;/p&gt;

&lt;h2 id="ball-placeholder"&gt;Ball Placeholder&lt;/h2&gt;

&lt;p&gt;To wrap things up for this session, I added a static ball in the center of the
screen. It has a radius, position, and velocity fields ready to go. Rendering is
straightforward:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;@import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"std"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;rl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;@import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"raylib"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Ball&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;@This&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;f32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;vel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;Vector2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Ball&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawCircleV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;white&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2 id="whats-next"&gt;What’s Next&lt;/h2&gt;

&lt;p&gt;This was mostly about warming up, but the next episode will tackle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding velocity to the ball&lt;/li&gt;
&lt;li&gt;Basic collision detection with paddles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ll try and think about simplifying paddle logic, especially the awkward
symmetry between left and right.&lt;/p&gt;

&lt;p&gt;You can find the full source code
&lt;a href="https://github.com/drone-ah/wordsonsand/tree/main/games/pong" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;
.&lt;/p&gt;

&lt;p&gt;See you in part 2!&lt;/p&gt;

&lt;h2 id="links"&gt;Links&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
      &lt;a href="https://www.youtube.com/watch?v=ICq2D_na6zc&amp;amp;list=PLX7QRDvlHn4J5uVbmVlkyDaGJ8utKR9K_" rel="noopener noreferrer"&gt;YouTube Video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/drone-ah/wordsonsand/tree/shri-codes/pong/part-1/games/pong" rel="noopener noreferrer"&gt;Full Source Code (at this point)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next: &lt;a href="https://drone-ah.com/2025/07/04/building-pong-in-zig-with-raylib-part-2-ball-movement-paddle-collisions/" rel="noopener noreferrer"&gt;Ball Movement &amp;amp; Paddle Collisions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>zig</category>
      <category>raylib</category>
      <category>gamedev</category>
      <category>pong</category>
    </item>
  </channel>
</rss>
