<?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: Omar Afifi</title>
    <description>The latest articles on DEV Community by Omar Afifi (@omarafifi).</description>
    <link>https://dev.to/omarafifi</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%2F3954903%2F1b239da9-6bec-4707-b255-626d891d9dd7.png</url>
      <title>DEV Community: Omar Afifi</title>
      <link>https://dev.to/omarafifi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/omarafifi"/>
    <language>en</language>
    <item>
      <title>The Ghost in the View Transitions API: A Bizarre Debugging Saga with Google AI 👻</title>
      <dc:creator>Omar Afifi</dc:creator>
      <pubDate>Fri, 07 Aug 2026 17:35:35 +0000</pubDate>
      <link>https://dev.to/omarafifi/the-ghost-in-the-view-transitions-api-a-bizarre-debugging-saga-with-google-ai-5aph</link>
      <guid>https://dev.to/omarafifi/the-ghost-in-the-view-transitions-api-a-bizarre-debugging-saga-with-google-ai-5aph</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🎨 Project Overview
&lt;/h2&gt;

&lt;p&gt;While working on a major upgrade for my personal portfolio (Check it out here: &lt;a href="https://omar-afifi.com/" rel="noopener noreferrer"&gt;Live Portfolio Link&lt;/a&gt; | &lt;a href="https://github.com/omarafifi-cse/omarafifi-cse.github.io" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;), I built a feature called the &lt;strong&gt;Theme Showcase&lt;/strong&gt;. When a user first loads the homepage, a slick &lt;code&gt;View Transitions API&lt;/code&gt; animation sweeps across the screen, temporarily switching the site to one of my custom showcase themes (like "Obsidian" or "Midnight"), before gracefully sweeping back to their chosen default theme. It was a 1.7-second "Wow Effect" designed to show off the site's dynamic color system.&lt;/p&gt;

&lt;h2&gt;
  
  
  🐛 Bug Fix
&lt;/h2&gt;

&lt;p&gt;Soon after deploying, I noticed a terrifying, chaotic bug. Sometimes, I would open the website and quickly switch to another tab to check an email, or minimize the browser window completely. When I switched back... &lt;/p&gt;

&lt;p&gt;Instead of my beautiful default theme, the website was permanently stuck on one of the other showcase themes! The animation had frozen mid-way, leaving the site in an awkward intermediate state. While the website was still fully functional and I could manually fix it by selecting my original theme from the dropdown menu, it was a terrible first impression for a professional portfolio. It was as if a ghost had possessed the CSS.&lt;/p&gt;

&lt;p&gt;My first instinct? Smash the refresh button. Refreshing would actually fix the glitch and play the animation perfectly—but &lt;em&gt;only&lt;/em&gt; if I stayed patiently on the tab and watched it finish! If I refreshed and immediately switched tabs again, the ghost would return.&lt;/p&gt;

&lt;h2&gt;
  
  
  💻 Code
&lt;/h2&gt;

