<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nandawula Kabali-Kagwa</title>
    <description>The latest articles on DEV Community by Nandawula Kabali-Kagwa (@nanda-regine).</description>
    <link>https://dev.to/nanda-regine</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3706972%2F43b6285f-dd21-440c-a25a-75ddf1e0aa54.jpg</url>
      <title>DEV Community: Nandawula Kabali-Kagwa</title>
      <link>https://dev.to/nanda-regine</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nanda-regine"/>
    <language>en</language>
    <item>
      <title>Building for Real Realities: Why We Eliminated Em Dashes and Standardized Equal Thirds</title>
      <dc:creator>Nandawula Kabali-Kagwa</dc:creator>
      <pubDate>Thu, 01 Oct 2026 13:02:32 +0000</pubDate>
      <link>https://dev.to/nanda-regine/building-for-real-realities-why-we-eliminated-em-dashes-and-standardized-equal-thirds-15d9</link>
      <guid>https://dev.to/nanda-regine/building-for-real-realities-why-we-eliminated-em-dashes-and-standardized-equal-thirds-15d9</guid>
      <description>&lt;p&gt;When you are writing code that represents a physical and digital studio from East London, South Africa, aesthetic choices quickly become engineering constraints. This week, our main repository (&lt;code&gt;CreativelyNanda.co.za&lt;/code&gt;) underwent a massive structural refinement. We didn't add a flashy new database or migrate to an exotic edge runtime. Instead, we focused on the bones: equal thirds, strict color tokens, print stylesheets, and brutal typographic minimalism.&lt;/p&gt;

&lt;p&gt;Let’s talk about the stack decisions behind this week's commits.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Geometry of Equal Thirds
&lt;/h3&gt;

&lt;p&gt;Layout drift is the silent killer of frontend polish. Across our shop, Notion integration, business archives, and the Poem Wall, we enforced an equal-thirds layout structure. This wasn't just a whim; it was a deliberate move to create predictable mental models for users navigating heavy literary and commercial content simultaneously. We extended this rhythm to the legal pages, roots, testimonials, and imprints, ensuring that whether a user is reading a poem or buying a physical volume, the container feels anchored.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Styling for Paper: When CSS Meets the Physical World
&lt;/h3&gt;

&lt;p&gt;One of the most challenging frontend tasks this week was making the digital design system translate seamlessly to physical print. We configured the remaining navy pages to print accurately on paper, ensuring that coloured buttons retain stark white labels and Forge masthead pockets align precisely with the words beneath them. Stage reel captions were locked directly under their frames.&lt;/p&gt;

&lt;p&gt;In a region where load shedding is a structural reality, thinking about how a web page behaves when printed or saved offline isn't a retro novelty, it's robust engineering. It’s the same philosophy that drives VarsityOS to remain fully functional offline for students navigating NSFAS budgets.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Typography as an API
&lt;/h3&gt;

&lt;p&gt;We instituted a strict style rule: &lt;strong&gt;no em dashes anywhere on the site.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;From a parser and readability perspective, em dashes often act as a crutch for run-on architectural logic in copywriting. By stripping them out, we forced our prose to rely on hinge words, pauses, and clean pulse lines. We updated our X-ray poetry modes and garden layouts to respect this newfound restraint.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The Gotchas: JSX Comments and Selector Escaping
&lt;/h3&gt;

&lt;p&gt;No build log is complete without a bug post-mortem. Early in the week, a stray comment hidden inside a JSX conditional broke our product page build. It's a humbling reminder that even with mature tooling, JSX parsing edge cases will catch you off guard if your components mix declarative markup with raw scripting comments.&lt;/p&gt;

&lt;p&gt;We also escaped our CSS remap selectors (&lt;code&gt;fix(css): escape the remap selectors; journal §25&lt;/code&gt;), cleaning up specificity wars before they could compound. &lt;/p&gt;

&lt;p&gt;Building apps like AdminOS (our six-agent SME back office) and the K53 Drill Master (rigorously audited against official DLTC docs this week) teaches you one unchanging truth: discipline in the small files saves your life in the large ones. Clean up your tokens. Check your build logs. And let your code breathe.&lt;/p&gt;

