<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>MITM LLC Blog</title>
    <link>https://mitmllc.com/blog/</link>
    <atom:link href="https://mitmllc.com/blog/feed.xml" rel="self" type="application/rss+xml" />
    <description>Notes from MITM LLC on product launches, technical decisions, and lessons learned building indie Mac software.</description>
    <language>en-us</language>
    <lastBuildDate>Sat, 11 Jul 2026 10:39:27 GMT</lastBuildDate>
    <item>
      <title>The customers who helped me fix my own app</title>
      <link>https://mitmllc.com/blog/customers-who-helped-fix-my-app/</link>
      <guid isPermaLink="true">https://mitmllc.com/blog/customers-who-helped-fix-my-app/</guid>
      <pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate>
      <description>A stranger caught a real bug in my WhisperPad code, and helped me diagnose it with logs. Here&apos;s what happened during launch week.</description>
      <author>noreply@mitmllc.com (Rene Zelaya)</author>
      <content:encoded><![CDATA[<p>Launch was in early June. By Thursday morning of that week I woke up to an email from a paying customer. He&#39;d bought the lifetime license. And his microphone didn&#39;t work.</p>
<p>That was the moment I decided my morning routine could wait.</p>
<p>The first emails about this issue had actually come in earlier, from free-tier users right after launch. I engage with free users too. Why not? They often give the most useful early signal anyway. But when the paying customer wrote in, there was a different kind of pressure. Someone trusted me enough to commit to a lifetime license. I needed to make that good.</p>
<p>When the first free-tier email came in, my honest first reaction was, oh no, this is a nightmare. That instant shock of did I just release something that wasn&#39;t ready. But I took a step back. I&#39;d tested this thing across ten other Macs before launch. So I told myself, okay, hop back in the driver&#39;s seat and start diagnosing.</p>
<p>I&#39;ve done this work before. I used to be a customer support agent, all of it over the phone. This time it&#39;s email, but the empathy and the acknowledging, that part is muscle memory. I could do it in my sleep. The funny thing is it feels better doing it for my own product. When I was getting paid to support someone else&#39;s thing, sure, I had some loyalty to it because I worked there. But it wasn&#39;t mine. And I think a lot of founders and engineers and designers get so far removed from the front line that they forget who they&#39;re actually building for. Sitting on the front line again was humbling. You get much faster signal on what&#39;s broken. You become the pattern detection.</p>
<h2>What Tom did</h2>
<p>I&#39;ll call him Tom. Tom was using the free version, so he didn&#39;t really owe me any debugging time. The first thing he did that I&#39;m thankful for is he reached out at all instead of just bouncing, which is already more than most people do. Then I gave him some troubleshooting steps, and he actually did them. Some of those steps meant punching commands into the terminal, collecting logs, and sending the output back to me.</p>
<p>Here&#39;s the part that made me laugh. Tom opened up his own Claude Code to look at the logs, and his Claude pointed at a theory. So we had this back and forth where he&#39;s telling me, my Claude says it&#39;s this, and from the logs it wasn&#39;t definitive. Then my Claude agent took a look. What confirmed it was an actual bug was a second email, same issue. Then a third.</p>
<p>Even though Tom eventually found a workaround for his own machine, those initial logs gave me enough signal to go looking. And I found a real bug. The Direct-distribution build had shipped without a microphone entitlement. The Mac App Store version got the microphone entitlement automatically through the sandbox settings, but the Direct version disables the sandbox so it can do auto-paste, and I&#39;d forgotten to manually add the entitlements file as a replacement. Without that entitlement, the app technically had no permission to access the microphone. Most people worked fine because macOS was lenient about checking. A few hit stricter enforcement and the app just failed silently. Tom helped me, not just himself.</p>
<h2>The thing I was actually scared of</h2>
<p>Watching it fail in front of the biggest one-day audience I&#39;ve ever had was terrifying. But the fear was specific. I&#39;m putting a product out there and I&#39;m putting a price on it. I don&#39;t want to be embarrassed. I don&#39;t want to ship something pre-baked and burn goodwill by rushing to market. I&#39;ve seen that happen a lot, and as a UX researcher it&#39;s a pattern I criticize constantly: teams ship features without testing and the users pay for it with a bad experience. I did not want to be that guy. I really don&#39;t like making users your guinea pigs unless they actually volunteered, with consent and compensation, not by downloading something they thought was finished.</p>
<p>To be fair, there were close to 200 direct downloads by that point, plus whatever came through the Mac App Store, and only three people hit this. It&#39;s rare. And they were chill about it. Nobody wants to create a bad experience for somebody else, but the people who ran into it gave me a way out.</p>
<h2>Shipping the fix</h2>
<p>The fix itself wasn&#39;t stressful. From the time I sat down to the time it was out the door was about an hour and twenty.</p>
<p>If this had happened to me back when I built websites by hand, it would have been a different night. Stuff like this did happen when I freelanced, and I&#39;d be up at 1am on a customer&#39;s downed site, working for two hours, hunting for the right line of code, trying to calm myself down while something is actively broken. The hardest part was always just finding the file to touch. With Claude Code I find it fast, make the change, and ship. Honestly I spent more time writing emails to customers than I spent in the codebase.</p>
<p>And it wasn&#39;t stressful because I already knew what was wrong. The back and forth with Tom had handed me that. With the logs, I can track a bug down and squash it. Without them it&#39;s just &quot;my mic didn&#39;t work, figure it out, it&#39;s on you.&quot;</p>
<p>To the people who wrote in: you gave my product a chance, and even when there was some turbulence, you reached out to help me fix it. Thank you.</p>
<hr>
<p><em>This post was dictated using <a href="https://mitmllc.com/whisperpad">WhisperPad</a>. The cadence is the way I actually talk.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Apple changed their mind</title>
      <link>https://mitmllc.com/blog/apple-changed-their-mind/</link>
      <guid isPermaLink="true">https://mitmllc.com/blog/apple-changed-their-mind/</guid>
      <pubDate>Tue, 09 Jun 2026 00:00:00 GMT</pubDate>
      <description>Apple reversed the 2.4.5 rejection of WhisperPad. The full version is back on the Mac App Store.</description>
      <author>noreply@mitmllc.com (Rene Zelaya)</author>
      <content:encoded><![CDATA[<p>It was Friday night. I was out with classmates after class, drinks at a bar, the kind of after-week wind-down where you&#39;re not expecting anything important to happen. My phone rang. Caller ID said Apple. I was in the middle of a busy bar and didn&#39;t want to pick up, and honestly part of me was skeptical too. Apple calling me directly seemed like the kind of thing a scam might pretend to be.</p>
<p>I let it go to voicemail.</p>
<p>When I checked later, the auto-transcription Apple does on incoming voicemails gave me the gist. A member of Apple&#39;s App Review team said he wanted to talk about WhisperPad. He didn&#39;t say why. I tried to call back at 9:30 Barcelona time and it was already after his working hours in Pacific. I told my classmates Apple was calling me. It felt unreal. Apple is giving me a phone call. That was the framing in my head all weekend.</p>
<h2>The wait</h2>
<p>I had a long weekend ahead of me wondering what the call was about. I let myself hope it was a reversal. I&#39;d been very public about the original rejection.  Part of me was nervous Apple might be calling for a different reason. Maybe they wanted to communicate something firm in private rather than in writing.</p>
<h2>The call</h2>
<p>On Monday I was in class. They called during a break. I picked up.</p>
<p>The conversation was three or four minutes. The Apple reviewer was calm and friendly. He told me the App Review board had taken a closer look at my original rejection and decided to reverse it. WhisperPad&#39;s use of the Accessibility API was approved. I could resubmit the version they&#39;d previously rejected, using the same method, and reference our conversation in the review notes.</p>
<p>He said: &quot;Sometimes a single reviewer&#39;s call gets revisited by the board, and the decision flips.&quot; I thanked him for his time. End of call.</p>
<h2>What I felt</h2>
<p>Mostly confused, then thankful. I&#39;d been operating for two weeks on the assumption that this path was permanently closed. I&#39;d forked the app into two separate SKUs. I&#39;d stood up direct distribution with Paddle for payments and Sparkle for updates. I&#39;d written architectural decision records explaining why the fork was permanent. And now, with one phone call, all of that had changed.</p>
<p>Ultimately, I&#39;m happy Apple reached out. They approved the app. I&#39;m stoked about it. The story is closed in the best possible way.</p>
<h2>The work of un-doing the fork</h2>
<p>The reversal sounded simple on the phone. Resubmit the version you originally built. In code, it was anything but simple.</p>
<p>When I split WhisperPad into two versions in May, I&#39;d guarded the differences behind a compile flag called <code>MAS_BUILD</code>. The flag had quietly come to mean two unrelated things. One: legitimate, permanent exclusions required by the App Store sandbox, like the in-app crash reporter that reads files the sandbox blocks. Two: feature reduction to dodge the 2.4.5 rejection — the clipboard-only paste path, the disabled transcription modes, the abbreviated onboarding. With the reversal, I needed to keep the first kind and undo the second.</p>
<p>Restoring the full app meant unwinding <code>MAS_BUILD</code> guards across seven files. Core insertion logic. App startup permissions. Settings UI. The onboarding flow. The FAQ. The result panel. Most of the work was deleting code. The compliance changes had added complexity, and the reversal removed it.</p>
<p>Net result: forty-four lines fewer than before.</p>
<h2>The Sparkle dance</h2>
<p>The trickier piece was the auto-updater. Sparkle, the long-standing framework that handles updates for non-App-Store Mac apps, ships helper executables that are incompatible with the sandbox by design. The sandbox exists to keep apps from escaping into the rest of the system. Sparkle&#39;s whole job is to escape and replace the parent app. The App Store will reject any bundle that contains a non-sandboxed executable.</p>
<p>So Sparkle has been toggled in and out of the codebase four times in two weeks. Out for the compliant 1.7. Back in for the direct launch. Out again on Monday, briefly, because the moment I tried to upload the 1.8 App Store build the validation failed with the exact error from before — Sparkle&#39;s helpers were back in the bundle because Sparkle is linked as a Swift Package and embeds automatically. Restored immediately after the MAS upload cleared.</p>
<p>That dance is fragile and manual. The permanent fix is conditional framework linking or revisiting the single-target decision, both of which are on the list. For now, git is the safety net.</p>
<h2>What this means for the studio</h2>
<p>The direct-distribution channel from mitmllc.com still exists. I&#39;m keeping it. People who want to save the App Store markup can buy direct. People who want Apple&#39;s trusted purchase flow can buy from the App Store. The same product, two channels, neither one a compromise.</p>
<p>Without the original rejection, I never would have built the direct channel. Sometimes the workaround you&#39;re forced to build becomes the thing you&#39;re glad you have. I learned a lot in two weeks I wouldn&#39;t have learned in two months of straight App Store work. The infrastructure I now have for direct distribution will outlast WhisperPad. It&#39;s there for whatever I ship next under MITM LLC.</p>
<h2>What I&#39;d say to other developers</h2>
<p>Build your own distribution channel anyway. Not because it&#39;ll force Apple to reverse a rejection ...though, hey, maybe it will, who knows. Build it because it&#39;s an asset you&#39;ll want to have regardless. And triple-check your work when you do. The biggest headache I had during the launch wasn&#39;t anything Apple did. It was that some users downloaded a version with a broken auto-updater and a stale purchase link, and they couldn&#39;t get to my payment page. That cost me real customers and a lot of email apologies. Sparkle, entitlements, license-key delivery etc.</p>
<h2>Closing</h2>
<p>Overall I&#39;m thankful this saga happened. I made real connections answering emails from customers, from people reaching out about RSI in general, from other Mac developers who&#39;d hit the same wall. It was good to see those connections form and to talk with people dealing with similar issues. I learned a lot.</p>
<p>It&#39;s been an interesting two weeks. Super hectic, super fun, and all of it on top of writing a master&#39;s thesis and building my final presentation. June is going to be a busy month.</p>
<p>WhisperPad is now what it was always supposed to be, in both places it was supposed to be. The loop is closed.</p>
<p>If you want to try it, the direct version is at <a href="https://mitmllc.com/whisperpad/">mitmllc.com/whisperpad</a>. The Mac App Store version is also live with the full auto-paste functionality now. The free tier is 120 minutes a month either way. If you want to write to me here: <a href="mailto:contact@mitmllc.com">contact@mitmllc.com</a></p>
<hr>
<p><em>This post was dictated using WhisperPad.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Apple rejected my dictation app for using the accessibility API</title>
      <link>https://mitmllc.com/blog/apple-rejected-my-dictation-app/</link>
      <guid isPermaLink="true">https://mitmllc.com/blog/apple-rejected-my-dictation-app/</guid>
      <pubDate>Wed, 27 May 2026 00:00:00 GMT</pubDate>
      <description>Apple rejected WhisperPad under Guideline 2.4.5 for using the accessibility API to paste transcribed text. After an appeal that ended in another rejection, I split it into two versions — a compliant one on the App Store, and the original auto-paste version, direct.</description>
      <author>noreply@mitmllc.com (Rene Zelaya)</author>
      <content:encoded><![CDATA[<p>I built WhisperPad because I needed it. In the fall of 2024 the joints in my fingers started to hurt when I typed. Maybe the bill came due for spending most of my life on a keyboard: a childhood of video games, then 10 years working in tech. It got worse throughout the winter, and by early 2025, I could not type for sustained stretches without triggering an unsustainable level of pain. It was a progressive injury, so there was no single dramatic moment; just a slow narrowing of how much I could do in a day.</p>
<p>That narrowing arrived at an inconvenient time. I was between jobs and trying to decide what came next, and I had landed on applying to a master&#39;s program in human-computer interaction. My biggest fear about it was not whether I could keep up mentally. It was whether my hands would let me produce the work fast enough to keep pace.</p>
<h2>What WhisperPad does (or did, anyway)</h2>
<p>WhisperPad lives in your menu bar. You press a keyboard shortcut, you talk, it transcribes what you said locally on your Mac, and it places the text into whatever field your cursor is in. Nothing is sent to a server. If you have clicked away by the time it finishes, the text is on your clipboard and you can paste it wherever you want. That is the whole app. The point was to make getting words out of my head and into the computer cost as few hand movements as possible.</p>
<figure>
  <video controls preload="metadata" playsinline poster="/blog/assets/apple-rejected-my-dictation-app/window-indicator-poster.jpg">
    <source src="/blog/assets/apple-rejected-my-dictation-app/window-indicator.mp4" type="video/mp4">
    Your browser does not support the video tag. <a href="/blog/assets/apple-rejected-my-dictation-app/window-indicator.mp4">Download the video</a>.
  </video>
<figcaption>The window indicator that appears while you&#39;re dictating.</figcaption>
</figure>

<p>In April, Apple rejected an update to my Mac dictation app, WhisperPad, under Guideline 2.4.5. Their position was that I was using the accessibility API in a way that wasn&#39;t an accessibility use. The app exists because I have a hand injury. Apple had approved earlier versions doing the same thing. This time they did not.</p>
<p>I had used Apple&#39;s built-in dictation first, and the experience was a particular kind of frustrating. The transcription was close but rarely right, and every correction meant going back in with the keyboard, deleting, retyping. I was hurting my hands to fix the tool that was supposed to be saving them. That is the specific problem I wanted to solve: not &quot;transcribe my voice,&quot; but &quot;transcribe my voice well enough that I am not constantly typing corrections.&quot;</p>
<p>I will be honest about how I approached this. I did not do much market research. There were probably good tools out there already, but most sent your audio to a server somewhere, and I wanted one that didn&#39;t. When I want something, my first question is usually &quot;can I just build it,&quot; not &quot;what can I buy.&quot;</p>
<p>The first version was rough, but I used it every day and kept improving it. I shared it with a couple of classmates and watched them fold it into their own daily workflows. Seeing other people quietly come to rely on it made me want to release it properly.</p>
<h2>The rejection</h2>
<p>Version 1.0 shipped in the winter, and it was free to download from the App Store. Next I added features early users asked for, and I decided to make it a paid app. That update was version 1.5.</p>
<p>Apple rejected it under Guideline 2.4.5. The rejection was specifically about the accessibility permission. WhisperPad uses it to inject transcribed text into other applications, and Apple&#39;s position was that this was not an approved use of that API. Earlier, approved versions of the app already did the identical thing with the identical permissions, so this was hard to swallow.</p>
<p>The rejection explanation had some fair feedback, too. I fixed those issues, but the 2.4.5 issue was the real wall. So I appealed, explaining that WhisperPad functioned as an accessibility tool, that I had a repetitive strain injury, and that the whole reason the app injected text the way it did was to spare the hands of people who, like me, could not afford the extra keystrokes. I asked Apple to look at it in that light.</p>
<p>They responded that they would take a closer look. They told me not to reply in the thread, and said they would come back with a decision. That was April 21st.</p>
<p>Then it went quiet. By May 21st I had heard nothing, so I sent a short, polite note asking about the status. I have worked in customer support. I know tickets fall through the cracks, and I wanted to nudge it back onto the pile.</p>
<p>They responded quickly, and the answer was another rejection.</p>
<h2>The decision to split</h2>
<p>That second rejection is what forced a real decision, and I want to explain, because it would have been easy to make a worse one.</p>
<p>I spent some time weighing two options: comply with Apple&#39;s restrictions and release a version that fit their rules on the App Store (sacrificing the direct paste), or release WhisperPad through direct distribution. I decided I couldn&#39;t sacrifice the reach of the App Store. So I would build a version that complied with their rules and put it there, and at the same time move ahead with direct distribution to deliver the full version I had originally set out to build.</p>
<p>Here is the thing about the constraint. Apple&#39;s guideline, as far as I can tell, is meant to govern apps injecting text into themselves for accessibility, rather than into other apps. That is my assumption, since I never got a perfectly clear explanation. (If you understand this guideline better and can correct me, reach out — I want to hear it.) Either way, an app reaching into every other app on your system is something to be careful about, and I can see how WhisperPad sits in an awkward spot relative to that line.</p>
<p>So I split WhisperPad into two versions.</p>
<p>The version on the Mac App Store does not auto-paste. It puts your transcribed text on the clipboard, and you press Command-V to place it. That takes the core flow from roughly four steps to six. That does not sound like much, and for most people it is not. The entire design goal of the app was to remove hand movements for people who need to be economical with them, so a 50% increase in steps is significant. It is a compromised version of the idea. But it is a real, useful app, and it is on the store where people with or without accessibility needs can stumble onto it. The App Store makes it discoverable.</p>
<p>The original vision, the one that pastes directly where your cursor is, I shipped myself — outside the App Store.</p>
<h2>Shipping it directly</h2>
<p>Shipping software outside the App Store means rebuilding the things the store normally does for you. There&#39;s a new trust hurdle, too. You&#39;re asking people to trust you to process their credit card on your own site.</p>
<p>For payments I went with Paddle, after some research into the options. For updates I am using Sparkle, the long-standing framework for keeping Mac apps up to date outside the store. Licensing is handled with license keys checked against a small server. None of this is exotic, it is the well-worn path for independent Mac software, but it was all new to me, and getting it right took real work. Practically, I forked the app into separate build targets: one configured for the App Store, one for direct release. I finished wiring up the direct release pipeline on May 27, the morning I started writing this.</p>
<h2>What I took from this</h2>
<p>What first looked like an obstacle turned into a constraint that pushed me to build a better-organized product. I now understand my own build configuration, my update path, and my payment flow far better than I did in March, because Apple&#39;s &quot;no&quot; forced me to learn all of it. Apple&#39;s guidelines exist for defensible reasons, even if I wish my particular app had landed on the other side of the line.</p>
<p>When a platform tells you no, you usually have more options than the moment makes it feel like. The choice is rarely &quot;comply or quit.&quot; It is often &quot;comply here, and do the fuller thing somewhere else.&quot; Shipping two versions, each one serving its channel as well as that channel allows, was not a defeat. It was just the next piece of work.</p>
<p>WhisperPad is on the Mac App Store now, with a free tier of 120 minutes a month so you can find out whether it fits how you work before paying for anything. The direct version, with full auto-paste, is available at <a href="/whisperpad">mitmllc.com/whisperpad</a>. If you want to talk to me about it, I am Rene Zelaya, and you can reach me at <a href="mailto:contact@mitmllc.com">contact@mitmllc.com</a>.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
