<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Serhiy</title>
    <description>The latest articles on DEV Community by Serhiy (@serhiy_a149d8bc6468aa1a97).</description>
    <link>https://dev.to/serhiy_a149d8bc6468aa1a97</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3835790%2F7dad0516-2265-4a7e-be6a-f69520a9cbdb.png</url>
      <title>DEV Community: Serhiy</title>
      <link>https://dev.to/serhiy_a149d8bc6468aa1a97</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/serhiy_a149d8bc6468aa1a97"/>
    <language>en</language>
    <item>
      <title>How we made our Mac launcher feel instant by killing slow providers</title>
      <dc:creator>Serhiy</dc:creator>
      <pubDate>Tue, 26 May 2026 18:00:18 +0000</pubDate>
      <link>https://dev.to/serhiy_a149d8bc6468aa1a97/how-we-made-our-mac-launcher-feel-instant-by-killing-slow-providers-2603</link>
      <guid>https://dev.to/serhiy_a149d8bc6468aa1a97/how-we-made-our-mac-launcher-feel-instant-by-killing-slow-providers-2603</guid>
      <description>&lt;p&gt;``&lt;br&gt;
There's this thing Spotlight does where you press cmd+space, type two letters, and then wait. Not a long wait. But you can feel it. And once you've felt it enough times you stop trusting it for quick lookups. You reach for the trackpad instead, or you remember you have iTerm open already, or you just don't bother.&lt;/p&gt;

&lt;p&gt;That little gap between thinking and seeing is the entire reason Raycast exists, and Alfred before it, and now &lt;a href="https://brow-app.com" rel="noopener noreferrer"&gt;Brow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I assumed for a long time that the gap was hardware. It isn't. M-series Macs are absurdly fast. The gap is architectural: most launchers wait for the slowest provider before they paint anything. If your file index provider is having a bad morning, the whole UI sits there with you.&lt;/p&gt;

&lt;p&gt;So Brow doesn't wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three tiers, three deadlines
&lt;/h2&gt;

&lt;p&gt;Every resolver in the app is assigned to one of three tiers. Each tier has a deadline. Miss it, you're out for this keystroke.&lt;/p&gt;

&lt;p&gt;The Instant tier is for things that are basically a dictionary lookup with some parsing on top. Apps, math, color parsing, unit conversion, system commands. We expect these to come back effectively immediately.&lt;/p&gt;

&lt;p&gt;The Fast tier is for things that touch an in-memory cache but never disk. Window list, clipboard search, snippets, frecency lookups.&lt;/p&gt;

&lt;p&gt;The Deferred tier is everything that's allowed to be slow. Files, browser history, calendar, web search, the AI overview. Disk-bound or network-bound by nature. We never let it block the first paint.&lt;/p&gt;