&lt;p&gt;Here is the exact code change we made to exorcise the ghost. &lt;em&gt;(You can view the full fix in this &lt;a href="https://github.com/OmarAfifi-CSE/omarafifi-cse.github.io/commit/e5563dd648562f10317cd382b7876efad9285517" rel="noopener noreferrer"&gt;GitHub Commit&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Buggy Code (Before):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&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="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// If the browser aborts the transition here, the Promise rejects and execution halts forever!&lt;/span&gt;
&lt;span class="nx"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&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;animation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onfinish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resolve&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;&lt;strong&gt;The Bulletproof Code (After):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isCancelled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Gracefully handle tab switching&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleVisibility&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;isCancelled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;originalTheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Instantly restore&lt;/span&gt;
    &lt;span class="nb"&gt;document&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;visibilitychange&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleVisibility&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="nb"&gt;document&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;visibilitychange&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleVisibility&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Bulletproof the View Transition Promise&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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;transition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isCancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isCancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;resolve&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;animation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onfinish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oncancel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resolve&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// THE MAGIC FIX: If the browser aborts the transition (background tab), resolve immediately!&lt;/span&gt;
      &lt;span class="nf"&gt;resolve&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Fallback if startViewTransition throws synchronously&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isCancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;duration&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;
  
  
  🛠️ My Improvements
&lt;/h2&gt;

&lt;p&gt;My technical approach to resolving this was two-fold:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;State Management on Visibility Change:&lt;/strong&gt; I added a &lt;code&gt;visibilitychange&lt;/code&gt; event listener. If the user navigates away, we instantly cancel the sequence and forcefully restore the original theme to prevent intermediate states.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Handling for the View Transitions API:&lt;/strong&gt; The core issue was that browsers forcefully abort the &lt;code&gt;startViewTransition&lt;/code&gt; when a tab is hidden to save battery. This caused the &lt;code&gt;transition.ready&lt;/code&gt; Promise to reject. Because there was no &lt;code&gt;.catch()&lt;/code&gt; block, the unhandled rejection caused the entire async sequence to hang permanently. By wrapping the transition in a &lt;code&gt;try/catch&lt;/code&gt; and adding a &lt;code&gt;.catch(() =&amp;gt; resolve())&lt;/code&gt; handler, we guarantee that the execution flow always continues gracefully, even if the browser interrupts it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🤖 Best Use of Google AI
&lt;/h2&gt;

&lt;p&gt;I couldn't figure out why this bug was only happening &lt;em&gt;sometimes&lt;/em&gt;, and no errors were showing up in the console. So, I turned to my trusty pair programmer: &lt;strong&gt;Google AI integrated within the Antigravity IDE&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;What's really cool about my workflow is that I chat with my AI agent completely casually in Arabic. It feels incredibly natural, like brainstorming with a senior engineer sitting right next to me. But debugging with AI isn't always a "one-shot" magic trick. It’s an iterative journey. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Note: I interact with my AI agent natively in Arabic! For this post, I have translated our debugging conversation. I've kept the original Arabic screenshots on hand just in case the judges want to see them!)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: The Misdiagnosis
&lt;/h3&gt;

&lt;p&gt;At first, I explained the symptoms loosely to the AI: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; &lt;em&gt;"I have another issue. Sometimes the theme changes to dark suddenly and weird things happen. I don't know what it is."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI analyzed the code and thought the &lt;em&gt;animation feature itself&lt;/em&gt; was the bug (assuming it was just annoying the user). So, it enthusiastically offered a "fix" by completely disabling the showcase animation function! &lt;/p&gt;

&lt;p&gt;I had to intervene and correct its assumption: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; &lt;em&gt;"Bro, no, you misunderstood! The animation at the beginning is natural and fine. What's NOT natural is leaving the website open, going to another tab, and coming back to find it permanently stuck on a new theme."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Phase 2: The Deep Dive
&lt;/h3&gt;

&lt;p&gt;Once I steered the AI in the right direction, it immediately dug into the mechanics of tab-switching. It realized that modern browsers aggressively pause &lt;code&gt;requestAnimationFrame&lt;/code&gt; when a tab goes to the background. &lt;/p&gt;

&lt;p&gt;The AI wrote the &lt;code&gt;visibilitychange&lt;/code&gt; listener, but then we ran into another issue when I was testing it locally with my dev server.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; &lt;em&gt;"Why does it open on a different theme every time I hit save in my editor? I have to manually refresh to get my original theme back."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI assumed I was just being impatient with the 1.7-second animation delay during live-reloads:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI:&lt;/strong&gt; &lt;em&gt;"When you save, the dev server refreshes the page. The animation starts, switches to the showcase theme, and then returns. You are just manually refreshing before the 1.7 seconds finish, trapping yourself in a loop! Just wait 1.7 seconds."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Phase 3: The "Aha!" Moment
&lt;/h3&gt;

&lt;p&gt;I knew I wasn't just being impatient. The UI was &lt;em&gt;literally getting stuck&lt;/em&gt;. I pushed the AI to look deeper: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; &lt;em&gt;"Bro, I'm not rushing. I open it, find it on a new theme, and it is completely STUCK on it. This causes problems if the user goes to another tab. Focus well, think carefully, and take your time."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s when the AI hit the absolute jackpot. &lt;/p&gt;

