<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Wassim Mansouri</title>
    <link>https://wassimans.com/</link>
    <description>Indie developer based in Sète, France.</description>
    <language>en-us</language>
    <copyright>&#169; 2026 Wassim Mansouri</copyright>
    <lastBuildDate>Tue, 30 Jun 2026 18:50:45 +0000</lastBuildDate>
    <atom:link href="https://wassimans.com/index.xml" rel="self" type="application/rss+xml" />
    
    
      
      
    
    
    <item>
      <title>Autographe</title>
      <link>https://wassimans.com/blog/autographe/</link>
      <pubDate>Thu, 11 Jun 2026 15:30:44 +0200</pubDate>
      <guid>https://wassimans.com/blog/autographe/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m starting a new project: &lt;a href=&#34;https://autographe.app&#34;&gt;Autographe&lt;/a&gt;. It&amp;rsquo;s a desktop app for prose writers.&lt;/p&gt;
&lt;h2 id=&#34;why-yet-another-desktop-app-for-writers-isnt-there-enough-of-them&#34;&gt;Why yet another desktop app for writers, isn&amp;rsquo;t there enough of them?&lt;/h2&gt;
&lt;p&gt;The software development industry has gone absolutely crazy with AI. Developers of all levels are using LLMs to produce code to the detriment of their hard-won skills. Not long before, we were arguing about, and almost shy talking about the myth of the &lt;a href=&#34;https://www.simplethread.com/the-10x-programmer-myth/&#34;&gt;10x programmer&lt;/a&gt;; today everyone and his dog is &lt;a href=&#34;https://9to5mac.com/2026/04/06/app-store-sees-84-surge-in-new-apps-as-ai-coding-tools-take-off/&#34;&gt;developing apps&lt;/a&gt;, they are &lt;em&gt;effectively&lt;/em&gt; a 100x developers in terms of the amount of code produced.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;m starting a new project: <a href="https://autographe.app">Autographe</a>. It&rsquo;s a desktop app for prose writers.</p>
<h2 id="why-yet-another-desktop-app-for-writers-isnt-there-enough-of-them">Why yet another desktop app for writers, isn&rsquo;t there enough of them?</h2>
<p>The software development industry has gone absolutely crazy with AI. Developers of all levels are using LLMs to produce code to the detriment of their hard-won skills. Not long before, we were arguing about, and almost shy talking about the myth of the <a href="https://www.simplethread.com/the-10x-programmer-myth/">10x programmer</a>; today everyone and his dog is <a href="https://9to5mac.com/2026/04/06/app-store-sees-84-surge-in-new-apps-as-ai-coding-tools-take-off/">developing apps</a>, they are <em>effectively</em> a 100x developers in terms of the amount of code produced.</p>
<p><strong>Autographe</strong> is like an <strong>antidote</strong> for me, an attempt to preserve my software craftsmanship and stay true to my values: producing software crafted by a human for humans. I want to do it &ldquo;à l&rsquo;ancienne&rdquo;, no Claude, no Codex, no Cursor, no LLMs, just me and my hands.</p>
<h2 id="that-was-you-what-about-writers">That was you, what about writers?</h2>
<p>I&rsquo;ve always wanted to be some sort of writer, but never got the time nor energy to work it out. Fortunately I read a lot; and as a reader, I&rsquo;ve learned that writing is a huge intellectual endeavor that comes way before the styling aspect of it, that most readers are going to judge. Unfortunately, AI <em>enshittification</em> made its effect on so many writers (or pseudo-writers should I say), and the results, even at this early point of AI history are so bad: masses of text that are banal, generic and shapeless.</p>
<p>What made things worse is that writing apps started including AI services. Writers can now be more productive (more text in less time!) thanks to AI! This is insane! I don&rsquo;t want to read &ldquo;productive&rdquo; writers text, I want them to take their time!</p>
<p>So Autographe will be my response to today&rsquo;s prose writing tools, an app that:</p>
<ul>
<li>empowers true writers to write in their own words in their keyboard</li>
<li>is local-first: no remote server</li>
<li>no AI, with intuitive file based organization</li>
<li>has a clean design optimized for flow: writers will write, the app will disappear while they write</li>
<li>offers real productivity with a modal-first editing (à la Emacs and Vim)</li>
<li>has intuitive version control powered by Git: for writers, not for developers</li>
<li>markdown based</li>
</ul>
<h2 id="autographe-for-writers-is-what-emacs-or-vim-is-for-developers">Autographe for writers is what Emacs or Vim is for developers</h2>
<p>A macOS app, built for maximum snappiness, and true/genuine writers happiness.</p>
<p>AI usage is eroding craft, I&rsquo;m building an antidote for myself and for writers who feel the same. I&rsquo;m very excited and honored to start this project. No growth hacking for this one, no build-in-public bullshit, I&rsquo;ll take my time to work on it, get feedback, optimize and use it myself. If some day, a dozen writers are happy with it, I&rsquo;ll consider myself a hero.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Autographe is text written by your own hand.
</span></span></code></pre></div>]]></content:encoded>
    </item>
    
    <item>
      <title>Building IOS Apps With Doom Emacs</title>
      <link>https://wassimans.com/blog/building-ios-apps-with-doom-emacs/</link>
      <pubDate>Thu, 21 May 2026 10:38:23 +0200</pubDate>
      <guid>https://wassimans.com/blog/building-ios-apps-with-doom-emacs/</guid>
      <description>&lt;p&gt;I shipped &lt;a href=&#34;https://speem.app&#34;&gt;SPEEM&lt;/a&gt;, my latest iOS app, from Doom Emacs. Not from Xcode.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t mean I just edited a few files in Emacs and switched back when it was time to build. I mean the whole loop: write Swift, build, boot a simulator, install the app, launch it, stream logs, restart LSP, scaffold new projects. All from inside Emacs, all driven by &lt;code&gt;SPC i&lt;/code&gt; keybindings I wrote myself.&lt;/p&gt;