&lt;p&gt;The first frame ships when the Instant tier returns. Fast and Deferred stream in after that. If a resolver is sick today (helper daemon's gone, network's flaky, disk's spinning up), it gets cancelled. The user sees the 59 resolvers that did finish, not the one that didn't.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`swift&lt;br&gt;
await withThrowingTaskGroup(of: [Hit].self) { group in&lt;br&gt;
    for resolver in tier.resolvers {&lt;br&gt;
        group.addTask {&lt;br&gt;
            try await withTimeout(tier.budget) {&lt;br&gt;
                await resolver.resolve(query)&lt;br&gt;
            }&lt;br&gt;
        }&lt;br&gt;
    }&lt;br&gt;
}&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's most of it. Structured concurrency does the cleanup for free - cancel the group and every child dies with it. No orphaned tasks waking up two seconds later and trying to push results into a UI that's already moved on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ranker is dumb on purpose
&lt;/h2&gt;

&lt;p&gt;People assume the hard part is ranking 60 providers against each other in real time. It isn't. The hard part is keeping the score function cheap enough that the ranker is never the bottleneck.&lt;/p&gt;

&lt;p&gt;Every candidate gets an 18-dim feature vector: recency, frecency, co-occurrence, Markov predictions, per-app priors, contextual boosts, a few other things I keep meaning to write down somewhere. The score is just a dot product against a learned weight vector. Vectorized, sub-microsecond per candidate. You can rank thousands and not notice.&lt;/p&gt;

&lt;p&gt;The weight vector updates online. Every time you pick result #3 over result #1, the weights for the features that made #3 attractive nudge up. Slow, conservative, no learning rate tuning required.&lt;/p&gt;

&lt;p&gt;After two or three weeks your Brow stops looking like mine. Mine ranks &lt;code&gt;iTerm&lt;/code&gt; first when I type &lt;code&gt;it&lt;/code&gt;. Yours probably won't. Nobody trained anything. It just watched.&lt;/p&gt;

&lt;p&gt;No cloud involved. The weights live in a file at &lt;code&gt;~/Library/Application Support/Brow/ranker.bin&lt;/code&gt;. You can delete it. Nothing else holds your data hostage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unsexy resolvers are doing all the work
&lt;/h2&gt;

&lt;p&gt;When I started I spent way too much time on the AI overview resolver. Built a whole streaming UI for it. Tuned the prompts. Got the avg latency down to something reasonable.&lt;/p&gt;

&lt;p&gt;Then I looked at the telemetry. Almost nobody uses it. (Sorry, AI overview.)&lt;/p&gt;

&lt;p&gt;The resolvers people actually live in are the boring ones:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kill chrome&lt;/code&gt;, enter. Chrome is gone.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;port 3000&lt;/code&gt;, enter. Whatever Node process was squatting on that port is dead.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;5ft in cm&lt;/code&gt;, the answer's already on your clipboard before you read it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#FF5733&lt;/code&gt;, you get a swatch and every format conversion and one of them is selected.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;uuid&lt;/code&gt;, &lt;code&gt;3pm in tokyo&lt;/code&gt;, &lt;code&gt;128*3+15%&lt;/code&gt;, &lt;code&gt;base64 hello&lt;/code&gt;, &lt;code&gt;md5 password&lt;/code&gt;, &lt;code&gt;weather&lt;/code&gt;, &lt;code&gt;airpods&lt;/code&gt;, &lt;code&gt;mute spotify&lt;/code&gt;. None of these need anything fancy. They need to be available faster than the cost of deciding which app to open.&lt;/p&gt;

&lt;p&gt;I keep wanting to add more clever stuff and the data keeps telling me to make the boring stuff faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it lands
&lt;/h2&gt;

&lt;p&gt;About 60 resolvers across the three tiers. 18-feature ranker, weights learned locally. No backend in the instant and fast paths. One Swift 6 binary, no Electron, no JS runtime, no 400MB install.&lt;/p&gt;

&lt;p&gt;I'm not going to put a benchmark number here because I haven't done the kind of rigorous measurement that would let me defend one with a straight face. What I can say is that it feels different. The result you wanted shows up at the same time the dropdown does, instead of a quarter beat later. Once you've used it for a day you stop trusting Spotlight again.&lt;/p&gt;

&lt;p&gt;The Mac is a fast computer. Structured concurrency is free now. If your launcher feels sluggish on this hardware it's almost always because somebody didn't enforce a deadline.&lt;/p&gt;

&lt;p&gt;We enforced the deadline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://brow-app.com" rel="noopener noreferrer"&gt;brow-app.com&lt;/a&gt;. Free, macOS 14+. Runs fine next to Spotlight or Raycast - I still have all of them installed because old habits.&lt;/p&gt;

&lt;p&gt;If you try it and find a query that feels slow, tell me which one. We'll figure out which tier it landed in and why.&lt;/p&gt;

&lt;p&gt;(The other half of the story, why this is Swift instead of Electron, is &lt;a href="https://dev.to/serhiy_a149d8bc6468aa1a97/brow-why-we-built-brow-in-swift-not-electron-d0l"&gt;here&lt;/a&gt;.)&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How we made our Mac launcher feel instant by killing slow providers</title>
      <dc:creator>Serhiy</dc:creator>
      <pubDate>Tue, 26 May 2026 18:00:18 +0000</pubDate>
      <link>https://dev.to/serhiy_a149d8bc6468aa1a97/how-we-made-our-mac-launcher-feel-instant-by-killing-slow-providers-h82</link>
      <guid>https://dev.to/serhiy_a149d8bc6468aa1a97/how-we-made-our-mac-launcher-feel-instant-by-killing-slow-providers-h82</guid>
      <description>&lt;p&gt;There's this thing Spotlight does where you press cmd+space, type two letters, and then wait. Not a long wait. But you can feel it. And once you've felt it enough times you stop trusting it for quick lookups. You reach for the trackpad instead, or you remember you have iTerm open already, or you just don't bother.&lt;/p&gt;

&lt;p&gt;That little gap between thinking and seeing is the entire reason Raycast exists, and Alfred before it, and now &lt;a href="https://brow-app.com" rel="noopener noreferrer"&gt;Brow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I assumed for a long time that the gap was hardware. It isn't. M-series Macs are absurdly fast. The gap is architectural: most launchers wait for the slowest provider before they paint anything. If your file index provider is having a bad morning, the whole UI sits there with you.&lt;/p&gt;

&lt;p&gt;So Brow doesn't wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three tiers, three deadlines
&lt;/h2&gt;

&lt;p&gt;Every resolver in the app is assigned to one of three tiers. Each tier has a deadline. Miss it, you're out for this keystroke.&lt;/p&gt;

&lt;p&gt;The Instant tier is for things that are basically a dictionary lookup with some parsing on top. Apps, math, color parsing, unit conversion, system commands. We expect these to come back effectively immediately.&lt;/p&gt;

&lt;p&gt;The Fast tier is for things that touch an in-memory cache but never disk. Window list, clipboard search, snippets, frecency lookups.&lt;/p&gt;

&lt;p&gt;The Deferred tier is everything that's allowed to be slow. Files, browser history, calendar, web search, the AI overview. Disk-bound or network-bound by nature. We never let it block the first paint.&lt;/p&gt;

&lt;p&gt;The first frame ships when the Instant tier returns. Fast and Deferred stream in after that. If a resolver is sick today (helper daemon's gone, network's flaky, disk's spinning up), it gets cancelled. The user sees the 59 resolvers that did finish, not the one that didn't.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;withThrowingTaskGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Hit&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;resolver&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolvers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addTask&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;withTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;resolver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's most of it. Structured concurrency does the cleanup for free - cancel the group and every child dies with it. No orphaned tasks waking up two seconds later and trying to push results into a UI that's already moved on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ranker is dumb on purpose
&lt;/h2&gt;

&lt;p&gt;People assume the hard part is ranking 60 providers against each other in real time. It isn't. The hard part is keeping the score function cheap enough that the ranker is never the bottleneck.&lt;/p&gt;

&lt;p&gt;Every candidate gets an 18-dim feature vector: recency, frecency, co-occurrence, Markov predictions, per-app priors, contextual boosts, a few other things I keep meaning to write down somewhere. The score is just a dot product against a learned weight vector. Vectorized, sub-microsecond per candidate. You can rank thousands and not notice.&lt;/p&gt;

&lt;p&gt;The weight vector updates online. Every time you pick result #3 over result #1, the weights for the features that made #3 attractive nudge up. Slow, conservative, no learning rate tuning required.&lt;/p&gt;

&lt;p&gt;After two or three weeks your Brow stops looking like mine. Mine ranks &lt;code&gt;iTerm&lt;/code&gt; first when I type &lt;code&gt;it&lt;/code&gt;. Yours probably won't. Nobody trained anything. It just watched.&lt;/p&gt;

&lt;p&gt;No cloud involved. The weights live in a file at &lt;code&gt;~/Library/Application Support/Brow/ranker.bin&lt;/code&gt;. You can delete it. Nothing else holds your data hostage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unsexy resolvers are doing all the work
&lt;/h2&gt;

&lt;p&gt;When I started I spent way too much time on the AI overview resolver. Built a whole streaming UI for it. Tuned the prompts. Got the avg latency down to something reasonable.&lt;/p&gt;

&lt;p&gt;Then I looked at the telemetry. Almost nobody uses it. (Sorry, AI overview.)&lt;/p&gt;

&lt;p&gt;The resolvers people actually live in are the boring ones:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kill chrome&lt;/code&gt;, enter. Chrome is gone.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;port 3000&lt;/code&gt;, enter. Whatever Node process was squatting on that port is dead.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;5ft in cm&lt;/code&gt;, the answer's already on your clipboard before you read it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#FF5733&lt;/code&gt;, you get a swatch and every format conversion and one of them is selected.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;uuid&lt;/code&gt;, &lt;code&gt;3pm in tokyo&lt;/code&gt;, &lt;code&gt;128*3+15%&lt;/code&gt;, &lt;code&gt;base64 hello&lt;/code&gt;, &lt;code&gt;md5 password&lt;/code&gt;, &lt;code&gt;weather&lt;/code&gt;, &lt;code&gt;airpods&lt;/code&gt;, &lt;code&gt;mute spotify&lt;/code&gt;. None of these need anything fancy. They need to be available faster than the cost of deciding which app to open.&lt;/p&gt;

&lt;p&gt;I keep wanting to add more clever stuff and the data keeps telling me to make the boring stuff faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it lands
&lt;/h2&gt;

&lt;p&gt;About 60 resolvers across the three tiers. 18-feature ranker, weights learned locally. No backend in the instant and fast paths. One Swift 6 binary, no Electron, no JS runtime, no 400MB install.&lt;/p&gt;

&lt;p&gt;I'm not going to put a benchmark number here because I haven't done the kind of rigorous measurement that would let me defend one with a straight face. What I can say is that it feels different. The result you wanted shows up at the same time the dropdown does, instead of a quarter beat later. Once you've used it for a day you stop trusting Spotlight again.&lt;/p&gt;

&lt;p&gt;The Mac is a fast computer. Structured concurrency is free now. If your launcher feels sluggish on this hardware it's almost always because somebody didn't enforce a deadline.&lt;/p&gt;

&lt;p&gt;We enforced the deadline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://brow-app.com" rel="noopener noreferrer"&gt;brow-app.com&lt;/a&gt;. Free, macOS 14+. Runs fine next to Spotlight or Raycast - I still have all of them installed because old habits.&lt;/p&gt;

&lt;p&gt;If you try it and find a query that feels slow, tell me which one. We'll figure out which tier it landed in and why.&lt;/p&gt;

&lt;p&gt;(The other half of the story, why this is Swift instead of Electron, is &lt;a href="https://dev.to/serhiy_a149d8bc6468aa1a97/brow-why-we-built-brow-in-swift-not-electron-d0l"&gt;here&lt;/a&gt;.)&lt;/p&gt;

</description>
      <category>swift</category>
      <category>performance</category>
      <category>macos</category>
      <category>programming</category>
    </item>
    <item>
      <title>Brow (Why We Built Brow in Swift, Not Electron)</title>
      <dc:creator>Serhiy</dc:creator>
      <pubDate>Fri, 20 Mar 2026 16:30:39 +0000</pubDate>
      <link>https://dev.to/serhiy_a149d8bc6468aa1a97/brow-why-we-built-brow-in-swift-not-electron-d0l</link>
      <guid>https://dev.to/serhiy_a149d8bc6468aa1a97/brow-why-we-built-brow-in-swift-not-electron-d0l</guid>
      <description>&lt;p&gt;A lot of Mac utilities talk about speed.&lt;/p&gt;

&lt;p&gt;What matters more is &lt;em&gt;why&lt;/em&gt; they feel fast, where the limits come from, and what trade-offs the architecture creates later.&lt;/p&gt;

&lt;p&gt;That was one of the earliest decisions behind Brow: build it as a native Swift app from day one.&lt;/p&gt;

&lt;p&gt;Not because “native” sounds good in marketing.&lt;/p&gt;

&lt;p&gt;Because for the kind of product we wanted to build — launcher, notch layer, system utilities, hardware-aware modules — architecture changes what is possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real question was never just “Swift vs Electron”
&lt;/h2&gt;

&lt;p&gt;The real question was:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do we want a Mac app that happens to run on macOS, or a Mac app that is built for macOS from the inside?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Electron is a valid choice for many teams. It optimizes for one codebase, faster iteration, and easier cross-platform shipping.&lt;/p&gt;

&lt;p&gt;Raycast took a different route. Its app is native, but its extension ecosystem is built around React, TypeScript, and Node so developers can build on top of it quickly.&lt;/p&gt;

&lt;p&gt;That is a smart trade-off.&lt;/p&gt;

&lt;p&gt;But it is still a trade-off.&lt;/p&gt;

&lt;p&gt;We wanted a different one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the difference starts to matter
&lt;/h2&gt;

&lt;p&gt;If you compare products like Brow and Raycast, the real difference is not just “which launcher is faster”.&lt;/p&gt;

&lt;p&gt;The deeper difference is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how much of the product sits directly on native macOS frameworks&lt;/li&gt;
&lt;li&gt;how much UI or extension logic depends on web tooling&lt;/li&gt;
&lt;li&gt;how much low-level system behavior you can control directly&lt;/li&gt;
&lt;li&gt;how much overhead you carry as the product grows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That architecture decision affects everything after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we chose Swift for Brow
&lt;/h2&gt;

&lt;p&gt;We built &lt;a href="https://macbrow.app" rel="noopener noreferrer"&gt;Brow&lt;/a&gt; in Swift because we wanted the critical path to stay native.&lt;/p&gt;

&lt;p&gt;That means no Electron shell, no Chromium runtime, and no browser stack in the core app flow.&lt;/p&gt;

&lt;p&gt;For us, that mattered for four reasons.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Native system access is not a nice-to-have
&lt;/h2&gt;

&lt;p&gt;Once you move beyond “open apps and search commands”, launcher products start touching awkward parts of the OS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;window management&lt;/li&gt;
&lt;li&gt;clipboard pipelines&lt;/li&gt;
&lt;li&gt;display control&lt;/li&gt;
&lt;li&gt;hardware stats&lt;/li&gt;
&lt;li&gt;menu bar behavior&lt;/li&gt;
&lt;li&gt;screenshot flows&lt;/li&gt;
&lt;li&gt;accessibility hooks&lt;/li&gt;
&lt;li&gt;notch-adjacent UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not areas where web-style abstraction helps much.&lt;/p&gt;

&lt;p&gt;They are areas where being close to AppKit, Swift, macOS services, and system APIs matters.&lt;/p&gt;

&lt;p&gt;This does &lt;strong&gt;not&lt;/strong&gt; mean every Mac app must be written fully in Swift.&lt;/p&gt;

&lt;p&gt;It means that if deep OS integration is central to the product, native architecture compounds in your favor.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Extension velocity and product coherence usually pull in opposite directions
&lt;/h2&gt;

&lt;p&gt;This is one of the most interesting trade-offs in tools like Raycast.&lt;/p&gt;

&lt;p&gt;A React/TypeScript-based extension system makes extension development much more accessible to web developers.&lt;/p&gt;

&lt;p&gt;That helps ecosystem growth.&lt;/p&gt;

&lt;p&gt;But it also means the platform has to constantly mediate between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;native host behavior&lt;/li&gt;
&lt;li&gt;extension runtime constraints&lt;/li&gt;
&lt;li&gt;API boundaries&lt;/li&gt;
&lt;li&gt;consistency across third-party commands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That model is great for extensibility.&lt;/p&gt;

&lt;p&gt;It is not automatically great for tight, system-level product design.&lt;/p&gt;

&lt;p&gt;We wanted tighter control over behavior, performance envelopes, and module integration — even if that meant a slower road to ecosystem scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. “Fast” is not only about launch time
&lt;/h2&gt;

&lt;p&gt;People often reduce desktop performance to one thing: how quickly the window appears.&lt;/p&gt;

&lt;p&gt;That matters, but it is only the surface layer.&lt;/p&gt;

&lt;p&gt;The more important questions are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How much memory does the product carry at idle?&lt;/li&gt;
&lt;li&gt;What happens when multiple modules are active?&lt;/li&gt;
&lt;li&gt;How much work is happening in the background?&lt;/li&gt;
&lt;li&gt;How direct is the rendering path?&lt;/li&gt;
&lt;li&gt;How many layers are sitting between intent and UI update?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For us, the goal was simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;keep the stack small, direct, and native.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the advantage of building the core this way.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The longer-term constraint is complexity, not initial speed
&lt;/h2&gt;

&lt;p&gt;Web-heavy architectures often win early.&lt;/p&gt;

&lt;p&gt;They are easier to prototype, easier to staff, and easier to ship across platforms.&lt;/p&gt;

&lt;p&gt;But the question we cared about was not:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What gets us to v1 fastest?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It was:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What architecture do we still want when the app becomes much more ambitious?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because once a launcher becomes a platform, every abstraction starts charging rent.&lt;/p&gt;

&lt;p&gt;That rent appears as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;more edge cases&lt;/li&gt;
&lt;li&gt;more memory pressure&lt;/li&gt;
&lt;li&gt;more runtime boundaries&lt;/li&gt;
&lt;li&gt;more UI inconsistency&lt;/li&gt;
&lt;li&gt;more special handling for modules that do not quite fit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Native code is not magically simple.&lt;/p&gt;

&lt;p&gt;But for a Mac-only product, it can keep the stack more honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is Brow better than Raycast?
&lt;/h2&gt;

&lt;p&gt;That depends on what you value.&lt;/p&gt;

&lt;p&gt;Raycast has a strong ecosystem, a mature extension story, and a smart developer adoption strategy.&lt;/p&gt;

&lt;p&gt;But our argument is narrower:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For the kind of product Brow wants to be, staying fully native in the core gives us better foundations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not universally better.&lt;/p&gt;

&lt;p&gt;Better for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deep macOS integration&lt;/li&gt;
&lt;li&gt;tighter control over performance behavior&lt;/li&gt;
&lt;li&gt;lower architectural overhead in the core path&lt;/li&gt;
&lt;li&gt;building features that feel like part of the OS, not layered on top of it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was the point of choosing Swift.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-off we accepted
&lt;/h2&gt;

&lt;p&gt;This path is slower.&lt;/p&gt;

&lt;p&gt;The ecosystem is smaller.&lt;br&gt;&lt;br&gt;
The tooling is narrower.&lt;br&gt;&lt;br&gt;
Some things are harder than they would be in web stacks.&lt;/p&gt;

&lt;p&gt;But you also get something important in return:&lt;/p&gt;

&lt;p&gt;a product whose architecture matches its platform.&lt;/p&gt;

&lt;p&gt;For Brow, that trade was worth it.&lt;/p&gt;




&lt;p&gt;Original posts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://macbrow.app/blog/why-we-built-brow-in-swift-not-electron" rel="noopener noreferrer"&gt;Why We Built Brow in Swift, Not Electron&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://brow-app.com/blog/brow-vs-raycast-honest-comparison" rel="noopener noreferrer"&gt;Brow vs Raycast&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://macbrow.app" rel="noopener noreferrer"&gt;Brow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>performance</category>
      <category>showdev</category>
      <category>swift</category>
    </item>
  </channel>
</rss>
