<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[SEWB]]></title><description><![CDATA[Software Engineering Without Borders]]></description><link>https://blog.sewbs.dev</link><image><url>https://substackcdn.com/image/fetch/$s_!jtXK!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27fb93c0-27bf-440d-944e-0f5c2a98b348_1200x1200.png</url><title>SEWB</title><link>https://blog.sewbs.dev</link></image><generator>Substack</generator><lastBuildDate>Wed, 08 Apr 2026 13:44:46 GMT</lastBuildDate><atom:link href="https://blog.sewbs.dev/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[SEWB]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[sewb@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[sewb@substack.com]]></itunes:email><itunes:name><![CDATA[SEWB]]></itunes:name></itunes:owner><itunes:author><![CDATA[SEWB]]></itunes:author><googleplay:owner><![CDATA[sewb@substack.com]]></googleplay:owner><googleplay:email><![CDATA[sewb@substack.com]]></googleplay:email><googleplay:author><![CDATA[SEWB]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[ICallOn v2 - Daily Challenge]]></title><description><![CDATA[Part 2: Implementing the Daily Challenge game mode]]></description><link>https://blog.sewbs.dev/p/icallon-v2-daily-challenge</link><guid isPermaLink="false">https://blog.sewbs.dev/p/icallon-v2-daily-challenge</guid><dc:creator><![CDATA[Wolemercy]]></dc:creator><pubDate>Fri, 20 Feb 2026 18:54:05 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/bf09c7e7-4fc5-403b-8ddb-6a64e28c3328_1200x630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi! If you haven't read the first part of this series, where I cover the work that went into improving the design, architecture, and UI/UX of <strong><a href="https://icallon.xyz/">ICallOn</a></strong>, read it below.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;19947374-5ee6-4e0a-ad60-4ee6549d6357&quot;,&quot;caption&quot;:&quot;I released the first iteration of ICallOn in December 2024. It was a throwback to a game I and many others enjoyed playing as kids, and I thought it'd be fun to experience it digitally as a real-time multiplayer game.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;ICallOn v2&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:4875054,&quot;name&quot;:&quot;Wolemercy&quot;,&quot;bio&quot;:&quot;Like the lot of us, I'm a little sick. Thankfully, writing is therapeutic.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c6bae1bc-e2b6-426b-9614-03cc6a5f4dd5_2048x2048.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-02-09T15:02:52.505Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a6b13e8f-5080-4c54-905a-f8cb883b21e6_1200x630.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://blog.sewbs.dev/p/icallon-v2&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:187375749,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6778196,&quot;publication_name&quot;:&quot;SEWB&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!jtXK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27fb93c0-27bf-440d-944e-0f5c2a98b348_1200x1200.png&quot;,&quot;belowTheFold&quot;:false,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div><hr></div><p>In this second (and final) part, I cover the implementation of the new Daily Challenge mode, specifically</p><ul><li><p>how it works</p></li><li><p>answer validation and caching</p></li><li><p>scoring and leaderboards</p></li><li><p>automated daily/weekly summaries </p></li></ul><p>Welcome!</p><div><hr></div><p>With the architecture in better shape and an improved UI, I added something new: a game mode that would keep players engaged with the app, even if they had no one to play with. In the <strong>Daily Challenge</strong> mode, every player gets the same letter and categories each day. You have 30 seconds to fill in your answers, which are then validated by an LLM.</p><h2>LLM Validation</h2><p>This was the answer to the most important question of the game mode: <em>how would the answers be validated</em>? In normal PvP gameplay, answers are graded by players and disputes are resolved outside the app (players could argue about it on a phone call, for example).</p><p>This wouldn&#8217;t work for a Daily Challenge mode. And unlike other word games where challenges have specific solutions used to validate players&#8217; submissions, a letter+category combination in ICallOn could have several valid answers.</p><p>The solution here was to rely on an LLM. With good prompts, LLMs excel at category validation and I chose Google&#8217;s Gemini 2.5 Flash model for this purpose.</p><p>There were some cost considerations in this decision. Gemini has a generous free tier, but I eventually hit its daily request limits and upgraded. So far, costs have stayed comfortably low.</p><p>To preemptively minimise costs, I added a caching layer that stores validation results in the database.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NRV_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa886bdc8-c17a-430a-8c25-d2a72c083c41_3624x1992.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NRV_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa886bdc8-c17a-430a-8c25-d2a72c083c41_3624x1992.png 424w, https://substackcdn.com/image/fetch/$s_!NRV_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa886bdc8-c17a-430a-8c25-d2a72c083c41_3624x1992.png 848w, https://substackcdn.com/image/fetch/$s_!NRV_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa886bdc8-c17a-430a-8c25-d2a72c083c41_3624x1992.png 1272w, https://substackcdn.com/image/fetch/$s_!NRV_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa886bdc8-c17a-430a-8c25-d2a72c083c41_3624x1992.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NRV_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa886bdc8-c17a-430a-8c25-d2a72c083c41_3624x1992.png" width="728" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a886bdc8-c17a-430a-8c25-d2a72c083c41_3624x1992.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/edca2633-30d0-4909-9fac-4219c9f220de_3624x1992.png&quot;,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:800,&quot;width&quot;:1456,&quot;resizeWidth&quot;:728,&quot;bytes&quot;:490611,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.sewbs.dev/i/188632740?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedca2633-30d0-4909-9fac-4219c9f220de_3624x1992.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NRV_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa886bdc8-c17a-430a-8c25-d2a72c083c41_3624x1992.png 424w, https://substackcdn.com/image/fetch/$s_!NRV_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa886bdc8-c17a-430a-8c25-d2a72c083c41_3624x1992.png 848w, https://substackcdn.com/image/fetch/$s_!NRV_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa886bdc8-c17a-430a-8c25-d2a72c083c41_3624x1992.png 1272w, https://substackcdn.com/image/fetch/$s_!NRV_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa886bdc8-c17a-430a-8c25-d2a72c083c41_3624x1992.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Conceptual example of the answer validation cache</figcaption></figure></div><p>Since release, I&#8217;ve cached 500+ validations and avoided ~200 same-day duplicate LLM calls which saves tokens (and money).</p><h2>Scoring</h2><p>Scoring in a PvP ICallOn game is straightforward. You get 10 points for a unique answer, 5 for a duplicate, and 0 for an invalid answer. In the Daily Challenge mode, such a scoring formula would not work because the frequency of duplicates could vary significantly, and the points penalty should be proportional to that variance.</p><p>Therefore, the scoring goal (pun intended) was to give unique answers a perfect score (10), and assign a score in the range of 1 to 9 for a duplicate answer such that the higher the frequency of duplicates, the lower your score.</p><p>I considered three ways of doing this</p><ol><li><p>Direct Deduction: for every duplicate of your answer, you lose a point. It&#8217;s simple, intuitive, and ideal for a small pool of players. If 3 other players submitted the same answer, you each score 7 points (3 points lost).</p></li><li><p>Proportional Deduction: the deducted points is based on the proportion of players with the same answer as you. It&#8217;s less intuitive, but great for mid-sized pool of players. If 3 other players submitted the same answer and there were 16 players in total, you each lose 3/16 * 10 points. And if there were more players in the pool, then the penalty is reduced.</p></li><li><p>Exponential Deduction: it&#8217;s similar to Proportional deduction, except that many duplicates are punished more harshly than fewer duplicates using an exponential function.</p></li></ol><p>I settled for Direct Deduction for two reasons. First there aren&#8217;t a lot of players, so simple and intuitive rocks. Second, it&#8217;s not hard to imagine scenarios with the Proportional and Exponential Deductions where approximations would be required, and perhaps even result in fractional scores. It&#8217;d take some explaining for players to understand that their low score is because 65% of the players tendered the same answer they did.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-J4a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa41fb134-907b-439e-9a19-962258b143b9_1080x1682.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-J4a!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa41fb134-907b-439e-9a19-962258b143b9_1080x1682.png 424w, https://substackcdn.com/image/fetch/$s_!-J4a!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa41fb134-907b-439e-9a19-962258b143b9_1080x1682.png 848w, https://substackcdn.com/image/fetch/$s_!-J4a!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa41fb134-907b-439e-9a19-962258b143b9_1080x1682.png 1272w, https://substackcdn.com/image/fetch/$s_!-J4a!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa41fb134-907b-439e-9a19-962258b143b9_1080x1682.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-J4a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa41fb134-907b-439e-9a19-962258b143b9_1080x1682.png" width="1080" height="1682" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a41fb134-907b-439e-9a19-962258b143b9_1080x1682.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1682,&quot;width&quot;:1080,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:678886,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.sewbs.dev/i/188632740?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62544381-9dff-4504-be63-4b751b8a1008_1080x2400.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-J4a!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa41fb134-907b-439e-9a19-962258b143b9_1080x1682.png 424w, https://substackcdn.com/image/fetch/$s_!-J4a!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa41fb134-907b-439e-9a19-962258b143b9_1080x1682.png 848w, https://substackcdn.com/image/fetch/$s_!-J4a!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa41fb134-907b-439e-9a19-962258b143b9_1080x1682.png 1272w, https://substackcdn.com/image/fetch/$s_!-J4a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa41fb134-907b-439e-9a19-962258b143b9_1080x1682.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Scoring Screen showing correct, duplicate, and incorrect answers</figcaption></figure></div><p>One important detail is that scores are recalculated as new submissions come in. If Taiwo submits the same answer you did later in the day, your points drop accordingly. That means scores (and ranks) can shift throughout the day, and the leaderboard only settles at day&#8217;s end.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.sewbs.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.sewbs.dev/subscribe?"><span>Subscribe now</span></a></p><h3>Leaderboard</h3><p>Players are ranked on the leaderboard by their points tally. If two players score the same, then the player with the fastest submission edges it. So while each challenge lasts for 30 seconds, players are encouraged to submit quickly.</p><p>For ICallOn v2, I added three leaderboards</p><ul><li><p>Daily: shows player rankings on the present day</p></li><li><p>Weekly: shows player rankings for the present week (Monday - Sunday, UTC)</p></li><li><p>All Time: shows player rankings since the Daily Challenge started</p></li></ul><p>I did consider a Monthly Leaderboard. Over time, perhaps it&#8217;d be more useful than the All Time Leaderboard.</p><h3>Usernames</h3><p>Since ICallOn v1, authentication has been via Google and it remains so. Users needed to sign in to create game sessions, but players could join existing sessions anonymously. For the Daily Challenge mode, authentication is required so that scores can be attributed consistently. This can be changed at any time on a new Profile page I added.</p><h3><strong>Sharing Results</strong></h3><p>Like other word games with daily challenges, part of the fun is showing off your undeniably good or embarrassingly poor performance. As such, players can share their gameplay results which includes the following</p><ul><li><p>Points per category</p></li><li><p>Total points</p></li><li><p>Rank</p></li><li><p>Time Taken</p></li></ul><p>To prevent spoilers, I don&#8217;t include the letter, categories, or answers.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qyP3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07ae0739-e55c-432a-8bb6-62ad33a9ae45_1078x480.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qyP3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07ae0739-e55c-432a-8bb6-62ad33a9ae45_1078x480.png 424w, https://substackcdn.com/image/fetch/$s_!qyP3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07ae0739-e55c-432a-8bb6-62ad33a9ae45_1078x480.png 848w, https://substackcdn.com/image/fetch/$s_!qyP3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07ae0739-e55c-432a-8bb6-62ad33a9ae45_1078x480.png 1272w, https://substackcdn.com/image/fetch/$s_!qyP3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07ae0739-e55c-432a-8bb6-62ad33a9ae45_1078x480.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qyP3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07ae0739-e55c-432a-8bb6-62ad33a9ae45_1078x480.png" width="1078" height="480" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/07ae0739-e55c-432a-8bb6-62ad33a9ae45_1078x480.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a997d981-9b90-4241-ae60-348f5961db6f_1078x480.png&quot;,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:480,&quot;width&quot;:1078,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:59354,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.sewbs.dev/i/188632740?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa997d981-9b90-4241-ae60-348f5961db6f_1078x480.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qyP3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07ae0739-e55c-432a-8bb6-62ad33a9ae45_1078x480.png 424w, https://substackcdn.com/image/fetch/$s_!qyP3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07ae0739-e55c-432a-8bb6-62ad33a9ae45_1078x480.png 848w, https://substackcdn.com/image/fetch/$s_!qyP3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07ae0739-e55c-432a-8bb6-62ad33a9ae45_1078x480.png 1272w, https://substackcdn.com/image/fetch/$s_!qyP3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07ae0739-e55c-432a-8bb6-62ad33a9ae45_1078x480.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Sharing Results</figcaption></figure></div><h3>Question Bank</h3><p>Each daily question includes a letter and four random categories (from a growing list of 11 categories). The questions are seeded in a question bank, and the application serves players the question of the day.</p><h2>Automated Summaries and Tweets</h2><p>Since developing ICallOn, I&#8217;ve shared updates about the features and rollout on Twitter via <a href="https://x.com/wolemercy">@wolemercy</a>. After deploying the daily challenge feature, I figured it&#8217;d be great to make commentary on the leaderboard, and highlight any notable rank changes from day to day. It would also be fun to highlight the player of the week, I thought.</p><p>Since the Daily Challenge can be taken at any time, and a player&#8217;s points and rank can change at any point during the day, it meant the daily leaderboard was only set in stone at midnight. As such, leaderboard analysis could only happen the following day. To make this work, I needed three components: a summary generator, a Twitter posts integration, and a cron job.</p><h3>Generating Summaries</h3><p>As with answer validation, I relied on an LLM for summary generation. First, I fetch the leaderboard data (daily or weekly, as the case may be). If it&#8217;s the daily leaderboard, I also compute rank changes, which compares each player&#8217;s current rank with their rank the day before.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!z_Q-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F225d9a2c-64a6-4ba1-8e90-2de0ecc305f6_3408x2172.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!z_Q-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F225d9a2c-64a6-4ba1-8e90-2de0ecc305f6_3408x2172.png 424w, https://substackcdn.com/image/fetch/$s_!z_Q-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F225d9a2c-64a6-4ba1-8e90-2de0ecc305f6_3408x2172.png 848w, https://substackcdn.com/image/fetch/$s_!z_Q-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F225d9a2c-64a6-4ba1-8e90-2de0ecc305f6_3408x2172.png 1272w, https://substackcdn.com/image/fetch/$s_!z_Q-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F225d9a2c-64a6-4ba1-8e90-2de0ecc305f6_3408x2172.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!z_Q-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F225d9a2c-64a6-4ba1-8e90-2de0ecc305f6_3408x2172.png" width="1456" height="928" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/225d9a2c-64a6-4ba1-8e90-2de0ecc305f6_3408x2172.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4ec17c06-17a3-4552-b24b-af076071ea76_3408x2172.png&quot;,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:928,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:460610,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.sewbs.dev/i/188632740?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ec17c06-17a3-4552-b24b-af076071ea76_3408x2172.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!z_Q-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F225d9a2c-64a6-4ba1-8e90-2de0ecc305f6_3408x2172.png 424w, https://substackcdn.com/image/fetch/$s_!z_Q-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F225d9a2c-64a6-4ba1-8e90-2de0ecc305f6_3408x2172.png 848w, https://substackcdn.com/image/fetch/$s_!z_Q-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F225d9a2c-64a6-4ba1-8e90-2de0ecc305f6_3408x2172.png 1272w, https://substackcdn.com/image/fetch/$s_!z_Q-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F225d9a2c-64a6-4ba1-8e90-2de0ecc305f6_3408x2172.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Summary payload data</figcaption></figure></div><p>This data is fed into a prompt that I send to an LLM (<code>gemini-2.5-flash</code>). If this fails, it falls back to a hardcoded template summary.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!u4dT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92474b91-9e21-408f-b921-2f852153f628_1082x1062.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!u4dT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92474b91-9e21-408f-b921-2f852153f628_1082x1062.png 424w, https://substackcdn.com/image/fetch/$s_!u4dT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92474b91-9e21-408f-b921-2f852153f628_1082x1062.png 848w, https://substackcdn.com/image/fetch/$s_!u4dT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92474b91-9e21-408f-b921-2f852153f628_1082x1062.png 1272w, https://substackcdn.com/image/fetch/$s_!u4dT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92474b91-9e21-408f-b921-2f852153f628_1082x1062.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!u4dT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92474b91-9e21-408f-b921-2f852153f628_1082x1062.png" width="1082" height="1062" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/92474b91-9e21-408f-b921-2f852153f628_1082x1062.png&quot;,&quot;srcNoWatermark&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4072e910-616a-4e45-887c-ccc833695ebc_1082x1062.png&quot;,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1062,&quot;width&quot;:1082,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:370267,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.sewbs.dev/i/188632740?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4072e910-616a-4e45-887c-ccc833695ebc_1082x1062.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!u4dT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92474b91-9e21-408f-b921-2f852153f628_1082x1062.png 424w, https://substackcdn.com/image/fetch/$s_!u4dT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92474b91-9e21-408f-b921-2f852153f628_1082x1062.png 848w, https://substackcdn.com/image/fetch/$s_!u4dT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92474b91-9e21-408f-b921-2f852153f628_1082x1062.png 1272w, https://substackcdn.com/image/fetch/$s_!u4dT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92474b91-9e21-408f-b921-2f852153f628_1082x1062.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A Daily Summary Tweet</figcaption></figure></div><p>In addition to the text summaries, I also generate a leaderboard image of the top 5 players, and it&#8217;s posted alongside the summary.</p><h3>Twitter Posts</h3><p>I created a twitter account specifically for ICallOn <a href="https://x.com/icallon_xyz">@icallon_xyz</a>, for tweeting these summaries. To achieve this programmatically, I make two calls to Twitter&#8217;s API for each summary tweet. The first call uploads the leaderboard image, while the second posts the tweet.</p><h3>Cron Jobs</h3><p>To generate and post tweets on schedule, I rely on Vercel Cron, which fires at 8 AM every day for the daily tweets, and 8AM every Monday for the weekly tweets.</p><h2>Wrapping Up</h2><p>ICallOn has seen a lot of changes from v1 to v2, and I&#8217;m hard-pressed to see any other major additions I could make. The PvP mode provides an experience that embodies the spirit of the traditional game. And the Daily Challenge mode, which was one thing I&#8217;d always wanted to add, provides a way for players to compete with others every day.</p><p>One thing I would like to do is make the game snappier. There are certain transitions within the app that could be faster, which would make for a better gaming experience. I would also like to handle certain failure scenarios better, such as an LLM failure during answer validation.</p><p>Thank you for following along. Please, check out the game if you haven&#8217;t already: <a href="http://www.icallon.xyz/daily-challenge">www.icallon.xyz.</a> And if you have any feedback, feature requests, or general comments, please tweet me <a href="https://x.com/wolemercy"> @wolemercy</a>.</p><p>Thanks. See you around.</p><p>- Wolemercy</p><div><hr></div><p><strong>P.S </strong>Read Part 1 here</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;57b89c0b-c83a-4842-9485-2379fd217ec2&quot;,&quot;caption&quot;:&quot;I released the first iteration of ICallOn in December 2024. It was a throwback to a game I and many others enjoyed playing as kids, and I thought it'd be fun to experience it digitally as a real-time multiplayer game.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;md&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;ICallOn v2&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:4875054,&quot;name&quot;:&quot;Wolemercy&quot;,&quot;bio&quot;:&quot;Like the lot of us, I'm a little sick. Thankfully, writing is therapeutic.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c6bae1bc-e2b6-426b-9614-03cc6a5f4dd5_2048x2048.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-02-09T15:02:52.505Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a6b13e8f-5080-4c54-905a-f8cb883b21e6_1200x630.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://blog.sewbs.dev/p/icallon-v2&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:187375749,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:6778196,&quot;publication_name&quot;:&quot;SEWB&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!jtXK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27fb93c0-27bf-440d-944e-0f5c2a98b348_1200x1200.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div><hr></div><p>Thanks for reading! Hope you hang around for more Software Engineering posts.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.sewbs.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.sewbs.dev/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[ICallOn v2]]></title><description><![CDATA[Part 1: UI refresh and technical enhancements]]></description><link>https://blog.sewbs.dev/p/icallon-v2</link><guid isPermaLink="false">https://blog.sewbs.dev/p/icallon-v2</guid><dc:creator><![CDATA[Wolemercy]]></dc:creator><pubDate>Mon, 09 Feb 2026 15:02:52 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a6b13e8f-5080-4c54-905a-f8cb883b21e6_1200x630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I released the first iteration of <strong><a href="https://icallon.xyz/">ICallOn</a></strong> in December 2024. It was a throwback to a game I and many others enjoyed playing as kids, and I thought it'd be fun to experience it digitally as a real-time multiplayer game.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://x.com/i/status/1869875271702069759" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kLzg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ccd2a8-1e0a-42a5-83e8-89f168bd7978_540x607.png 424w, https://substackcdn.com/image/fetch/$s_!kLzg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ccd2a8-1e0a-42a5-83e8-89f168bd7978_540x607.png 848w, https://substackcdn.com/image/fetch/$s_!kLzg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ccd2a8-1e0a-42a5-83e8-89f168bd7978_540x607.png 1272w, https://substackcdn.com/image/fetch/$s_!kLzg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ccd2a8-1e0a-42a5-83e8-89f168bd7978_540x607.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kLzg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ccd2a8-1e0a-42a5-83e8-89f168bd7978_540x607.png" width="540" height="607" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f2ccd2a8-1e0a-42a5-83e8-89f168bd7978_540x607.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:607,&quot;width&quot;:540,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:164771,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://x.com/i/status/1869875271702069759&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.sewbs.dev/i/187375749?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ccd2a8-1e0a-42a5-83e8-89f168bd7978_540x607.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kLzg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ccd2a8-1e0a-42a5-83e8-89f168bd7978_540x607.png 424w, https://substackcdn.com/image/fetch/$s_!kLzg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ccd2a8-1e0a-42a5-83e8-89f168bd7978_540x607.png 848w, https://substackcdn.com/image/fetch/$s_!kLzg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ccd2a8-1e0a-42a5-83e8-89f168bd7978_540x607.png 1272w, https://substackcdn.com/image/fetch/$s_!kLzg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ccd2a8-1e0a-42a5-83e8-89f168bd7978_540x607.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://x.com/i/status/1869875271702069759">ICallOn release - Dec 19, 2024</a></figcaption></figure></div><p>In building it, I wasn&#8217;t trying to reinvent the game. As such, it was important for me to preserve its core parts which were the way the &#8220;caller&#8221; rotates each round, and the small power they hold to end a round whenever they choose.</p><p>At launch, ICallOn supported the following:</p><ol><li><p>Creating and joining game sessions</p><ol><li><p>Custom word categories</p></li><li><p>30/60/90 second timers</p></li></ol></li><li><p>The core gameplay loop</p><ol><li><p>Selecting a letter each round</p></li><li><p>Ending the round early as the caller</p></li><li><p>Grading another player&#8217;s answers at the end of each round</p></li></ol></li><li><p>Lobby and gameplay notifications</p></li><li><p>Game results and game history</p></li></ol><p>To pull off the real-time multiplayer, I leaned heavily on WebSockets. It&#8217;s a protocol I&#8217;d read about, but never properly used in a project before. The tech stack (and deployment) looked like this:</p><ol><li><p><strong>Next.js</strong> &#8212; UI &amp; API (Vercel)</p></li><li><p><strong>Postgres</strong> &#8212; Database (Supabase)</p></li><li><p><strong>Soketi</strong> &#8212; WebSockets (DigitalOcean)</p></li></ol><p>In terms of cost, it was refreshingly simple. I only had to pay for the Digital Ocean droplet and the domain (<a href="https://icallon.xyz/">icallon.xyz</a>).</p><p>A few months later in May 2025, I hosted a Twitter Space where we (my friends and I) played ICallOn live. The game supports up to 10 players per session, so I had what I thought was a brilliant idea: I&#8217;d build a spectator view. That way, people who weren&#8217;t playing could still watch the game unfold in real time.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://x.com/wolemercy/status/1917972213841474041?s=20" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!G5id!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba268f6-dac9-4a14-b8e7-f02283fbe845_1080x339.png 424w, https://substackcdn.com/image/fetch/$s_!G5id!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba268f6-dac9-4a14-b8e7-f02283fbe845_1080x339.png 848w, https://substackcdn.com/image/fetch/$s_!G5id!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba268f6-dac9-4a14-b8e7-f02283fbe845_1080x339.png 1272w, https://substackcdn.com/image/fetch/$s_!G5id!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba268f6-dac9-4a14-b8e7-f02283fbe845_1080x339.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!G5id!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba268f6-dac9-4a14-b8e7-f02283fbe845_1080x339.png" width="1080" height="339" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6ba268f6-dac9-4a14-b8e7-f02283fbe845_1080x339.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:339,&quot;width&quot;:1080,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:96050,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://x.com/wolemercy/status/1917972213841474041?s=20&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.sewbs.dev/i/187375749?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba268f6-dac9-4a14-b8e7-f02283fbe845_1080x339.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!G5id!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba268f6-dac9-4a14-b8e7-f02283fbe845_1080x339.png 424w, https://substackcdn.com/image/fetch/$s_!G5id!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba268f6-dac9-4a14-b8e7-f02283fbe845_1080x339.png 848w, https://substackcdn.com/image/fetch/$s_!G5id!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba268f6-dac9-4a14-b8e7-f02283fbe845_1080x339.png 1272w, https://substackcdn.com/image/fetch/$s_!G5id!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba268f6-dac9-4a14-b8e7-f02283fbe845_1080x339.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://x.com/wolemercy/status/1917972213841474041?s=20">Spectator view - May 25, 2025</a></figcaption></figure></div><p>When the space actually happened, we were fewer than 10 people. We didn&#8217;t need spectators at all, and the feature was altogether unnecessary. I still think it was a brilliant idea though xD.</p><p>After that, I mostly left ICallOn alone. Every now and then, I&#8217;d ship a small improvement or fix a bug, and the game worked fine.</p><p>Over time, however, the app started to feel like a bare minimum game, mostly because it relied heavily on card components and stacked layouts. For a while, I&#8217;d been thinking about a proper upgrade. I wanted a second iteration of ICallOn that would include:</p><ol><li><p>A major UI/UX refresh</p></li><li><p>Technical enhancements</p></li><li><p>A Daily Challenge mode</p></li></ol><p>I finally got around to implementing these in the last month, and that&#8217;s what makes up <strong>ICallOn v2</strong>, which you can play here: <strong><a href="https://icallon.xyz/">icallon.xyz</a></strong>.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.sewbs.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.sewbs.dev/subscribe?"><span>Subscribe now</span></a></p><h2>UI/UX Refresh</h2><p>ICallOn v1 used Mantine&#8217;s default components to a fault. Back then, the goal was to ship something that worked, so I reached for the quickest building blocks available: cards. I whipped out a card for virtually everything. A card for this, a card for that, a card for you, and a card for me.</p><p>For v2, the guiding philosophy was to replace the generic UI patterns with more game-native ones. Score cards became leaderboard rows with rank badges. Progress bars became little avatar grids. I added more depth with shadows, improved spacing, and sprinkled in a few animations so the app felt less like paperwork.</p><p>Some highlights:</p><ul><li><p><strong>Tabbed gameplay navigation:</strong> Instead of stacking Game, Players, Rounds, and Info into one long screen, I moved them into tabs. There&#8217;s less clutter, and players can jump to whatever they need in one click.</p></li><li><p><strong>Custom score buttons:</strong> These went through several iterations. First it was a number input with increment(+) and decrement(-) controls. Then a Select with 0, 5, and 10. Now, I&#8217;ve made them buttons, and scoring is done with a click.</p></li><li><p><strong>Better loading experiences:</strong> In flows like creating a game or starting one, players now see a loading animation instead of wondering whether the app froze.</p></li><li><p><strong>Game results:</strong> Players always saw confetti when viewing Game Results and now, I&#8217;ve added a podium for the top 3 players in that game for a bit more drama.</p></li></ul><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9432511e-0f7f-4016-b878-0de988daca99_1080x1958.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d70afb0b-1c0c-41b4-9f5a-8053d13b6b94_1080x2205.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cbfe69d2-3a7f-46f5-b552-1536088b8224_1080x1236.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c696c53b-d684-4c71-b679-b2da622f4280_1080x1973.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/df9ac1de-844c-4279-a13d-0407a852ff76_1080x1964.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a9fd895d-42b6-428c-9a38-934b99eeb271_1080x1343.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a318bc92-5fe1-40f2-88ea-399fc82d0dea_1079x1353.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b7e641d7-892b-4bbc-aaa0-ac5b2ef5685f_1080x2189.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fbe4bbb3-d0bc-4294-9f6e-ece2ae31f003_1080x2202.png&quot;}],&quot;caption&quot;:&quot;ICallOn Gameplay Screens&quot;,&quot;alt&quot;:&quot;ICallOn Gameplay Screens&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/95dae3b6-2fce-4f48-8d8c-83f912c8f451_1456x1454.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p>I hadn&#8217;t originally planned to, but I also added some sound effects. You&#8217;ll hear them play on button clicks or after certain gameplay state changes (e.g. when a round ends or when time is almost up). You can toggle sound in the header, just beside the theme icon.</p><h2>Technical Enhancements</h2><p>One of the reasons I delayed working on v2 was that I wasn&#8217;t looking forward to cleaning up some of the bad code I&#8217;d written and correcting a few poor architecture decisions.</p><p>I group these issues into three buckets:</p><ol><li><p>Ghost Events</p></li><li><p>God Classes</p></li><li><p>State Management</p></li></ol><h3>Ghost Events</h3><p>In v1, the WebSocket events were emitted inside database transactions. When a game is started, for example, the database is updated. As part of that transaction, the <code>game:started</code> event is also emitted. This sounds fine, but if the transaction rolls back after the event has fired, players get notified even though the game state did not change.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-qDG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08e1ecb2-d39f-4a19-ad55-36e4c0cb8d4b_1514x744.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-qDG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08e1ecb2-d39f-4a19-ad55-36e4c0cb8d4b_1514x744.png 424w, https://substackcdn.com/image/fetch/$s_!-qDG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08e1ecb2-d39f-4a19-ad55-36e4c0cb8d4b_1514x744.png 848w, https://substackcdn.com/image/fetch/$s_!-qDG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08e1ecb2-d39f-4a19-ad55-36e4c0cb8d4b_1514x744.png 1272w, https://substackcdn.com/image/fetch/$s_!-qDG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08e1ecb2-d39f-4a19-ad55-36e4c0cb8d4b_1514x744.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-qDG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08e1ecb2-d39f-4a19-ad55-36e4c0cb8d4b_1514x744.png" width="1456" height="715" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/08e1ecb2-d39f-4a19-ad55-36e4c0cb8d4b_1514x744.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:715,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:219068,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.sewbs.dev/i/187375749?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08e1ecb2-d39f-4a19-ad55-36e4c0cb8d4b_1514x744.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-qDG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08e1ecb2-d39f-4a19-ad55-36e4c0cb8d4b_1514x744.png 424w, https://substackcdn.com/image/fetch/$s_!-qDG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08e1ecb2-d39f-4a19-ad55-36e4c0cb8d4b_1514x744.png 848w, https://substackcdn.com/image/fetch/$s_!-qDG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08e1ecb2-d39f-4a19-ad55-36e4c0cb8d4b_1514x744.png 1272w, https://substackcdn.com/image/fetch/$s_!-qDG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08e1ecb2-d39f-4a19-ad55-36e4c0cb8d4b_1514x744.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Events are emitted in the transaction</figcaption></figure></div><p>To address this, I introduced the Event Outbox Pattern. Instead of emitting events inside transactions, I write them to an outbox table in the same transaction as the state change. If the transaction fails, no event record exists. If it succeeds, the event is guaranteed to be recorded. It is then emitted over the WebSocket.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MlSg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9547ac6-9cf8-4159-89af-e6300f92d86a_1852x856.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MlSg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9547ac6-9cf8-4159-89af-e6300f92d86a_1852x856.png 424w, https://substackcdn.com/image/fetch/$s_!MlSg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9547ac6-9cf8-4159-89af-e6300f92d86a_1852x856.png 848w, https://substackcdn.com/image/fetch/$s_!MlSg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9547ac6-9cf8-4159-89af-e6300f92d86a_1852x856.png 1272w, https://substackcdn.com/image/fetch/$s_!MlSg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9547ac6-9cf8-4159-89af-e6300f92d86a_1852x856.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MlSg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9547ac6-9cf8-4159-89af-e6300f92d86a_1852x856.png" width="1456" height="673" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b9547ac6-9cf8-4159-89af-e6300f92d86a_1852x856.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:673,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:262848,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.sewbs.dev/i/187375749?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9547ac6-9cf8-4159-89af-e6300f92d86a_1852x856.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MlSg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9547ac6-9cf8-4159-89af-e6300f92d86a_1852x856.png 424w, https://substackcdn.com/image/fetch/$s_!MlSg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9547ac6-9cf8-4159-89af-e6300f92d86a_1852x856.png 848w, https://substackcdn.com/image/fetch/$s_!MlSg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9547ac6-9cf8-4159-89af-e6300f92d86a_1852x856.png 1272w, https://substackcdn.com/image/fetch/$s_!MlSg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9547ac6-9cf8-4159-89af-e6300f92d86a_1852x856.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Events are persisted to an Outbox Table, then emitted after the transaction succeeds</figcaption></figure></div><h3>God Classes</h3><p>The dread I had for the v2 upgrade was mostly due to v1&#8217;s poor maintainability. On the backend, I had a <code>GameService</code> with ~2000 lines of code that handled everything: sessions, rounds, scoring, and event emission. God classes like this make debugging difficult. Reading the code feels like a chore, and you want to get out as soon as you&#8217;re in. It wasn&#8217;t <a href="https://blog.sewbs.dev/p/towards-carefulness">code that was written by someone who cared</a>.</p><p>In v2, the responsibilities of <code>GameService</code> were split into five focused services, the largest having just over 500 LOC. The original <code>GameService</code> became a thin facade (~200 loc) that delegates to these services.</p><p>Similarly, on the frontend, v1 had a <code>GamePage</code> component with several responsibilities, including WebSocket event handling, notifications, server actions, and rendering. I also decomposed it into more focused components, and the codebase looks much better.</p><h3>State Management</h3><p>In v1, I relied heavily on Next.js <code>router.refresh()</code> to retrieve the latest game state from the database whenever an event was consumed. I didn&#8217;t need to make this round trip. Silly me. The WebSocket events already contain the data the client needed to display, and I made the most of this in v2, reducing the round-trips to the server.</p><p>Additionally, state management on the client was a mess in v1. It was the classic case of prop drilling, which made the application buggy. Refetching data from the DB helped address these bugs, but it wasn&#8217;t the most efficient solution. In v2, I introduced a <a href="https://zustand.docs.pmnd.rs/getting-started/introduction">Zustand</a> store for managing the game state. The server provides the initial state when the page is loaded, and after that, the client owns the state.</p><h2>Wrapping Up</h2><p>That was the bulk of the v2 upgrade, which wasn&#8217;t really about adding features. It was about making ICallOn feel like something you&#8217;d want to return to, and making the internals stable enough that I could build on top of them without fear.</p><p>In Part 2, I&#8217;ll talk about the new Daily Challenge mode, and the two questions it forced me to answer: how do you validate submissions without a human judge, and how do you score duplicates fairly when everyone is playing the same game?</p><p>In the meantime, you can check out the game <a href="https://icallon.xyz">here</a> or tweet at <a href="https://x.com/icallon_xyz">icallon_xyz</a>.</p><p>- Wolemercy</p><div><hr></div><p>Thanks for reading! Hope you hang around for more Software Engineering posts.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.sewbs.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.sewbs.dev/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[Hetzner Said No]]></title><description><![CDATA[I (almost) tried self-hosting]]></description><link>https://blog.sewbs.dev/p/hetzner-said-no</link><guid isPermaLink="false">https://blog.sewbs.dev/p/hetzner-said-no</guid><dc:creator><![CDATA[Wolemercy]]></dc:creator><pubDate>Tue, 13 Jan 2026 20:00:42 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1622926625330-c814dd2e21b4?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0Mnx8bm8lMjBmbHlpbmd8ZW58MHx8fHwxNzY4MzMxNzAzfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1622926625330-c814dd2e21b4?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0Mnx8bm8lMjBmbHlpbmd8ZW58MHx8fHwxNzY4MzMxNzAzfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1622926625330-c814dd2e21b4?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0Mnx8bm8lMjBmbHlpbmd8ZW58MHx8fHwxNzY4MzMxNzAzfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1622926625330-c814dd2e21b4?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0Mnx8bm8lMjBmbHlpbmd8ZW58MHx8fHwxNzY4MzMxNzAzfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1622926625330-c814dd2e21b4?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0Mnx8bm8lMjBmbHlpbmd8ZW58MHx8fHwxNzY4MzMxNzAzfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1622926625330-c814dd2e21b4?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0Mnx8bm8lMjBmbHlpbmd8ZW58MHx8fHwxNzY4MzMxNzAzfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1622926625330-c814dd2e21b4?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0Mnx8bm8lMjBmbHlpbmd8ZW58MHx8fHwxNzY4MzMxNzAzfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="5472" height="3648" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1622926625330-c814dd2e21b4?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0Mnx8bm8lMjBmbHlpbmd8ZW58MHx8fHwxNzY4MzMxNzAzfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3648,&quot;width&quot;:5472,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;person in parachute under cloudy sky during daytime&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="person in parachute under cloudy sky during daytime" title="person in parachute under cloudy sky during daytime" srcset="https://images.unsplash.com/photo-1622926625330-c814dd2e21b4?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0Mnx8bm8lMjBmbHlpbmd8ZW58MHx8fHwxNzY4MzMxNzAzfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1622926625330-c814dd2e21b4?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0Mnx8bm8lMjBmbHlpbmd8ZW58MHx8fHwxNzY4MzMxNzAzfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1622926625330-c814dd2e21b4?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0Mnx8bm8lMjBmbHlpbmd8ZW58MHx8fHwxNzY4MzMxNzAzfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1622926625330-c814dd2e21b4?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0Mnx8bm8lMjBmbHlpbmd8ZW58MHx8fHwxNzY4MzMxNzAzfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@reskp">Jametlene Reskp</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>One Friday night in December, I sat at my desk ready to deploy the app I&#8217;d spent the week building. The stack was simple: Next.js for the UI and API, and a Postgres database. Given that it wasn&#8217;t intended to be a public-facing app, I saw it as my introduction to self-hosting applications. It&#8217;s what the cool kids are doing, after all. Seriously though, there is something deeply satisfying about controlling every aspect of your system except perhaps, the hardware, if like me your intention is to use a Virtual Private Server.</p><p>I had a Sunday deadline, and the plan was to spend the weekend in a wrestling match with the deployment. Yes, a wrestling match with some fun, drama, hair-pulling, timeouts, more hair-pulling, and my inevitable conquest.</p><div class="pullquote"><p>Do you think bald people ever use the expression, &#8220;pull my hair out&#8221;? I think they deserve more kindness if only for that.</p></div><p>In the course of the week, I&#8217;d also researched the best (read: affordable and reliable, especially affordable) VPS to use, as well as how to configure and manage self-hosted apps and services with minimal effort. I came upon the <a href="https://www.hetzner.com/">Hetzner</a> + <a href="https://coolify.io/">Coolify</a> combo, which you may be familiar with if you&#8217;re into the business of hosting yourself. Hetzner&#8217;s servers are reasonably priced and their customer support is good, while Coolify simplifies the management of your deployments (and it can also be self hosted).</p><p>Cool. This was the way.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.sewbs.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.sewbs.dev/subscribe?"><span>Subscribe now</span></a></p><p>Until it wasn&#8217;t.</p><p>I created an account on Hetzner, but it needed to be verified. That&#8217;s not a train smash, I thought. I just needed to upload my ID document.</p><p>&#8220;Ah, these are stiff KYC requirements, Mr. H,&#8221; I all but said to Hetzner.</p><p>I didn&#8217;t have much of a choice. I uploaded it, only to be told that verification requests are processed only during normal business hours. This was around 21:00 UTC on Friday. I would have to wait until the following Monday to get any form of feedback. Bummer.</p><p>I searched for alternative VPS providers, but I wasn&#8217;t convinced by them. Frustrated, I ditched the plan entirely and pivoted to <a href="https://railway.com/">Railway</a> (which is its own story). It wasn&#8217;t much of a wrestling match in the end. I&#8217;d lost before I entered the ring. I&#8217;d lost to a KYC check.</p><p>The Hetzner story doesn&#8217;t end there. When Monday finally arrived, my account was deactivated because &#8221;Mr. H&#8221; had some &#8221;concerns&#8221; with the information I provided. I&#8217;m still not entirely sure what that meant. </p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/261f7ac4-c9b9-46c2-b1cf-7fcca3cc8770_939x1156.png&quot;}],&quot;caption&quot;:&quot;&#8220;This is as far as you go,&#8221; said Mr. H&quot;,&quot;alt&quot;:&quot;&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/261f7ac4-c9b9-46c2-b1cf-7fcca3cc8770_939x1156.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p>As it turns out, my situation wasn&#8217;t <a href="https://www.reddit.com/r/hetzner/comments/176vuw9/hetzner_account_rejection_and_recovery_any/">unique</a>. I reached out to them on Reddit, and they guided me through a re-verification process. My account was eventually verified a week to the day I signed up. Yeah, it took me seven days to get access to a VPS provider. And I&#8217;m not sure how I feel about it.</p><p>I hadn&#8217;t experienced such before, where I couldn&#8217;t simply sign up, pay for a service (SaaS/PaaS/Iaas), and use it immediately. On some level, I understand the need for KYC, especially to prevent servers from being used for malicious purposes, as this <a href="https://www.reddit.com/r/hetzner/comments/14fvotu/comment/jp2dtb1/?utm_source=share&amp;utm_medium=mweb3x&amp;utm_name=mweb3xcss&amp;utm_term=1&amp;utm_content=share_button">post</a> hints. Yet, the whole process seemed like an overkill.</p><p>I&#8217;m not sure how they determine which accounts require &#8221;extra&#8221; verification, or why a valid ID would be rejected so dismissively. It also didn&#8217;t help that the rejection seemed final. I mean, look at that mail. It reads like I was being served. I also had to do some digging to find a way to contact them, though, to their credit, they are responsive on Reddit.</p><p>Hetzner&#8217;s onboarding model likely works for them most of the time. But for <em>decent</em> users, waiting a week to be fully onboarded is a terrible experience. It&#8217;s a good thing I had an alternative, and so I got my deployment over the line. Perhaps a lesson here is that if you have any service you want to try over the weekend, don&#8217;t wait until Friday night to sign up.</p><p>Ultimately my foray into self-hosting was delayed&#8212;only delayed. I will be trying (read: wrestling) again soon. And there will be no KYC checks to stop me.</p><p>- Wolemercy</p><div><hr></div><p>Thanks for reading! Hope you hang around for more Software Engineering posts.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.sewbs.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.sewbs.dev/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[Towards Carefulness]]></title><description><![CDATA[We're only as careful as we are competent]]></description><link>https://blog.sewbs.dev/p/towards-carefulness</link><guid isPermaLink="false">https://blog.sewbs.dev/p/towards-carefulness</guid><dc:creator><![CDATA[Wolemercy]]></dc:creator><pubDate>Mon, 03 Nov 2025 18:20:28 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!wDSx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabb2f07-4eb2-4624-9d19-facee38dddcc_1536x1536.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.sewbs.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.sewbs.dev/subscribe?"><span>Subscribe now</span></a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wDSx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabb2f07-4eb2-4624-9d19-facee38dddcc_1536x1536.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wDSx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabb2f07-4eb2-4624-9d19-facee38dddcc_1536x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!wDSx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabb2f07-4eb2-4624-9d19-facee38dddcc_1536x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!wDSx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabb2f07-4eb2-4624-9d19-facee38dddcc_1536x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!wDSx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabb2f07-4eb2-4624-9d19-facee38dddcc_1536x1536.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wDSx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabb2f07-4eb2-4624-9d19-facee38dddcc_1536x1536.jpeg" width="1536" height="1536" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dabb2f07-4eb2-4624-9d19-facee38dddcc_1536x1536.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1536,&quot;width&quot;:1536,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:229978,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://sewb.substack.com/i/177909601?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bfd55a3-fd2b-4e50-a540-091143f5e59c_1536x2048.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wDSx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabb2f07-4eb2-4624-9d19-facee38dddcc_1536x1536.jpeg 424w, https://substackcdn.com/image/fetch/$s_!wDSx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabb2f07-4eb2-4624-9d19-facee38dddcc_1536x1536.jpeg 848w, https://substackcdn.com/image/fetch/$s_!wDSx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabb2f07-4eb2-4624-9d19-facee38dddcc_1536x1536.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!wDSx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdabb2f07-4eb2-4624-9d19-facee38dddcc_1536x1536.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Saucers (un)sorted - <a href="https://x.com/brookeab/status/1981512403990040933">Twitter</a></figcaption></figure></div><p>In the first chapter of Robert C. Martin&#8217;s <em>Clean Code&#8212;</em>a book about writing software that is easy to read, understand, and maintain<em>, </em>he takes a few stabs at the definition of clean code through the lens of other engineers. Each engineer described useful properties of clean code using words like elegant, efficient, simple, direct, and readable. However, the definition I found most striking was Michael Feathers&#8217;.</p><blockquote><p>I could list all of the qualities that I notice in clean code, but there is one overarching quality that leads to all of them. Clean code always looks like it was written by someone who cares. There is nothing obvious that you can do to make it better. All of those things were thought about by the code&#8217;s author, and if you try to imagine improvements, you&#8217;re led back to where you are, sitting in appreciation of the code someone left for you&#8212;code left by someone who cares deeply about the craft - <strong>Michael Feathers</strong></p></blockquote><p>This idea stayed with me. <em>Care</em> is the keyword here:<em> </em>&#8220;Clean Code always looks like it was written by someone who cares.&#8221; The book&#8217;s essence, then, is a lesson in caring for code. And although it is specifically about code, the lesson is transferrable to other aspects of life. I found myself asking how much care I put into the world: do I take care to do things? And you&#8212;are you full of care in the big things: your relationships, career, and health? Are you equally full of care in the little things&#8212;your conversations and routines? When you arrange saucers in the cupboard, do you stack them in order? When you write code and make a commit, is your commit message descriptive? How much care do you apply to things?</p><p>Your answer, like mine, is probably &#8220;not enough.&#8221; Every single thing we do, whether painfully mundane or remarkably grand, presents us with an opportunity to be careful in execution. Yet, as engineers, artisans, friends, parents, vendors, designers, and whatnot, we often fall short of carefulness. One common reason for this is the need to be fast, to finish quickly. This could very well be the case, but more often than not, it simply betrays a lack of ability&#8212;a skill issue, one might say. </p><p>Carefulness begins with intent and ends with competence. No amount of time will be enough if you do not intend to stack saucers in order, or if you do not know (and refuse to learn) how to do so. Yet intent and competence, even when joined, do not equal perfection; they merely make it possible. Perhaps this is where many of us stumble&#8212;we mistake competence for perfection, as though to care deeply is to get everything right. That is false. Carefulness doesn&#8217;t ask for perfection but for attention. We can tell when someone is giving us attention and when they&#8217;re not. In the same vein, we can tell when a piece of work has been suffused with attention and when it&#8217;s been starved of it. Perfection has little to do with it.</p><p>We should apply more care to the things we do. Since caring involves both intent and competence, we could learn to be better at it. How we care for people differs from how we care for code, which is different from how we might care for a spaceship, and so there is room for learning and growing. Ultimately, in our march towards cleanliness&#8212;towards carefulness&#8212;in our code, paintings, writings, relationships, designs, and anything at all&#8212;we go only as far as our competence allows us.</p><p>We can always apply more care to the things we do.  It starts with intent and ends with competence. We march towards cleanliness&#8212;towards carefulness&#8212;of code, paintings, relationships, designs, by growing and learning.</p><p>Thanks for reading!</p>]]></content:encoded></item></channel></rss>