&lt;p&gt;It didn't just hand me a snippet of code; it provided a masterclass on browser mechanics. It analyzed the &lt;code&gt;View Transitions API&lt;/code&gt; behavior and uncovered the true root cause: to save battery and resources, modern browsers aggressively throttle or abort animations when a tab is hidden. This aggressive optimization was causing the &lt;code&gt;transition.ready&lt;/code&gt; promise to reject silently, which hung the entire async sequence because it lacked a &lt;code&gt;.catch()&lt;/code&gt; block.&lt;/p&gt;

&lt;p&gt;I initially asked the AI if we could just "force" the browser to play the animation in the background anyway. Instead of blindly agreeing, the AI pushed back and explained the engineering philosophy behind this: it’s a hard performance constraint built into modern browsers. You can't fight the browser's resource management. Instead, it taught me the "Best Practice": monitor the tab's state (which led to the &lt;code&gt;visibilitychange&lt;/code&gt; listener), gracefully catch any forced abortions (the &lt;code&gt;.catch()&lt;/code&gt; fix), instantly restore the original UI, and respect the browser's rules.&lt;/p&gt;

&lt;p&gt;Thanks to Google AI in the Antigravity IDE, I didn't just fix a bug—I learned exactly &lt;em&gt;why&lt;/em&gt; the browser was behaving that way under the hood. It transformed a frustrating debugging session into a profound learning experience about advanced JavaScript APIs and browser rendering mechanics. This is exactly what the future of pair programming looks like!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>webdev</category>
      <category>googleai</category>
    </item>
    <item>
      <title>Reviving Daphq: How I Built an Open-Source, Bare-Metal File Transfer Engine Solo with GitHub Copilot</title>
      <dc:creator>Omar Afifi</dc:creator>
      <pubDate>Wed, 27 May 2026 21:12:32 +0000</pubDate>
      <link>https://dev.to/omarafifi/reviving-daphq-how-i-built-an-open-source-bare-metal-file-transfer-engine-solo-with-github-copilot-5cd8</link>
      <guid>https://dev.to/omarafifi/reviving-daphq-how-i-built-an-open-source-bare-metal-file-transfer-engine-solo-with-github-copilot-5cd8</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Daphq&lt;/strong&gt; is a robust, open-source, cross-platform file transfer application engineered entirely from scratch to deliver hardware-saturating speeds over local area networks (LAN), Wi-Fi, and Mobile Hotspots. &lt;/p&gt;

&lt;p&gt;As a solo developer, I built Daphq to solve a personal frustration: the bloated overhead, privacy concerns, and artificial speed limits of conventional HTTP-based or cloud-reliant file-sharing tools. Daphq bypasses the standard Web/REST layer completely, establishing direct, bare-metal &lt;strong&gt;TCP Sockets&lt;/strong&gt; (&lt;code&gt;Socket&lt;/code&gt; &amp;amp; &lt;code&gt;ServerSocket&lt;/code&gt;) with &lt;code&gt;SocketOption.tcpNoDelay&lt;/code&gt; enabled to eliminate protocol latency and push network hardware to its absolute physical limits.&lt;/p&gt;

&lt;p&gt;Built using &lt;strong&gt;Flutter (Dart)&lt;/strong&gt; for seamless cross-platform performance (currently native on Android and Windows), Daphq features a highly decoupled architecture using the &lt;strong&gt;BLoC/Cubit&lt;/strong&gt; pattern, multi-gigabyte memory protection guards, and background network synchronization, and intelligent user guidance. &lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The entire project is completely open-source and free to review, fork, or run locally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;Official Product Website:&lt;/strong&gt; &lt;a href="http://omar-afifi.com/daphq/" rel="noopener noreferrer"&gt;omar-afifi.com/daphq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/omarafifi-cse/daphq" rel="noopener noreferrer"&gt;github.com/omarafifi-cse/daphq&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Screenshots in Action
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Android UI Layout&lt;/th&gt;
&lt;th&gt;Windows Desktop Experience&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuhthhu31i9z3r5zp4c2v.png" alt="Android Interface" width="800" height="1666"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6s9qm7a2y59mo7hxic2o.png" alt="Windows Interface" width="799" height="510"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Clean, robust mobile layout with active background service tracking.&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Fluid, modern, and highly responsive desktop experience.&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Comeback Story
&lt;/h2&gt;

