<?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>Forem: Aaron LaBeau</title>
    <description>The latest articles on Forem by Aaron LaBeau (@biozal).</description>
    <link>https://forem.com/biozal</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F785391%2F8ac29615-c387-4f2c-af47-84f22a714619.jpg</url>
      <title>Forem: Aaron LaBeau</title>
      <link>https://forem.com/biozal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/biozal"/>
    <language>en</language>
    <item>
      <title>Porting a SwiftUI App to Avalonia: How does Cross-Platform .NET hold up</title>
      <dc:creator>Aaron LaBeau</dc:creator>
      <pubDate>Fri, 17 Apr 2026 14:06:26 +0000</pubDate>
      <link>https://forem.com/biozal/porting-a-swiftui-app-to-avalonia-how-does-cross-platform-net-hold-up-4ol0</link>
      <guid>https://forem.com/biozal/porting-a-swiftui-app-to-avalonia-how-does-cross-platform-net-hold-up-4ol0</guid>
      <description>&lt;p&gt;Let me start with a confession: I love SwiftUI. I don't love the fact that roughly 70% of developers outside of Apple's walled garden can run my SwiftUI app. That math doesn't work when you're building a developer tool.&lt;/p&gt;

&lt;p&gt;So when I set out to ship &lt;strong&gt;Ditto Edge Studio&lt;/strong&gt; — a debug and query tool for the &lt;a href="https://ditto.live" rel="noopener noreferrer"&gt;Ditto&lt;/a&gt; edge database — I needed it to run on Windows, Linux, &lt;em&gt;and&lt;/em&gt; macOS. On the Mac I already had a polished SwiftUI build. For everyone else, I turned to &lt;a href="https://avaloniaui.net/" rel="noopener noreferrer"&gt;Avalonia&lt;/a&gt;. This post is about what I learned bringing the two into the same family, specifically around two features that had no business being easy to port:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A three-pane layout with a &lt;strong&gt;Sidebar, detail view, and Inspector&lt;/strong&gt; (SwiftUI's &lt;code&gt;NavigationSplitView&lt;/code&gt; + &lt;code&gt;.inspector&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Presence Viewer&lt;/strong&gt; — an animated network graph of peers — built in &lt;strong&gt;SpriteKit&lt;/strong&gt; on macOS and reimplemented with &lt;strong&gt;SkiaSharp&lt;/strong&gt; on .NET&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Spoiler: it's pretty good. Not "identical-pixel-for-identical-pixel" good, but "I'd ship this to customers tomorrow" good. Let me show you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three-Pane Layout: Sidebar + Detail + Inspector
&lt;/h2&gt;

&lt;p&gt;On macOS, SwiftUI hands you this layout on a silver platter. &lt;code&gt;NavigationSplitView&lt;/code&gt; gives you the sidebar. &lt;code&gt;.inspector&lt;/code&gt; gives you the right-hand panel. You write about twelve lines and Apple's engineers do the rest of the work while you sip a latte.&lt;/p&gt;

&lt;h3&gt;
  
  
  SwiftUI: The Frictionless Version
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;MainStudioView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;columnVisibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NavigationSplitViewVisibility&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;showInspector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;NavigationSplitView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;columnVisibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;$columnVisibility&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Sidebar&lt;/span&gt;
            &lt;span class="nf"&gt;unifiedSidebarView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigationSplitViewColumnWidth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ideal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Center content — switches on the selected sidebar item&lt;/span&gt;
            &lt;span class="kt"&gt;Group&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selectedSidebarMenuItem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"Collections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;queryDetailView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"Observers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nf"&gt;observeDetailView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;            &lt;span class="nf"&gt;syncTabsDetailView&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="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selectedSidebarMenuItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blurReplace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigationSplitViewStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prominentDetail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;isPresented&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;$showInspector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;inspectorView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspectorColumnWidth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ideal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Resizable columns, animated transitions, proper collapsing on narrow windows, native macOS chrome, the works. If you've never built this before, you won't appreciate how much is happening for free. If you &lt;em&gt;have&lt;/em&gt; built it before — on, say, WPF in 2014 — you're probably reading this through quiet tears of envy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avalonia: Assembling the Ikea Version
&lt;/h3&gt;

&lt;p&gt;Avalonia ships a &lt;code&gt;SplitView&lt;/code&gt; control, but it's a two-pane component designed for the classic hamburger-nav pattern. Three panes with independent resizing means rolling your own with a &lt;code&gt;Grid&lt;/code&gt; and a couple of &lt;code&gt;GridSplitter&lt;/code&gt;s. Is it as elegant as SwiftUI? No. Is it actually fine and kind of fun to build? Eh, if you have AI to help you find the XAML bugs, then yes.&lt;/p&gt;

&lt;p&gt;Here's the load-bearing XAML from &lt;code&gt;EdgeStudioView.axaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Grid&lt;/span&gt; &lt;span class="na"&gt;Grid.Row=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Grid.ColumnDefinitions&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- Icon nav rail (always visible) --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ColumnDefinition&lt;/span&gt; &lt;span class="na"&gt;Width=&lt;/span&gt;&lt;span class="s"&gt;"48"&lt;/span&gt; &lt;span class="na"&gt;MinWidth=&lt;/span&gt;&lt;span class="s"&gt;"48"&lt;/span&gt; &lt;span class="na"&gt;MaxWidth=&lt;/span&gt;&lt;span class="s"&gt;"60"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- Splitter 1 --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ColumnDefinition&lt;/span&gt; &lt;span class="na"&gt;Width=&lt;/span&gt;&lt;span class="s"&gt;"Auto"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- Sidebar listing panel --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ColumnDefinition&lt;/span&gt; &lt;span class="na"&gt;Width=&lt;/span&gt;&lt;span class="s"&gt;"Auto"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- Splitter 2 --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ColumnDefinition&lt;/span&gt; &lt;span class="na"&gt;Width=&lt;/span&gt;&lt;span class="s"&gt;"Auto"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- Detail view (takes the rest) --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ColumnDefinition&lt;/span&gt; &lt;span class="na"&gt;Width=&lt;/span&gt;&lt;span class="s"&gt;"*"&lt;/span&gt; &lt;span class="na"&gt;MinWidth=&lt;/span&gt;&lt;span class="s"&gt;"400"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- Inspector splitter (only shown when inspector is open) --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ColumnDefinition&lt;/span&gt; &lt;span class="na"&gt;Width=&lt;/span&gt;&lt;span class="s"&gt;"Auto"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- Inspector panel (collapses via IsVisible) --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ColumnDefinition&lt;/span&gt; &lt;span class="na"&gt;Width=&lt;/span&gt;&lt;span class="s"&gt;"280"&lt;/span&gt; &lt;span class="na"&gt;MinWidth=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Grid.ColumnDefinitions&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;navigation:NavigationBar&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
                              &lt;span class="na"&gt;DataContext=&lt;/span&gt;&lt;span class="s"&gt;"{Binding NavigationViewModel}"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;GridSplitter&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;Width=&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt;
                  &lt;span class="na"&gt;ResizeDirection=&lt;/span&gt;&lt;span class="s"&gt;"Columns"&lt;/span&gt;
                  &lt;span class="na"&gt;IsVisible=&lt;/span&gt;&lt;span class="s"&gt;"{Binding IsListingPanelVisible}"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;Panel&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="na"&gt;MinWidth=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt; &lt;span class="na"&gt;MaxWidth=&lt;/span&gt;&lt;span class="s"&gt;"500"&lt;/span&gt;
           &lt;span class="na"&gt;IsVisible=&lt;/span&gt;&lt;span class="s"&gt;"{Binding IsListingPanelVisible}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ContentControl&lt;/span&gt; &lt;span class="na"&gt;Content=&lt;/span&gt;&lt;span class="s"&gt;"{Binding CurrentListingViewModel}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;ContentControl.DataTemplates&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;DataTemplate&lt;/span&gt; &lt;span class="na"&gt;DataType=&lt;/span&gt;&lt;span class="s"&gt;"{x:Type vm:QueryViewModel}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;sidebar:QueryListingView/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/DataTemplate&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;DataTemplate&lt;/span&gt; &lt;span class="na"&gt;DataType=&lt;/span&gt;&lt;span class="s"&gt;"{x:Type vm:SubscriptionViewModel}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;sidebar:SubscriptionListingView/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/DataTemplate&amp;gt;&lt;/span&gt;
                &lt;span class="c"&gt;&amp;lt;!-- ...etc. --&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/ContentControl.DataTemplates&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/ContentControl&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Panel&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;GridSplitter&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;Width=&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt; &lt;span class="na"&gt;ResizeDirection=&lt;/span&gt;&lt;span class="s"&gt;"Columns"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;ContentControl&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt; &lt;span class="na"&gt;Content=&lt;/span&gt;&lt;span class="s"&gt;"{Binding CurrentDetailViewModel}"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;GridSplitter&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt; &lt;span class="na"&gt;Width=&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt;
                  &lt;span class="na"&gt;ResizeDirection=&lt;/span&gt;&lt;span class="s"&gt;"Columns"&lt;/span&gt;
                  &lt;span class="na"&gt;IsVisible=&lt;/span&gt;&lt;span class="s"&gt;"{Binding IsInspectorVisible}"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;Panel&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="na"&gt;IsVisible=&lt;/span&gt;&lt;span class="s"&gt;"{Binding IsInspectorVisible}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- Inspector content here --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Panel&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Grid&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern — &lt;code&gt;DataTemplate&lt;/code&gt;s keyed to ViewModel types inside a &lt;code&gt;ContentControl&lt;/code&gt; — is Avalonia's idiomatic answer to SwiftUI's &lt;code&gt;switch&lt;/code&gt; over an enum inside a &lt;code&gt;Group&lt;/code&gt;. The MVVM ViewModel swaps, and Avalonia's &lt;code&gt;ViewLocator&lt;/code&gt; or inline templates pick the matching view. Same conceptual dance, just with more angle brackets.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Lost (Being Honest)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transitions.&lt;/strong&gt; SwiftUI's &lt;code&gt;.transition(.blurReplace)&lt;/code&gt; is a one-liner. In Avalonia I'd need to animate opacity/translation myself on &lt;code&gt;DataContext&lt;/code&gt; change. I skipped it. Nobody filed a bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The collapse-on-narrow-window magic.&lt;/strong&gt; SwiftUI knows when to hide the sidebar. On Avalonia I decide via a bound &lt;code&gt;IsListingPanelVisible&lt;/code&gt; property and a toggle button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native macOS chrome.&lt;/strong&gt; On Avalonia I use &lt;a href="https://github.com/kikipoulet/SukiUI" rel="noopener noreferrer"&gt;SukiUI&lt;/a&gt;'s &lt;code&gt;SukiWindow&lt;/code&gt;, which looks great and consistent on all three OSes — but it's not &lt;em&gt;Apple-native&lt;/em&gt;. That's a feature, not a bug: the whole point was consistency across Windows/Linux/macOS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What I Gained
&lt;/h3&gt;

&lt;p&gt;One codebase. Three operating systems. Zero Electron. Native compiled performance with &lt;code&gt;dotnet publish -r win-x64 --self-contained&lt;/code&gt;. I'll take that trade.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Presence Viewer: SpriteKit vs. SkiaSharp
&lt;/h2&gt;

&lt;p&gt;This is where things got interesting. The Presence Viewer shows a live network diagram of Ditto peers — nodes floating around, edges animating between them as connections come and go, color-coded by transport (Bluetooth, P2P Wi-Fi, WebSocket, etc.). It's the feature that makes people go "oh, cool" in demos. Losing it in the Avalonia build was not an option.&lt;/p&gt;

&lt;h3&gt;
  
  
  SwiftUI: SpriteKit Does the Heavy Lifting
&lt;/h3&gt;

&lt;p&gt;On macOS I used SpriteKit — Apple's 2D game engine. Overkill for a debug UI? Absolutely. And that's the point. I get a physics-backed scene graph, built-in node dragging, and buttery 60fps animations that I didn't have to think about.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SpriteKit&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;PresenceViewerSK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PresenceNetworkScene&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;ZStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bottomTrailing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;SpriteKitSceneView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;$scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;maxWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;infinity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;maxHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;infinity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focusable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="c1"&gt;// Overlay controls on top of the scene&lt;/span&gt;
            &lt;span class="kt"&gt;VStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trailing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;directConnectedToggle&lt;/span&gt;
                &lt;span class="n"&gt;zoomControls&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trailing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;72&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;class&lt;/span&gt; &lt;span class="kt"&gt;PresenceNetworkScene&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SKScene&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;updatePresenceGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;localPeer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PeerProtocol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                             &lt;span class="nv"&gt;remotePeers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;PeerProtocol&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Diff peers, add/remove SKNodes, animate with SKAction&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;SpriteKit handles hit-testing, &lt;code&gt;SKAction&lt;/code&gt; handles the tweens, and &lt;code&gt;SKCameraNode&lt;/code&gt; gives me zoom and pan for the price of a couple of properties. If you ever wondered why Apple developers sometimes sound smug — this is why.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avalonia: Hello, Skia
&lt;/h3&gt;

&lt;p&gt;SpriteKit does not run on Windows. Shocker, I know. But Avalonia ships with &lt;strong&gt;SkiaSharp&lt;/strong&gt; baked in — the same Skia that powers Chrome, Android, and sort of Flutter (it's been replaced by Impeller and I can see why after doing this). If you're drawing pixels on .NET and using Avalonia, Skia is your friend.&lt;/p&gt;

&lt;p&gt;I built a custom &lt;code&gt;Control&lt;/code&gt; that owns a &lt;code&gt;WriteableBitmap&lt;/code&gt;, locks it every frame, and hands Skia an &lt;code&gt;SKSurface&lt;/code&gt; to draw into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PresenceGraphControl&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Control&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;WriteableBitmap&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_bitmap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;PresenceGraphRenderer&lt;/span&gt; &lt;span class="n"&gt;_renderer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;PresenceGraphAnimator&lt;/span&gt; &lt;span class="n"&gt;_animator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;DispatcherTimer&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_animationTimer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;StyledProperty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PresenceGraphSnapshot&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SnapshotProperty&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;AvaloniaProperty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PresenceGraphControl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PresenceGraphSnapshot&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;(&lt;/span&gt;
            &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;PresenceGraphControl&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;AffectsRender&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PresenceGraphControl&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="n"&gt;SnapshotProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PositionsProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ZoomLevelProperty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DrawingContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Bounds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ph&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Height&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;_bitmap&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
            &lt;span class="n"&gt;_bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PixelSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Width&lt;/span&gt;  &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;pw&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
            &lt;span class="n"&gt;_bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PixelSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Height&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;ph&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_bitmap&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;_bitmap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;WriteableBitmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PixelSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ph&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;96&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;96&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;PixelFormats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bgra8888&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;AlphaFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Premul&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;locked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SKImageInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;SKColorType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bgra8888&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SKAlphaType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Premul&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;surface&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SKSurface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RowBytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;_renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="nf"&gt;GetEffectivePositions&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;_zoom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_panX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_panY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DrawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_bitmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Rect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;AffectsRender&lt;/code&gt; call is the Avalonia equivalent of saying "hey framework, when any of these properties change, please call &lt;code&gt;Render&lt;/code&gt; again." A &lt;code&gt;DispatcherTimer&lt;/code&gt; ticks 60 times a second to drive the animator, which interpolates node positions with a simple spring-ish easing. Pointer events get routed from &lt;code&gt;OnPointerPressed&lt;/code&gt; / &lt;code&gt;OnPointerMoved&lt;/code&gt; to figure out whether the user is panning the camera or dragging an individual node.&lt;/p&gt;

&lt;p&gt;The renderer itself is refreshingly boring C#:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PresenceGraphRenderer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SKColor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ConnectionColors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&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="s"&gt;"Bluetooth"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SKColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="m"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;217&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AccessPoint"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SKColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="m"&gt;133&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"P2PWifi"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SKColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;199&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="m"&gt;56&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"WebSocket"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SKColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;217&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;122&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AWDL"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SKColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;136&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;68&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="m"&gt;221&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Cloud"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SKColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;115&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;38&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="m"&gt;184&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SKCanvas&lt;/span&gt; &lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PresenceGraphSnapshot&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;snap&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="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SKColors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transparent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;DrawEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;snap&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;DrawNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;snap&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;DrawLegend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;canvas&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;h3&gt;
  
  
  The Honest Trade-Off
&lt;/h3&gt;

&lt;p&gt;SpriteKit's animations are &lt;em&gt;noticeably&lt;/em&gt; nicer. When a peer drops off the mesh, SwiftUI gives me a particle-poof-esque fade that took zero effort. On Skia I get a clean alpha interpolation. It's smooth, it's readable, it does the job — but it isn't magic. If your users are going to stare at this for hours, SpriteKit wins. If they glance at it to debug a sync issue and move on, nobody notices.&lt;/p&gt;

&lt;p&gt;What Skia &lt;em&gt;does&lt;/em&gt; give me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identical rendering on Windows, Linux, and macOS.&lt;/strong&gt; No surprises.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic control.&lt;/strong&gt; Every pixel is my fault, which is occasionally humbling and often useful when debugging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testability.&lt;/strong&gt; I can unit-test the renderer and animator without standing up a UI — &lt;code&gt;PresenceGraphRendererTests.cs&lt;/code&gt; is a real thing in the repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance.&lt;/strong&gt; Skia is not slow. It's what your web browser uses. It does not need your pity.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  So, Should You Use Avalonia?
&lt;/h2&gt;

&lt;p&gt;If any of these describe you, yes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need &lt;strong&gt;one codebase&lt;/strong&gt; for Windows, Linux, and macOS and you don't want to pay the Electron tax.&lt;/li&gt;
&lt;li&gt;You already know C# / XAML, or you're happy to learn MVVM (it's not scary; it's WPF with better manners).&lt;/li&gt;
&lt;li&gt;You can live without a few SwiftUI luxuries — &lt;code&gt;.transition&lt;/code&gt;, implicit animations, the occasional bit of "how did that just &lt;em&gt;work&lt;/em&gt;?" Apple magic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're shipping Mac-only and SwiftUI meets your needs, keep shipping SwiftUI. It's a wonderful framework designed by people who clearly enjoy their jobs.&lt;/p&gt;

&lt;p&gt;But for everyone else? Avalonia isn't a compromise — it's a serious, production-ready option that happens to run everywhere. I ported a real app. I shipped a real animated graph. I did not cry (much). My Linux users can finally stop asking if there will ever be a Linux version available.&lt;/p&gt;

&lt;p&gt;And honestly, there's something poetic about Microsoft-stewarded .NET running a native app on Linux to manage an edge database that syncs over Bluetooth. We live in weird, wonderful times.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Avalonia UI:&lt;/strong&gt; &lt;a href="https://avaloniaui.net/" rel="noopener noreferrer"&gt;avaloniaui.net&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SukiUI (the theming I use):&lt;/strong&gt; &lt;a href="https://github.com/kikipoulet/SukiUI" rel="noopener noreferrer"&gt;github.com/kikipoulet/SukiUI&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SkiaSharp:&lt;/strong&gt; &lt;a href="https://github.com/mono/SkiaSharp" rel="noopener noreferrer"&gt;github.com/mono/SkiaSharp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ditto:&lt;/strong&gt; &lt;a href="https://ditto.live" rel="noopener noreferrer"&gt;ditto.live&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Now if you'll excuse me, I have &lt;code&gt;NavigationSplitView&lt;/code&gt; animations to jealously admire.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>showdev</category>
      <category>ui</category>
    </item>
    <item>
      <title>Claude Mythos and the Mobile App Security Wake-Up Call: Why Mobile Developers Can't Afford to Wait</title>
      <dc:creator>Aaron LaBeau</dc:creator>
      <pubDate>Wed, 15 Apr 2026 15:21:12 +0000</pubDate>
      <link>https://forem.com/biozal/claude-mythos-and-the-mobile-app-security-wake-up-call-why-mobile-developers-cant-afford-to-wait-58pd</link>
      <guid>https://forem.com/biozal/claude-mythos-and-the-mobile-app-security-wake-up-call-why-mobile-developers-cant-afford-to-wait-58pd</guid>
      <description>&lt;p&gt;Anthropic's new Claude Mythos model has already uncovered thousands of critical vulnerabilities — some of them hidden in production software for 27 years. The cybersecurity conversation has focused on operating systems, browsers, and cloud infrastructure, but mobile apps sit squarely in the blast radius. If your iOS or Android app ships without code hardening, runtime protection, and continuous security testing, an AI agent will find the cracks long before your next release.&lt;/p&gt;

&lt;h2&gt;
  
  
  An AI model just rewrote the threat model
&lt;/h2&gt;

&lt;p&gt;On April 15, 2026, the industry began absorbing what Consultancy.eu called a "shockwave through the cybersecurity landscape": Anthropic's Claude Mythos is so effective at finding software flaws that it isn't being released to the public. Instead, access is restricted to a small consortium — "Project Glasswing" — so companies like Apple, AWS, Google, Microsoft, and NVIDIA can patch what the model finds before attackers do. And buried inside Anthropic's own red-team preview of the model is a finding that, frankly, stopped me cold: a 27-year-old bug in OpenBSD.&lt;/p&gt;

&lt;p&gt;For anyone who didn't cut their teeth on it, OpenBSD is the project whose tagline — "Only two remote holes in the default install, in a heck of a long time" — was half-joke, half-mission statement. I ran OpenBSD on the edge of the ISP I worked at back in the 1990s.  It was the operating system you picked when "secure by default" actually had to mean something, because your pager would go off if it didn't. Proactive code audits, memory-safety work, privilege separation, pledge, unveil — OpenBSD spent thirty years being the most paranoid, most carefully reviewed Unix on the planet.&lt;/p&gt;

&lt;p&gt;The bug Mythos Preview surfaced is a textbook example of why that matters. It lives in OpenBSD's TCP Selective Acknowledgement (SACK) implementation, added to the kernel in 1998 shortly after RFC 2018 standardized SACK. OpenBSD tracks SACK state as a singly linked list of "holes" — byte ranges sent but not yet acknowledged. A subtle logic mistake, pivoting on a signed-integer overflow in the 32-bit TCP sequence-number comparison (int)(a - b), makes a normally unreachable codepath reachable. The net effect: a remote attacker can repeatedly crash any OpenBSD host that responds over TCP. Firewalls. Routers. Core internet plumbing. A denial-of-service primitive sitting in the network stack, untouched, for 27 years of some of the most adversarial human code review on the planet — found autonomously by an AI model for, per Anthropic's own figures, under $50 of compute on the run that surfaced it. If an AI can shake loose a latent flaw there, the baseline assumption for every other piece of software on earth needs to be re-examined.&lt;/p&gt;

&lt;p&gt;The numbers are staggering. Thousands of critical vulnerabilities. Bugs that survived nearly three decades of code review. And as AI security expert Nanne van 't Klooster of Rewire put it, we are now watching "an arms race between autonomous AI agents — one that humans can no longer keep up with."&lt;/p&gt;

&lt;p&gt;Read that sentence again. Then open your mobile app's repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why mobile developers need to take this seriously
&lt;/h2&gt;

&lt;p&gt;If you build mobile apps, it is tempting to watch this story from a distance. Claude Mythos is chasing OS-level flaws. The headlines name chipmakers and hyperscalers. Mobile apps don't appear in the press release.&lt;/p&gt;

&lt;p&gt;That is exactly the problem.&lt;/p&gt;

&lt;p&gt;The same AI capability that finds decades-old bugs in kernels will absolutely be turned on compiled mobile binaries. Everyone is a target, no one is too small. Mobile apps are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Distributed as binaries.&lt;/strong&gt; Anyone can pull your IPA or APK off a device and reverse-engineer it. There is no server in the way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handling the crown jewels.&lt;/strong&gt; Authentication tokens, session cookies, payment credentials, cryptographic keys, health records, location data. Mobile is where the sensitive stuff lives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Widely deployed and slow to patch.&lt;/strong&gt; Even when you ship a fix, users lag behind for weeks or months.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built on third-party dependencies.&lt;/strong&gt; A single vulnerable SDK can expose millions of installs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When autonomous agents can systematically explore every pathway in your app, your obscurity is no longer a defense. If your app's logic is readable, your logic will be read.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mobile threat landscape, in plain terms
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.guardsquare.com/what-is-mobile-app-security" rel="noopener noreferrer"&gt;Guardsquare's overview of mobile app security&lt;/a&gt; makes the point bluntly: too many mobile projects treat security as an afterthought. Research cited on that page is hard to ignore — roughly three quarters of developers admit iOS and Android standard security isn't sufficient, many still rely on the OS alone, and 95% of survey respondents see room to improve their security program.&lt;/p&gt;

&lt;p&gt;The recent &lt;a href="https://www.guardsquare.com/blog/coruna-ios-exploit" rel="noopener noreferrer"&gt;Coruna and DarkSword iOS exploit kits&lt;/a&gt; are a preview of what's coming. As Guardsquare's research team documented, a single visit to a compromised site can bypass iOS sandboxing, hook sensitive functions inside your app, and exfiltrate credentials and keys in real time. Function hooks sit at the top of a method and redirect execution to the attacker's code before your logic runs. Hook &lt;code&gt;SecTrustEvaluateWithError()&lt;/code&gt; to always return true, and SSL pinning is gone. Hook Keychain access, and your secrets walk out the door.&lt;/p&gt;

&lt;p&gt;Now imagine that threat class accelerated by a model like Claude Mythos — an AI agent enumerating hook targets, chaining primitives, and writing working exploit code on its own timeline. Don't assume the operating system is a walled garden protecting your app's memory. The new exploits, and the new models, are a wake-up call.&lt;/p&gt;

&lt;h2&gt;
  
  
  What mobile app security actually looks like
&lt;/h2&gt;

&lt;p&gt;Mobile application security is the protection of mobile apps against cyber attacks across the full software development lifecycle. It isn't one control. It's layers, and each layer reinforces the others. The practical pillars:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mobile application security testing (MAST).&lt;/strong&gt; Scan your code and dependencies continuously, early, and in your CI pipeline. The earlier an issue is found, the cheaper it is to fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code hardening and obfuscation.&lt;/strong&gt; Rename symbols, obfuscate control flow, encrypt strings, and virtualize sensitive routines so your binary doesn't read like source code when an attacker — or an AI agent — disassembles it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime application self-protection (RASP).&lt;/strong&gt; Detect jailbreaks, root access, debuggers, hooks, emulators, and tampered system libraries at runtime, and respond — crash, degrade, revoke sessions, alert the backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App attestation.&lt;/strong&gt; Prove that the app talking to your API at runtime is a genuine, unmodified build on a trustworthy device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Threat monitoring.&lt;/strong&gt; See what's actually happening in the wild, because static assumptions don't survive contact with real users and real attackers.&lt;/p&gt;

&lt;p&gt;Single-layer defenses fall. Layered defenses make each successive step exponentially harder. That is the whole game.&lt;/p&gt;

&lt;h2&gt;
  
  
  How AppSweep fits in: shift security left, catch issues before release
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.guardsquare.com/appsweep-mobile-application-security-testing" rel="noopener noreferrer"&gt;AppSweep&lt;/a&gt; is Guardsquare's mobile application security testing platform, and it's built for the way modern mobile teams actually work.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-analysis testing&lt;/strong&gt; — static and interactive — tuned specifically for iOS and Android risks, not generic web scanners awkwardly retrofitted to binaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OWASP MASVS-aligned findings&lt;/strong&gt; that prioritize what matters and map directly to the industry standard your auditors already know.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI-first CI integration&lt;/strong&gt; with Bitrise, Fastlane, GitHub Actions, Jenkins, and the rest of your DevOps toolchain. Every build gets scanned. Regressions get caught before they ship.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer-first UI&lt;/strong&gt; that produces actionable remediation guidance rather than a 400-page PDF nobody reads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a world where AI agents can enumerate every weakness in your compiled app, shipping without automated mobile security testing is like pushing to production without unit tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Yes, these are the ProGuard people
&lt;/h2&gt;

&lt;p&gt;Worth pausing on this, because it matters: Guardsquare is the team behind &lt;strong&gt;ProGuard&lt;/strong&gt;, the open-source shrinker, optimizer, and obfuscator that has been the default bytecode-processing step for Android developers for more than a decade. If you've shipped an Android app, you've almost certainly shipped with ProGuard — or with R8, the Android Gradle Plugin's ProGuard-compatible successor that still consumes ProGuard configuration syntax.&lt;/p&gt;

&lt;p&gt;Anyone who shipped non-trivial Android apps in the 2010s remembers the ritual. I certainly do. Back when I was working on Xamarin.Android apps that sat on top of native Android code and bound Java libraries, I spent more hours than I'd like to admit hand-tuning &lt;code&gt;proguard.cfg&lt;/code&gt; files to make sure controls, custom renderers, and reflection-accessed types didn't get stripped.&lt;/p&gt;

&lt;p&gt;Xamarin.Android actually had &lt;em&gt;two&lt;/em&gt; tree-shakers in the pipeline, and it's worth being precise about which did what:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Xamarin/Mono managed linker&lt;/strong&gt; (later evolved into &lt;code&gt;ILLink&lt;/code&gt; in .NET) operated on the managed .NET IL side. It aggressively removed .NET types and members it couldn't prove were reachable. Anything accessed via reflection — custom &lt;code&gt;Xamarin.Forms&lt;/code&gt; renderers, value converters, effects, platform-specific controls — would disappear silently unless you annotated them with &lt;code&gt;[Preserve]&lt;/code&gt; or added a &lt;code&gt;LinkDescription.xml&lt;/code&gt;. Setting &lt;code&gt;Linker behavior&lt;/code&gt; to &lt;code&gt;Link All Assemblies&lt;/code&gt; and watching half your UI vanish at runtime was a rite of passage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ProGuard&lt;/strong&gt; (and, in later toolchains, R8) operated on the Java bytecode side. It shrank the compiled Android code and any bound Java libraries. Miss a &lt;code&gt;-keep&lt;/code&gt; rule for a class that was only referenced from JNI, from an &lt;code&gt;AndroidManifest.xml&lt;/code&gt; entry, from a bound AAR, or from a library's reflection-based DI, and you'd get a &lt;code&gt;ClassNotFoundException&lt;/code&gt; or &lt;code&gt;NoSuchMethodError&lt;/code&gt; at runtime — usually only in a release build, because debug builds typically skipped shrinking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The classic debugging loop was: ship a release build to UAT, watch a control fail to render or a callback fail to fire, grep the ProGuard mapping file, add a &lt;code&gt;-keep class com.example.**&lt;/code&gt; rule, rebuild, repeat. Painful. But there was an unambiguous upside — the APK shrank. Dead code, unused library surface area, and unreachable methods all got cut. Smaller download, faster cold start, less memory pressure.&lt;/p&gt;

&lt;p&gt;And here is the point that maps directly onto the Claude Mythos moment: &lt;strong&gt;code you don't ship is code that can't be attacked.&lt;/strong&gt; Unused methods are ROP/JOP gadgets waiting to be chained. Unused reflection entry points are pivots. Unused debug scaffolding is a map of your app's internals sitting on every user's device. An unused vulnerable dependency is still a vulnerable dependency if its classes are in your DEX. Dead code is attack surface.&lt;/p&gt;

&lt;p&gt;Shrinking is a security control, not just a size optimization. ProGuard's minification, combined with renaming and optimization, quietly removes functionality that an attacker — or an autonomous AI agent enumerating your binary — would otherwise have available to misuse. The same discipline that kept our Xamarin builds small is the discipline that keeps a modern app's attack surface tight.&lt;/p&gt;

&lt;p&gt;iXGuard, DexGuard, and AppSweep are what happens when the people who wrote the tool the industry already trusts take that philosophy — remove what you don't need, protect what you do — and extend it to the full scope of modern mobile threats: static analysis, runtime attacks, supply-chain exposure, and the coming AI-driven wave.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Claude Mythos lesson for mobile teams
&lt;/h2&gt;

&lt;p&gt;Claude Mythos is a single data point in a much larger trend. The lesson is not "panic about one model." The lesson is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Vulnerabilities that survived human review for 27 years were found in days.&lt;/li&gt;
&lt;li&gt;Organizations with resources are racing to patch before the model goes public.&lt;/li&gt;
&lt;li&gt;Mobile apps, which ship their attack surface directly to every user's pocket, are possibly next.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your mobile security program is one annual pen test and a hope that the OS will hold, you are running on borrowed time. The organizations that come through the next 12 months intact are the ones that treat mobile app security the way they treat version control — continuous, layered, automated, and non-negotiable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Research mobile app security before you ship another release
&lt;/h2&gt;

&lt;p&gt;You don't need to buy anything today. You do need to know more than you did yesterday.  This is real technical debt!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start here.&lt;/strong&gt; Read &lt;a href="https://www.guardsquare.com/what-is-mobile-app-security" rel="noopener noreferrer"&gt;What is Mobile App Security&lt;/a&gt;. Learn the vocabulary — code hardening, RASP, obfuscation, attestation, MAST, OWASP MASVS. Understand the specific threats facing iOS and Android apps, and understand what a real defense-in-depth program looks like on mobile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then go deeper.&lt;/strong&gt; Evaluate where your own app stands. Is your binary readable? Do you detect hooking? Are your builds scanned in CI? Does your security testing produce findings your developers actually fix? Look at &lt;a href="https://www.guardsquare.com/appsweep-mobile-application-security-testing" rel="noopener noreferrer"&gt;AppSweep&lt;/a&gt; for testing and &lt;a href="https://www.guardsquare.com/ixguard" rel="noopener noreferrer"&gt;iXGuard&lt;/a&gt; for iOS hardening, and benchmark whatever you're doing today against what a layered program can look like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then act.&lt;/strong&gt; The gap between organizations that take mobile app security seriously and organizations that don't is about to widen dramatically. The question is no longer &lt;em&gt;if&lt;/em&gt; your mobile app will be targeted by an AI-assisted attacker. It's &lt;em&gt;when&lt;/em&gt; — and whether you'll be ready.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is Claude Mythos?&lt;/strong&gt;&lt;br&gt;
Claude Mythos is an AI model from Anthropic, released in April 2026, that has demonstrated the ability to identify thousands of critical security vulnerabilities in operating systems and browsers — including flaws that went undetected for up to 27 years. Anthropic has restricted access to a consortium of major technology companies through an initiative called Project Glasswing so they can remediate before broader release.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does Claude Mythos matter for mobile app developers?&lt;/strong&gt;&lt;br&gt;
AI models capable of finding deep vulnerabilities in system software will be turned on mobile binaries next. Mobile apps ship their attack surface directly to end users, handle high-value data, and are frequently under-protected. Developers who rely on OS security alone are increasingly exposed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is mobile app security?&lt;/strong&gt;&lt;br&gt;
Mobile app security is the practice of protecting mobile applications from cyber attacks throughout the development lifecycle. It includes security testing, code hardening, obfuscation, runtime application self-protection (RASP), app attestation, and threat monitoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is AppSweep?&lt;/strong&gt;&lt;br&gt;
AppSweep is Guardsquare's mobile application security testing tool. It performs static and interactive analysis of iOS and Android apps, aligns findings with OWASP MASVS, and integrates directly into CI/CD pipelines such as GitHub Actions, Fastlane, Bitrise, and Jenkins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is iXGuard?&lt;/strong&gt;&lt;br&gt;
iXGuard is Guardsquare's iOS app hardening product. It applies polymorphic, compiler-based obfuscation and runtime protections to defend iOS apps against static analysis, reverse engineering, dynamic instrumentation, hooking, and tampering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Guardsquare the company behind ProGuard?&lt;/strong&gt;&lt;br&gt;
Yes. Guardsquare is the team behind ProGuard, the widely used open-source shrinker and obfuscator for Java bytecode. iXGuard, DexGuard, and AppSweep extend that heritage into comprehensive mobile application security.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should I do right now?&lt;/strong&gt;&lt;br&gt;
Research mobile app security. Understand the threats, the defensive layers, and how your current app stacks up. The sources linked throughout this article are a strong starting point.&lt;/p&gt;




&lt;p&gt;Sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.consultancy.eu/news/13511/ai-model-claude-mythos-sends-shockwaves-through-cybersecurity-landscape" rel="noopener noreferrer"&gt;AI model Claude Mythos sends shockwaves through cybersecurity landscape — Consultancy.eu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red.anthropic.com/2026/mythos-preview/" rel="noopener noreferrer"&gt;Mythos Preview — Anthropic Red Team&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.guardsquare.com/what-is-mobile-app-security" rel="noopener noreferrer"&gt;What is Mobile App Security — Guardsquare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.guardsquare.com/appsweep-mobile-application-security-testing" rel="noopener noreferrer"&gt;AppSweep Mobile Application Security Testing — Guardsquare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.guardsquare.com/ixguard" rel="noopener noreferrer"&gt;iXGuard iOS App Security — Guardsquare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.guardsquare.com/blog/coruna-ios-exploit" rel="noopener noreferrer"&gt;Can Your App Survive Coruna and DarkSword iOS Exploits? — Guardsquare&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>mobile</category>
      <category>programming</category>
    </item>
    <item>
      <title>Simplifying macOS App Deployment with Xcode Cloud: A Developer's Guide</title>
      <dc:creator>Aaron LaBeau</dc:creator>
      <pubDate>Tue, 03 Feb 2026 19:39:46 +0000</pubDate>
      <link>https://forem.com/biozal/simplifying-macos-app-deployment-with-xcode-cloud-a-developers-guide-2e6h</link>
      <guid>https://forem.com/biozal/simplifying-macos-app-deployment-with-xcode-cloud-a-developers-guide-2e6h</guid>
      <description>&lt;p&gt;Building and deploying MacOS applications doesn't have to be complicated. If you're an iOS or macOS developer, you've probably heard of Xcode Cloud—Apple's native CI/CD solution for app development. But is it worth using for your side projects? After switching from GitHub Actions to Xcode Cloud for deploying my SwiftUI macOS app, I can confidently say: absolutely. This guide walks through why Xcode Cloud might be the best choice for your macOS app deployment workflow, especially for side projects and small teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this article:&lt;/strong&gt; We'll compare Xcode Cloud and GitHub Actions for macOS development, explore real-world workflow automation, and see how Xcode Cloud handles app notarization and distribution—all through the lens of a production developer tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Estimated reading time:&lt;/strong&gt; 8-10 minutes&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Xcode Cloud?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.apple.com/xcode-cloud/" rel="noopener noreferrer"&gt;Xcode Cloud&lt;/a&gt; is Apple's continuous integration and delivery (CI/CD) service built directly into Xcode. It streamlines the app development workflow by automating builds, testing, and distribution — all while keeping everything integrated into the tools you already use.&lt;/p&gt;

&lt;p&gt;The service is designed specifically for Apple platforms, which means it handles all the Apple-specific quirks that can make CI/CD challenging. It automates builds with every code commit, runs tests across multiple device configurations in parallel, and integrates seamlessly with TestFlight and App Store Connect.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Use Case: Edge Debug Helper
&lt;/h3&gt;

&lt;p&gt;For context, the macOS app I'm deploying with Xcode Cloud is &lt;strong&gt;&lt;a href="https://github.com/biozal/ditto-edge-studio" rel="noopener noreferrer"&gt;Edge Debug Helper&lt;/a&gt;&lt;/strong&gt;—a SwiftUI-based developer tool for debugging applications built with &lt;a href="https://www.ditto.com/" rel="noopener noreferrer"&gt;Ditto&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ditto is an offline-first database that enables apps to work reliably without constant connectivity by using mesh networking and CRDTs (Conflict-Free Replicated Data Types). It's particularly valuable for building edge applications where devices need to sync data peer-to-peer across Bluetooth, WiFi, and LAN—without relying on cloud infrastructure.&lt;/p&gt;

&lt;p&gt;Edge Debug Helper allows developers to query and inspect Ditto databases across different environments (cloud, local servers, and peer-to-peer connections), making it easier to debug complex scenarios involving mesh networks, distributed data synchronization, and offline-first architectures. When you're dealing with CRDTs and multi-device sync in edge computing scenarios, having visibility into what's happening across your mesh network is invaluable.&lt;/p&gt;

&lt;p&gt;For a macOS developer tool like this—where builds need to be notarized and distributed outside the Mac App Store—Xcode Cloud's automated workflow has been a perfect fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Xcode Cloud vs GitHub Actions: Why Make the Switch?
&lt;/h2&gt;

&lt;p&gt;GitHub Actions is a powerful and flexible CI/CD platform, and many developers use it successfully for iOS and macOS projects. However, there's a significant complexity trade-off that's worth considering, especially for solo developers and small teams building native Apple applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  The iOS/macOS Certificate Management Challenge
&lt;/h3&gt;

&lt;p&gt;One of the biggest pain points with GitHub Actions for iOS and macOS development is &lt;strong&gt;certificate and provisioning profile management&lt;/strong&gt;. Unlike Xcode Cloud, which automatically handles code signing with your Apple ID, GitHub Actions requires you to manually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Export your certificates and provisioning profiles&lt;/li&gt;
&lt;li&gt;Securely store them as GitHub secrets or in encrypted repositories&lt;/li&gt;
&lt;li&gt;Set up tools like Fastlane Match for certificate synchronization&lt;/li&gt;
&lt;li&gt;Handle certificate rotation and expiration&lt;/li&gt;
&lt;li&gt;Manage Apple Developer Portal integration with two-factor authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;According to recent developer surveys, &lt;a href="https://www.velotio.com/engineering-blog/github-ci-cd-vs-xcode-cloud-a-comprehensive-comparison-for-ios-platform" rel="noopener noreferrer"&gt;certificate management is consistently cited as one of the most frustrating aspects&lt;/a&gt; of using general-purpose CI/CD tools for Apple platform development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Scripts and YAML Configuration
&lt;/h3&gt;

&lt;p&gt;With GitHub Actions, you need to write custom YAML workflow files and often shell scripts to handle the build process. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Learning GitHub Actions' YAML syntax&lt;/li&gt;
&lt;li&gt;Writing scripts to install dependencies (CocoaPods, SPM, etc.)&lt;/li&gt;
&lt;li&gt;Configuring build commands for different schemes and configurations&lt;/li&gt;
&lt;li&gt;Setting up artifact uploads and downloads&lt;/li&gt;
&lt;li&gt;Debugging workflow failures in a remote environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While this flexibility is valuable for complex, multi-platform projects, &lt;a href="https://ravi6997.medium.com/ci-cd-pipelines-for-ios-backend-a-practical-guide-for-mobile-devops-in-2025-d3a4440ee46d" rel="noopener noreferrer"&gt;it adds significant overhead for iOS-only or macOS-only projects&lt;/a&gt;. One developer aptly described their experience as: &lt;a href="https://tthemolex.medium.com/github-actions-for-ios-project-afded180a604" rel="noopener noreferrer"&gt;"If I ever end up in hell, my punishment would be setting up GitHub Actions for iOS projects"&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Simplicity Trade-off
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rentamac.io/ios-app-development-statistics/" rel="noopener noreferrer"&gt;Recent statistics from 2025&lt;/a&gt; show that among iOS developers, the majority now use Xcode Cloud (41%) followed by GitHub Actions (31%). This shift reflects a growing preference for simplicity over flexibility for many developers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.velotio.com/engineering-blog/github-ci-cd-vs-xcode-cloud-a-comprehensive-comparison-for-ios-platform" rel="noopener noreferrer"&gt;Industry analysis&lt;/a&gt; confirms that while GitHub Actions offers more customization options, Xcode Cloud provides a significantly simpler setup with native iOS/macOS support. For solo developers and small teams working exclusively on Apple platforms, this simplicity is invaluable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Xcode Cloud in Action
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Setup Right from Xcode
&lt;/h3&gt;

&lt;p&gt;Setting up Xcode Cloud is refreshingly simple. You can &lt;a href="https://developer.apple.com/documentation/xcode/configuring-your-first-xcode-cloud-workflow/" rel="noopener noreferrer"&gt;configure a workflow directly from Xcode&lt;/a&gt; in just a few minutes — no separate platform to learn, no YAML files to write, no certificate juggling required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdr3spye4qxlx0k8ljllh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdr3spye4qxlx0k8ljllh.png" alt=" " width="800" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything is configured through a familiar interface within Xcode itself. You can set triggers (like pushing to a branch), choose build actions, specify test configurations, and set up deployment options — all through point-and-click.&lt;/p&gt;

&lt;h3&gt;
  
  
  Web Dashboard for Monitoring
&lt;/h3&gt;

&lt;p&gt;While you can configure workflows in Xcode, you also have access to a comprehensive web dashboard through App Store Connect for monitoring your builds:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7anohgo3ciptf1ig4x6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7anohgo3ciptf1ig4x6g.png" alt=" " width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dashboard provides an overview of all your workflows, recent builds, and their status at a glance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41d2238e3ojaxf1vsb15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41d2238e3ojaxf1vsb15.png" alt=" " width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each build provides detailed information about the build process, test results, and any issues that occurred.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnffnwqulrprjs1z9tqxn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnffnwqulrprjs1z9tqxn.png" alt=" " width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once your build completes successfully, Xcode Cloud handles notarization automatically (for macOS apps), and you can download the notarized artifacts directly from the web interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Free Tier That Actually Works for Side Projects
&lt;/h3&gt;

&lt;p&gt;Here's the best part: Xcode Cloud comes with &lt;strong&gt;25 compute hours per month for free&lt;/strong&gt; when you have an Apple Developer Program membership ($99/year). For side projects and developer tools like Edge Debug Helper, this is more than enough.&lt;/p&gt;

&lt;p&gt;To put this in perspective, a compute hour is one hour of cloud execution time. If your typical build and test cycle takes 10 minutes, those 25 free hours give you 150 builds per month. That's plenty for most side projects and small team workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallel Testing Across Multiple Simulators
&lt;/h3&gt;

&lt;p&gt;One of Xcode Cloud's standout features is its ability to run your tests in parallel across multiple device configurations in the cloud. This means you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test on multiple iOS versions simultaneously&lt;/li&gt;
&lt;li&gt;Verify your app works across different device sizes (iPhone SE, iPhone Pro Max, iPads, etc.)&lt;/li&gt;
&lt;li&gt;Catch device-specific bugs early&lt;/li&gt;
&lt;li&gt;Do all of this without blocking your local development machine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://developer.apple.com/xcode-cloud/" rel="noopener noreferrer"&gt;This cloud-based parallel testing capability&lt;/a&gt; is particularly valuable because it prevents you from having to tie up your local machine running lengthy test suites, and it catches edge cases you might not test for locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  My macOS App Deployment Workflow with Xcode Cloud
&lt;/h2&gt;

&lt;p&gt;Here's how I use Xcode Cloud to build and deploy Edge Debug Helper, my macOS SwiftUI developer tool:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Automatic Builds on Push
&lt;/h3&gt;

&lt;p&gt;I've configured my workflow to trigger whenever I push to my &lt;code&gt;main&lt;/code&gt; branch. This ensures that every release-ready change gets built and tested automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Wait for the Build
&lt;/h3&gt;

&lt;p&gt;Once I push, Xcode Cloud takes over. It builds my app, runs my test suite, and notarizes the macOS app — all without any intervention from me. I can monitor progress in Xcode or the web dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Download the Notarized Build
&lt;/h3&gt;

&lt;p&gt;When the build completes, I download the notarized app from the build artifacts through the web interface. This notarized binary is ready to be distributed outside the Mac App Store.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Create a Distributable DMG
&lt;/h3&gt;

&lt;p&gt;I place the notarized app in the Scripts folder of my project repository (you can see this structure in the &lt;a href="https://github.com/biozal/ditto-edge-studio/tree/main/SwiftUI/scripts" rel="noopener noreferrer"&gt;Edge Debug Helper SwiftUI/scripts directory&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I then run my &lt;code&gt;create-build.sh&lt;/code&gt; script, which packages the notarized macOS app into a DMG file suitable for distribution. This script handles creating a professional-looking disk image with a custom background and window layout.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Create a GitHub Release
&lt;/h3&gt;

&lt;p&gt;Finally, I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to GitHub and create a new release&lt;/li&gt;
&lt;li&gt;Tag the branch with the version number&lt;/li&gt;
&lt;li&gt;Upload the DMG file&lt;/li&gt;
&lt;li&gt;Add release notes describing what's new&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This entire workflow is straightforward and requires minimal maintenance. Once set up, it just works—allowing me to focus on improving Edge Debug Helper rather than maintaining build infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Is Xcode Cloud Right for Your macOS App?
&lt;/h2&gt;

&lt;p&gt;For Edge Debug Helper—my macOS SwiftUI developer tool for debugging &lt;a href="https://www.ditto.com/" rel="noopener noreferrer"&gt;Ditto&lt;/a&gt; edge applications—Xcode Cloud has been a game-changer. After wrestling with GitHub Actions for months, the switch to Xcode Cloud felt like a breath of fresh air.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key benefits for side projects:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Effortless setup&lt;/strong&gt;: Configure workflows in minutes, not hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero certificate management&lt;/strong&gt;: Apple handles code signing automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generous free tier&lt;/strong&gt;: 25 hours/month is perfect for side projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud-based parallel testing&lt;/strong&gt;: Test across multiple device configurations without tying up your Mac&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native integration&lt;/strong&gt;: Everything works seamlessly with Xcode and App Store Connect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Is Xcode Cloud perfect for every project? No. If you're building cross-platform apps, need extensive customization, or require integration with non-Apple tools, GitHub Actions or other CI/CD platforms might be better choices.&lt;/p&gt;

&lt;p&gt;But if you're a solo developer or small team working on an iOS or macOS app — especially a side project or developer tool — &lt;a href="https://medium.com/@gayeugur/what-is-xcode-cloud-and-how-to-use-it-d7c11c7b12af" rel="noopener noreferrer"&gt;Xcode Cloud's simplicity and tight integration with Apple's ecosystem&lt;/a&gt; make it the obvious choice. It lets you focus on building your app instead of maintaining your build infrastructure.&lt;/p&gt;

&lt;p&gt;And with &lt;a href="https://developer.apple.com/news/?id=ik9z4ll6" rel="noopener noreferrer"&gt;25 free compute hours per month now included with every Apple Developer Program membership&lt;/a&gt;, there's really no reason not to give it a try.&lt;/p&gt;

&lt;h3&gt;
  
  
  About Edge Debug Helper
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/biozal/ditto-edge-studio" rel="noopener noreferrer"&gt;Edge Debug Helper on GitHub&lt;/a&gt; is a free, open-source developer tool for debugging applications built with &lt;a href="https://www.ditto.com/" rel="noopener noreferrer"&gt;Ditto's offline-first database platform&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The tool helps developers working with edge computing challenges—whether you're building apps that use mesh networking, need to visualize CRDT-based data synchronization, or want to debug complex multi-device scenarios where data syncs peer-to-peer across Bluetooth and WiFi. I use Edge Debug Helper to help me see what's happening under the hood.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions (FAQ)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How much does Xcode Cloud cost?
&lt;/h3&gt;

&lt;p&gt;Xcode Cloud includes &lt;strong&gt;25 compute hours per month for free&lt;/strong&gt; with your Apple Developer Program membership ($99/year). If you need more capacity, paid plans start at $49.99/month for 100 hours, scaling up to $3,999.99/month for 10,000 hours. For most side projects and small teams, the free tier is sufficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Xcode Cloud better than GitHub Actions for macOS apps?
&lt;/h3&gt;

&lt;p&gt;For macOS and iOS-only projects, Xcode Cloud is generally simpler and faster to set up. It automatically handles certificate management, code signing, and app notarization—tasks that require significant manual configuration in GitHub Actions. However, GitHub Actions offers more flexibility for cross-platform projects or complex custom workflows. According to &lt;a href="https://rentamac.io/ios-app-development-statistics/" rel="noopener noreferrer"&gt;2025 developer statistics&lt;/a&gt;, 41% of iOS developers now use Xcode Cloud compared to 31% using GitHub Actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I set up Xcode Cloud for a macOS app?
&lt;/h3&gt;

&lt;p&gt;Setting up Xcode Cloud takes just a few minutes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your project in Xcode&lt;/li&gt;
&lt;li&gt;Go to the Product menu and select "Xcode Cloud" → "Create Workflow"&lt;/li&gt;
&lt;li&gt;Choose your triggers (branch pushes, pull requests, tags)&lt;/li&gt;
&lt;li&gt;Configure build actions, testing, and distribution settings&lt;/li&gt;
&lt;li&gt;Connect your source control repository (GitHub, GitLab, or Bitbucket)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No YAML files or custom scripts required—everything is configured through Xcode's interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Xcode Cloud automatically handle app notarization?
&lt;/h3&gt;

&lt;p&gt;Yes, Xcode Cloud automatically notarizes macOS apps during the build process. This is a major advantage over GitHub Actions, where you would need to manually configure notarization scripts using &lt;code&gt;notarytool&lt;/code&gt; or similar utilities. The notarized app is available as a downloadable artifact immediately after the build completes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use Xcode Cloud with SwiftUI apps?
&lt;/h3&gt;

&lt;p&gt;Absolutely. Xcode Cloud works with all iOS, iPadOS, macOS, watchOS, and tvOS apps, regardless of whether they're built with SwiftUI, UIKit, or AppKit. It supports both Swift and Objective-C projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are compute hours in Xcode Cloud?
&lt;/h3&gt;

&lt;p&gt;A compute hour represents one hour of cloud execution time. For example, if your build and test workflow takes 10 minutes to complete, that consumes 0.17 compute hours (10/60). If you run 5 simultaneous tests that each take 12 minutes, that equals 1 compute hour total (5 × 12 minutes = 60 minutes).&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to manage certificates and provisioning profiles with Xcode Cloud?
&lt;/h3&gt;

&lt;p&gt;No, this is one of Xcode Cloud's biggest advantages. It automatically handles certificate management and code signing using your Apple ID credentials. You don't need to export certificates, store them as secrets, or use tools like Fastlane Match—Xcode Cloud manages everything for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Xcode Cloud run tests on multiple devices simultaneously?
&lt;/h3&gt;

&lt;p&gt;Yes, Xcode Cloud can run your test suite in parallel across multiple iOS versions and device types (iPhone SE, iPhone Pro, iPad, etc.) simultaneously in the cloud. This helps catch device-specific bugs and significantly speeds up your testing workflow compared to running tests sequentially on a local machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Xcode Cloud compare to other CI/CD tools like Bitrise or CircleCI?
&lt;/h3&gt;

&lt;p&gt;Xcode Cloud is the most tightly integrated solution for Apple platform development, with automatic certificate management and native Xcode integration. Tools like &lt;a href="https://www.runway.team/blog/comparing-the-top-10-mobile-ci-cd-providers" rel="noopener noreferrer"&gt;Bitrise&lt;/a&gt; offer more flexibility and cross-platform support, while CircleCI provides broader DevOps capabilities. The choice depends on your needs: choose Xcode Cloud for simplicity and Apple-ecosystem focus, or alternative platforms for more customization and multi-platform requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Ditto and why would I use Edge Debug Helper?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.ditto.com/" rel="noopener noreferrer"&gt;Ditto&lt;/a&gt; is an offline-first database platform that enables apps to sync data peer-to-peer using mesh networking and CRDTs (Conflict-Free Replicated Data Types). It's used by companies like Alaska Airlines, Chick-fil-A, and the U.S. Air Force for mission-critical apps that must work reliably without constant connectivity. Edge Debug Helper is a developer tool that helps visualize and debug Ditto databases, making it easier to troubleshoot complex distributed data scenarios, mesh network synchronization, and edge computing challenges.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;This article was informed by research from the following sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/xcode-cloud/" rel="noopener noreferrer"&gt;Xcode Cloud Overview - Apple Developer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.velotio.com/engineering-blog/github-ci-cd-vs-xcode-cloud-a-comprehensive-comparison-for-ios-platform" rel="noopener noreferrer"&gt;GitHub CI/CD vs. Xcode Cloud: A Comprehensive Comparison for iOS Platform - Velotio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ravi6997.medium.com/ci-cd-pipelines-for-ios-backend-a-practical-guide-for-mobile-devops-in-2025-d3a4440ee46d" rel="noopener noreferrer"&gt;CI/CD Pipelines for iOS and Backend in 2025: Xcode Cloud + GitHub Actions Guide - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rentamac.io/ios-app-development-statistics/" rel="noopener noreferrer"&gt;iOS App Development Statistics 2025: Real Data from 404 Devs - RentaMac&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tthemolex.medium.com/github-actions-for-ios-project-afded180a604" rel="noopener noreferrer"&gt;Github Actions for iOS project - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@gayeugur/what-is-xcode-cloud-and-how-to-use-it-d7c11c7b12af" rel="noopener noreferrer"&gt;What is Xcode Cloud and How to Use It? - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/news/?id=ik9z4ll6" rel="noopener noreferrer"&gt;25 hours of Xcode Cloud now included with the Apple Developer Program - Apple Developer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/xcode/configuring-your-first-xcode-cloud-workflow" rel="noopener noreferrer"&gt;Configuring your first Xcode Cloud workflow - Apple Developer Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cicd</category>
      <category>swift</category>
      <category>devops</category>
      <category>programming</category>
    </item>
    <item>
      <title>Hot Restart for iOS is not included in Visual Studio 2026: What .NET MAUI Developers Need to Know</title>
      <dc:creator>Aaron LaBeau</dc:creator>
      <pubDate>Fri, 16 Jan 2026 16:03:23 +0000</pubDate>
      <link>https://forem.com/biozal/hot-restart-for-ios-is-not-included-in-visual-studio-2026-what-net-maui-developers-need-to-know-28n8</link>
      <guid>https://forem.com/biozal/hot-restart-for-ios-is-not-included-in-visual-studio-2026-what-net-maui-developers-need-to-know-28n8</guid>
      <description>&lt;p&gt;If you're a .NET MAUI developer who's been enjoying the convenience of Hot Restart for iOS development on Windows, I have some bad news: this beloved feature didn't make it into Visual Studio 2026. For several developers, Hot Restart was one of the standout features that made mobile development accessible without the hassle of maintaining Mac hardware. Its removal has left many in the community frustrated and searching for alternatives.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Hot Restart?
&lt;/h2&gt;

&lt;p&gt;Hot Restart was a game-changing feature that allowed developers to deploy and test .NET MAUI (and previously Xamarin) iOS apps directly to a physical iOS device from a Windows machine - no Mac required. Instead of going through the full build cycle that typically requires a Mac build host, &lt;a href="https://learn.microsoft.com/en-us/dotnet/maui/ios/hot-restart?view=net-maui-10.0" rel="noopener noreferrer"&gt;Hot Restart streamlined the deployment process&lt;/a&gt; by pushing only code changes and resources to an existing app bundle already installed on your connected iPhone or iPad.&lt;/p&gt;

&lt;p&gt;For Windows-based developers, this was huge. You could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test your app on real iOS hardware without owning a Mac&lt;/li&gt;
&lt;li&gt;Avoid the complexity of setting up and maintaining a Mac build environment&lt;/li&gt;
&lt;li&gt;Quickly iterate on your UI and business logic during development&lt;/li&gt;
&lt;li&gt;Test hardware features like the camera, Bluetooth, and WiFi on actual devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The traditional iOS development workflow requires you to pair Visual Studio with a Mac, ensure Xcode and all build tools are up to date, and maintain network connectivity between your Windows development machine and your Mac. Hot Restart eliminated all of that friction for day-to-day development tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  How React Native with Expo Go Works
&lt;/h2&gt;

&lt;p&gt;If you're familiar with React Native development, Hot Restart worked similarly to &lt;a href="https://docs.expo.dev/faq/" rel="noopener noreferrer"&gt;Expo Go&lt;/a&gt;. Expo Go lets React Native developers test their apps on iOS and Android devices without owning a Mac or going through the full native build process. You simply scan a QR code, and your app loads on your device through the Expo Go client app.&lt;/p&gt;

&lt;p&gt;However, both solutions share a critical limitation: they only work with certain types of apps. Expo Go can't handle third-party libraries that require custom native code - the moment you need to add a native module or framework that isn't already bundled with Expo, you have to switch to a &lt;a href="https://docs.expo.dev/workflow/using-libraries/" rel="noopener noreferrer"&gt;development build&lt;/a&gt;, which requires the full native toolchain (including a Mac for iOS).&lt;/p&gt;

&lt;p&gt;Hot Restart had a similar constraint. According to the &lt;a href="https://learn.microsoft.com/en-us/dotnet/maui/ios/hot-restart?view=net-maui-10.0" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;, it didn't support static iOS libraries, certain frameworks, or XCFrameworks containing static libraries. This limitation is what ultimately led to its demise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Did Microsoft Remove Hot Restart?
&lt;/h2&gt;

&lt;p&gt;The short answer: XCFramework compatibility. As .NET MAUI matured, the team adopted XCFramework as the standard for packaging native iOS dependencies. The problem? Building with XCFramework requires macOS-specific build tools that simply aren't available on Windows.&lt;/p&gt;

&lt;p&gt;In a &lt;a href="https://github.com/dotnet/maui/discussions/32536" rel="noopener noreferrer"&gt;GitHub discussion about the removal&lt;/a&gt;, developers pieced together the technical reasons. One contributor noted that "Building with XCFramework has never been supported in HotRestart due to the fact that some build tools are only available on MacOS." This architectural incompatibility meant Hot Restart couldn't evolve alongside the platform.&lt;/p&gt;

&lt;p&gt;There's also an operational angle. Gerald Versluis, a Microsoft engineer, commented on a &lt;a href="https://www.linkedin.com/posts/max-lock_net-maui-hot-restart-for-ios-device-deployment-activity-7414184382388903937-3Wvx/" rel="noopener noreferrer"&gt;LinkedIn post&lt;/a&gt; about the feature's removal:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'm glad the experience was great for you! Unfortunately I don't think that many people were using it and the maintenance effort on it was pretty high. So that's not a great combination."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, Hot Restart was expensive to maintain for what Microsoft perceived as a relatively small user base. The team had to make a tough call between continuing to support a feature with significant technical debt or focusing resources elsewhere.&lt;/p&gt;

&lt;p&gt;Could Hot Restart have worked with XCFramework? Theoretically, yes - if Microsoft pre-built and packaged XCFrameworks with each .NET MAUI release. But as developers in the GitHub thread acknowledged, this would create a substantial maintenance burden and potentially introduce versioning and compatibility headaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does This Mean for .NET MAUI Developers?
&lt;/h2&gt;

&lt;p&gt;If you're currently using Hot Restart, you have a few options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stay on Visual Studio 2022&lt;/strong&gt; - Hot Restart is fully supported in &lt;a href="https://learn.microsoft.com/en-us/dotnet/maui/ios/hot-restart?view=net-maui-10.0" rel="noopener noreferrer"&gt;Visual Studio 2022 (version 17.3 and later)&lt;/a&gt;. If this feature is critical to your workflow, you can simply avoid upgrading to Visual Studio 2026 for now. Several developers in the community have stated they're doing exactly this.  My gut feeling is that eventually a XCode release will break this feature, but until then you can continue to use Hot Restart.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Invest in Mac hardware&lt;/strong&gt; - The traditional "Pair to Mac" workflow is now the standard approach for iOS development in Visual Studio 2026. You'll need a Mac on your network (or use a cloud-based Mac service) to build and deploy iOS apps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consider cloud Mac solutions&lt;/strong&gt; - If purchasing Mac hardware isn't feasible, services like MacStadium or MacinCloud offer cloud-hosted Mac build agents that can integrate with your Visual Studio workflow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use a Mac with JetBrains Rider&lt;/strong&gt; - JetBrains Rider is a cross-platform IDE that supports .NET MAUI development and runs on the Mac. It allows you to develop on a Mac and deploy to iOS devices using Hot Restart.  While this is not a perfect solution, it is a solution that will allow you to continue testing iOS apps with Maui.  This is what I do - when I need to test MacOS and iOS I use a Mac with Rider.  When I want to test PC I use Windows with Visual Studio 2022/2026.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The bottom line is this: if you want to continue developing .NET MAUI apps for iOS, having access to macOS is no longer optional - it's a requirement. The brief window where Windows-only developers could build for iOS without Apple hardware has closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The removal of Hot Restart is disappointing for developers who relied on it, especially those working in resource-constrained environments or teams without dedicated Mac hardware. While Microsoft's reasoning makes sense from a technical and resource perspective, it does raise the barrier to entry for .NET MAUI development.  When I worked at EY this was pretty amazing for developers because we didn't need to have two laptops to do development - one for Windows and one for Mac.&lt;/p&gt;

&lt;p&gt;If Hot Restart was a critical part of your workflow, I'd encourage you to make your voice heard in the upcoming .NET MAUI community surveys. Microsoft has indicated they're open to feedback, and if enough developers demonstrate the value of this feature, there's a chance - however slim - that a solution could be reconsidered.&lt;/p&gt;

&lt;p&gt;For now, though, if you're serious about .NET MAUI development for iOS, it's time to add a Mac to your development toolkit.&lt;/p&gt;




&lt;h3&gt;
  
  
  Sources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/maui/ios/hot-restart?view=net-maui-10.0" rel="noopener noreferrer"&gt;.NET MAUI hot restart for iOS device deployment - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dotnet/maui/discussions/32536" rel="noopener noreferrer"&gt;Hot Restart not working in VS 2025 - GitHub Discussion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/max-lock_net-maui-hot-restart-for-ios-device-deployment-activity-7414184382388903937-3Wvx/" rel="noopener noreferrer"&gt;Gerald Versluis's LinkedIn Post on Hot Restart Removal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.expo.dev/faq/" rel="noopener noreferrer"&gt;Expo Go FAQ - Expo Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.expo.dev/workflow/using-libraries/" rel="noopener noreferrer"&gt;Using third-party libraries with Expo - Expo Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>programming</category>
      <category>mobile</category>
    </item>
    <item>
      <title>The Cascading Complexity of Offline-First Sync: Why CRDTs Alone Aren't Enough</title>
      <dc:creator>Aaron LaBeau</dc:creator>
      <pubDate>Tue, 06 Jan 2026 16:32:39 +0000</pubDate>
      <link>https://forem.com/biozal/the-cascading-complexity-of-offline-first-sync-why-crdts-alone-arent-enough-2gf</link>
      <guid>https://forem.com/biozal/the-cascading-complexity-of-offline-first-sync-why-crdts-alone-arent-enough-2gf</guid>
      <description>&lt;p&gt;Picture this: Three warehouse workers standing less than 400 feet apart, each holding a smartphone thousands of times more powerful than the computers that landed astronauts on the moon. They need to share inventory updates with each other to coordinate shipments. But the WiFi access point just went down, and there's no cellular signal inside the metal building.  They can't share a single piece of data.  &lt;/p&gt;

&lt;p&gt;In 1969, NASA successfully transmitted telemetry data 238,900 miles through the vacuum of space to astronauts on the lunar surface. The communications equipment was primitive by today's standards—the entire Apollo Guidance Computer had less processing power than a modern calculator. Yet we solved that problem.&lt;/p&gt;

&lt;p&gt;So why, in 2026, can't three people standing in the same building share data between devices that are literally millions of times more capable?&lt;/p&gt;

&lt;p&gt;The answer isn't technological capability—it's architecture. We built cloud-dependent systems that require internet connectivity for everything. When that connectivity fails, our applications stop working, even though the devices themselves are perfectly functional.&lt;/p&gt;

&lt;p&gt;After 30 years of building software systems, I've learned that every complex problem is solvable. But here's the catch: every solution introduces new complexity. The offline data synchronization problem isn't a single challenge—it's a cascade of interconnected problems where each solution reveals the next layer of difficulty.&lt;/p&gt;

&lt;p&gt;This is the story of that cascade, and what it takes to truly solve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution Cascade: How Each Fix Creates New Problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Problem #1: The Cloud Dependency Trap
&lt;/h3&gt;

&lt;p&gt;Modern mobile applications are built on a simple assumption: the cloud is always reachable. When a user updates data, it goes to a server. When another user needs that data, they fetch it from the server. The server is the single source of truth, the arbiter of conflicts, the coordinator of everything.&lt;br&gt;
This architecture works beautifully—until it doesn't.&lt;/p&gt;

&lt;p&gt;Airlines ground flights when connectivity fails. Healthcare workers can't access patient records in basements or rural clinics. Construction managers lose hours of productivity when they move between sites. Retail workers can't process transactions when the network goes down.&lt;/p&gt;

&lt;p&gt;The problem isn't just complete outages. It's the "janky" networks—connections that drop every few minutes, bandwidth that fluctuates wildly, or latency that makes applications feel broken even when technically connected.&lt;/p&gt;
&lt;h4&gt;
  
  
  The Solution: Hardware First... Then Reality Sets In
&lt;/h4&gt;

&lt;p&gt;The obvious answer is to throw hardware at the problem: deploy local servers at each site, add redundant network infrastructure, install cellular backup connections. You're essentially replicating your cloud infrastructure on-premises.&lt;/p&gt;

&lt;p&gt;But this "solution" creates its own cascade of problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capital costs&lt;/strong&gt;: Servers, networking equipment, and redundant systems for every location&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational burden&lt;/strong&gt;: Each site needs monitoring, maintenance, updates, and eventual hardware replacement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expertise requirement&lt;/strong&gt;: Local IT staff or expensive managed services contracts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single points of failure&lt;/strong&gt;: The local server becomes the new bottleneck—if it fails, you're back to square one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synchronization complexity&lt;/strong&gt;: Now you need to sync between local servers and the cloud, managing conflicts at a new architectural layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You've just traded one dependency for another, more expensive one.&lt;/p&gt;
&lt;h4&gt;
  
  
  The Better Solution: Offline-First Databases
&lt;/h4&gt;

&lt;p&gt;Instead of fighting the hardware battle, move the database to where your users already are: on their devices. Let users read and write data locally with zero latency, regardless of network conditions. Sync changes peer-to-peer or through the cloud when connectivity is available.&lt;/p&gt;

&lt;p&gt;This isn't just about adding a local cache—it's about architectural inversion. The device becomes the source of truth, not a mirror of the cloud. Your application works the same whether you're connected or not.&lt;/p&gt;
&lt;h4&gt;
  
  
  The New Problem: Sync Conflicts
&lt;/h4&gt;

&lt;p&gt;But now you've introduced a new fundamental challenge: what happens when two devices edit the same data while offline, then try to sync?&lt;/p&gt;

&lt;p&gt;Consider our warehouse scenario. Worker A marks a pallet as "shipped" on their device. Worker B, unable to reach the server, marks the same pallet as "damaged" on their device. Two hours later, when WiFi comes back online, which change wins?&lt;/p&gt;

&lt;p&gt;Traditional database replication uses locks and transactions to prevent this—but locks require coordination, and coordination requires connectivity. You can't lock what you can't reach.&lt;/p&gt;
&lt;h3&gt;
  
  
  Problem #2: The Conflict Resolution Challenge
&lt;/h3&gt;

&lt;p&gt;This isn't a theoretical problem. I've seen production systems lose data, duplicate orders, and corrupt inventory counts because they didn't properly handle offline conflicts. The naive solution—"last write wins"—loses data. And prompting users to manually resolve conflicts creates terrible user experiences.  As developers we all know how bad it feels to try and merge a complex conflict with git - now think about how an average end user must feel and then you add on to this problem the UI designer needs to design something that is usable.  &lt;strong&gt;Sigh&lt;/strong&gt;. This is a bad experience.&lt;/p&gt;
&lt;h4&gt;
  
  
  The Solution: CRDTs (Conflict-Free Replicated Data Types)
&lt;/h4&gt;

&lt;p&gt;CRDTs are mathematically-sound data structures designed for distributed systems. They have a beautiful property: replicas can be updated independently and merged automatically, with guaranteed convergence to the same state.&lt;/p&gt;

&lt;p&gt;The key is that CRDTs use deterministic merge rules based on data type semantics and causal ordering, not arbitrary timestamps. A counter CRDT knows how to merge increments. A set CRDT knows how to merge additions and removals..&lt;/p&gt;

&lt;p&gt;Typically developers use business logic on top of CRDTs—perhaps a LWW-register with vector clocks, or a workflow state machine that respects causal dependencies. The CRDT guarantees the underlying data merges correctly; your application logic defines what "correct" means for your business case.&lt;/p&gt;
&lt;h4&gt;
  
  
  The New Problem: Getting Data to Sync More Often
&lt;/h4&gt;

&lt;p&gt;Here's where most teams discover that solving conflict resolution doesn't mean you've solved sync. Your CRDTs can perfectly resolve conflicts—but only when devices actually connect and exchange data.&lt;/p&gt;

&lt;p&gt;In our warehouse scenario, workers might spend 15 to 20 minutes offline before WiFi comes back. By that time, they've made dozens of decisions based on stale data. The CRDT will eventually make everything consistent, but "eventual" can mean "too late for business needs."&lt;/p&gt;
&lt;h3&gt;
  
  
  Problem #3: The Single Transport Limitation
&lt;/h3&gt;

&lt;p&gt;The typical approach is to wait for a stable WiFi or cellular connection. But what if we could make devices sync more frequently by using whatever connectivity is available?&lt;/p&gt;

&lt;p&gt;Modern smartphones have multiple radios: WiFi, cellular, and Bluetooth Low Energy. Tablets and IoT devices have similar capabilities. Why not use them all?&lt;/p&gt;
&lt;h4&gt;
  
  
  The Solution: Multi-Transport Mesh Networking
&lt;/h4&gt;

&lt;p&gt;Instead of depending on a single transport, build a mesh where devices communicate directly with each other using whatever works: Bluetooth LE when devices are close, peer-to-peer WiFi when available, LAN when on the same access point, or cellular/internet when connected to infrastructure.&lt;/p&gt;

&lt;p&gt;I wrote about this extensively in my article on &lt;a href="https://dev.to/biozal/transport-multiplexing-in-mobile-sync-why-multi-transport-beats-single-transport-systems-l37"&gt;transport multiplexing&lt;/a&gt;, but the key insight is simple: decentralized mesh networks offer multiple pathways for communication, eliminating single points of failure.&lt;/p&gt;

&lt;p&gt;In the warehouse, even with WiFi down, workers can sync directly device-to-device via Bluetooth LE or peer-to-peer WiFi. Data flows through the mesh, hopping from device to device until it reaches everyone who needs it.&lt;/p&gt;
&lt;h4&gt;
  
  
  The New Problem: Intelligent Transport Selection
&lt;/h4&gt;

&lt;p&gt;But now you face a new challenge: how do you decide which transport to use, and when?&lt;/p&gt;

&lt;p&gt;Each transport has different characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WiFi: 50-100+ Mbps, but requires access points and different devices might have different versions of WiFi which can limit distance and speeds (WiFi 5, 6, 6e, 7, etc)&lt;/li&gt;
&lt;li&gt;Bluetooth LE: ~2 Mbps, works peer-to-peer, lower power&lt;/li&gt;
&lt;li&gt;Peer-to-Peer WiFi: High bandwidth, no infrastructure needed, but platform-specific (Apple &lt;a href="https://wlanprofessionals.com/an-overview-of-apple-wireless-direct/" rel="noopener noreferrer"&gt;AWDL&lt;/a&gt; vs &lt;a href="https://en.wikipedia.org/wiki/Wi-Fi_Direct" rel="noopener noreferrer"&gt;WiFi Direct&lt;/a&gt; ) &lt;/li&gt;
&lt;li&gt;Cellular: Variable bandwidth, requires subscription, costs money&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your mesh networking solution needs to constantly evaluate: Which transport is available? Which offers the best performance right now? How do you handle transitions when a transport fails mid-sync?&lt;/p&gt;
&lt;h3&gt;
  
  
  Problem #4: Network Instability and Transport Switching
&lt;/h3&gt;

&lt;p&gt;Networks in the real world aren't clean. Connections don't just work or fail—they degrade. You get the "janky" networks I mentioned earlier: a Bluetooth connection that drops packets, a WiFi signal that fluctuates, a cellular connection that's technically up but practically unusable.&lt;/p&gt;
&lt;h4&gt;
  
  
  The Solution: A Transport Multiplexer
&lt;/h4&gt;

&lt;p&gt;You need intelligence sitting between your sync engine and the various transports—a multiplexer that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monitor the health of each transport in real-time&lt;/li&gt;
&lt;li&gt;Dynamically switch between transports based on conditions&lt;/li&gt;
&lt;li&gt;Fragment data across multiple transports simultaneously&lt;/li&gt;
&lt;li&gt;Reassemble packets that arrived via different paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.ditto.com/solutions/peer-to-peer-mesh-networking" rel="noopener noreferrer"&gt;Ditto&lt;/a&gt; calls this the "Rainbow Connection" because each transport is like a different color of the rainbow, all working together to move data.&lt;/p&gt;
&lt;h4&gt;
  
  
  The New Problem: Bandwidth Constraints
&lt;/h4&gt;

&lt;p&gt;Now you can use multiple transports and switch intelligently between them. But you've just surfaced another problem: Bluetooth LE operates at roughly 2 Mbps, while WiFi can handle 50-100+ Mbps. That's a 25-50x difference.&lt;/p&gt;

&lt;p&gt;If your sync protocol tries to send too much data, Bluetooth becomes a bottleneck. The mesh might technically work, but performance becomes unacceptable for business operations.&lt;/p&gt;
&lt;h3&gt;
  
  
  Problem #5: The Document Flooding Disaster
&lt;/h3&gt;

&lt;p&gt;Here's where most CRDT implementations hit a wall. The typical approach is to sync entire documents whenever any field changes.&lt;/p&gt;

&lt;p&gt;Let's do the math. Say you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documents averaging 100KB each&lt;/li&gt;
&lt;li&gt;50 devices in the mesh&lt;/li&gt;
&lt;li&gt;10 updates per minute across the mesh&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If every field change triggers a full document broadcast, you're looking at:&lt;/p&gt;

&lt;p&gt;100KB × 50 devices × 10 updates/min = 50MB/minute = 3GB/hour&lt;/p&gt;

&lt;p&gt;Over Bluetooth LE at 2 Mbps, 50MB takes over 3 minutes to transfer. Your mesh can't keep up. Updates queue behind each other, lag compounds, and the system grinds to a halt.&lt;/p&gt;

&lt;p&gt;This isn't a theoretical problem. I've seen this exact scenario kill production deployments. The CRDT works perfectly in the lab with a few devices, but collapses under real-world load.&lt;/p&gt;
&lt;h4&gt;
  
  
  The Solution: Property-Level Diffs (Delta Sync)
&lt;/h4&gt;

&lt;p&gt;The answer is to sync only what changed. If a document has 20 fields and only the status field changed, send only that one property update—not the entire 100KB document.&lt;/p&gt;

&lt;p&gt;This is harder than it sounds. You need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Track changes at the property level within your CRDT&lt;/li&gt;
&lt;li&gt;Calculate diffs efficiently&lt;/li&gt;
&lt;li&gt;Maintain causality across partial updates&lt;/li&gt;
&lt;li&gt;Handle cases where different properties on the same document change on different devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;State-based CRDTs traditionally sync full state. Some research papers discuss "delta-state CRDTs" that send incremental changes, but implementing them correctly is complex. You need version vectors, hybrid logical clocks, or similar mechanisms to track causality at the property level.&lt;/p&gt;

&lt;p&gt;When implemented well, property-level sync reduces bandwidth by orders of magnitude. That 100KB document? Maybe you're now sending 200 bytes. Suddenly Bluetooth LE looks much more viable.&lt;/p&gt;
&lt;h4&gt;
  
  
  The New Problem: Broadcast Storm Inefficiency
&lt;/h4&gt;

&lt;p&gt;But even with property-level diffs, you're still broadcasting. Every update goes to every device in the mesh, whether they need it or not.&lt;/p&gt;

&lt;p&gt;In an enterprise deployment, this gets expensive fast. A retail chain might have thousands of stores, each with dozens of devices. Not every device needs every piece of data. &lt;/p&gt;
&lt;h3&gt;
  
  
  Problem #6: Indiscriminate Data Propagation
&lt;/h3&gt;

&lt;p&gt;Broadcasting everything creates several problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bandwidth waste: Sending data that recipients don't need&lt;/li&gt;
&lt;li&gt;Storage waste: Devices store data they'll never use&lt;/li&gt;
&lt;li&gt;Security concerns: Devices see data they shouldn't access&lt;/li&gt;
&lt;li&gt;Battery drain: Radios constantly active processing irrelevant updates&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  The Solution: Query-Based Subscriptions
&lt;/h4&gt;

&lt;p&gt;Instead of broadcasting everything, let devices declare what they want using queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;storeId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'store-seattle-42'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;lastUpdated&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2025-01-01'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your sync engine only sends documents matching the subscription. When a document stops matching (because status changed to archived, for example), it's automatically removed from the device.&lt;/p&gt;

&lt;p&gt;This is a fundamental architectural shift. You're moving from "sync everything and let the application filter" to "sync only what's needed." The database itself becomes aware of what data should exist on which devices.&lt;/p&gt;

&lt;h4&gt;
  
  
  The New Problem: Data Lifecycle and Eviction
&lt;/h4&gt;

&lt;p&gt;Query-based subscriptions solve propagation, but introduce yet another challenge: how do you safely delete data?&lt;/p&gt;

&lt;p&gt;In a distributed system with CRDTs, deletion is complex. You can't just remove a document—other devices might still have operations referencing it. If Device A deletes a document while Device B is offline, then Device B comes back online with an update to that "deleted" document, what happens?&lt;/p&gt;

&lt;p&gt;The standard CRDT solution is "tombstones"—you mark documents as deleted but keep metadata around so you can resolve conflicts. But tombstones accumulate forever, consuming storage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem #7: Safe Data Eviction
&lt;/h3&gt;

&lt;p&gt;You need to safely remove data—both documents that no longer match queries and deleted documents—without breaking causality or losing conflict resolution capabilities.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Solution: Query-Driven Eviction
&lt;/h4&gt;

&lt;p&gt;The elegant solution is to make eviction a natural consequence of subscriptions rather than a separate garbage collection problem:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Subscription-Based Lifecycle: Documents are automatically removed from a device when they no longer match any active subscription query&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automatic Cleanup: Once a deleted document stops matching subscription queries (because it's marked deleted), it's automatically evicted from devices&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, if your subscription is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;storeId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'store42'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When an order's status changes to completed, it automatically evicts from your device—no separate garbage collection needed. The same principle works for deletions: mark the document as deleted, and if "deleted" documents don't match your queries, they evict automatically.&lt;/p&gt;

&lt;p&gt;This approach requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hybrid Logical Clocks (HLC) to track causality even when device clocks drift&lt;/li&gt;
&lt;li&gt;Incremental query evaluation to detect when documents stop matching&lt;/li&gt;
&lt;li&gt;Grace periods to account for devices that might be offline temporarily&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key insight: instead of building a separate eviction protocol, let the query subscription system handle it. Data lifecycle becomes declarative—you specify what you want, and the system manages what stays and what goes.&lt;/p&gt;

&lt;h4&gt;
  
  
  The New Problem: Implementation Complexity
&lt;/h4&gt;

&lt;p&gt;And here's where we arrive at the final challenge: building all of this correctly is incredibly hard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is Hard to Build
&lt;/h2&gt;

&lt;p&gt;Let me be direct: after three decades of building distributed systems I can confidently say that implementing this stack correctly is a multi-year engineering effort.&lt;/p&gt;

&lt;p&gt;Here's what you're really signing up for:&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Transport Networking
&lt;/h3&gt;

&lt;p&gt;Each transport has platform-specific APIs and quirks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;iOS uses Apple Wireless Direct Link (AWDL) for peer-to-peer WiFi&lt;/li&gt;
&lt;li&gt;Android uses WiFi Aware or WiFi Direct&lt;/li&gt;
&lt;li&gt;Bluetooth LE behaves differently across platforms and OS versions&lt;/li&gt;
&lt;li&gt;Discovery protocols (mDNS, BLE advertising) need tuning for battery life vs responsiveness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You need to handle graceful degradation, automatic reconnection, and intelligent fallback between transports.&lt;/p&gt;

&lt;h3&gt;
  
  
  CRDT Implementation and Optimization
&lt;/h3&gt;

&lt;p&gt;State-based CRDTs are conceptually simple but have serious practical challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Metadata overhead: Version vectors grow with the number of devices&lt;/li&gt;
&lt;li&gt;Merge complexity: Combining states efficiently at scale&lt;/li&gt;
&lt;li&gt;Property-level granularity: Most academic papers describe document-level CRDTs, not field-level&lt;/li&gt;
&lt;li&gt;Tombstone accumulation: Growing garbage that impacts performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You need a production-grade CRDT implementation optimized for mobile constraints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sync Protocol Design
&lt;/h3&gt;

&lt;p&gt;Your protocol needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calculate diffs efficiently between device states&lt;/li&gt;
&lt;li&gt;Compress payloads for bandwidth-constrained transports&lt;/li&gt;
&lt;li&gt;Handle partial sync over unreliable connections&lt;/li&gt;
&lt;li&gt;Resume interrupted transfers&lt;/li&gt;
&lt;li&gt;Avoid thundering herd problems when many devices sync simultaneously&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Query Engine Integration
&lt;/h3&gt;

&lt;p&gt;Query-based subscriptions require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A query parser and optimizer&lt;/li&gt;
&lt;li&gt;Incremental view maintenance algorithms&lt;/li&gt;
&lt;li&gt;Indexing for efficient query evaluation&lt;/li&gt;
&lt;li&gt;Integration with the CRDT layer so updates trigger query re-evaluation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing at Scale
&lt;/h3&gt;

&lt;p&gt;You can't just unit test this. You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Network simulation with realistic latency, packet loss, and bandwidth constraints&lt;/li&gt;
&lt;li&gt;Large-scale mesh simulations with hundreds of devices&lt;/li&gt;
&lt;li&gt;Chaos testing—randomly disconnecting devices, corrupting data, simulating clock drift&lt;/li&gt;
&lt;li&gt;Platform-specific testing across iOS, Android, and other targets&lt;/li&gt;
&lt;li&gt;Performance benchmarking under load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've seen teams underestimate this by 10x or more. What looks like a few months of work turns into years.&lt;/p&gt;

&lt;h2&gt;
  
  
  The NASA Analogy: Systematic Problem Solving
&lt;/h2&gt;

&lt;p&gt;The Apollo program faced similar cascading complexity. Getting to the moon wasn't a single problem—it was a stack of interconnected challenges where each solution revealed new obstacles.&lt;/p&gt;

&lt;p&gt;They needed rockets powerful enough to escape Earth's gravity. &lt;/p&gt;

&lt;p&gt;Solution: staged rockets. But now they needed to figure out how to rendezvous in orbit. &lt;/p&gt;

&lt;p&gt;Solution: the Lunar Orbit Rendezvous approach. But now they needed a way for astronauts to survive re-entry heat. &lt;/p&gt;

&lt;p&gt;Solution: heat shields. But now they needed guidance systems accurate enough to navigate in space. And so on.&lt;/p&gt;

&lt;p&gt;NASA succeeded not by finding one brilliant solution, but by systematically working through each layer of complexity. They built subsystems, tested them independently, integrated them carefully, and refined relentlessly.&lt;/p&gt;

&lt;p&gt;Offline-first data sync requires the same systematic approach. You can't just drop in a CRDT library and call it done. You need the full stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Offline-first database&lt;/li&gt;
&lt;li&gt;CRDT-based conflict resolution&lt;/li&gt;
&lt;li&gt;Multi-transport mesh networking&lt;/li&gt;
&lt;li&gt;Intelligent transport multiplexing&lt;/li&gt;
&lt;li&gt;Property-level delta sync&lt;/li&gt;
&lt;li&gt;Query-based subscriptions&lt;/li&gt;
&lt;li&gt;Causality tracking and safe eviction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each component must work correctly in isolation and integrate seamlessly with the others.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Complete Solutions Look Like
&lt;/h2&gt;

&lt;p&gt;This is why comprehensive platforms have emerged. Building this stack from scratch takes years and specialized expertise. But the good news is that the problem is solved—you can use platforms that have done the engineering work.&lt;/p&gt;

&lt;p&gt;Let me show you what a complete solution looks like, using &lt;a href="https://www.ditto.com" rel="noopener noreferrer"&gt;Ditto&lt;/a&gt; as a concrete example (since it's the platform I work with daily and know inside-out).&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture: Edge P2P Query-Correct Sync
&lt;/h3&gt;

&lt;p&gt;The fundamental shift is from "server-controlled sync" to "edge P2P query-correct sync."&lt;/p&gt;

&lt;p&gt;Traditional architecture:&lt;br&gt;
Device -&amp;gt; WiFi/Cellular -&amp;gt; Server -&amp;gt; WiFi/Cellular -&amp;gt; Device&lt;/p&gt;

&lt;p&gt;Edge-first architecture:&lt;br&gt;
Device -&amp;gt; Direct P2P -&amp;gt; Device&lt;br&gt;
Device -&amp;gt; Mesh Network -&amp;gt; Device (with optional server sync)&lt;/p&gt;

&lt;p&gt;The database lives on the device. Sync happens peer-to-peer using whatever transport works. The server (if present) is just another peer, not a required coordinator.&lt;/p&gt;
&lt;h3&gt;
  
  
  Multi-Transport Mesh with Multiplexer
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.ditto.com/solutions/peer-to-peer-mesh-networking" rel="noopener noreferrer"&gt;Ditto's&lt;/a&gt; mesh automatically uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bluetooth LE: For close-range, low-power connectivity&lt;/li&gt;
&lt;li&gt;Local Area Network (LAN): When devices share a WiFi access point&lt;/li&gt;
&lt;li&gt;Peer-to-peer WiFi: Platform-specific (AWDL on iOS, WiFi Aware on Android)&lt;/li&gt;
&lt;li&gt;WebSockets/Internet: For connecting to servers or remote peers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The multiplexer (Rainbow Connection) intelligently switches between transports and can even fragment data across multiple simultaneous connections.&lt;/p&gt;
&lt;h3&gt;
  
  
  Property-Level CRDT Sync
&lt;/h3&gt;

&lt;p&gt;Instead of syncing 100KB documents, &lt;a href="https://www.ditto.com/solutions/conflict-resolution-powered-by-crdts" rel="noopener noreferrer"&gt;Ditto&lt;/a&gt; syncs field-level changes. A document with 20 fields where one field changed? You're syncing just that field's update—typically hundreds of bytes instead of kilobytes.&lt;/p&gt;

&lt;p&gt;The CRDT implementation uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MAP types for objects (add-wins semantics)&lt;/li&gt;
&lt;li&gt;REGISTER types for simple values (last-write-wins)&lt;/li&gt;
&lt;li&gt;Counters and other specialized types as needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each field is independently tracked, so concurrent updates to different fields on the same document merge cleanly.&lt;/p&gt;
&lt;h3&gt;
  
  
  Query-Based Subscriptions with Incremental View Maintenance
&lt;/h3&gt;

&lt;p&gt;Here's what it looks like &lt;a href="https://docs.ditto.live/key-concepts/syncing-data#subscription-queries" rel="noopener noreferrer"&gt;in practice&lt;/a&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="c1"&gt;// Register a subscription - determines what data syncs to this device&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ditto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerSubscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`SELECT * FROM orders
   WHERE storeId = 'store-seattle-42'
     AND status != 'completed'`&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When an order's status changes to completed, it automatically evicts from devices that have this subscription. The sync engine evaluates queries incrementally—updates trigger query re-evaluation without full table scans.&lt;/p&gt;

&lt;p&gt;This solves multiple problems at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bandwidth efficiency: Only sync relevant data&lt;/li&gt;
&lt;li&gt;Storage management: Devices don't accumulate irrelevant documents&lt;/li&gt;
&lt;li&gt;Security: Devices only receive data matching their authorization scope&lt;/li&gt;
&lt;li&gt;Automatic cleanup: Documents evict when they stop matching&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Query-Driven Data Lifecycle
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.ditto.live/sdk/latest/crud/delete" rel="noopener noreferrer"&gt;Ditto&lt;/a&gt; takes a unique approach to eviction by making it a natural consequence of query subscriptions rather than a separate garbage collection process:&lt;/p&gt;

&lt;p&gt;Automatic Eviction: When a document no longer matches any active subscription on a device, it's automatically removed. This works for both state changes (like an order moving to "completed" status) and deletions.&lt;/p&gt;

&lt;p&gt;Deletion Handling: When you delete a document in Ditto, it's marked as deleted in the CRDT. If your subscription queries exclude deleted documents (which is typical), the deleted document automatically evicts from devices that don't need it.&lt;/p&gt;

&lt;p&gt;Causality Tracking: Ditto uses hybrid logical clocks (HLC) to maintain causality, ensuring that late-arriving updates are handled correctly even after eviction. The system tracks enough metadata to resolve conflicts without keeping full document tombstones forever.&lt;/p&gt;

&lt;p&gt;This happens automatically—you don't manually manage data lifecycle. You just define what data each device needs via subscriptions, and &lt;a href="https://docs.ditto.live/key-concepts/syncing-data" rel="noopener noreferrer"&gt;Ditto&lt;/a&gt; handles the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Path Forward
&lt;/h2&gt;

&lt;p&gt;Here's what I tell teams evaluating offline-first solutions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't underestimate the complexity&lt;/strong&gt;. This isn't just "add a CRDT library and you're done." Each layer of the problem is harder than it first appears, and the integration between layers is where most of the difficulty lies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decide: Build or Buy&lt;/strong&gt;. If you have a multi-year timeline, deep expertise in distributed systems, and can dedicate a team to this problem, building from scratch is possible. Most teams should use a comprehensive platform—the engineering effort saved is enormous.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Think Edge-Native&lt;/strong&gt;. The future of computing is increasingly decentralized. IoT devices, mobile workers, autonomous systems—these all need to operate independently and sync opportunistically. Architecting for edge-native scenarios now positions you better for what's coming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test Realistically&lt;/strong&gt;. Whatever solution you choose, test it under realistic conditions: flaky networks, limited bandwidth, devices coming online and offline at random, simultaneous updates across many peers. Lab tests don't surface the hard problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Engineering Is About Tradeoffs
&lt;/h2&gt;

&lt;p&gt;The warehouse problem I started with—three workers 400 feet apart who can't share data—is completely solvable. So is the broader challenge of offline-first data sync. We have the technology, we understand the mathematics, and we've built production systems that work at scale.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ditto.com/" rel="noopener noreferrer"&gt;Ditto&lt;/a&gt; has solved these problems comprehensively with its &lt;a href="https://www.ditto.com/solutions/offline-first-architecture" rel="noopener noreferrer"&gt;offline-first architecture&lt;/a&gt; that keeps applications working regardless of network conditions, &lt;a href="https://www.ditto.com/solutions/peer-to-peer-mesh-networking" rel="noopener noreferrer"&gt;peer-to-peer mesh networking&lt;/a&gt; that intelligently multiplexes across multiple transports, and &lt;a href="https://www.ditto.com/solutions/conflict-resolution-powered-by-crdts" rel="noopener noreferrer"&gt;CRDT-powered conflict resolution&lt;/a&gt; that automatically handles concurrent updates. The platform combines property-level delta sync, query-based subscriptions, and automatic data lifecycle management into a production-ready system that developers can integrate in days rather than years.&lt;/p&gt;

&lt;p&gt;But it's not simple. Like NASA's moon missions, success requires systematically solving each layer of cascading complexity. You need offline-first databases, CRDT-based conflict resolution, multi-transport mesh networking, intelligent multiplexing, property-level sync, query-based subscriptions, and safe eviction protocols—all working together.&lt;/p&gt;

&lt;p&gt;Every solution creates new problems. That's not a bug; it's the nature of complex engineering. The question is whether you're prepared to solve all the problems, not just the first few.&lt;/p&gt;

&lt;p&gt;After 30 years of building distributed systems, I've learned that the teams who succeed are the ones who understand this complexity upfront, respect the engineering effort required, and choose their battles wisely. Sometimes that means building from scratch. More often, it means leveraging platforms that have already done the hard work.&lt;/p&gt;

&lt;p&gt;The offline-first future is here. The question is whether your architecture is ready for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why can't devices sync data without WiFi or cellular?
&lt;/h3&gt;

&lt;p&gt;Devices absolutely can sync without WiFi or cellular—using technologies like Bluetooth LE and peer-to-peer WiFi. The limitation isn't hardware; it's architecture. Most applications are built assuming cloud connectivity and don't include peer-to-peer mesh networking capabilities. Modern smartphones have multiple radios (Bluetooth, WiFi Direct, etc.) that enable direct device-to-device communication, but applications need to be specifically designed to use these capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are CRDTs and why aren't they enough for offline sync?
&lt;/h3&gt;

&lt;p&gt;CRDTs (Conflict-Free Replicated Data Types) are data structures that automatically resolve conflicts when multiple devices edit the same data offline. They guarantee eventual consistency using mathematical properties. However, CRDTs alone don't solve the complete sync problem because they don't address: when and how data syncs (transport layer), bandwidth efficiency (most implementations send full documents), what data each device should receive (subscription management), or when to safely delete data (eviction protocols). CRDTs are the foundation, not the complete solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does mesh networking solve connectivity problems?
&lt;/h3&gt;

&lt;p&gt;Mesh networking creates multiple pathways for data to flow between devices. Instead of all devices connecting to a central hub (like a WiFi access point), devices connect directly to nearby peers. Data can "hop" from device to device until it reaches its destination. If one connection fails, data routes through alternative paths. This eliminates single points of failure and enables operation even when infrastructure (WiFi, cellular) is unavailable. Modern mesh implementations use multiple transports simultaneously—Bluetooth LE, peer-to-peer WiFi, and LAN connections—maximizing resilience.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a transport multiplexer?
&lt;/h3&gt;

&lt;p&gt;A transport multiplexer is an intelligent switching layer that manages multiple network transports (WiFi, Bluetooth LE, cellular, etc.) simultaneously. It monitors the health and performance of each available transport in real-time, dynamically routes data through the best available option, and can even fragment data across multiple transports at once. Think of it like a smart router at the device level—it ensures data flows using whatever connectivity is available, automatically failing over when transports become unavailable or degraded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do most CRDT implementations have bandwidth problems?
&lt;/h3&gt;

&lt;p&gt;Traditional CRDT implementations sync entire document states when any field changes. If you have a 100KB document with 20 fields and one field update, most systems broadcast the full 100KB to all peers. In bandwidth-constrained environments (Bluetooth LE operates at ~2 Mbps), this creates bottlenecks. With many devices and frequent updates, the math breaks down: 100KB × 50 devices × 10 updates/minute = 50MB/minute, which exceeds the capacity of low-bandwidth transports. This "document flooding" problem makes naive CRDT implementations impractical at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are property-level diffs and why do they matter?
&lt;/h3&gt;

&lt;p&gt;Property-level diffs (also called delta sync) means sending only the changed fields rather than entire documents. If a document has 20 fields and one field changes, you transmit just that field's new value—typically a few hundred bytes instead of kilobytes. This reduces bandwidth by 10-100x or more, making sync practical over constrained transports like Bluetooth LE. Implementing this correctly requires tracking causality at the field level, calculating diffs efficiently, and maintaining CRDT semantics for partial updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does query-based sync differ from full replication?
&lt;/h3&gt;

&lt;p&gt;Full replication broadcasts all data to all devices, letting applications filter what they need. Query-based sync inverts this: devices declaratively specify what data they want using queries (e.g., SELECT * FROM orders WHERE storeId = 'seattle'), and the sync engine only sends matching documents. When a document stops matching (because a field changed), it's automatically removed from that device. This reduces bandwidth (only send relevant data), manages storage automatically (devices don't accumulate irrelevant documents), improves security (devices only receive authorized data), and enables automatic cleanup (documents evict when they stop matching).&lt;/p&gt;

&lt;h3&gt;
  
  
  What is causality tracking in distributed systems?
&lt;/h3&gt;

&lt;p&gt;Causality tracking means recording the order of operations across distributed devices so you can determine which updates happened before others, even when clocks aren't synchronized. This is critical for conflict resolution and safe data deletion. Common techniques include vector clocks (each device maintains a counter of operations from all known peers) and hybrid logical clocks (combining physical timestamps with logical counters). Causality tracking lets you say "Device A's update happened before Device B's update" even if Device A's clock was wrong, ensuring correct conflict resolution and safe eviction of old data.&lt;/p&gt;

&lt;h3&gt;
  
  
  What makes offline-first sync implementation difficult?
&lt;/h3&gt;

&lt;p&gt;The difficulty comes from the intersection of multiple complex problems: distributed systems theory (CRDTs, causality, consistency), networking (multi-transport protocols, discovery, NAT traversal), platform-specific APIs (iOS vs Android vs web), performance optimization (minimizing bandwidth, battery, storage), and security (distributed authorization without central authority). Each problem alone is manageable, but they interact in complex ways. Additionally, testing requires simulating realistic network conditions at scale, which most teams aren't equipped to do. The full implementation is typically a multi-year engineering effort requiring specialized expertise.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Ditto's approach differ from other CRDT implementations?
&lt;/h3&gt;

&lt;p&gt;Ditto provides a complete integrated stack rather than just CRDT primitives. Key differentiators include: property-level delta sync (most implementations sync full documents), query-based subscriptions with automatic eviction (most broadcast all data and require manual data lifecycle management), multi-transport mesh with intelligent multiplexing (most use single transports), and cross-platform device discovery and connection management. The integration between layers—how the query engine triggers sync, how subscriptions control data lifecycle, how the multiplexer optimizes for bandwidth constraints—is where much of the value lies.&lt;/p&gt;

&lt;h3&gt;
  
  
  When should you use an offline-first database?
&lt;/h3&gt;

&lt;p&gt;Use offline-first databases when: &lt;/p&gt;

&lt;p&gt;1 - Users need to work in environments with unreliable or absent connectivity (field work, healthcare, aviation, retail, construction)&lt;/p&gt;

&lt;p&gt;2 - Your application requires low-latency interactions even when cloud-connected&lt;/p&gt;

&lt;p&gt;3 - Data must sync peer-to-peer without routing through servers&lt;/p&gt;

&lt;p&gt;4 - High availability is critical—the app must keep working regardless of network conditions&lt;/p&gt;

&lt;p&gt;5 -  You need eventual consistency across distributed devices. Don't use offline-first when you have no offline use case, or can tolerate application unavailability during network outages.&lt;/p&gt;

&lt;h2&gt;
  
  
  About Me
&lt;/h2&gt;

&lt;p&gt;I am a Developer Advocate at with over 30 years of experience building distributed systems, mobile applications, and developer tools currently working as a Developer Advocate @  &lt;a href="https://www.ditto.com" rel="noopener noreferrer"&gt;Ditto&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Previously I was an Associate Director/Tech Lead of Client Technologies - Mobile Technologies @ &lt;a href="https://www.ey.com" rel="noopener noreferrer"&gt;EY&lt;/a&gt; and Senior Developer Advocate/Principal Software Engineer @ &lt;a href="https://www.couchbase.com/" rel="noopener noreferrer"&gt;Couchbase&lt;/a&gt;.  &lt;/p&gt;

</description>
      <category>database</category>
      <category>offline</category>
      <category>architecture</category>
      <category>p2psync</category>
    </item>
    <item>
      <title>The .NET Cross-Platform Showdown: MAUI vs Uno vs Avalonia (And Why Avalonia Won)</title>
      <dc:creator>Aaron LaBeau</dc:creator>
      <pubDate>Mon, 22 Dec 2025 15:42:04 +0000</pubDate>
      <link>https://forem.com/biozal/the-net-cross-platform-showdown-maui-vs-uno-vs-avalonia-and-why-avalonia-won-ian</link>
      <guid>https://forem.com/biozal/the-net-cross-platform-showdown-maui-vs-uno-vs-avalonia-and-why-avalonia-won-ian</guid>
      <description>&lt;p&gt;I've been writing .NET code since the first beta. Over three decades of professional development, I've seen the good, the bad, and the abandoned. I love C#—it remains one of the most elegant languages for building production software. But Microsoft's UI framework strategy? That's been a different story.&lt;/p&gt;

&lt;p&gt;Recently, I needed to port &lt;a href="https://github.com/biozal/ditto-edge-studio" rel="noopener noreferrer"&gt;a tool&lt;/a&gt; I'd built in SwiftUI to Windows. What started as a simple requirement turned into a deep dive through Microsoft's fragmented ecosystem and ultimately led me to abandon MAUI entirely. Here's what I learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Project: Edge Debug Helper Needs Windows Support
&lt;/h2&gt;

&lt;p&gt;In my current role as a Developer Advocate at Ditto, I work with developers building applications using &lt;a href="https://docs.ditto.live/home/about-ditto" rel="noopener noreferrer"&gt;Ditto&lt;/a&gt;—an offline-first mobile database platform that enables &lt;a href="https://www.ditto.com/solutions/peer-to-peer-mesh-networking" rel="noopener noreferrer"&gt;peer-to-peer mesh networking&lt;/a&gt; and &lt;a href="https://www.ditto.com/solutions/offline-first-architecture" rel="noopener noreferrer"&gt;real-time data synchronization&lt;/a&gt;. Ditto solves a critical problem: applications that need to function reliably regardless of network connectivity. Using &lt;a href="https://www.ditto.com/solutions/conflict-resolution-powered-by-crdts" rel="noopener noreferrer"&gt;Conflict-Free Replicated Data Types (CRDTs)&lt;/a&gt;, Ditto automatically handles data synchronization across devices via Bluetooth, Wi-Fi, or LAN without requiring constant cloud connectivity. It's particularly valuable for field operations, collaborative applications, and scenarios where internet access is unreliable or unavailable.&lt;/p&gt;

&lt;p&gt;I wrote &lt;a href="https://github.com/biozal/ditto-edge-studio" rel="noopener noreferrer"&gt;Edge Debug Helper&lt;/a&gt; to debug &lt;a href="https://www.ditto.com/" rel="noopener noreferrer"&gt;Ditto apps&lt;/a&gt; when working with the &lt;a href="https://docs.ditto.live/cloud/mongodb-connector" rel="noopener noreferrer"&gt;MongoDB Connector&lt;/a&gt;. SwiftUI let me prototype quickly—within days I had a functional macOS tool. But as I started working with developers on Windows, the limitation became obvious: I needed cross-platform support.&lt;/p&gt;

&lt;p&gt;My first instinct was &lt;a href="https://github.com/emilk/egui?tab=readme-ov-file#demo" rel="noopener noreferrer"&gt;Rust&lt;/a&gt;. I'm learning the language, and it offers genuine cross-platform capabilities. But the learning curve was steeper than I anticipated with limited UI libraries, and I was spending more time fighting the borrow checker than building features.&lt;/p&gt;

&lt;p&gt;Then I thought: why not return to .NET? I've used it throughout my entire career. Like SwiftUI, I should be able to get something running quickly. C# is a joy to work with. The question wasn't whether to use .NET—it was &lt;em&gt;which&lt;/em&gt; .NET UI framework to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Microsoft's Platform Abandonment Problem
&lt;/h2&gt;

&lt;p&gt;Here's where things get frustrating. Microsoft has a terrible track record with UI frameworks. Let me count the casualties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows Forms&lt;/strong&gt; - Still technically supported, but ancient&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WPF&lt;/strong&gt; - 20 years old, Windows-only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silverlight&lt;/strong&gt; - &lt;a href="https://en.wikipedia.org/wiki/Microsoft_Silverlight" rel="noopener noreferrer"&gt;Abandoned&lt;/a&gt;, leaving countless developers stranded&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UWP&lt;/strong&gt; - Dead, officially discontinued after promising write-once, run-everywhere&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WinUI&lt;/strong&gt; - Uncertain future&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Xamarin&lt;/strong&gt; - &lt;a href="https://learn.microsoft.com/en-us/dotnet/maui/migration/?view=net-maui-10.0" rel="noopener noreferrer"&gt;Forcibly migrated to MAUI&lt;/a&gt; with support ending May 2024&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MAUI&lt;/strong&gt; - The latest attempt, and spoiler: it's not great for desktop apps &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This pattern represents millions in wasted development effort across the industry. Every few years, Microsoft pivots to a new framework, and developers are left rewriting applications or maintaining legacy code. It's exhausting.&lt;/p&gt;

&lt;p&gt;I stumbled across &lt;a href="https://avaloniaui.net/blog/winui-vs-wpf-vs-uwp" rel="noopener noreferrer"&gt;this blog post from Avalonia&lt;/a&gt; comparing WinUI, WPF, and UWP. Reading their analysis of Microsoft's broken strategy made me smile—finally, someone saying what we've all been thinking. The constant churn isn't sustainable. Microsoft cannot be trusted with long-term UI framework investment.&lt;/p&gt;

&lt;p&gt;But I still needed to build my app. So I started with the obvious choice: MAUI.&lt;/p&gt;

&lt;h2&gt;
  
  
  MAUI: The Choice That Disappointed
&lt;/h2&gt;

&lt;p&gt;MAUI is Microsoft's official cross-platform framework, so naturally, I tried it first. The experience was terrible when using it to target desktop apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  macOS Performance Was Abysmal
&lt;/h3&gt;

&lt;p&gt;MAUI on macOS uses Mac Catalyst, not native AppKit. The performance was horrible. This isn't a full macOS application—it's an iOS app awkwardly shoehorned onto the desktop. For a developer tool that needs to feel responsive, this was unacceptable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Theming Was Painful
&lt;/h3&gt;

&lt;p&gt;Getting a Windows app to look decent took far too much work. Making the same app look nice on macOS? Even worse. The default controls feel out of place on every platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Signing and Packaging Was a Nightmare
&lt;/h3&gt;

&lt;p&gt;To ship a proof-of-concept to a colleague's Windows machine—without requiring dev tools—I needed to sign the application. The process was painful.&lt;/p&gt;

&lt;p&gt;I get it: Apple requires signing too. But Xcode Cloud auto-signs builds and makes this essentially a non-issue for macOS/iOS developers. Microsoft provides no equivalent service. You can cobble together GitHub Actions workflows, but they're complex, especially for someone who hasn't done full-time .NET development since 2019.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Jank Persists in .NET 9
&lt;/h3&gt;

&lt;p&gt;Even in the latest stable release, MAUI has rough edges. I tried the .NET 10 beta hoping CollectionView improvements would help, and they did—but relying on beta releases for basic functionality isn't acceptable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux Support? Forget About It
&lt;/h3&gt;

&lt;p&gt;MAUI's Linux support is effectively non-existent. This became critical when I bought a &lt;a href="https://www.clockworkpi.com/uconsole" rel="noopener noreferrer"&gt;uConsole&lt;/a&gt;—a portable Linux device perfect for testing Ditto's &lt;a href="https://www.ditto.com/solutions/peer-to-peer-mesh-networking" rel="noopener noreferrer"&gt;peer-to-peer mesh networking&lt;/a&gt;. The moment Linux became a requirement, MAUI was out.&lt;/p&gt;

&lt;p&gt;MAUI isn't ready for production cross-platform development that need to target MacOS, Windows, and Linux . Maybe it'll improve, but I've heard that promise before with Silverlight, UWP, and every other framework Microsoft has abandoned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uno Platform: Impressive DevTools, But Still Some Jank
&lt;/h2&gt;

&lt;p&gt;Next, I explored &lt;a href="https://platform.uno/" rel="noopener noreferrer"&gt;Uno Platform&lt;/a&gt;. This framework impressed me immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Developer Tools Are First-Class
&lt;/h3&gt;

&lt;p&gt;Uno's tooling is everything MAUI should be but isn't. Hot Design with AI assistance, real-time XAML updates, Figma integration—these features make writing XAML applications genuinely enjoyable. I can't emphasize enough how good Uno's developer experience is compared to MAUI. They're leading the industry in making XAML development productive.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Project Structure Is Complex
&lt;/h3&gt;

&lt;p&gt;Despite the excellent tooling, Uno's project structure left something to be desired. For someone returning to full-time .NET development after several years, the complexity was daunting. Multiple projects, conditional compilation, platform-specific implementations—it all felt heavier than necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux Support Was Better, But Still Not Great
&lt;/h3&gt;

&lt;p&gt;Uno supports Linux, which put it ahead of MAUI. But the support isn't quite production-ready. I needed something more mature.&lt;/p&gt;

&lt;h3&gt;
  
  
  There Was Still Some Jank
&lt;/h3&gt;

&lt;p&gt;I documented issues I encountered during livestreams on &lt;a href="https://www.youtube.com/costoda" rel="noopener noreferrer"&gt;my YouTube channel&lt;/a&gt;. To Uno's credit, the team released updates that resolved some of these problems. They clearly care about the developer community, and my interactions with the team have been extremely positive.&lt;/p&gt;

&lt;p&gt;Uno Platform is impressive. If Avalonia hadn't worked out, Uno would have been my choice. But then I discovered something that changed everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Game Changer: Avalonia's Packaging Solution
&lt;/h2&gt;

&lt;p&gt;I shelved the project for a few months—my wife and I had a baby in September. When I picked it back up in October, I was nearly ready to commit to Uno. Then I saw &lt;a href="https://www.linkedin.com/posts/micjames_packaging-apps-is-a-nightmare-weve-added-activity-7368605405716942848-RMZj" rel="noopener noreferrer"&gt;this LinkedIn post&lt;/a&gt; from Mike at Avalonia about their new packaging solution.&lt;/p&gt;

&lt;p&gt;This was like Xcode Cloud, but for .NET. You can tell an AI assistant: "Package my Avalonia app for macOS as a signed DMG," and two minutes later, you have a production-ready, signed application. No certificate wrangling. No build scripts. No wasted afternoons.&lt;/p&gt;

&lt;p&gt;Here's the kicker: you can build and sign macOS DMG files &lt;em&gt;from Windows&lt;/em&gt;. This is typically impossible through conventional methods.&lt;/p&gt;

&lt;p&gt;I got excited. Really excited. Excited enough to try Avalonia for the first time, even though I was nearly finished with my Uno POC.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building with Avalonia: Fast and Productive
&lt;/h2&gt;

&lt;p&gt;I started with two screens: a login screen and a main UI displaying connected Ditto SDK peers. If Avalonia worked well for these screens and the packaging was solid, I'd migrate from Uno.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation Was Clear and Comprehensive
&lt;/h3&gt;

&lt;p&gt;Both screens came together fast. Avalonia's documentation is excellent—clear, comprehensive, with good examples. Controls worked as expected. Everything felt solid.&lt;/p&gt;

&lt;h3&gt;
  
  
  The First UI Wasn't Pretty
&lt;/h3&gt;

&lt;p&gt;My initial attempt didn't look great. I explored theming options and found Material Design support. But it wasn't complete Material Design—it was an older version with glitches like outline text not rendering properly.&lt;/p&gt;

&lt;p&gt;I tried Fluent Design next, but it looks terrible on macOS. Nothing close to the polished UI that SwiftUI provides out of the box.&lt;/p&gt;

&lt;p&gt;I was getting discouraged. Then I discovered SukiUI.&lt;/p&gt;

&lt;h2&gt;
  
  
  SukiUI: The Missing Piece
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/kikipoulet/SukiUI" rel="noopener noreferrer"&gt;SukiUI&lt;/a&gt; is an incredible project—a comprehensive desktop UI library specifically for Avalonia. With minimal setup (just following the installation instructions), my app immediately looked more attractive.&lt;/p&gt;

&lt;p&gt;I switched to their &lt;a href="https://kikipoulet.github.io/SukiUI/documentation/controls/layout/glasscard.html" rel="noopener noreferrer"&gt;GlassCard control&lt;/a&gt;, and suddenly my app looked &lt;em&gt;really nice&lt;/em&gt;. Professional. Polished. Modern.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffiq2j1im0ef6xup1yew5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffiq2j1im0ef6xup1yew5.png" alt=" " width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, I refactored my navigation to use SukiUI's &lt;a href="https://kikipoulet.github.io/SukiUI/documentation/controls/navigation/sidemenu.html" rel="noopener noreferrer"&gt;Side Menu&lt;/a&gt;. The transformation was remarkable. My app went from "functional but ugly" to "something I'd actually want to use."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffz2u7oa32fein3rhbpsg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffz2u7oa32fein3rhbpsg.png" alt=" " width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Not Everything Was Perfect
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://kikipoulet.github.io/SukiUI/documentation/controls/layout/dock.html" rel="noopener noreferrer"&gt;Dock control&lt;/a&gt; had some issues. I &lt;a href="https://github.com/kikipoulet/SukiUI/discussions/544" rel="noopener noreferrer"&gt;posted in their forums&lt;/a&gt;, and they recommended upgrading to a nightly build. Since I wanted to stay on stable releases, I simply removed the Dock code temporarily. This is active development—issues get fixed quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance: Avalonia Edges Out Uno
&lt;/h2&gt;

&lt;p&gt;I didn't see massive performance differences between Uno and Avalonia. Both render quickly on my target platforms. But Avalonia seemed slightly better in memory usage, and from a UI standpoint, I loved how my app looked and felt with SukiUI more than my Uno implementation.&lt;/p&gt;

&lt;p&gt;Performance on Windows, macOS, and Linux was solid across the board.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avalonia's Bright Future
&lt;/h2&gt;

&lt;p&gt;Two developments have me particularly excited about Avalonia's roadmap:&lt;/p&gt;

&lt;h3&gt;
  
  
  Next-Generation Rendering
&lt;/h3&gt;

&lt;p&gt;Avalonia is &lt;a href="https://avaloniaui.net/blog/avalonia-partners-with-google-s-flutter-t-eam-to-bring-impeller-rendering-to-net" rel="noopener noreferrer"&gt;exploring new rendering&lt;/a&gt; with Impeller, the rendering engine that Google wrote for Flutter. Impeller delivers smooth, consistent performance, higher average frame rates, and reduced VRAM use. It is unapologetically GPU-first, making it ideal for modern hardware where predictable performance and power efficiency are essential.&lt;/p&gt;

&lt;p&gt;This isn't ripping out the stable SkiaSharp backend. Avalonia is taking a measured approach, offering multiple rendering backends so developers can choose what works best for their use cases. Stability first, with experimental options for those who need cutting-edge performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  MAUI Linux Rendering
&lt;/h3&gt;

&lt;p&gt;Avalonia is now &lt;a href="https://avaloniaui.net/blog/avalonia-maui-progress-update" rel="noopener noreferrer"&gt;providing the rendering layer for MAUI on Linux&lt;/a&gt;. Read that again: Microsoft's own framework is depending on Avalonia for Linux support. That's a remarkable validation of Avalonia's technical foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Verdict: Avalonia Won
&lt;/h2&gt;

&lt;p&gt;After building POCs in MAUI, Uno, and Avalonia, I chose Avalonia. Here's why:&lt;/p&gt;

&lt;h3&gt;
  
  
  SukiUI Makes My App Beautiful
&lt;/h3&gt;

&lt;p&gt;The transformation SukiUI provides is remarkable. My app looks professional with minimal effort, and working with the component library has been smooth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation and Community Are Strong
&lt;/h3&gt;

&lt;p&gt;Avalonia's documentation is comprehensive and clear. The community is active and helpful. When I have questions, I get answers quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  DevTools and Parcel Are Essential
&lt;/h3&gt;

&lt;p&gt;Avalonia's &lt;a href="https://avaloniaui.net/accelerate/devtools" rel="noopener noreferrer"&gt;DevTools&lt;/a&gt; and &lt;a href="https://avaloniaui.net/accelerate/parcel" rel="noopener noreferrer"&gt;Parcel&lt;/a&gt; are game-changers. I'll admit Uno's designer tools are better than Avalonia's DevTools in some ways, but Parcel—especially the packaging and signing automation—more than makes up for it.&lt;/p&gt;

&lt;p&gt;The subscription cost is affordable, even for small teams. If it means I don't have to deal with signing and packaging nightmares, it's worth every penny.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance Is Solid Across All Platforms
&lt;/h3&gt;

&lt;p&gt;Windows, macOS, and Linux all perform well. No Mac Catalyst compromises. No platform-specific jank. Just consistent, responsive applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  I'm Past the POC Phase
&lt;/h3&gt;

&lt;p&gt;With Avalonia, I moved from proof-of-concept to feature development quickly. Good documentation and extensive example code mean I'm productive, not fighting the framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About Uno?
&lt;/h2&gt;

&lt;p&gt;Uno Platform is genuinely impressive. Their &lt;a href="https://platform.uno/studio/" rel="noopener noreferrer"&gt;developer tooling&lt;/a&gt; is industry-leading, and the team clearly cares about developers. If you're heavily invested in the Microsoft ecosystem or need the &lt;a href="https://platform.uno/platform/" rel="noopener noreferrer"&gt;specific features&lt;/a&gt; Uno provides, it's a solid choice.&lt;/p&gt;

&lt;p&gt;For my project, Avalonia was the better fit. But Uno deserves recognition for pushing the entire .NET UI ecosystem forward with their tooling innovations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About MAUI?
&lt;/h2&gt;

&lt;p&gt;I can't recommend MAUI for serious cross-platform development that need desktop support for MacOS, Linux, and Windows. The macOS performance is poor, Linux support is essentially non-existent, and the rough edges suggest it's not production-ready. Maybe Microsoft will fix these issues, but I've been a .NET developer for three decades. I've seen this pattern before.  In my humble opinion Microsoft is not investing enough in Maui when you see small companies like Uno and Avalonia making better solutions than the huge company that is Microsoft.  How serious is Microsoft about Maui when Uno and Avalonia are just SO much better?&lt;/p&gt;

&lt;p&gt;Avalonia works &lt;em&gt;today&lt;/em&gt;. I'm not waiting for Microsoft's next pivot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving Forward
&lt;/h2&gt;

&lt;p&gt;I'm excited to finish &lt;a href="https://github.com/biozal/edge-studio-dotnet" rel="noopener noreferrer"&gt;my app&lt;/a&gt; over the holiday break. Avalonia has proven to be fast, stable, and genuinely cross-platform. The documentation is excellent, the community is strong, and the tooling removes the pain points that have plagued .NET desktop development for years.&lt;/p&gt;

&lt;p&gt;After 30 years of .NET development—through Windows Forms, WPF, Silverlight, UWP, Xamarin, and MAUI—I've finally found a framework I can trust for the long term. It's not from Microsoft, and maybe that's exactly why it works.&lt;/p&gt;

&lt;p&gt;If you're building cross-platform .NET applications, give &lt;a href="https://avaloniaui.net/" rel="noopener noreferrer"&gt;Avalonia&lt;/a&gt; a serious look. It's what MAUI should have been.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>mobile</category>
      <category>database</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Transport Multiplexing in Mobile Sync: Why Multi-Transport Beats Single-Transport Systems</title>
      <dc:creator>Aaron LaBeau</dc:creator>
      <pubDate>Wed, 29 Oct 2025 19:28:26 +0000</pubDate>
      <link>https://forem.com/biozal/transport-multiplexing-in-mobile-sync-why-multi-transport-beats-single-transport-systems-l37</link>
      <guid>https://forem.com/biozal/transport-multiplexing-in-mobile-sync-why-multi-transport-beats-single-transport-systems-l37</guid>
      <description>&lt;p&gt;Building reliable data synchronization in distributed systems is one of the toughest challenges for mobile developers. The core problem: most synchronization systems rely on single-transport architectures—typically WiFi or cellular alone—creating fundamental limitations that cause failures when network conditions change.&lt;/p&gt;

&lt;p&gt;The solution is transport multiplexing: intelligently switching between multiple network transports (WiFi, Bluetooth LE, peer-to-peer WiFi, and cellular) based on real-time conditions. This approach, combined with mesh networking, transforms rigid single-transport systems into adaptive, resilient architectures that maintain connectivity regardless of environmental challenges.&lt;/p&gt;

&lt;p&gt;This matters every day in airline operations, healthcare facilities, construction sites, and retail environments where field workers need reliable data access. Traditional cloud-first architectures break down at the edge when connectivity is intermittent, expensive, or unavailable. Transport multiplexing solves this by using whatever connectivity is most efficient at any given moment, automatically adapting without application intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Network Transport Comparison: Why Single-Transport Systems Fail
&lt;/h2&gt;

&lt;p&gt;Most synchronization systems rely on a single network transport – typically TCP/IP over WiFi or cellular. This creates a fundamental problem: no single networking technology excels in all environmental conditions. Each transport has distinct tradeoffs between range, bandwidth, power consumption, cost, and infrastructure requirements. By committing to one transport, systems become brittle; they work well in specific scenarios but fail or perform poorly when conditions change.&lt;/p&gt;

&lt;p&gt;Here's how each transport stacks up:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Transport&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Bandwidth&lt;/th&gt;
&lt;th&gt;Power&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;Limitations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WiFi Infrastructure&lt;/td&gt;
&lt;td&gt;30-45m 5 GHz, 50m-1.5km 2.4 GHz&lt;/td&gt;
&lt;td&gt;50-100+ Mbps&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;High-bandwidth sync&lt;/td&gt;
&lt;td&gt;Single point of failure, requires access point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bluetooth LE 5.2&lt;/td&gt;
&lt;td&gt;30-100m (up to 400m)&lt;/td&gt;
&lt;td&gt;2 Mbps&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Wearables, close-range comms&lt;/td&gt;
&lt;td&gt;Limited bandwidth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WiFi P2P (AWDL, WiFi Direct, WiFi Aware)&lt;/td&gt;
&lt;td&gt;30-100m&lt;/td&gt;
&lt;td&gt;50-100+ Mbps&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Direct device communication&lt;/td&gt;
&lt;td&gt;Platform-specific, complex setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cellular&lt;/td&gt;
&lt;td&gt;Broad&lt;/td&gt;
&lt;td&gt;Variable (5-100 Mbps)&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Geographic coverage&lt;/td&gt;
&lt;td&gt;Expensive, latency issues, coverage gaps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wired Ethernet&lt;/td&gt;
&lt;td&gt;Wired&lt;/td&gt;
&lt;td&gt;1Gbps+&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Infrastructure deployments, IoT&lt;/td&gt;
&lt;td&gt;Requires cabling and hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Single-Transport vs. Multi-Transport Mesh: A Critical Distinction
&lt;/h2&gt;

&lt;p&gt;Before diving into individual transport details, it's important to understand a fundamental architectural difference. Single-transport mesh systems—even those supporting WiFi-based peer-to-peer networks—remain constrained to one transport layer. &lt;/p&gt;

&lt;p&gt;For example, WiFi-only mesh implementations using DNS-SD (Bonjour) for peer discovery require all devices to be on the same WiFi network. When that network becomes unavailable or a device moves out of range, synchronization stops entirely.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6hkrw7m4ts966komxn8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6hkrw7m4ts966komxn8.png" alt="Traditional Architecture" width="800" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Multi-transport mesh systems take a fundamentally different approach. They maintain simultaneous connections across multiple transports and automatically fail over between them based on real-time conditions. A device might sync over WiFi infrastructure, then seamlessly switch to peer-to-peer WiFi, then fall back to Bluetooth LE as it moves between environments—all without interrupting the application or requiring manual reconfiguration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1l7med2zil025x6gxip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1l7med2zil025x6gxip.png" alt="Ditto Edge Native Architecture" width="800" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This architectural difference determines whether your system can truly adapt to unpredictable network conditions or remains dependent on a single transport's availability.  Let's take a closer look at each of the individual transports now.&lt;/p&gt;

&lt;h3&gt;
  
  
  WiFi Infrastructure Mode: The Access Point Bottleneck
&lt;/h3&gt;

&lt;p&gt;In standard WiFi networks, all traffic routes through an access point. When two devices are physically close but both distant from the access point, their data must travel the combined distance to and from the access point. This introduces latency and creates a single point of failure. If the access point goes down, nearby devices can't communicate even though they're within direct radio range of each other.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bluetooth Low Energy: Range and Bandwidth Constraints
&lt;/h3&gt;

&lt;p&gt;Bluetooth Low Energy (BLE) offers excellent power efficiency and works well for wearables and personal devices, but it's fundamentally limited by its short range—typically 30-100 meters depending on the device and environment (theoretically allows for ranges up to 240 meters). The bandwidth is also constrained compared to WiFi; Bluetooth LE 5.2 achieves maximum speeds around 2 Mbps, while WiFi typically delivers 50-100+ Mbps or higher depending on the standard. This significant speed difference makes Bluetooth unsuitable for syncing large datasets or multiple files within reasonable timeframes. Additionally, Bluetooth requires pairing and can support only a limited number of simultaneous connections, making it impractical for scenarios where many devices need to coordinate together. In crowded environments with multiple Bluetooth devices, interference and connection instability become significant problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Peer-to-Peer WiFi Protocols: Platform Fragmentation Challenges
&lt;/h3&gt;

&lt;p&gt;Peer-to-peer WiFi technologies like AWDL, WiFi Direct, and WiFi Aware enable direct device-to-device communication without requiring an access point, eliminating the latency and single point of failure inherent in infrastructure mode. However, these technologies are highly platform-specific—WiFi Direct works differently on Android versus iOS, and WiFi Aware has limited availability across devices. Setup complexity is another challenge; establishing P2P connections often requires manual user intervention or complex background negotiation. Battery drain can also be significant since devices must maintain active WiFi radios even when not connected to a traditional network.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cellular Networks
&lt;/h3&gt;

&lt;p&gt;Cellular networks provide broad geographic coverage and work reliably in many environments, but they come with substantial limitations for distributed systems. Cellular connectivity is expensive, especially for high-volume data sync, and isn't always available in remote areas or indoors where signal is weak. The latency on cellular networks can be higher than local options, and metered data plans create cost concerns for applications that need frequent synchronization. Additionally, cellular connectivity depends on external infrastructure and carrier policies, making it unsuitable as a primary sync mechanism for devices that need to communicate independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wired LAN (Ethernet)
&lt;/h3&gt;

&lt;p&gt;While wired LAN (Ethernet) connections offer excellent reliability and bandwidth, they're impractical for most mobile and distributed scenarios. Devices require physical cabling, network switches, routers, and proper infrastructure setup—an overhead that makes LAN unsuitable for field operations, remote locations, or dynamic environments where devices are constantly moving. LAN is also limited by distance; cables can only extend so far without additional infrastructure investment. For truly distributed systems where devices need mobility and flexibility, LAN represents a rigid solution that conflicts with the need for adaptability.&lt;/p&gt;

&lt;p&gt;The reality: No single transport is optimal for all scenarios. The ideal solution adapts to whatever connectivity is available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Single Transport: The Case for Intelligent Multiplexing
&lt;/h2&gt;

&lt;p&gt;The fundamental insight that solves the single-transport problem is deceptively simple: run multiple transports simultaneously and intelligently select which one to use. This approach, called transport multiplexing, isn't new to networking. In the early days of the internet, hardware multiplexers from companies like Cisco were expensive, specialized devices that could cost tens or hundreds of thousands of dollars. These devices occupied entire racks and required dedicated teams to manage, making them accessible only to large enterprises with substantial infrastructure budgets.&lt;/p&gt;

&lt;p&gt;Today, the same principle is being reimplemented in software, eliminating the cost barrier that once made multiplexing an exclusive luxury. Modern applications can now dynamically select among multiple transports—WiFi, Bluetooth LE, peer-to-peer WiFi, and cellular—making intelligent decisions about which to use at any given moment based on real-time network conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Intelligent Transport Multiplexing Works: Dynamic Transport Selection
&lt;/h3&gt;

&lt;p&gt;At its core, intelligent transport multiplexing operates on a simple premise: establish connections across all available transports simultaneously, then prioritize the best performer for each data transfer. The system continuously monitors metrics like bandwidth, latency, packet loss, and connection stability across all active transports. When conditions change—a device moves out of WiFi range, a peer comes within Bluetooth distance, or cellular signal strengthens—the system automatically adapts without requiring application intervention.&lt;/p&gt;

&lt;p&gt;This approach transforms the tradeoffs inherent in individual transports from hard constraints into soft preferences. Bandwidth limitations on Bluetooth LE no longer prevent synchronization; they just mean the system will prefer WiFi when available. Platform-specific quirks with peer-to-peer WiFi become non-issues when Bluetooth LE provides a reliable fallback. The brittleness of single-transport systems gives way to resilience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Implementation: Ditto's Mesh Networking
&lt;/h2&gt;

&lt;p&gt;A concrete example of intelligent transport multiplexing in action is &lt;a href="https://docs.ditto.live/key-concepts/mesh-networking" rel="noopener noreferrer"&gt;Ditto's mesh networking&lt;/a&gt; approach. When peers with the same database ID connect to form a mesh network, they automatically use a &lt;a href="https://docs.ditto.live/key-concepts/mesh-networking#mesh-formation" rel="noopener noreferrer"&gt;mixture of communication transports&lt;/a&gt;, with Ditto prioritizing WiFi for its high bandwidth and falling back to Bluetooth LE when needed due to poor connectivity. This isn't a manual configuration—it happens automatically based on real-time network assessment.&lt;/p&gt;

&lt;p&gt;The key advantage of this approach is that unlike typical home networks with a star topology where all devices connect to a central access point, a peer-to-peer mesh network offers multiple pathways for communication. This architectural difference matters profoundly: when one transport becomes unavailable or degrades, alternative paths remain open. Data continues flowing through whatever transport remains most effective.&lt;/p&gt;

&lt;p&gt;For platform-specific implementations, Ditto leverages the best transport available on each operating system. On Apple devices, this includes Apple Wireless Direct Link (AWDL), while Android devices gain access to WiFi Aware—technologies that would be unavailable to single-transport applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Transport Selection: The Mesh Coordination Problem
&lt;/h2&gt;

&lt;p&gt;Intelligent transport multiplexing alone isn't sufficient. Once a mesh network is established, three additional challenges must be solved: discovering other nodes without complex configuration, determining what data each device should sync, and minimizing the amount of data transferred over bandwidth-constrained connections like Bluetooth LE.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic Node Discovery with Zero Configuration
&lt;/h3&gt;

&lt;p&gt;Node discovery must happen automatically. Without it, users would need to manually configure which devices join the mesh—an operational burden that defeats the purpose of building resilient systems for field operations. Effective mesh implementations use &lt;a href="https://en.wikipedia.org/wiki/Multicast_DNS" rel="noopener noreferrer"&gt;multicast DNS&lt;/a&gt; (mDNS) and similar &lt;a href="https://en.wikipedia.org/wiki/Zero-configuration_networking" rel="noopener noreferrer"&gt;zero-configuration&lt;/a&gt; technologies to automatically detect peers on local networks, allowing devices to form meshes organically without administrative overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conflict Resolution with CRDTs
&lt;/h3&gt;

&lt;p&gt;Data conflicts present another challenge in distributed systems. When devices in a mesh edit the same data independently, conflicts arise. &lt;a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type" rel="noopener noreferrer"&gt;Conflict-Free Replicated Data Types&lt;/a&gt; (CRDTs) solve this by ensuring that &lt;a href="https://docs.ditto.live/key-concepts/syncing-data#conflict-resolution" rel="noopener noreferrer"&gt;concurrent updates&lt;/a&gt; can be merged deterministically without explicit conflict resolution logic. Every peer converges to the same final state regardless of the order in which updates arrive—a property essential for maintaining consistency across a mesh without central coordination.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subscription-Based Syncing with Delta Updates
&lt;/h3&gt;

&lt;p&gt;Data synchronization efficiency requires &lt;a href="https://docs.ditto.live/key-concepts/syncing-data" rel="noopener noreferrer"&gt;subscription-based syncing&lt;/a&gt; and delta updates. Rather than synchronizing entire datasets, devices declare interest in specific data through subscription queries. When data changes, only the deltas—the actual changes—are transmitted across the network. This is critical for Bluetooth LE and other bandwidth-constrained transports; syncing only relevant changes rather than full documents can reduce traffic by orders of magnitude.&lt;/p&gt;

&lt;h1&gt;
  
  
  Ditto: A Complete Platform Solution
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://docs.ditto.live/home/about-ditto" rel="noopener noreferrer"&gt;Ditto&lt;/a&gt; provides a unified platform that solves all these coordination challenges through a mobile database with built-in edge device connectivity and resiliency. Rather than requiring teams to implement discovery, subscription-based syncing, delta replication, and conflict resolution from scratch, Ditto bundles these capabilities into &lt;a href="https://docs.ditto.live/sdk/latest/install-guides/install-guides" rel="noopener noreferrer"&gt;SDKs&lt;/a&gt; across all major platforms: Swift for iOS and macOS, Kotlin and Java for Android and JVM environments, JavaScript and TypeScript for Node.js and web browsers, React Native for cross-platform mobile development, Flutter and Dart, C# and .NET across iOS, Android, Windows, Linux, and macOS, Kotlin Multiplatform, Java for server-side applications, C++ for Linux, Android, iOS, and embedded systems, and Rust for all platforms.&lt;/p&gt;

&lt;p&gt;This breadth of language and platform support means teams can build resilient, mesh-enabled applications using their existing technology stacks without learning entirely new frameworks or imposing constraints on their technology choices. Data synchronization, node discovery, selective subscription queries, delta updates, and CRDT-based conflict resolution all work consistently across these diverse environments, enabling truly platform-agnostic mesh networking that keeps mission-critical systems online when connectivity is unpredictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Economics of Software-Based Multiplexing
&lt;/h2&gt;

&lt;p&gt;The shift from hardware multiplexers to software-based transport selection represents more than just a technical evolution; it's an economic transformation. What once required six-figure capital expenditure and specialized expertise is now built into the application layer. Development teams can implement intelligent transport selection without purchasing specialized hardware, and the logic can adapt to new transports and conditions through simple software updates.&lt;/p&gt;

&lt;p&gt;This democratization means that applications serving airline operations, healthcare facilities, construction sites, and retail environments—scenarios where connectivity is unpredictable—can now have infrastructure-grade resilience without infrastructure-grade costs. By combining intelligent transport multiplexing, automatic node discovery, subscription-based syncing with delta updates, and CRDTs for conflict resolution, modern distributed systems can operate reliably in environments where traditional architectures fail.&lt;/p&gt;

&lt;h1&gt;
  
  
  Frequently Asked Questions
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Q: What's the difference between mesh networking and WiFi mesh systems like Eero or Netgear?
&lt;/h2&gt;

&lt;p&gt;A: Traditional WiFi mesh systems extend coverage through repeaters connected to infrastructure and a cloud backend. Mesh networking in distributed systems (like Ditto) is peer-to-peer without requiring any infrastructure or central server—devices communicate directly across multiple transports, making them suitable for offline scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q: Can I implement intelligent transport selection without a platform like Ditto?
&lt;/h2&gt;

&lt;p&gt;A: Yes, but it requires implementing multiple complex systems from scratch: automatic peer discovery (mDNS), subscription query logic, delta sync algorithms, transport selection heuristics, and CRDT data structures for conflict resolution. Most teams find this represents months of engineering effort, and years of maintenance costs which is why dedicated platforms exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q: Why is CRDT important for distributed systems?
&lt;/h2&gt;

&lt;p&gt;A: CRDTs eliminate the need for central coordination when resolving data conflicts. Without them, distributed systems need to either lock data during edits (eliminating offline capability) or implement complex manual conflict resolution logic. CRDTs make eventual consistency practical and predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q: Which transport should I prioritize for my application?
&lt;/h2&gt;

&lt;p&gt;A: Start with WiFi for high-bandwidth scenarios, but always have Bluetooth LE as a fallback. If your users operate in cellular-only regions, add cellular as a third transport. The beauty of intelligent multiplexing is that you don't need to choose—the system adapts automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q: Is mesh networking more secure than cloud-based sync?
&lt;/h2&gt;

&lt;p&gt;A: Security depends on implementation, not topology. Mesh networks can be as secure as cloud systems when properly encrypted. The advantage is privacy—data stays on-device by default, only syncing between authorized peers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps: Implementing Multi-Transport Sync
&lt;/h2&gt;

&lt;p&gt;For teams building applications that operate in unpredictable network environments, transport multiplexing isn't a luxury—it's a necessity. Here's how to get started:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Evaluate your connectivity requirements. Map out where your users operate and which transports they'll encounter. Warehouse workers might need WiFi and Bluetooth LE, while field technicians require cellular fallback.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose a platform that handles the complexity. Implementing transport multiplexing, automatic node discovery, delta sync, and CRDT conflict resolution from scratch represents months of engineering effort. Platforms like &lt;a href="https://www.ditto.com" rel="noopener noreferrer"&gt;Ditto&lt;/a&gt; provide these capabilities out of the box across iOS, Android, web, and embedded systems.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Design for offline-first from day one. Assume connectivity will fail and architect your application so data remains accessible locally. Your sync strategy should be invisible to users—data should always be available whether they're online or offline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test across all transports in production-like conditions. Validate behavior on WiFi, Bluetooth LE, and cellular to ensure graceful degradation. Monitor which transports your application uses in production to optimize performance.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Start building resilient distributed systems today. The technology to implement efficient transport multiplexing now exists, making infrastructure-grade resilience accessible to organizations of any size without infrastructure-grade costs.&lt;/p&gt;

</description>
      <category>p2psync</category>
      <category>mobile</category>
      <category>architecture</category>
      <category>offlinefirst</category>
    </item>
  </channel>
</rss>