</description>
      <category>nandapersonal</category>
    </item>
    <item>
      <title>VarsityOS: An AI Study OS Built for African Students</title>
      <dc:creator>Nandawula Kabali-Kagwa</dc:creator>
      <pubDate>Sun, 27 Sep 2026 16:03:11 +0000</pubDate>
      <link>https://dev.to/nanda-regine/varsityos-an-ai-study-os-built-for-african-students-3jcm</link>
      <guid>https://dev.to/nanda-regine/varsityos-an-ai-study-os-built-for-african-students-3jcm</guid>
      <description>&lt;h1&gt;
  
  
  VarsityOS: An AI Study OS Built for African Students
&lt;/h1&gt;

&lt;p&gt;South African students face study conditions Silicon Valley tools were never designed for. Load shedding, NSFAS deadlines, overcrowded libraries. VarsityOS was built to meet them where they are.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI-powered study sessions&lt;/strong&gt; that adapt to your energy levels and available time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline-first architecture&lt;/strong&gt; so load shedding never kills a session mid-flow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NSFAS deadline tracking&lt;/strong&gt; with intelligent reminders tied to the academic calendar&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Past paper engine&lt;/strong&gt; with 26 drill modes and step-by-step explanations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The stack and one interesting decision
&lt;/h2&gt;

&lt;p&gt;Next.js 14 App Router, Supabase, and Claude AI (routed through a Groq adapter on the free tier). Every AI call routes through Inngest workers rather than route handlers, so a slow generation never times out on Vercel and retries happen automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is next
&lt;/h2&gt;

&lt;p&gt;Collaborative study rooms and a curriculum-linked flashcard engine ship in October.&lt;/p&gt;

&lt;p&gt;Try it free at varsityos.co.za&lt;/p&gt;

</description>
      <category>creativelynanda</category>
    </item>
    <item>
      <title>Building an AI-Powered Engineering OS: Cron Jobs, ADRs, and Autonomous Workflows</title>
      <dc:creator>Nandawula Kabali-Kagwa</dc:creator>
      <pubDate>Sun, 27 Sep 2026 15:31:33 +0000</pubDate>
      <link>https://dev.to/nanda-regine/building-an-ai-powered-engineering-os-cron-jobs-adrs-and-autonomous-workflows-4p3g</link>
      <guid>https://dev.to/nanda-regine/building-an-ai-powered-engineering-os-cron-jobs-adrs-and-autonomous-workflows-4p3g</guid>
      <description>&lt;p&gt;There is a distinct moment in software development when you realize you are spending more time maintaining your workflow than writing code. As a self-taught engineer managing 8 production apps and a registered studio, I hit that wall hard. When you're building for African realities from East London, South Africa—where reliability and efficiency are non-negotiable—manual project management wastes precious cycles.&lt;/p&gt;

&lt;p&gt;So, I decided to let JarvisOS manage itself.&lt;/p&gt;

&lt;p&gt;Over the past week, I engineered a suite of autonomous background systems and architectural pipelines inside JarvisOS. Let's look at how automation can replace administrative fatigue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating the Paper Trail: Commit to ADRs
&lt;/h2&gt;

&lt;p&gt;Documentation is where good software goes to die. Everyone intends to write comprehensive Architecture Decision Records, but life moves fast. To fix this, I built an automated pipeline that intercepts git pushes, analyzes the diff, and auto-suggests structured ADRs.&lt;/p&gt;

&lt;p&gt;Instead of hunting through months of git logs to figure out &lt;em&gt;why&lt;/em&gt; a database schema was refactored, the system documents the decision context at the exact moment the code is shipped. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Autonomous Engineering Team
&lt;/h2&gt;

&lt;p&gt;When you don't have a large engineering team, you build synthetic guardrails. I set up five specialized cron jobs that act as automated colleagues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Cost Watchdog:&lt;/strong&gt; Runs every 2 hours, monitoring AI token expenditure to catch runaway recursive loops before they drain API budgets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Impact Detector:&lt;/strong&gt; Evaluates system health every 30 minutes following any production deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Decision Aging Monitor:&lt;/strong&gt; Daily alerts on technical debt and overdue architectural decisions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Dependency Brief:&lt;/strong&gt; A weekly pulse check on stale packages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Friday Retrospective:&lt;/strong&gt; Automatically compiles the week's output into a clean markdown retro every Friday at 17:00 SAST.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Content Systems at Scale: MirembeApp
&lt;/h2&gt;

&lt;p&gt;Automation isn't just for backend engineering; it applies to content delivery too. This week, MirembeApp migrated to a fully database-driven article architecture, seeding all 22 foundational pieces with a polished migration script. &lt;/p&gt;