&lt;p&gt;This post is about how, and why it&amp;rsquo;s even possible.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I shipped <a href="https://speem.app">SPEEM</a>, my latest iOS app, from Doom Emacs. Not from Xcode.</p>
<p>I don&rsquo;t mean I just edited a few files in Emacs and switched back when it was time to build. I mean the whole loop: write Swift, build, boot a simulator, install the app, launch it, stream logs, restart LSP, scaffold new projects. All from inside Emacs, all driven by <code>SPC i</code> keybindings I wrote myself.</p>
<p>This post is about how, and why it&rsquo;s even possible.</p>
<h2 id="tldr">TL;DR</h2>
<p>Apple ships a small army of command-line tools, <code>xcodebuild</code>, <code>xcrun simctl</code>, <code>xcrun swift-format</code>, <code>sourcekit-lsp</code>, and most iOS developers never touch them directly. Xcode is just a fancy wrapper around them (of course with many other utilities built into it). If you&rsquo;re willing to glue those tools together yourself, you can build a real iOS workflow in any editor that lets you run shell commands and read process output. Doom Emacs happens to be very, very good at that (or any Emacs distro for that matter really).</p>
<p>The result, for me, is a ~1000-line <code>modules/ios.el</code> that gives me an <code>SPC i b</code> to build, <code>SPC i s</code> to install and launch on selected simulators, <code>SPC i l</code> to stream filtered logs, <code>SPC i n</code> to scaffold a new SwiftUI project, and a few more things I&rsquo;ll walk through below.</p>
<h2 id="why-i-did-this">Why I did this</h2>
<p>I live in Emacs. I&rsquo;ve been using Doom Emacs daily for 4 years: Rust, Elixir, Kotlin/Android, web, org-mode, Magit, all of it. When I came back to iOS development for SPEEM, I tried Xcode for two weeks and it felt like wearing someone else&rsquo;s shoes. Not bad shoes. Just not mine.</p>
<p>I also have a multi-language config. I have a <code>modules/rust.el</code>, a <code>modules/elixir.el</code>, a <code>modules/kotlin-android.el</code>, a <code>modules/web.el</code>, and so on. Each language has the same shape: a major mode, LSP, keybindings for build/run/test. iOS being the one exception felt wrong.</p>
<p>So I sat down and wrote <code>modules/ios.el</code>. This is what&rsquo;s in it.</p>
<h2 id="what-apple-actually-gives-you-on-the-command-line">What Apple actually gives you on the command line</h2>
<p>Before any Emacs Lisp, it helps to know what&rsquo;s even there. When you <code>xcode-select -p</code> you get the active developer directory, which contains all the tools Xcode uses internally:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ xcode-select -p
</span></span><span style="display:flex;"><span>/Applications/Xcode.app/Contents/Developer
</span></span></code></pre></div><p>Inside that, the tools that matter for building and running an iOS app from a terminal are:</p>
<ul>
<li><strong><code>xcodebuild</code></strong>, compiles your <code>.xcodeproj</code> or <code>.xcworkspace</code>. It accepts a scheme, an SDK, a destination, and a list of actions like <code>build</code>, <code>clean</code>, <code>test</code>. This is what Xcode itself calls when you press Run.</li>
<li><strong><code>xcrun simctl</code></strong>, controls the iOS simulators. Boot, install, uninstall, launch, terminate, take screenshots, override the status bar. Anything you can do clicking around in the Simulator app, you can do with <code>simctl</code>.</li>
<li><strong><code>xcrun swift-format</code></strong>, Apple&rsquo;s official Swift formatter. Works on stdin/stdout.</li>
<li><strong><code>sourcekit-lsp</code></strong>, Apple&rsquo;s language server for Swift. Ships with Xcode at <code>Toolchains/XcodeDefault.xctoolchain/usr/bin/sourcekit-lsp</code>. This is what gives me completion, jump-to-definition, errors, refactors.</li>
<li><strong><code>xcode-build-server</code></strong>, not from Apple, but essential. It generates a <code>buildServer.json</code> that tells <code>sourcekit-lsp</code> how the project is actually configured (build flags, modules, etc). Without it, LSP works but it&rsquo;s blind to half your project. <code>brew install xcode-build-server</code>.</li>
<li><strong><code>xcodegen</code></strong>, also not from Apple. Generates an <code>.xcodeproj</code> from a small YAML file. I use it to scaffold new projects so I don&rsquo;t have to ever open Xcode&rsquo;s project editor.</li>
</ul>
<p>Once you know those exist, the rest is just orchestration.</p>
<h2 id="the-structure-of-my-doom-config">The structure of my Doom config</h2>
<p>My Doom config is split into modules. The top-level <code>config.el</code> is tiny:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span><span style="color:#75715e">;;; config.el -*- lexical-binding: t; -*-</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(setq doom-font              (<span style="color:#a6e22e">font-spec</span> :family <span style="color:#e6db74">&#34;Fira Code&#34;</span> :size <span style="color:#ae81ff">13</span> :weight <span style="color:#e6db74">&#39;semi-light</span>)
</span></span><span style="display:flex;"><span>      doom-variable-pitch-font (<span style="color:#a6e22e">font-spec</span> :family <span style="color:#e6db74">&#34;Fira Sans&#34;</span> :size <span style="color:#ae81ff">14</span>)
</span></span><span style="display:flex;"><span>      doom-theme             <span style="color:#e6db74">&#39;doom-monokai-pro</span>
</span></span><span style="display:flex;"><span>      display-line-numbers-type <span style="color:#66d9ef">t</span>
</span></span><span style="display:flex;"><span>      org-directory          <span style="color:#e6db74">&#34;~/org/&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(load! <span style="color:#e6db74">&#34;modules/core&#34;</span>)
</span></span><span style="display:flex;"><span>(load! <span style="color:#e6db74">&#34;modules/lsp&#34;</span>)
</span></span><span style="display:flex;"><span>(load! <span style="color:#e6db74">&#34;modules/rust&#34;</span>)
</span></span><span style="display:flex;"><span>(load! <span style="color:#e6db74">&#34;modules/elixir&#34;</span>)
</span></span><span style="display:flex;"><span>(load! <span style="color:#e6db74">&#34;modules/ios&#34;</span>)
</span></span><span style="display:flex;"><span>(load! <span style="color:#e6db74">&#34;modules/kotlin-android&#34;</span>)
</span></span><span style="display:flex;"><span>(load! <span style="color:#e6db74">&#34;modules/web&#34;</span>)
</span></span><span style="display:flex;"><span>(load! <span style="color:#e6db74">&#34;modules/slint&#34;</span>)
</span></span><span style="display:flex;"><span>(load! <span style="color:#e6db74">&#34;modules/projectile&#34;</span>)
</span></span><span style="display:flex;"><span>(load! <span style="color:#e6db74">&#34;modules/org&#34;</span>)
</span></span><span style="display:flex;"><span>(load! <span style="color:#e6db74">&#34;modules/tools&#34;</span>)
</span></span><span style="display:flex;"><span>(load! <span style="color:#e6db74">&#34;modules/keybindings&#34;</span>)   <span style="color:#75715e">; all map! calls live here, loaded last</span>
</span></span></code></pre></div><p>The iOS-specific bits live in <code>modules/ios.el</code>, and the keybindings for them in <code>modules/keybindings.el</code>. I keep keybindings separate from feature code so that everything <code>map!</code> is in one place, easier to grep, easier to reason about.</p>
<p>On the Doom-modules side, <code>init.el</code> enables the standard <code>(swift +lsp)</code> language module:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>:lang
</span></span><span style="display:flex;"><span>(swift +lsp)             <span style="color:#75715e">; who asked for emoji variables?</span>
</span></span></code></pre></div><p>That gives me <code>swift-mode</code> and LSP plumbing for free. Everything below is built on top of that.</p>
<h2 id="pointing-lsp-at-the-right-sourcekit-lsp">Pointing LSP at the right <code>sourcekit-lsp</code></h2>
<p>There&rsquo;s a subtlety here. There are usually two <code>sourcekit-lsp</code> binaries on a Mac: the one in <code>/Library/Developer/CommandLineTools/...</code> and the one inside the active Xcode. They&rsquo;re not always the same version, and using the wrong one can break diagnostics in confusing ways. I want the one that matches my active Xcode.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(let* ((dev   (string-trim (shell-command-to-string <span style="color:#e6db74">&#34;xcode-select -p&#34;</span>)))
</span></span><span style="display:flex;"><span>       (sklsp (<span style="color:#a6e22e">expand-file-name</span>
</span></span><span style="display:flex;"><span>               <span style="color:#e6db74">&#34;Toolchains/XcodeDefault.xctoolchain/usr/bin/sourcekit-lsp&#34;</span> dev)))
</span></span><span style="display:flex;"><span>  (setenv <span style="color:#e6db74">&#34;DEVELOPER_DIR&#34;</span> dev)
</span></span><span style="display:flex;"><span>  (setq lsp-sourcekit-executable sklsp
</span></span><span style="display:flex;"><span>        lsp-sourcekit-extra-args  <span style="color:#66d9ef">nil</span>))
</span></span></code></pre></div><p>I set <code>DEVELOPER_DIR</code> so any subprocess (<code>xcodebuild</code>, <code>xcrun ...</code>) inherits the same Xcode. Then I pin <code>lsp-sourcekit-executable</code> to that exact path.</p>
<p>For <code>swift-mode</code> itself the setup is small, turn on <code>lsp-deferred</code>, disable inlay hints (I find them noisy), set indentation to 4 spaces:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(use-package! swift-mode
</span></span><span style="display:flex;"><span>  :mode <span style="color:#e6db74">&#34;\\.swift\\&#39;&#34;</span>
</span></span><span style="display:flex;"><span>  :hook ((swift-mode <span style="color:#f92672">.</span> lsp-deferred)
</span></span><span style="display:flex;"><span>         (swift-mode <span style="color:#f92672">.</span> (lambda () (setq-local lsp-inlay-hint-enable <span style="color:#66d9ef">nil</span>))))
</span></span><span style="display:flex;"><span>  :config
</span></span><span style="display:flex;"><span>  (setq swift-mode:basic-offset <span style="color:#ae81ff">4</span>
</span></span><span style="display:flex;"><span>        indent-tabs-mode         <span style="color:#66d9ef">nil</span>))
</span></span></code></pre></div><p>Format-on-save is handled by <code>apheleia</code>, calling <code>swift-format</code> via <code>xcrun</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(after! apheleia
</span></span><span style="display:flex;"><span>  (setf (alist-get <span style="color:#e6db74">&#39;swift-format</span> apheleia-formatters)
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#39;</span>(<span style="color:#e6db74">&#34;xcrun&#34;</span> <span style="color:#e6db74">&#34;swift-format&#34;</span> <span style="color:#e6db74">&#34;format&#34;</span> <span style="color:#e6db74">&#34;--assume-filename&#34;</span> filepath <span style="color:#e6db74">&#34;-&#34;</span>))
</span></span><span style="display:flex;"><span>  (setf (alist-get <span style="color:#e6db74">&#39;swift-mode</span> apheleia-mode-alist) <span style="color:#f92672">&#39;</span>(swift-format)))
</span></span><span style="display:flex;"><span>(add-hook <span style="color:#e6db74">&#39;swift-mode-hook</span> <span style="color:#a6e22e">#&#39;</span>apheleia-mode)
</span></span></code></pre></div><p>That&rsquo;s it for the editor surface. Open a <code>.swift</code> file, get completion, jump-to-def, errors, format on save. Nothing exotic so far.</p>
<h2 id="auto-generating-buildserverjson">Auto-generating <code>buildServer.json</code></h2>
<p>This is the part that surprises people. Out of the box, <code>sourcekit-lsp</code> doesn&rsquo;t know what your project&rsquo;s build flags are. It guesses. The fix is <code>xcode-build-server</code>: it reads your <code>.xcodeproj</code>, asks <code>xcodebuild</code> how the project actually compiles, and writes a <code>buildServer.json</code> that LSP can read.</p>
<p>I don&rsquo;t want to remember to run it. So I hook it into <code>swift-mode</code>: the first time I open a Swift file in a project without a <code>buildServer.json</code>, generate one.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(defun wassim/+swift-ensure-build-server-json ()
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Generate buildServer.json for the current Xcode project if missing.&#34;</span>
</span></span><span style="display:flex;"><span>  (let* ((root      (wassim/swift-xcode-root))
</span></span><span style="display:flex;"><span>         (bsp-file  (<span style="color:#a6e22e">expand-file-name</span> <span style="color:#e6db74">&#34;buildServer.json&#34;</span> root))
</span></span><span style="display:flex;"><span>         (xcodeproj (<span style="color:#a6e22e">car</span> (<span style="color:#a6e22e">directory-files</span> root <span style="color:#66d9ef">t</span> <span style="color:#e6db74">&#34;\\.xcodeproj\\&#39;&#34;</span>))))
</span></span><span style="display:flex;"><span>    (when (and xcodeproj (not (<span style="color:#a6e22e">file-exists-p</span> bsp-file)))
</span></span><span style="display:flex;"><span>      (let* ((proj-name (file-name-base xcodeproj))
</span></span><span style="display:flex;"><span>             (cmd       (<span style="color:#a6e22e">format</span>
</span></span><span style="display:flex;"><span>                         <span style="color:#e6db74">&#34;cd %s &amp;&amp; xcode-build-server config -project %s.xcodeproj -scheme %s&#34;</span>
</span></span><span style="display:flex;"><span>                         (shell-quote-argument root)
</span></span><span style="display:flex;"><span>                         (shell-quote-argument proj-name)
</span></span><span style="display:flex;"><span>                         (shell-quote-argument proj-name))))
</span></span><span style="display:flex;"><span>        (when (executable-find <span style="color:#e6db74">&#34;xcode-build-server&#34;</span>)
</span></span><span style="display:flex;"><span>          (<span style="color:#a6e22e">message</span> <span style="color:#e6db74">&#34;Generating buildServer.json for sourcekit-lsp...&#34;</span>)
</span></span><span style="display:flex;"><span>          (shell-command cmd)
</span></span><span style="display:flex;"><span>          (<span style="color:#a6e22e">message</span> <span style="color:#e6db74">&#34;✅ buildServer.json generated. Restart LSP with SPC l R&#34;</span>))))))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(add-hook <span style="color:#e6db74">&#39;swift-mode-hook</span> <span style="color:#a6e22e">#&#39;</span>wassim/+swift-ensure-build-server-json)
</span></span></code></pre></div><p>The companion command for when something changes (new file added, scheme renamed) is bound to <code>SPC i g</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(defun wassim/swift-regenerate-build-server ()
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Manually regenerate buildServer.json for the current Xcode project.&#34;</span>
</span></span><span style="display:flex;"><span>  (interactive)
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">;; ... deletes the existing file and re-runs xcode-build-server ...</span>
</span></span><span style="display:flex;"><span>  )
</span></span></code></pre></div><h2 id="finding-the-project-root">Finding the project root</h2>
<p>This one is more annoying than it sounds. The &ldquo;root&rdquo; of an iOS project is the directory containing the <code>.xcworkspace</code> or <code>.xcodeproj</code>. But if your cursor is somewhere deep inside <code>App/Features/Auth/Views/LoginView.swift</code>, you have to climb up to find it. Worse, if you happen to be editing a file <em>inside</em> the <code>.xcodeproj</code> bundle (which happens, Xcode stores some files there), you need to climb out of the bundle first.</p>
<p>I wrote a small set of helpers for this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(defun wassim/xcode--unbundle (dir)
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Climb out if DIR is inside a .xcodeproj/.xcworkspace bundle.&#34;</span>
</span></span><span style="display:flex;"><span>  (let ((d (<span style="color:#a6e22e">file-name-as-directory</span> (<span style="color:#a6e22e">expand-file-name</span> dir))))
</span></span><span style="display:flex;"><span>    (while (string-match-p <span style="color:#e6db74">&#34;\\.xcodeproj/\\|\\.xcworkspace/&#34;</span> d)
</span></span><span style="display:flex;"><span>      (setq d (<span style="color:#a6e22e">file-name-directory</span> (<span style="color:#a6e22e">directory-file-name</span> d))))
</span></span><span style="display:flex;"><span>    d))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(defun wassim/swift-xcode-root ()
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Project root containing a top-level .xcworkspace or .xcodeproj.&#34;</span>
</span></span><span style="display:flex;"><span>  (let* ((start (wassim/xcode--unbundle
</span></span><span style="display:flex;"><span>                 (if <span style="color:#a6e22e">buffer-file-name</span>
</span></span><span style="display:flex;"><span>                     (<span style="color:#a6e22e">file-name-directory</span> <span style="color:#a6e22e">buffer-file-name</span>)
</span></span><span style="display:flex;"><span>                   default-directory)))
</span></span><span style="display:flex;"><span>         (root  (or (locate-dominating-file
</span></span><span style="display:flex;"><span>                     start
</span></span><span style="display:flex;"><span>                     (lambda (dir)
</span></span><span style="display:flex;"><span>                       (or (wassim/xcode--find-top-workspace dir)
</span></span><span style="display:flex;"><span>                           (wassim/xcode--find-project dir))))
</span></span><span style="display:flex;"><span>                    (vc-root-dir)
</span></span><span style="display:flex;"><span>                    (ignore-errors (projectile-project-root))
</span></span><span style="display:flex;"><span>                    default-directory)))
</span></span><span style="display:flex;"><span>    (<span style="color:#a6e22e">file-name-as-directory</span> (<span style="color:#a6e22e">expand-file-name</span> root))))
</span></span></code></pre></div><p>Everything that builds, runs, installs, or talks to the simulator calls <code>wassim/swift-xcode-root</code> first. I also force <code>default-directory</code> to that root for any Swift buffer, so <code>M-x compile</code> and friends always behave correctly:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(add-hook <span style="color:#e6db74">&#39;swift-mode-hook</span>              <span style="color:#a6e22e">#&#39;</span>wassim/swift-set-project-root-default-directory)
</span></span><span style="display:flex;"><span>(add-hook <span style="color:#e6db74">&#39;find-file-hook</span>               <span style="color:#a6e22e">#&#39;</span>wassim/swift-set-project-root-default-directory)
</span></span><span style="display:flex;"><span>(add-hook <span style="color:#e6db74">&#39;after-change-major-mode-hook</span> <span style="color:#a6e22e">#&#39;</span>wassim/swift-set-project-root-default-directory)
</span></span></code></pre></div><h2 id="building">Building</h2>
<p>The build command is just <code>xcodebuild</code> with the right arguments. There are two non-obvious things to get right:</p>
<ol>
<li>Pick the <code>.xcworkspace</code> if there is one (CocoaPods / SPM projects), otherwise the <code>.xcodeproj</code>.</li>
<li>Pick a scheme. If the project has shared schemes (in <code>xcshareddata/xcschemes</code>), use them. Otherwise fall back to the user&rsquo;s schemes or the target name.</li>
</ol>
<p>I have a helper <code>wassim/xcode--container+selector</code> that returns <code>(kind container selector-flag selector-value)</code> so the rest of the code doesn&rsquo;t have to care:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(defun wassim/xcode--container+selector ()
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Return (kind container selector sel-arg), using a scheme when available.&#34;</span>
</span></span><span style="display:flex;"><span>  (let* ((root   (wassim/swift-xcode-root))
</span></span><span style="display:flex;"><span>         (ws     (wassim/xcode--find-top-workspace root))
</span></span><span style="display:flex;"><span>         (proj   (wassim/xcode--find-project root))
</span></span><span style="display:flex;"><span>         (kind   (if ws <span style="color:#e6db74">&#34;workspace&#34;</span> <span style="color:#e6db74">&#34;project&#34;</span>))
</span></span><span style="display:flex;"><span>         (cont   (or ws proj (<span style="color:#a6e22e">user-error</span> <span style="color:#e6db74">&#34;No top-level .xcworkspace/.xcodeproj in %s&#34;</span> root)))
</span></span><span style="display:flex;"><span>         (scheme (wassim/xcode--prefer-scheme cont kind)))
</span></span><span style="display:flex;"><span>    (if scheme
</span></span><span style="display:flex;"><span>        (<span style="color:#a6e22e">list</span> kind cont <span style="color:#e6db74">&#34;-scheme&#34;</span> scheme)
</span></span><span style="display:flex;"><span>      (<span style="color:#a6e22e">list</span> <span style="color:#e6db74">&#34;project&#34;</span> (or proj cont) <span style="color:#e6db74">&#34;-target&#34;</span> (file-name-base cont)))))
</span></span></code></pre></div><p>The build itself is <code>SPC i b</code>. It resolves Swift Package dependencies first, then builds, then (on success) restarts <code>sourcekit-lsp</code> so the diagnostics refresh immediately:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(defun wassim/ios-build (<span style="color:#66d9ef">&amp;optional</span> clean)
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Resolve packages then build. C-u to clean first.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">Restarts sourcekit-lsp on success so diagnostics refresh immediately.&#34;</span>
</span></span><span style="display:flex;"><span>  (interactive <span style="color:#e6db74">&#34;P&#34;</span>)
</span></span><span style="display:flex;"><span>  (cl-destructuring-bind (kind cont sel selarg) (wassim/xcode--container+selector)
</span></span><span style="display:flex;"><span>    (let* ((root      (wassim/swift-xcode-root))
</span></span><span style="display:flex;"><span>           (dest      (shell-quote-argument (wassim/xcode--dest)))
</span></span><span style="display:flex;"><span>           (clean-cmd (when clean
</span></span><span style="display:flex;"><span>                        (<span style="color:#a6e22e">format</span> <span style="color:#e6db74">&#34;xcodebuild -%s %s %s %s -configuration Debug -sdk iphonesimulator -destination %s clean&#34;</span>
</span></span><span style="display:flex;"><span>                                kind (shell-quote-argument cont)
</span></span><span style="display:flex;"><span>                                sel  (shell-quote-argument selarg)
</span></span><span style="display:flex;"><span>                                dest)))
</span></span><span style="display:flex;"><span>           (resolve   <span style="color:#e6db74">&#34;xcodebuild -resolvePackageDependencies&#34;</span>)
</span></span><span style="display:flex;"><span>           (build     (<span style="color:#a6e22e">format</span>
</span></span><span style="display:flex;"><span>                       <span style="color:#e6db74">&#34;xcodebuild -%s %s %s %s -configuration Debug -sdk iphonesimulator -destination %s COMPILER_INDEX_STORE_ENABLE=YES build&#34;</span>
</span></span><span style="display:flex;"><span>                       kind (shell-quote-argument cont)
</span></span><span style="display:flex;"><span>                       sel  (shell-quote-argument selarg)
</span></span><span style="display:flex;"><span>                       dest))
</span></span><span style="display:flex;"><span>           (script    (string-join (<span style="color:#a6e22e">delq</span> <span style="color:#66d9ef">nil</span> (<span style="color:#a6e22e">list</span> (<span style="color:#a6e22e">format</span> <span style="color:#e6db74">&#34;cd %s&#34;</span> (shell-quote-argument root))
</span></span><span style="display:flex;"><span>                                                   clean-cmd resolve build))
</span></span><span style="display:flex;"><span>                                   <span style="color:#e6db74">&#34; &amp;&amp; &#34;</span>)))
</span></span><span style="display:flex;"><span>      (let ((buf (compile script)))
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">;; on success: restart LSP workspaces so diagnostics pick up the new build</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">;; ...</span>
</span></span><span style="display:flex;"><span>        ))))
</span></span></code></pre></div><p><code>COMPILER_INDEX_STORE_ENABLE=YES</code> is important: it tells the Swift compiler to write a clang-style index store to DerivedData, which I then point <code>sourcekit-lsp</code>&rsquo;s embedded <code>clangd</code> at. That gives me proper cross-file &ldquo;find references&rdquo; instead of textual guesses.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(defun wassim/+swift-point-clangd-to-index ()
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Point SourceKit&#39;s embedded clangd at Xcode&#39;s Index Store (buffer-local).&#34;</span>
</span></span><span style="display:flex;"><span>  (let* ((store (wassim/+swift-index-store-path))
</span></span><span style="display:flex;"><span>         (arg   (<span style="color:#a6e22e">concat</span> <span style="color:#e6db74">&#34;--index-store-path=&#34;</span> store)))
</span></span><span style="display:flex;"><span>    (setq-local lsp-sourcekit-extra-args
</span></span><span style="display:flex;"><span>                (<span style="color:#a6e22e">append</span> lsp-sourcekit-extra-args
</span></span><span style="display:flex;"><span>                        (when (<span style="color:#a6e22e">file-directory-p</span> store)
</span></span><span style="display:flex;"><span>                          (<span style="color:#a6e22e">list</span> <span style="color:#e6db74">&#34;-Xclangd&#34;</span> arg))))))
</span></span><span style="display:flex;"><span>(add-hook <span style="color:#e6db74">&#39;swift-mode-hook</span> <span style="color:#a6e22e">#&#39;</span>wassim/+swift-point-clangd-to-index)
</span></span></code></pre></div><p>Output goes into a regular <code>*compilation*</code> buffer. <code>xcodebuild</code> errors are clickable. <code>SPC c x</code> jumps to the next error, just like in any other language.</p>
<h2 id="picking-simulators">Picking simulators</h2>
<p>I run my app on several simulators at once. SPEEM ships in both English and Arabic, on iPhone and iPad. So my &ldquo;run&rdquo; command isn&rsquo;t &ldquo;send to one simulator&rdquo;, it&rsquo;s &ldquo;pick from this list, possibly several, and deploy to all of them&rdquo;.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(defvar wassim/ios-simulators
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#39;</span>(<span style="color:#e6db74">&#34;Wassim iPad AR (26.2)&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;Wassim iPad EN (26.2)&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;Wassim AR (26.2)&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;Wassim EN (26.2)&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;Tamim iPad AR (26.2)&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;Tamim iPad EN (26.2)&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;Tamim AR (26.2)&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;Tamim EN (26.2)&#34;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Named simulators available for multi-sim run commands.&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(defun wassim/ios--pick-simulators ()
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Prompt for one or more simulators using </span><span style="color:#e6db74">`completing-read-multiple&#39;</span><span style="color:#e6db74">.&#34;</span>
</span></span><span style="display:flex;"><span>  (let ((selected (completing-read-multiple
</span></span><span style="display:flex;"><span>                   <span style="color:#e6db74">&#34;Simulators: &#34;</span>
</span></span><span style="display:flex;"><span>                   wassim/ios-simulators
</span></span><span style="display:flex;"><span>                   <span style="color:#66d9ef">nil</span> <span style="color:#66d9ef">t</span>)))
</span></span><span style="display:flex;"><span>    (unless selected (<span style="color:#a6e22e">user-error</span> <span style="color:#e6db74">&#34;No simulators selected&#34;</span>))
</span></span><span style="display:flex;"><span>    selected))
</span></span></code></pre></div><p>With Vertico (Doom&rsquo;s default completion), <code>completing-read-multiple</code> is a real pleasure: I type &ldquo;ipad&rdquo;, I get the iPad ones, I press <code>TAB</code> to select more than one. That&rsquo;s the picker.</p>
<p>Booting a simulator by name resolves its UDID via <code>simctl list devices -j</code> (JSON output), then <code>simctl boot</code> plus <code>simctl bootstatus -b</code> to wait until it&rsquo;s actually ready:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(defun wassim/ios--boot-sim (name)
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Boot simulator NAME if not already booted. Returns its UDID.&#34;</span>
</span></span><span style="display:flex;"><span>  (let ((udid (wassim/ios--sim-udid name)))
</span></span><span style="display:flex;"><span>    (unless udid (<span style="color:#a6e22e">user-error</span> <span style="color:#e6db74">&#34;Simulator %s not found&#34;</span> name))
</span></span><span style="display:flex;"><span>    (<span style="color:#a6e22e">start-process</span> <span style="color:#e6db74">&#34;sim&#34;</span> <span style="color:#66d9ef">nil</span> <span style="color:#e6db74">&#34;open&#34;</span> <span style="color:#e6db74">&#34;-a&#34;</span> <span style="color:#e6db74">&#34;Simulator&#34;</span>)
</span></span><span style="display:flex;"><span>    (<span style="color:#a6e22e">call-process</span> <span style="color:#e6db74">&#34;bash&#34;</span> <span style="color:#66d9ef">nil</span> <span style="color:#66d9ef">nil</span> <span style="color:#66d9ef">nil</span> <span style="color:#e6db74">&#34;-lc&#34;</span>
</span></span><span style="display:flex;"><span>                  (<span style="color:#a6e22e">format</span> <span style="color:#e6db74">&#34;xcrun simctl boot %s 2&gt;/dev/null; xcrun simctl bootstatus %s -b 2&gt;/dev/null || true&#34;</span>
</span></span><span style="display:flex;"><span>                          (shell-quote-argument udid)
</span></span><span style="display:flex;"><span>                          (shell-quote-argument udid)))
</span></span><span style="display:flex;"><span>    (wassim/ios--apply-clean-statusbar udid)
</span></span><span style="display:flex;"><span>    udid))
</span></span></code></pre></div><p>The status-bar override on the last line is a small luxury: every booted simulator gets the App Store screenshot look applied automatically, <code>9:41</code>, full bars, 100% battery, no carrier name. That way any screenshot I take during development is already App Store-ready. There&rsquo;s a toggle, <code>wassim/ios-clean-statusbar-on-boot</code>, and an interactive command <code>wassim/ios-statusbar-clear</code> (bound to <code>SPC i T</code>) to undo it.</p>
<h2 id="running">Running</h2>
<p><code>SPC i s</code> is &ldquo;build + install + launch on all selected simulators&rdquo;. The flow is:</p>
<ol>
<li>Pick simulators.</li>
<li>Boot each one in parallel.</li>
<li>Build for <code>iphonesimulator</code>, destined to the first sim&rsquo;s UDID.</li>
<li>Find the resulting <code>.app</code> in DerivedData (newest one wins).</li>
<li>Read its bundle ID from <code>Info.plist</code> via <code>PlistBuddy</code>.</li>
<li>For each booted sim: terminate the old instance, install the new <code>.app</code>, launch it.</li>
<li>Start log streams (more on this below).</li>
</ol>
<p>The whole thing runs in a single <code>compile</code> invocation chained with <code>&amp;&amp;</code>, so any failure halts the chain and shows up in the compilation buffer:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(defun wassim/ios-run ()
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Build for simulator then install &amp; launch on selected simulators.&#34;</span>
</span></span><span style="display:flex;"><span>  (interactive)
</span></span><span style="display:flex;"><span>  (let ((sims (wassim/ios--pick-simulators)))
</span></span><span style="display:flex;"><span>    (cl-destructuring-bind (kind cont sel selarg) (wassim/xcode--container+selector)
</span></span><span style="display:flex;"><span>      (let* ((root    (wassim/swift-xcode-root))
</span></span><span style="display:flex;"><span>             (udids   (<span style="color:#a6e22e">mapcar</span> <span style="color:#a6e22e">#&#39;</span>wassim/ios--boot-sim sims))
</span></span><span style="display:flex;"><span>             (build   (<span style="color:#a6e22e">format</span>
</span></span><span style="display:flex;"><span>                       <span style="color:#e6db74">&#34;xcodebuild -%s %s %s %s -configuration Debug -sdk iphonesimulator -destination %s COMPILER_INDEX_STORE_ENABLE=YES build&#34;</span>
</span></span><span style="display:flex;"><span>                       kind (shell-quote-argument cont)
</span></span><span style="display:flex;"><span>                       sel  (shell-quote-argument selarg)
</span></span><span style="display:flex;"><span>                       (shell-quote-argument (wassim/xcode--dest (<span style="color:#a6e22e">car</span> udids)))))
</span></span><span style="display:flex;"><span>             (deploy  (cl-loop for udid in udids
</span></span><span style="display:flex;"><span>                               for name in sims
</span></span><span style="display:flex;"><span>                               <span style="color:#a6e22e">append</span>
</span></span><span style="display:flex;"><span>                               (<span style="color:#a6e22e">list</span>
</span></span><span style="display:flex;"><span>                                (<span style="color:#a6e22e">format</span> <span style="color:#e6db74">&#34;echo &#39;── Installing on %s ──&#39;&#34;</span> name)
</span></span><span style="display:flex;"><span>                                (<span style="color:#a6e22e">format</span> <span style="color:#e6db74">&#34;xcrun simctl terminate %s \&#34;$BID\&#34; &gt;/dev/null 2&gt;&amp;1 || true&#34;</span>
</span></span><span style="display:flex;"><span>                                        (shell-quote-argument udid))
</span></span><span style="display:flex;"><span>                                (<span style="color:#a6e22e">format</span> <span style="color:#e6db74">&#34;xcrun simctl install %s \&#34;$APP\&#34;&#34;</span> (shell-quote-argument udid))))))
</span></span><span style="display:flex;"><span>        (let ((buf (compile
</span></span><span style="display:flex;"><span>                    (<span style="color:#a6e22e">mapconcat</span>
</span></span><span style="display:flex;"><span>                     <span style="color:#a6e22e">#&#39;identity</span>
</span></span><span style="display:flex;"><span>                     (<span style="color:#a6e22e">append</span>
</span></span><span style="display:flex;"><span>                      (<span style="color:#a6e22e">list</span> (<span style="color:#a6e22e">format</span> <span style="color:#e6db74">&#34;cd %s&#34;</span> (shell-quote-argument root))
</span></span><span style="display:flex;"><span>                            <span style="color:#e6db74">&#34;xcodebuild -resolvePackageDependencies&#34;</span>
</span></span><span style="display:flex;"><span>                            build
</span></span><span style="display:flex;"><span>                            <span style="color:#e6db74">&#34;APP=$(ls -dt ~/Library/Developer/Xcode/DerivedData/*/Build/Products/Debug-iphonesimulator/*.app 2&gt;/dev/null | head -1)&#34;</span>
</span></span><span style="display:flex;"><span>                            <span style="color:#e6db74">&#34;[ -n \&#34;$APP\&#34; ] || { echo &#39;No .app found in DerivedData&#39;; exit 1; }&#34;</span>
</span></span><span style="display:flex;"><span>                            <span style="color:#e6db74">&#34;BID=$(/usr/libexec/PlistBuddy -c &#39;Print :CFBundleIdentifier&#39; \&#34;$APP/Info.plist\&#34;)&#34;</span>)
</span></span><span style="display:flex;"><span>                      deploy)
</span></span><span style="display:flex;"><span>                     <span style="color:#e6db74">&#34; &amp;&amp; &#34;</span>))))
</span></span><span style="display:flex;"><span>          (wassim/ios-logs--hook-after-compile buf sims udids))))))
</span></span></code></pre></div><p>There&rsquo;s a sibling, <code>SPC i S</code>, that does the same but uninstalls first, useful when you want to wipe app state (Core Data, UserDefaults, etc.) and start fresh.</p>
<h2 id="auto-reload">Auto-reload</h2>
<p>For tight UI iteration I have <code>SPC i r</code>, which uses <code>fd</code> and <code>entr</code> to rebuild and redeploy whenever a <code>.swift</code> file changes:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(defun wassim/ios-autoreload-start ()
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Watch Swift files and auto build/install/launch on selected simulators.&#34;</span>
</span></span><span style="display:flex;"><span>  (interactive)
</span></span><span style="display:flex;"><span>  (unless (executable-find <span style="color:#e6db74">&#34;entr&#34;</span>) (<span style="color:#a6e22e">user-error</span> <span style="color:#e6db74">&#34;Missing `entr` (brew install entr)&#34;</span>))
</span></span><span style="display:flex;"><span>  (unless (executable-find <span style="color:#e6db74">&#34;fd&#34;</span>)   (<span style="color:#a6e22e">user-error</span> <span style="color:#e6db74">&#34;Missing `fd` (brew install fd)&#34;</span>))
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">;; ... pick sims, boot them, build/install/launch ...</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">;; ... then: fd -t f -e swift | entr -r sh -c &#39;&lt;full build+deploy command&gt;&#39;</span>
</span></span><span style="display:flex;"><span>  )
</span></span></code></pre></div><p>It&rsquo;s a poor man&rsquo;s hot-reload, but for iterating on layouts and small SwiftUI tweaks it works really well. <code>SPC i R</code> (capital) stops it.</p>
<h2 id="logs">Logs</h2>
<p>This was the part where I really started to feel the &ldquo;Emacs is a buffer manager&rdquo; thing pay off. Each simulator gets its own log buffer, <code>*iOS Logs: &lt;name&gt;*</code>, and I can have several open at once side by side. Logs come from two sources:</p>
<ol>
<li><strong>stdout/stderr of the app process</strong>, via <code>simctl launch --console-pty</code>. That gets me every <code>print()</code> call.</li>
<li><strong>The system log stream</strong>, via <code>simctl spawn UDID log stream --predicate 'process == &quot;MyApp&quot;'</code>. That gets me errors, warnings, and <code>os_log</code> output.</li>
</ol>
<p>Raw simulator logs are extremely noisy, most of it is internal Apple chatter you don&rsquo;t care about. So I run everything through a whitelist filter:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(defun wassim/ios-logs--keep-p (line)
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Return non-nil if LINE should be shown.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">Whitelist approach: only show app prints, errors, warnings, and CloudKit issues.&#34;</span>
</span></span><span style="display:flex;"><span>  (let ((lc (<span style="color:#a6e22e">downcase</span> line)))
</span></span><span style="display:flex;"><span>    (or
</span></span><span style="display:flex;"><span>     <span style="color:#75715e">;; App print() statements (emoji markers or [PRINT] tag)</span>
</span></span><span style="display:flex;"><span>     (string-match-p <span style="color:#e6db74">&#34;✅\\|❌\\|⚠️\\|📡\\|📜\\|☁️\\|📝\\|🆕\\|📊\\|🔍\\|📣\\|\\[PRINT\\]&#34;</span> line)
</span></span><span style="display:flex;"><span>     <span style="color:#75715e">;; Errors and faults</span>
</span></span><span style="display:flex;"><span>     (string-match-p <span style="color:#e6db74">&#34;\\bfault\\b\\|\\bfatal\\b\\|\\bpanic\\b\\|\\bcrash\\b\\|\\bassertion failed\\b\\|\\bterminat\\w*\\b&#34;</span> lc)
</span></span><span style="display:flex;"><span>     (string-match-p <span style="color:#e6db74">&#34;\\berror\\b\\|\\bfailed\\b\\|\\bexception\\b&#34;</span> lc)
</span></span><span style="display:flex;"><span>     <span style="color:#75715e">;; Warnings</span>
</span></span><span style="display:flex;"><span>     (string-match-p <span style="color:#e6db74">&#34;\\bwarn\\b&#34;</span> lc)
</span></span><span style="display:flex;"><span>     <span style="color:#75715e">;; CloudKit problems specifically</span>
</span></span><span style="display:flex;"><span>     (and (string-match-p <span style="color:#e6db74">&#34;cloudkit&#34;</span> lc)
</span></span><span style="display:flex;"><span>          (string-match-p <span style="color:#e6db74">&#34;\\bbad container\\b\\|\\berror\\b\\|\\bfailed\\b\\|\\bretry\\b\\|\\bcouldn&#39;?t\\b&#34;</span> lc)))))
</span></span></code></pre></div><p>The emoji prefixes come from a convention I keep in my Swift code: <code>print(&quot;✅ user signed in&quot;)</code>, <code>print(&quot;❌ failed to sync: \(err)&quot;)</code>, <code>print(&quot;☁️ CloudKit sync started&quot;)</code>. The filter recognises them as &ldquo;this came from my code, definitely keep it&rdquo;. Errors and warnings always come through. Apple&rsquo;s <code>nw_connection</code>, <code>BackBoardServices</code>, <code>RunningBoard</code> noise gets dropped.</p>
<p>Each line is then colored by severity:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(defun wassim/ios-logs--face (lvl)
</span></span><span style="display:flex;"><span>  (pcase lvl
</span></span><span style="display:flex;"><span>    (<span style="color:#e6db74">&#39;error</span> <span style="color:#e6db74">&#39;compilation-error</span>)
</span></span><span style="display:flex;"><span>    (<span style="color:#e6db74">&#39;warn</span>  <span style="color:#e6db74">&#39;compilation-warning</span>)
</span></span><span style="display:flex;"><span>    (_      <span style="color:#e6db74">&#39;success</span>)))
</span></span></code></pre></div><p><code>SPC i l</code> starts the streams (against whichever simulator is currently booted), <code>SPC i L</code> stops them, <code>SPC i c</code> clears the buffers.</p>
<h2 id="scaffolding-a-new-project">Scaffolding a new project</h2>
<p><code>SPC i n</code> runs <code>wassim/ios-new-project</code>, which prompts for a name, bundle ID prefix, deployment target, and parent directory, then writes a minimal SwiftUI app and a <code>project.yml</code> for XcodeGen:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span><span style="color:#75715e">;; ── project.yml (XcodeGen spec) ──</span>
</span></span><span style="display:flex;"><span>(<span style="color:#a6e22e">write-region</span>
</span></span><span style="display:flex;"><span> (<span style="color:#a6e22e">format</span> <span style="color:#e6db74">&#34;name: %s
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">options:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  bundleIdPrefix: %s
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  deploymentTarget:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    iOS: \&#34;%s\&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">targets:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  %s:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    type: application
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    platform: iOS
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    sources:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - %s
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    info:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      path: %s/Info.plist
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    settings:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      INFOPLIST_KEY_UIApplicationSceneManifest_Generation: YES
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents: YES
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      INFOPLIST_KEY_UILaunchScreen_Generation: YES
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad: \&#34;...\&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone: \&#34;...\&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      SWIFT_EMIT_LOC_STRINGS: YES
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;</span> name prefix target name name name)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">nil</span> (<span style="color:#a6e22e">expand-file-name</span> <span style="color:#e6db74">&#34;project.yml&#34;</span> root))
</span></span></code></pre></div><p>Then it shells out to <code>xcodegen generate</code> to produce the <code>.xcodeproj</code>, runs <code>xcode-build-server config</code> to write the initial <code>buildServer.json</code>, and opens the freshly-created <code>ContentView.swift</code>. By the time I see the file, LSP is already alive and <code>SPC i b</code> builds.</p>
<p>I never have to open Xcode&rsquo;s project editor.</p>
<h2 id="the-keybindings-all-in-one-place">The keybindings, all in one place</h2>
<p>This is the whole iOS prefix, lifted verbatim from <code>modules/keybindings.el</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(map! :leader
</span></span><span style="display:flex;"><span>      (:prefix (<span style="color:#e6db74">&#34;i&#34;</span> <span style="color:#f92672">.</span> <span style="color:#e6db74">&#34;iOS build/run&#34;</span>)
</span></span><span style="display:flex;"><span>       :desc <span style="color:#e6db74">&#34;Build (resolve+build, restart LSP)&#34;</span>     <span style="color:#e6db74">&#34;b&#34;</span> <span style="color:#a6e22e">#&#39;</span>wassim/ios-build
</span></span><span style="display:flex;"><span>       :desc <span style="color:#e6db74">&#34;Clean+Build&#34;</span>                            <span style="color:#e6db74">&#34;B&#34;</span> (cmd! (let ((current-prefix-arg <span style="color:#f92672">&#39;</span>(<span style="color:#ae81ff">4</span>)))
</span></span><span style="display:flex;"><span>                                                                  (<span style="color:#a6e22e">call-interactively</span> <span style="color:#a6e22e">#&#39;</span>wassim/ios-build)))
</span></span><span style="display:flex;"><span>       :desc <span style="color:#e6db74">&#34;Clean + wipe index&#34;</span>                     <span style="color:#e6db74">&#34;k&#34;</span> <span style="color:#a6e22e">#&#39;</span>wassim/ios-clean
</span></span><span style="display:flex;"><span>       :desc <span style="color:#e6db74">&#34;Run (build+install+launch)&#34;</span>             <span style="color:#e6db74">&#34;s&#34;</span> <span style="color:#a6e22e">#&#39;</span>wassim/ios-run
</span></span><span style="display:flex;"><span>       :desc <span style="color:#e6db74">&#34;Run FRESH (wipe state)&#34;</span>                 <span style="color:#e6db74">&#34;S&#34;</span> <span style="color:#a6e22e">#&#39;</span>wassim/ios-run-fresh
</span></span><span style="display:flex;"><span>       :desc <span style="color:#e6db74">&#34;Auto-reload start&#34;</span>                      <span style="color:#e6db74">&#34;r&#34;</span> <span style="color:#a6e22e">#&#39;</span>wassim/ios-autoreload-start
</span></span><span style="display:flex;"><span>       :desc <span style="color:#e6db74">&#34;Auto-reload stop&#34;</span>                       <span style="color:#e6db74">&#34;R&#34;</span> <span style="color:#a6e22e">#&#39;</span>wassim/ios-autoreload-stop
</span></span><span style="display:flex;"><span>       :desc <span style="color:#e6db74">&#34;Logs (Xcode-like)&#34;</span>                      <span style="color:#e6db74">&#34;l&#34;</span> <span style="color:#a6e22e">#&#39;</span>wassim/ios-logs-start
</span></span><span style="display:flex;"><span>       :desc <span style="color:#e6db74">&#34;Logs stop&#34;</span>                              <span style="color:#e6db74">&#34;L&#34;</span> <span style="color:#a6e22e">#&#39;</span>wassim/ios-logs-stop
</span></span><span style="display:flex;"><span>       :desc <span style="color:#e6db74">&#34;Logs clear&#34;</span>                             <span style="color:#e6db74">&#34;c&#34;</span> <span style="color:#a6e22e">#&#39;</span>wassim/ios-logs-clear
</span></span><span style="display:flex;"><span>       :desc <span style="color:#e6db74">&#34;Status bar: restore live indicators&#34;</span>    <span style="color:#e6db74">&#34;T&#34;</span> <span style="color:#a6e22e">#&#39;</span>wassim/ios-statusbar-clear
</span></span><span style="display:flex;"><span>       :desc <span style="color:#e6db74">&#34;Regenerate buildServer.json&#34;</span>            <span style="color:#e6db74">&#34;g&#34;</span> <span style="color:#a6e22e">#&#39;</span>wassim/swift-regenerate-build-server
</span></span><span style="display:flex;"><span>       :desc <span style="color:#e6db74">&#34;New project (xcodegen)&#34;</span>                 <span style="color:#e6db74">&#34;n&#34;</span> <span style="color:#a6e22e">#&#39;</span>wassim/ios-new-project))
</span></span></code></pre></div><p>That&rsquo;s the contract. I learned 13 keystrokes and they cover my entire day.</p>
<h2 id="my-daily-loop">My daily loop</h2>
<p>A normal day looks like this. I open a project, <code>SPC SPC</code> (Projectile), pick one. A <code>.swift</code> file opens, <code>lsp-deferred</code> kicks in, <code>buildServer.json</code> is generated if missing. I write some code, <code>apheleia</code> formats on save. When I want to see it run, <code>SPC i s</code>, pick &ldquo;Tamim EN (26.2)&rdquo;, wait for the build, the simulator opens, the app launches, the log buffer fills with my <code>print()</code> lines, all colored. If I want to iterate fast, <code>SPC i r</code> and just save the file repeatedly. If something looks wrong, <code>SPC i k</code> to wipe and rebuild.</p>
<p>I almost never touch Xcode.</p>
<h2 id="what-still-needs-xcode-being-honest">What still needs Xcode (being honest)</h2>
<p>Xcode hasn&rsquo;t gone away on my machine. There are a few things I still open it for:</p>
<ul>
<li><strong>Provisioning, signing, and capabilities.</strong> The signing UI is fine. I haven&rsquo;t tried to replace it because the pain isn&rsquo;t worth the gain. Once a year I tap through it for a new device or capability and move on.</li>
<li><strong>Asset catalogs.</strong> I edit <code>Assets.xcassets</code> from Xcode when I&rsquo;m doing serious work on app icons or color sets. For trivial changes I edit the JSON directly.</li>
<li><strong>Storyboards and <code>.xib</code> files.</strong> I avoid these by using SwiftUI everywhere I can. When I have to touch one, it&rsquo;s Xcode.</li>
<li><strong>Instruments.</strong> Profiling, CPU, memory, leaks, runs from Xcode. No good Emacs story here, and I&rsquo;m fine with that.</li>
<li><strong>App Store Connect uploads.</strong> I use <code>xcodebuild -exportArchive</code> for parts of it, but the actual upload still goes through <code>xcrun altool</code> or Xcode. Same for screenshot uploads; <code>simctl</code> gives me the files, but App Store Connect is where they go.</li>
</ul>
<p>Most of those are small, infrequent tasks. None of them are in my daily loop.</p>
<h2 id="would-i-recommend-this">Would I recommend this?</h2>
<p>If you&rsquo;re already an Emacs user and you&rsquo;ve been holding off on iOS because Xcode is &ldquo;the only way&rdquo;, no, it isn&rsquo;t, and it never really was. Apple&rsquo;s command-line tools are good. You can put your editor around them.</p>
<p>If you&rsquo;re not already an Emacs user, this is not a reason to become one. Xcode is genuinely fine. Pick the tool that fits your hands.</p>
<p>I&rsquo;m sharing this mostly because I spent weeks figuring out the small frictions, which <code>sourcekit-lsp</code> to pick, how to feed it <code>buildServer.json</code>, how to find the <code>.app</code> after a build, how to filter the log noise, and I would have loved to read a post like this when I started. Now there is one.</p>
<p>If you try this and get stuck, my email is on the <a href="/about">about page</a>. I&rsquo;ll do my best to help.</p>
<hr>
<h2 id="corrections">Corrections</h2>
<p>(June 11th 2026) - Changed &ldquo;first&rdquo; to &ldquo;latest&rdquo; in the first sentence, as SPEEM wasn&rsquo;t my first iOS project.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>The build log moves to speem.app</title>
      <link>https://wassimans.com/blog/the-build-log-moves-to-speem-app/</link>
      <pubDate>Mon, 27 Apr 2026 11:00:00 +0200</pubDate>
      <guid>https://wassimans.com/blog/the-build-log-moves-to-speem-app/</guid>
      <description>&lt;p&gt;For the past year I&amp;rsquo;ve been building SPEEM mostly in private. Now that it&amp;rsquo;s shipping publicly and I&amp;rsquo;m trying to make a living from it, I want to write about it more openly: what I&amp;rsquo;m working on, what I&amp;rsquo;m learning, what&amp;rsquo;s working, what isn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;But I&amp;rsquo;d rather keep that conversation in one place, with the people who actually use the app, students, parents, other indie devs interested in study tools. So I started a journal section on the SPEEM website itself.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>For the past year I&rsquo;ve been building SPEEM mostly in private. Now that it&rsquo;s shipping publicly and I&rsquo;m trying to make a living from it, I want to write about it more openly: what I&rsquo;m working on, what I&rsquo;m learning, what&rsquo;s working, what isn&rsquo;t.</p>
<p>But I&rsquo;d rather keep that conversation in one place, with the people who actually use the app, students, parents, other indie devs interested in study tools. So I started a journal section on the SPEEM website itself.</p>
<p>From now on, anything related to SPEEM (releases, post-mortems, feature decisions, monthly numbers) goes there:</p>
<p><a href="https://speem.app/journal/">speem.app/journal</a></p>
<p>This blog stays alive for everything else: career stuff, burnout posts, programming I find interesting unrelated to my apps, life, geopolitics, the occasional rant. The kind of writing that doesn&rsquo;t really fit inside a product context.</p>
<p>Two reasons for the split:</p>
<ul>
<li>People who come to SPEEM to learn about studying habits don&rsquo;t need to read about my hacking experiments or family stuff.</li>
<li>People who come here for off-topic thinking shouldn&rsquo;t have to scroll past SPEEM changelog entries.</li>
</ul>
<p>So if you want to follow the indie journey of the app, that&rsquo;s now at <a href="https://speem.app/journal/">speem.app/journal</a>. If you want to follow whatever I&rsquo;m thinking about more broadly, that&rsquo;s still here.</p>
<p>See you in both places.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>I&#39;m Building an iOS App in Emacs</title>
      <link>https://wassimans.com/blog/building-ios-apps-in-emacs/</link>
      <pubDate>Wed, 05 Nov 2025 10:55:43 +0200</pubDate>
      <guid>https://wassimans.com/blog/building-ios-apps-in-emacs/</guid>
      <description>&lt;p&gt;So I’m starting a new mobile app project: &lt;a href=&#34;https://speem.app&#34;&gt;SPEEM&lt;/a&gt;, an app that helps middle and high school students build their study discipline by empowering their study habits. Uses simple UI workflows to craft custom study plans for urgent situations like exams. There’s nothing fancy about this app, even if I find it very useful especially for my daughters in their first high school year; what really sets it apart, at least in regards to my mobile app developer experience, is that I’ll build it using Emacs, Doom Emacs to be precise.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>So I’m starting a new mobile app project: <a href="https://speem.app">SPEEM</a>, an app that helps middle and high school students build their study discipline by empowering their study habits. Uses simple UI workflows to craft custom study plans for urgent situations like exams. There’s nothing fancy about this app, even if I find it very useful especially for my daughters in their first high school year; what really sets it apart, at least in regards to my mobile app developer experience, is that I’ll build it using Emacs, Doom Emacs to be precise.</p>
<p>Why? Because I used Emacs exclusively for every text editing task for the past three years, and I don’t think I’ll ever need something else. Of course, no tool will ever match the Xcode experience for building apps, this is why I’m willing to use them in tandem.</p>
<p>Over the next posts, I’ll share short, practical notes about the journey of adapting my Doom Emacs config to be suitable for iOS development, and of course the status of the app it self. Exciting times ahead!</p>
<p>’Til next time.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Pausing My Blockchain Endeavors and Exploring New Things</title>
      <link>https://wassimans.com/blog/pausing-my-blockchain-endeavours/</link>
      <pubDate>Tue, 18 Jun 2024 10:13:45 +0200</pubDate>
      <guid>https://wassimans.com/blog/pausing-my-blockchain-endeavours/</guid>
      <description>&lt;p&gt;I discovered Blockchain in early 2022, where I was hooked by the cipher-punk culture and how much of human modern suffering is derived by the unfair financial system. I decided then to deepen my knowledge, and that led me to learn the basics of applied cryptography, systems programming with Rust, and eventually the &lt;a href=&#34;https://github/paritytech/polkadot-sdk&#34; title=&#34;Polkadot SDK&#34;&gt;Polkadot SDK&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This was a lot of fun, and full of big discoveries; the most important one was &lt;strong&gt;Systems Programming&lt;/strong&gt;. I genuinely enjoyed the systems programming part and how my previous experience with programming using high level languages now seems like playing Lego!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I discovered Blockchain in early 2022, where I was hooked by the cipher-punk culture and how much of human modern suffering is derived by the unfair financial system. I decided then to deepen my knowledge, and that led me to learn the basics of applied cryptography, systems programming with Rust, and eventually the <a href="https://github/paritytech/polkadot-sdk" title="Polkadot SDK">Polkadot SDK</a>.</p>
<p>This was a lot of fun, and full of big discoveries; the most important one was <strong>Systems Programming</strong>. I genuinely enjoyed the systems programming part and how my previous experience with programming using high level languages now seems like playing Lego!</p>
<p>I also must say that trust-less computing was truly illuminating for me, and how cryptography is leveraged programmatically to ditch trust entirely in favor of a model that gives the users true privacy and security.</p>
<p>That led me to do various open-source contributions, creating a handful of open-source projects and even engaging in the broder crypto community (attending the Polkadot Blockchain Academy).</p>
<p>However, I eventually realized that I was not too keen to continue. This is not a critique of Blockchain as a technology—I truly believe it is here to stay and that its major scalability and interoperability issues will eventually be resolved. It’s rather because; as of today the Blockchain technology is mostly used in cryptocurrency projects, and a lot of them are arguably hard scams. I can work in Blockchain based open-source projects all my life but it doesn&rsquo;t pay the bills, and the vast majority of job opportunities I get are in the so called &ldquo;Cryptocurrency trading startups&rdquo;, structures that I truly doubt the core values and what problems they try to solve. That why I decided to pause my Blockchain development efforts entirely.</p>
<p>My intellectual curiosity was and still is in the Swift ecosystem. I just need to revive my mobile app development projects and invest more time on the indie side of it. No more cryptocurrency projects. We can be a privacy activist without touching the crypto world, to the contrary, I believe that privacy doesn&rsquo;t have to be based on trustless protocols, Web2 can still be privacy focused while being based on trust.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Burnout in Tech: a bit about my journey</title>
      <link>https://wassimans.com/blog/burnout-in-tech/</link>
      <pubDate>Fri, 31 May 2024 10:09:13 +0200</pubDate>
      <guid>https://wassimans.com/blog/burnout-in-tech/</guid>
      <description>&lt;p&gt;Burnout in tech is hard. I’ve experienced it twice in my career, and it negatively influenced every aspect of my life.&lt;/p&gt;
&lt;p&gt;The first time was in early 2022. The second was just months ago, but I managed it more efficiently this time. I had learned the hard way how to detect the red flags and stop right there: no more work, cutting off all links to work. I even abandoned a project I was developing for a special client dear to me. I never abandoned my work before, but this time it was either me or that project. Burnout is hard.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Burnout in tech is hard. I’ve experienced it twice in my career, and it negatively influenced every aspect of my life.</p>
<p>The first time was in early 2022. The second was just months ago, but I managed it more efficiently this time. I had learned the hard way how to detect the red flags and stop right there: no more work, cutting off all links to work. I even abandoned a project I was developing for a special client dear to me. I never abandoned my work before, but this time it was either me or that project. Burnout is hard.</p>
<p>It’s funny how much clearer the vision is when the storm is behind us. I took the time to reflect on the reasons behind my two burnout episodes, and much to my surprise, I discovered that it wasn’t because of overwork or project-specific pressure. No! It was mainly because I worked as a software developer in a tech stack I didn’t choose and with technologies I didn’t like. I’ve done full-stack development for about 13 years non-stop.</p>
<p>But we’re engineers, right? We’re pragmatic and supposed to solve problems, not live the tech-stack drama! Absolutely correct, but I don’t care. Not anymore.</p>
<p>I took an undefined time off months ago to explore new things. I like software engineering and technical challenges, so doing something else is out of the question. I needed fresh air, to touch on new things, to dive deep into low-level programming (like Rust), to explore some controversial technologies (like Blockchain), and to reinvent some wheels.</p>
<p>Now, I’m on to new shores.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Hello World</title>
      <link>https://wassimans.com/blog/hello-world/</link>
      <pubDate>Mon, 09 Oct 2023 18:58:06 +0200</pubDate>
      <guid>https://wassimans.com/blog/hello-world/</guid>
      <description>&lt;p&gt;Welcome to my weblog !&lt;/p&gt;
&lt;p&gt;I like writing, I feel more intelligent when I write! I certainly learn much more by writing. I’ll try to keep writing here as a habit, mainly to force myself to learn new things and share what I learn.&lt;/p&gt;
&lt;p&gt;The main topics of my blog are software engineering, Swift programming, indie app development and privacy focused systems. But I’m sure I’ll get the urging desire to talk about life and what makes me vibrate whether horizontally or vertically (read: positively or negatively).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Welcome to my weblog !</p>
<p>I like writing, I feel more intelligent when I write! I certainly learn much more by writing. I’ll try to keep writing here as a habit, mainly to force myself to learn new things and share what I learn.</p>
<p>The main topics of my blog are software engineering, Swift programming, indie app development and privacy focused systems. But I’m sure I’ll get the urging desire to talk about life and what makes me vibrate whether horizontally or vertically (read: positively or negatively).</p>
<p>If you have some time to waste, maybe you’ll find something new reading my blog, I’ll certainly be surprised though.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
