<?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>ChatGPT started recommending my Mac app</title>
      <dc:creator>Serhiy</dc:creator>
      <pubDate>Sat, 06 Jun 2026 09:07:11 +0000</pubDate>
      <link>https://dev.to/serhiy_a149d8bc6468aa1a97/chatgpt-started-recommending-my-mac-app-20mi</link>
      <guid>https://dev.to/serhiy_a149d8bc6468aa1a97/chatgpt-started-recommending-my-mac-app-20mi</guid>
      <description>&lt;p&gt;I was scrolling the acquisition report for Brow, my Mac app, when I hit a row that wasn't there a few months ago.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;chatgpt.com&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Real people, real clicks. Somebody asked ChatGPT something like "best app to manage menu bar icons on Mac" or "a good Spotlight alternative for macOS," and the answer came back with Brow in it, by name, with a link.&lt;/p&gt;

&lt;p&gt;For context: Brow is a Mac launcher that also tidies your menu bar, keeps your clipboard history, and bundles a stack of small utilities. So those are exactly the questions I'd want it surfaced for. I just never expected a model to do the surfacing.&lt;/p&gt;

&lt;p&gt;I never pitched ChatGPT. No form, no ad slot, no deal. The model picked Brow on its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  The numbers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;30 downloads in 5 days, attributed to ChatGPT. Zero before this spring.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two things matter more than the size.&lt;/p&gt;

&lt;p&gt;It went from a flat zero, for the whole life of the site, to a number that now shows up every week. Nothing to something is the only jump that counts.&lt;/p&gt;

&lt;p&gt;And these people convert. A Google click is still shopping. A ChatGPT click already got told "use this one" by an advisor they trust, so they land sold. A surprising share install. Cold search traffic doesn't behave like that.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to test it without fooling yourself
&lt;/h3&gt;

&lt;p&gt;You can probe the model, but only from a logged-out incognito session. No account, no memory, no custom instructions.&lt;/p&gt;

&lt;p&gt;Ask from your own ChatGPT and it already knows you, so it tells you what you want to hear. A clean session answers cold, the way it would for a stranger who's never heard of you. That's the only answer worth trusting, and it's the first thing people get wrong when they "test" whether AI recommends them.&lt;/p&gt;

&lt;p&gt;We took it further and built an internal tool that runs a batch of realistic prompts on a schedule and logs two things: which apps get named, and which source URLs the model cites to back the pick. So we watch which resources actually get picked up instead of guessing. The early patterns are interesting. I'll write them up once there's enough signal to say something solid.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually moves it
&lt;/h3&gt;

&lt;p&gt;The model learned about Brow from the open web: the site, the App Store page, a few blog posts, the odd Reddit thread. It compressed the internet's opinion of Brow into a sentence and handed it back. So "optimizing" is old-school:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Say plainly what the app does, on a page a crawler can read. The model can only recommend Brow as a "menu bar manager" or "Spotlight alternative" if the web already ties those words to it.&lt;/li&gt;
&lt;li&gt;Earn real mentions. Reviews, threads, word of mouth. That's the training signal, and there's no shortcut around humans liking it first.&lt;/li&gt;
&lt;li&gt;Don't keyword-stuff. Models smell it, and those visitors bounce hardest.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the plumbing, because the model only knows what got indexed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IndexNow via Cloudflare&lt;/strong&gt; (one toggle, still in &lt;a href="https://www.bing.com/indexnow/getstarted" rel="noopener noreferrer"&gt;beta&lt;/a&gt;) so content changes hit the index immediately instead of waiting for a crawl.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bing Webmaster Tools + Search Console&lt;/strong&gt;, both domains, to see real queries and confirm pages are even indexed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Study competitors' results, not their rankings.&lt;/strong&gt; What queries they own, what rich snippets and sitelinks they pull, what their landing pages look like.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review domains.&lt;/strong&gt; Sites with "reviews" in the domain name rank hard and feed the model dense social proof. Getting Brow onto the right ones is on the list.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why a small number is worth a whole post
&lt;/h3&gt;

&lt;p&gt;Thirty downloads won't change my month. But how people find software is splitting in two: the old pile of blue links, and a model deciding whether to say your name, trained on data you can't see, handing you back one anonymous row after the fact.&lt;/p&gt;

&lt;p&gt;Most people find out it stopped saying their name by accident, months later. We stopped leaving that to chance and started watching the prompts directly. We can see it move now, and there's a lot more to say once the monitoring has run a while.&lt;/p&gt;

&lt;p&gt;More soon.&lt;/p&gt;

&lt;p&gt;(Brow is a Mac launcher, menu bar manager, and clipboard tool: &lt;a href="https://brow-app.com" rel="noopener noreferrer"&gt;brow-app.com&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>marketing</category>
      <category>productivity</category>
      <category>showdev</category>
    </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-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 vs Raycast: An Honest Comparison</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 Brow 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://brow-app.com/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://brow-app.com" 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>