&lt;p&gt;We paired this with robust ISR (Incremental Static Regeneration) and an hourly revalidating sitemap with article priorities set to 0.8/weekly. By ensuring analytics (GA4 with Consent Mode v2) load unconditionally and cleanly, we maintain strict visibility while respecting user privacy.&lt;/p&gt;

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

&lt;p&gt;Engineering is ultimately about leverage. By turning administrative overhead into automated background pipelines, we free up cognitive bandwidth to focus on what matters: shipping resilient software that serves real people.&lt;/p&gt;

</description>
      <category>nandapersonal</category>
    </item>
    <item>
      <title>Article about how I actually build for Africa and why</title>
      <dc:creator>Nandawula Kabali-Kagwa</dc:creator>
      <pubDate>Sat, 26 Sep 2026 18:51:09 +0000</pubDate>
      <link>https://dev.to/nanda-regine/article-about-how-i-actually-build-for-africa-and-why-4oec</link>
      <guid>https://dev.to/nanda-regine/article-about-how-i-actually-build-for-africa-and-why-4oec</guid>
      <description>&lt;p&gt;TITLE: How I Build Software That Survives Load-Shedding and Spotty Connectivity in South Africa&lt;/p&gt;

&lt;p&gt;If you build software from a comfortable office with a gigabit fibre connection and uninterrupted grid power, your assumptions about network resilience are almost certainly broken.&lt;/p&gt;

&lt;p&gt;Over the last two years building apps like AdminOS and VarsityOS from East London, South Africa, I’ve had to throw away standard web development playbooks. When Eskom stage 4 load-shedding hits mid-afternoon, your fancy cloud-native microservices architecture doesn't matter if the local cellphone tower drops offline ten minutes later.&lt;/p&gt;

&lt;p&gt;Here is the exact technical stack and architectural pattern I use to build offline-first, resilient web applications that don't choke when the power goes out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Local-First Reality
&lt;/h2&gt;

&lt;p&gt;In San Francisco, local-first is a philosophical choice about data ownership. In the Eastern Cape, local-first is survival. If a spaza shop owner or a university student in Braamfontein loses their internet connection for three hours, they cannot afford an infinite loading spinner.&lt;/p&gt;