&lt;p&gt;Daphq was born under high pressure, but after shipping the bare bones of version &lt;strong&gt;v1.0.0&lt;/strong&gt;, it was essentially abandoned in my local repository directory. The project had immense raw speed potential, but it was plagued by architectural gaps, clunky user workflows, and high-stress bugs that made it unviable for everyday users. &lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;GitHub Finish-Up-A-Thon Challenge&lt;/strong&gt; was the exact catalyst I needed to dust off the codebase and transform it from a fragile command-like utility into a highly polished, resilient consumer application (&lt;code&gt;v2.3.0&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Deep Evolution: v1.0.0 vs. v2.3.0
&lt;/h3&gt;

&lt;p&gt;Here is exactly how the architecture and UX matured over the course of this challenge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discovery, Connectivity &amp;amp; Resilience:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Before (v1.0.0):&lt;/em&gt; Zero automation. Users manually typed cryptic IPv4 addresses, and any connection drop required a full app restart.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;After (v2.3.0):&lt;/em&gt; A custom &lt;strong&gt;UDP Auto-Discovery Service&lt;/strong&gt; with an integrated watchdog. Backed by a rewritten, &lt;strong&gt;Bulletproof Receiver Engine&lt;/strong&gt;, the app instantly bounces back if a connection drops or cancels abruptly—never getting stuck on "Connecting...".&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Interface, Fluidity &amp;amp; Storage:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Before (v1.0.0):&lt;/em&gt; Rigid layouts, storage bloat from cached files, and main-thread hangs (UI freezing) when selecting large directories.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;After (v2.3.0):&lt;/em&gt; A unified dashboard anchored by an &lt;strong&gt;"Orbital Radar"&lt;/strong&gt; and Non-Blocking Async UI. Sharing 1,000+ files is now buttery smooth with zero lagging. Plus, it features &lt;strong&gt;Persistent Folders&lt;/strong&gt; to permanently remember your download path and &lt;strong&gt;Smart Auto-Cleaning&lt;/strong&gt; to wipe cache bloat automatically.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart User Guidance:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Before (v1.0.0):&lt;/em&gt; Users suffered from slow transfers due to ISP router bottlenecks without knowing why.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;After (v2.3.0):&lt;/em&gt; Integrated a real-time speed monitor that triggers a &lt;strong&gt;Smart Hotspot Guide&lt;/strong&gt; if speeds drop below 2.0 MB/s, guiding users to utilize 5GHz direct hotspots for up to 10x faster speeds.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory Safety &amp;amp; Disk I/O Flow Control:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Before (v1.0.0):&lt;/em&gt; High risk of Out-Of-Memory (OOM) crashes when streaming multi-gigabyte files due to unthrottled asynchronous disk reads.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;After (v2.3.0):&lt;/em&gt; Implemented zero-copy byte slicing (&lt;code&gt;Uint8List.sublistView&lt;/code&gt;) and &lt;strong&gt;Natural Socket Backpressure&lt;/strong&gt; (&lt;code&gt;await socket.flush()&lt;/code&gt;). Daphq now self-throttles to match real-time network speeds, saving physical RAM.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep System-Wide Integrations:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Before (v1.0.0):&lt;/em&gt; Isolated sandboxed app.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;After (v2.3.0):&lt;/em&gt; Deployed deep OS hooks, including native Android Share Menu entry and background execution. On Android, &lt;strong&gt;Instant Wake-up&lt;/strong&gt; intelligently turns on the screen for incoming requests. On Windows, the &lt;strong&gt;Context Menu Shell Extension&lt;/strong&gt; was completely rebuilt to be unbreakable, eliminating all File Explorer hangs.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  My Experience with GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;As a solo developer, you are the software architect, the UI designer, the QA technician, and the DevOps engineer all at once. Time is your greatest bottleneck. Throughout this sprint, GitHub Copilot didn’t just autocomplete lines of boilerplate—it acted as an incredibly knowledgeable senior engineering peer, unlocking complex architectural problems.&lt;/p&gt;

&lt;p&gt;Here are three distinct breakthroughs where GitHub Copilot completely changed the trajectory of the project:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Solving the Math of the Orbital Radar UI
&lt;/h3&gt;

&lt;p&gt;I wanted the home dashboard to feel alive with an evenly distributed, deterministic "Orbital Radar" circle showing available peer devices. Distributing dynamic widgets symmetrically on a circular plane without them overlapping can quickly become a headache.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Copilot's Intervention:&lt;/strong&gt; By providing Copilot with my layout boundaries and device state arrays, it instantly mapped out the exact trigonometric distribution math using &lt;code&gt;cos&lt;/code&gt; and &lt;code&gt;sin&lt;/code&gt; over a mapped index. Furthermore, it proactively recommended assigning unique &lt;code&gt;ValueKey&lt;/code&gt;s to the dynamically generated widgets, preventing unnecessary re-paints and maximizing rendering performance on low-end hardware.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Eliminating the Windows FFI Mutex Deadlock
&lt;/h3&gt;

&lt;p&gt;A critical, highly frustrating platform bug on Windows caused Daphq to accidentally trigger background execution loops whenever a user performed completely unrelated right-click shell actions elsewhere in the OS. Worse, exiting the app caused a stubborn Dart VM deadlock.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Copilot's Intervention:&lt;/strong&gt; I explained the process conflict to Copilot. It guided me through structuring a clean, platform-specific local session mutex check. When it came to bypassing the Dart VM shutdown hang, Copilot instantly generated the safe native Windows FFI code block to execute a clean &lt;code&gt;ExitProcess&lt;/code&gt; command, killing the deadlocks instantly and safely.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Engineering Flow-Safe Dynamic Streaming Buffer
&lt;/h3&gt;

&lt;p&gt;To stop aggressive disk-read bursts from triggering Out-Of-Memory exceptions, I needed a way to throttle file streams smoothly based on network capacity.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Copilot's Intervention:&lt;/strong&gt; Copilot analyzed my asynchronous data-sync loop and suggested transitioning away from arbitrary chunk delay timers. It helped refactor the code to explicitly await &lt;code&gt;socket.flush()&lt;/code&gt; based on monitored &lt;code&gt;bytesBuffered&lt;/code&gt; thresholds. The result was an automated backpressure loop that scales throughput up or down safely based on real-time network conditions.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Standard path for larger files: stream in chunks&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fileToRead&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;openRead&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="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reader&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="n"&gt;_isCancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="s"&gt;"Transfer Cancelled"&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="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;bytesBuffered&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&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="n"&gt;bytesBuffered&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AppConstants&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;socketFlushThresholdBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Awaited flush: provides backpressure so the disk read loop&lt;/span&gt;
        &lt;span class="c1"&gt;// runs at network speed, not disk speed. Without this, a fast&lt;/span&gt;
        &lt;span class="c1"&gt;// UFS device would read the entire file in &amp;lt;500ms before any&lt;/span&gt;
        &lt;span class="c1"&gt;// onUpdate fires, causing the snapshot/stutter UI bug.&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;bytesBuffered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="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="n"&gt;e&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="n"&gt;_isCancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="s"&gt;"Transfer Cancelled"&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="n"&gt;e&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;SocketException&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"reset by peer"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="s"&gt;"Receiver disconnected during transfer."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;rethrow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... UI update logic &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;
  
  
  The Verdict
&lt;/h2&gt;

&lt;p&gt;Building Daphq independently meant encountering dozens of architectural edge cases. Having GitHub Copilot available to instantly generate platform-specific guards, analyze multi-threaded socket behavior, and handle tedious Win32 registry bindings allowed me to focus on what mattered most: delivering a breathtakingly fast file transfer engine. &lt;/p&gt;

&lt;p&gt;Daphq went from an unpolished repository artifact to a production-grade open-source engine capable of hitting &lt;strong&gt;80-90 MB/s (~93% bandwidth saturation)&lt;/strong&gt; over a standard local Wi-Fi 5 connection. I couldn't be prouder of how this challenge ended.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>flutter</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