&lt;p&gt;Our architecture rests on three pillars:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SQLite at the edge (via Turso or local IndexedDB adapters).&lt;/li&gt;
&lt;li&gt;Optimistic UI updates with zero-latency local state mutation.&lt;/li&gt;
&lt;li&gt;Background sync queues that retry silently when carrier signal returns.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s look at how we handle local mutations without losing transactional integrity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;turso&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SyncQueueItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INSERT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UPDATE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DELETE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useOfflineSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isOnline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsOnline&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onLine&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleOnline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="nf"&gt;setIsOnline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;flushQueue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleOffline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setIsOnline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;online&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleOnline&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;offline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleOffline&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;online&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleOnline&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;offline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleOffline&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mutateLocally&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;localId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Write immediately to local IndexedDB/SQLite cache&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`INSERT INTO &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (id, data, synced) VALUES (?, ?, 0)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;localId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isOnline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;syncToServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;queueForLater&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;queueForLater&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isOnline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mutateLocally&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;queueForLater&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sync_queue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sync_queue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}]));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;flushQueue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SyncQueueItem&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sync_queue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/sync&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="c1"&gt;// Remove successfully synced item&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Stop flushing if connection drops again&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling Latency Spikes (3G vs Fibre)
&lt;/h2&gt;

&lt;p&gt;Most Western users are on broadband. In South Africa, a huge portion of users access web applications via mobile data on 3G or unstable 4G networks where latency can spike from 50ms to 4,000ms instantly.&lt;/p&gt;

&lt;p&gt;If you use heavy client-side hydration frameworks without aggressive code-splitting, your JavaScript bundle size will penalize users paying high rates per megabyte. We enforce a strict rule across Mirembe Muse products: initial JavaScript bundles must stay under 85KB compressed.&lt;/p&gt;

&lt;p&gt;We achieve this by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eliminating heavy UI component libraries in favour of bespoke, headless Tailwind primitives.&lt;/li&gt;
&lt;li&gt;Server-side rendering critical shell layouts so the user sees painted pixels in under 300ms, even on a budget Android device.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Matters Beyond Africa
&lt;/h2&gt;

&lt;p&gt;When you build for extreme constraints—unstable power grids, high data costs, intermittent latency—you don't just build for Africa. You build better software. &lt;/p&gt;

&lt;p&gt;The same resilience that keeps an inventory app running during stage 3 load-shedding will keep your app running smoothly for a user on a shaky commuter train in London or a subway in New York.&lt;/p&gt;

&lt;p&gt;Stop designing for the ideal network condition. Build for the real world.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Rebuilt JarvisOS Marketing Wing From Scratch Mid-Sprint — Here's Every Trade-Off</title>
      <dc:creator>Nandawula Kabali-Kagwa</dc:creator>
      <pubDate>Fri, 11 Sep 2026 10:31:14 +0000</pubDate>
      <link>https://dev.to/nanda-regine/i-rebuilt-jarvisos-marketing-wing-from-scratch-mid-sprint-heres-every-trade-off-5b25</link>
      <guid>https://dev.to/nanda-regine/i-rebuilt-jarvisos-marketing-wing-from-scratch-mid-sprint-heres-every-trade-off-5b25</guid>
      <description>&lt;p&gt;There is a specific kind of dread that arrives when a &lt;code&gt;save + publish&lt;/code&gt; button silently breaks in production. No 500 in the logs. No red banner for the user. The data just... stops moving. That was Monday.&lt;/p&gt;

&lt;p&gt;This is the technical record of a week inside JarvisOS — an AI operating system I'm building for African founders — where the marketing wing collapsed under its own accumulated shortcuts and I had to decide: patch it again, or tear the architecture down and rebuild it correctly.&lt;/p&gt;

&lt;p&gt;I rebuilt it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Root Cause: Schema Drift
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;save + publish&lt;/code&gt; failure traced back to a schema mismatch. Columns I was writing to in the API layer didn't exist in the actual database table — specifically &lt;code&gt;thumbnail_url&lt;/code&gt;, &lt;code&gt;image_brief&lt;/code&gt;, and &lt;code&gt;external_url&lt;/code&gt;. The inserts were silently swallowed because the ORM wasn't set to strict mode. The user saw a success toast. The row was incomplete. Every downstream read that touched those fields returned a 400.&lt;/p&gt;

&lt;p&gt;This is a category of bug that feels embarrassing in retrospect but is genuinely easy to accumulate when you are shipping fast and migrating features across wings of a larger system. The fix was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add the missing columns via migration&lt;/li&gt;
&lt;li&gt;Audit every SELECT and INSERT that touched the marketing content table&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;NOT NULL&lt;/code&gt; constraint with a safe default on &lt;code&gt;external_url&lt;/code&gt; to make future drift loud and immediate&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Trade-off:&lt;/strong&gt; Strict constraints slow down exploratory schema work. I accepted that cost. At production scale, silent data corruption is worse than a loud migration failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five Live Crashes, One Sweep
&lt;/h2&gt;

&lt;p&gt;Once I was in the codebase, I ran a full sweep. What I found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;withCostLogging&lt;/code&gt; not a function&lt;/strong&gt; — a utility I'd refactored had changed its export signature. Call sites weren't updated. Classic named-vs-default export drift in a JavaScript monorepo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;visual_brief.slice()&lt;/code&gt; crash&lt;/strong&gt; — &lt;code&gt;visual_brief&lt;/code&gt; was returning &lt;code&gt;null&lt;/code&gt; from the DB on older rows. I was calling &lt;code&gt;.slice()&lt;/code&gt; directly without a null guard. Fixed with optional chaining: &lt;code&gt;visual_brief?.slice(0, 200) ?? ''&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;brand_posts&lt;/code&gt; lane skipped&lt;/strong&gt; — when &lt;code&gt;content_calendar&lt;/code&gt; was empty, the lane generation function returned early before populating brand posts. The condition was checking the wrong array. One line fix, embarrassing in scale, real in consequence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TikTok &lt;code&gt;StrategyTab&lt;/code&gt; syntax errors&lt;/strong&gt; — apostrophes in JSX string props that weren't escaped. South African copy tends to contract heavily (&lt;code&gt;it's&lt;/code&gt;, &lt;code&gt;you're&lt;/code&gt;, &lt;code&gt;brand's&lt;/code&gt;) and I hadn't sanitised the AI-generated content before rendering it as JSX attributes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON crash on paste queue hashtags&lt;/strong&gt; — &lt;code&gt;JSON.parse()&lt;/code&gt; on a value that was already a parsed object. &lt;code&gt;typeof&lt;/code&gt; guard added.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Five crashes. One sweep. Three hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cockpit Architecture Rebuild
&lt;/h2&gt;

&lt;p&gt;The deeper issue the bug sweep revealed: the marketing wing had grown as a series of bolted-on tabs rather than as a coherent architecture. Each platform (TikTok, Threads, etc.) had its own component with its own data-fetching logic, its own state shape, its own styling overrides.&lt;/p&gt;

&lt;p&gt;I introduced what I'm calling the &lt;strong&gt;platform cockpit architecture&lt;/strong&gt;. The design principle: every social platform is a &lt;em&gt;wing&lt;/em&gt; of the cockpit, and every wing shares a single contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PlatformWing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;platformId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;strategyTab&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WingProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;calendarTab&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WingProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;analyticsTab&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WingProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;dataFetcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PlatformData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means adding a new platform — say, LinkedIn — requires implementing the interface, not copy-pasting a component tree. The &lt;code&gt;dataFetcher&lt;/code&gt; contract also forced me to centralise error handling: one boundary, one logging call, one cost-tracking wrapper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'd do differently:&lt;/strong&gt; Define this interface at week one, not month four. The refactor cost was non-trivial. The lesson isn't novel but it bears repeating for anyone building multi-platform SaaS: your platforms are a collection, not a list of one-offs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Threads OAuth + Cloudinary Migration
&lt;/h2&gt;

&lt;p&gt;Two infrastructure moves happened in parallel.&lt;/p&gt;

&lt;p&gt;First, Threads OAuth. I shipped the callback route, the deauthorize webhook, and the delete webhook — all required by Meta's API compliance checklist. The deauthorize and delete routes are easy to defer (&lt;code&gt;we'll do it later&lt;/code&gt;) and catastrophic to forget when your app goes for review. They're in now.&lt;/p&gt;

&lt;p&gt;Second, a full storage migration from Supabase Storage to Cloudinary. Supabase Storage is fine for getting started. Under real media volume — campaign thumbnails, visual briefs, CEO wing assets — the transformation pipeline limitations and egress costs became visible. Cloudinary's upload API with eager transformations gives me WebP conversion, responsive breakpoints, and CDN delivery in a single upload call. The migration script ran against existing rows, updated every &lt;code&gt;storage_url&lt;/code&gt; column, and verified the new URLs returned 200 before deleting originals.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cron That Keeps the DB Honest
&lt;/h2&gt;

&lt;p&gt;One small but meaningful addition: a monthly pruning cron that runs at 03:00 SAST on the first of every month, sweeping 7 tables of rows older than the retention window. Running on African infrastructure means cost discipline matters at every layer. A database that quietly grows forever is a bill that quietly grows forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Git Push → Sprint Board
&lt;/h2&gt;

&lt;p&gt;Finally, a quality-of-life engineering feature that I'll write more about separately: push a commit, Haiku (the AI agent inside JarvisOS's engineering wing) reads the commit message, parses the scope and description, and auto-populates the sprint board task. It sounds small. For a solo founder shipping across 8 apps, it removes the gap between &lt;em&gt;doing the work&lt;/em&gt; and &lt;em&gt;tracking the work&lt;/em&gt;. That gap is where context goes to die.&lt;/p&gt;




&lt;p&gt;The marketing wing is stable now. The cockpit architecture is clean. The schema is honest.&lt;/p&gt;

&lt;p&gt;Next week I turn back to HCI — the human-computer interaction layer that makes the AI agents in JarvisOS feel less like tools and more like a team. Phase 4 closed this week. Phase 5 is already open.&lt;/p&gt;

</description>
      <category>nandapersonal</category>
    </item>
    <item>
      <title>Building in Africa</title>
      <dc:creator>Nandawula Kabali-Kagwa</dc:creator>
      <pubDate>Fri, 11 Sep 2026 08:45:35 +0000</pubDate>
      <link>https://dev.to/nanda-regine/building-in-africa-35eo</link>
      <guid>https://dev.to/nanda-regine/building-in-africa-35eo</guid>
      <description>&lt;p&gt;Welcome to a journey of a lifetime, building for Africa in Africa.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>buildforafrica</category>
    </item>
    <item>
      <title>Building in Africa</title>
      <dc:creator>Nandawula Kabali-Kagwa</dc:creator>
      <pubDate>Fri, 11 Sep 2026 08:37:52 +0000</pubDate>
      <link>https://dev.to/nanda-regine/building-in-africa-28m1</link>
      <guid>https://dev.to/nanda-regine/building-in-africa-28m1</guid>
      <description>&lt;p&gt;Welcome to a journey of a lifetime, building for Africa in Africa.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>buildforafrica</category>
    </item>
  </channel>
</rss>
