<?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: (ハオ)</title>
    <description>The latest articles on DEV Community by (ハオ) (@kunkka19xx).</description>
    <link>https://dev.to/kunkka19xx</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1283647%2F9c6cc6c2-691b-46de-9442-33234201fae5.png</url>
      <title>DEV Community: (ハオ)</title>
      <link>https://dev.to/kunkka19xx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kunkka19xx"/>
    <language>en</language>
    <item>
      <title>The Best macOS Launcher? I Compared Look, Raycast, Alfred and Spotlight (and Look Won)</title>
      <dc:creator>(ハオ)</dc:creator>
      <pubDate>Thu, 27 Aug 2026 09:02:00 +0000</pubDate>
      <link>https://dev.to/kunkka19xx/the-best-macos-launcher-i-compared-look-raycast-alfred-and-spotlight-and-look-won-3fgb</link>
      <guid>https://dev.to/kunkka19xx/the-best-macos-launcher-i-compared-look-raycast-alfred-and-spotlight-and-look-won-3fgb</guid>
      <description>&lt;h1&gt;
  
  
  The Best macOS Launcher? I Compared Look, Raycast, Alfred and Spotlight (and Look Won)
&lt;/h1&gt;

&lt;p&gt;Full disclosure before we start: I built one of the four apps in this post. &lt;strong&gt;This is the kind of article where the referee is also playing in the match.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I am going to be genuinely fair to the other three. If I inflate their weaknesses, you have no reason to believe anything I say about mine either.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why launchers deserve this much thought
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Cmd+Space&lt;/code&gt; is probably the key you press most on a Mac. I have never counted. I suspect counting would be upsetting.&lt;/p&gt;

&lt;p&gt;Everything goes through that door: opening an app, finding a file, doing quick math, jumping to a settings pane. If that door is half a second slow, you lose about a minute a day. A minute sounds like nothing. &lt;strong&gt;The real cost is the small "ugh" you feel while waiting.&lt;/strong&gt; That part adds up in a way minutes do not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Look.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Fast. Free. Open source. Fully local. AI that runs on your own machine. Same app on macOS, Windows and Linux.&lt;/p&gt;

&lt;p&gt;No other launcher gives you all six. Plenty give you one or two. &lt;strong&gt;Nobody gives you all of them.&lt;/strong&gt; That is why I built it.&lt;/p&gt;

&lt;p&gt;That said, the others genuinely win in some situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need a deep extension store to wire up your company's tools → Raycast&lt;/li&gt;
&lt;li&gt;You have ten years of hand made Workflows that are now real assets → Alfred&lt;/li&gt;
&lt;li&gt;You truly do not want to install anything → Spotlight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything outside those three cases, I think Look is the right pick. Here is why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The comparison table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Look&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Raycast&lt;/th&gt;
&lt;th&gt;Alfred&lt;/th&gt;
&lt;th&gt;Spotlight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Price&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Free, all of it&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free + paid plan&lt;/td&gt;
&lt;td&gt;Free + Powerpack (one off)&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Open source (GPLv3)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Closed&lt;/td&gt;
&lt;td&gt;Closed&lt;/td&gt;
&lt;td&gt;Closed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platforms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;macOS / Windows / Linux&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;macOS / Windows (beta)&lt;/td&gt;
&lt;td&gt;macOS only&lt;/td&gt;
&lt;td&gt;macOS only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Account&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not required&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Needed for some features&lt;/td&gt;
&lt;td&gt;Not required&lt;/td&gt;
&lt;td&gt;Apple ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stays local&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Partly (cloud sync)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Partly (web results mixed in)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telemetry&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Sent to Apple&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background daemon&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;System resident&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;On device (Apple Intelligence / Ollama)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cloud LLM (paid)&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Siri / ChatGPT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Six rows won outright, two tied. I made the table, so grade the objectivity accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spotlight: it is already there and it costs nothing
&lt;/h2&gt;

&lt;p&gt;That is a real advantage. Zero setup, zero decisions. It wins on that alone for a lot of people.&lt;/p&gt;

&lt;p&gt;But use it daily and the friction accumulates.&lt;/p&gt;

&lt;p&gt;You type "bluetooth" because you want the Bluetooth settings pane. That is the whole goal. What comes back on top is some random document with the word bluetooth in it, or a web search suggestion. &lt;strong&gt;The thing you actually wanted is two arrow keys down.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Worse, the order changes from day to day for the same query. So you end up looking at the screen every single time to confirm. If you have to look at the screen, it is not really a keyboard launcher anymore.&lt;/p&gt;

&lt;p&gt;There is also the part where you wanted a local file and got news, a dictionary entry and Wikipedia. You can turn some of that off in settings. Not all of it.&lt;/p&gt;

&lt;p&gt;This is the app that pushed me into writing my own, so, you know, consider the source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Raycast: I am not going to pretend I can beat the ecosystem
&lt;/h2&gt;

&lt;p&gt;The extension store is enormous. Linear, Notion, GitHub, Jira, internal tools. Whatever you use, something probably exists for it. The UI is polished and it feels good from the moment you install it. Credit where it is due.&lt;/p&gt;

&lt;p&gt;The tradeoffs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extensions run on a Node.js runtime, so memory usage is permanently non trivial&lt;/li&gt;
&lt;li&gt;Cloud sync and AI features require an account, and &lt;strong&gt;the best parts sit behind the paid plan&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;It is closed source, so you cannot verify what it does with what you type&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your job is "connect my work tools and go faster on a company Mac", Raycast is still the first thing I would recommend. I recommend it to people regularly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alfred: still standing after more than a decade
&lt;/h2&gt;

&lt;p&gt;That fact alone says a lot. It is local, it does not chatter over the network, and it is rock solid. Buy the Powerpack and you unlock Workflows, where shell scripts and AppleScript stack up into automation nobody else has. If you are deep into that, you do not need another launcher.&lt;/p&gt;

&lt;p&gt;The weak spots: macOS only, and the UI feels a bit classical by current standards. Also the paywall sits at the entrance rather than at the advanced end, so the free version stays close to a plain app launcher.&lt;/p&gt;

&lt;h2&gt;
  
  
  Look: this is the part where I stop being modest
&lt;/h2&gt;

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

&lt;p&gt;&lt;a href="https://github.com/kunkka19xx/look" rel="noopener noreferrer"&gt;Look&lt;/a&gt; is a keyboard first, local first launcher. Native SwiftUI on macOS, search engine written in Rust, Tauri on Windows and Linux. No Electron anywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Speed was the constraint, not the goal
&lt;/h3&gt;

&lt;p&gt;Under 1 ms per query on a 2000 item index. 30 microseconds to browse with an empty query. Holding those numbers is exactly why the app is a Rust core under a native shell. &lt;strong&gt;The flip side is that plenty of other things got sacrificed to keep them.&lt;/strong&gt; More on that near the end. There is no background daemon.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Free, and open source under GPLv3
&lt;/h3&gt;

&lt;p&gt;There is no paid tier. There is no "that feature is Pro". Partly on principle, partly because I do not want to run a subscription business.&lt;/p&gt;

&lt;p&gt;The source is all on GitHub, so what it reads and what it sends is verifiable rather than promised. No account, no telemetry.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The AI runs on your machine
&lt;/h3&gt;

&lt;p&gt;Apple Intelligence, or whatever model you have pulled in Ollama.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Natural language file search ("pdfs from last week")&lt;/li&gt;
&lt;li&gt;Summarize, translate or rewrite whatever you copied&lt;/li&gt;
&lt;li&gt;Calendar and reminders in plain language ("move my dentist to friday")&lt;/li&gt;
&lt;li&gt;Type &lt;code&gt;&amp;gt;&lt;/code&gt; and the panel becomes a conversation. Every change previews first, &lt;code&gt;Enter&lt;/code&gt; confirms, &lt;code&gt;Cmd+Z&lt;/code&gt; undoes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The preview step and &lt;code&gt;Cmd+Z&lt;/code&gt; were there from day one. &lt;strong&gt;Watching a model confidently create the wrong calendar event once is all the design research you need&lt;/strong&gt; on that subject.&lt;/p&gt;

&lt;p&gt;Personal context (your calendar, your clipboard, your saved facts) is held to a stricter rule: it is attached only when inference is running on this machine. Point Look at a remote model and none of it goes out unless you explicitly allow it in settings.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  4. The tools are already inside
&lt;/h3&gt;

&lt;p&gt;No extensions to install. &lt;code&gt;Cmd+/&lt;/code&gt; and they are there.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;calc&lt;/code&gt;: factorials, constants, and notation like &lt;code&gt;1920x1080&lt;/code&gt; handled correctly&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pomo&lt;/code&gt;: pomodoro timer with a menu bar readout and a background music folder&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;todo&lt;/code&gt;: tasks grouped by day, completion rates, and a GitHub style year heatmap&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;speed&lt;/code&gt;: internet speed test on a live dial, which I built that way because it looks cool&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kill&lt;/code&gt;: process killer that also finds things by port (&lt;code&gt;:3000&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Clipboard history (&lt;code&gt;c"&lt;/code&gt;), translation (&lt;code&gt;t"&lt;/code&gt;), dictionary (&lt;code&gt;tw"&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Super actions
&lt;/h3&gt;

&lt;p&gt;With an empty query you get a control strip: Wi-Fi, Bluetooth, mic, theme, keep awake, screensaver, weather, battery, now playing, restart, shut down.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Cmd+W&lt;/code&gt; for Wi-Fi, &lt;code&gt;Cmd+B&lt;/code&gt; for Bluetooth. It visibly cuts how often you open System Settings. Restart and Shut Down need two presses to fire. There was a version where one press was enough, and &lt;strong&gt;I learned why that was a bad idea through direct personal experience.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Add your own rows
&lt;/h3&gt;

&lt;p&gt;Drop a TOML file in &lt;code&gt;~/.look/sources/&lt;/code&gt; and the contents of a directory, the lines of a file, or the output of a command get indexed and ranked next to your apps and files. Git repos, SSH hosts, docker containers, project folders.&lt;/p&gt;

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

&lt;p&gt;No plugin store, no JavaScript. Ready made definitions live in &lt;a href="https://github.com/kunkka19xx/lookbook" rel="noopener noreferrer"&gt;lookbook&lt;/a&gt;: copy one in, reload, done.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Same app on your other machines
&lt;/h3&gt;

&lt;p&gt;Windows and Linux run the same core (&lt;code&gt;Alt+Space&lt;/code&gt;). Mac at work, Linux at home, and your muscle memory transfers intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Look is weak
&lt;/h2&gt;

&lt;p&gt;Leaving this out would make the rest less believable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The extension ecosystem does not come close to Raycast's.&lt;/strong&gt; There is no store. There is no plan to build one&lt;/li&gt;
&lt;li&gt;Natural language AI actions need an Ollama setup (the deterministic paths, like the &lt;code&gt;@&lt;/code&gt; syntax, work with no model at all)&lt;/li&gt;
&lt;li&gt;Development moves fast, so behavior does shift between versions&lt;/li&gt;
&lt;li&gt;It is one person. File an issue when you hit a bug and it will probably be me reading it&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap kunkka19xx/tap
brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; look
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then go to &lt;code&gt;System Settings &amp;gt; Keyboard &amp;gt; Keyboard Shortcuts &amp;gt; Spotlight&lt;/code&gt;, unbind &lt;code&gt;Cmd+Space&lt;/code&gt; there, and give it to Look. &lt;strong&gt;Skip this step and you will conclude the app does not launch.&lt;/strong&gt; (I have made this mistake. On software I wrote.)&lt;/p&gt;

&lt;p&gt;Release builds are signed and notarized, so no Gatekeeper workarounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Raycast wins on extensions. Alfred wins if you already own a decade of Workflows. I will happily concede both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything else, Look wins.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sub millisecond search. No paid tier. Full source. Zero telemetry. No account. No daemon. AI on your own machine. The same app across three operating systems. &lt;strong&gt;All of it free.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I never accepted that paying monthly for a moderately fast launcher was supposed to be the normal state of things. So I built the alternative. Try it once and &lt;code&gt;Cmd+Space&lt;/code&gt; stops being something you can give back.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/kunkka19xx/look" rel="noopener noreferrer"&gt;https://github.com/kunkka19xx/look&lt;/a&gt;&lt;br&gt;
Docs: &lt;a href="https://noah-code.com/docs/look" rel="noopener noreferrer"&gt;https://noah-code.com/docs/look&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A star on the repo makes the next release arrive sooner. That is not a figure of speech.&lt;/p&gt;

</description>
      <category>macos</category>
      <category>raycast</category>
      <category>alfred</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How I put Apple Intelligence into my app Look</title>
      <dc:creator>(ハオ)</dc:creator>
      <pubDate>Sun, 28 Jun 2026 13:30:00 +0000</pubDate>
      <link>https://dev.to/kunkka19xx/how-i-put-apple-intelligence-into-my-app-look-3kjg</link>
      <guid>https://dev.to/kunkka19xx/how-i-put-apple-intelligence-into-my-app-look-3kjg</guid>
      <description>&lt;p&gt;So this is a little story about how I added on-device AI to my macOS app, &lt;strong&gt;Look&lt;/strong&gt;, using Apple's Foundation Models framework. I wanna share what was good, what was painful, some real code, and why I think building on this now is actually a good chance even if the model is still small.&lt;/p&gt;

&lt;h3&gt;
  
  
  First, what is Look?
&lt;/h3&gt;

&lt;p&gt;Look is a keyboard launcher for macOS. You press a hotkey, you type, and it find your apps, files and folders super fast. The search core is written in Rust so it is quick.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/kunkka19xx/look" rel="noopener noreferrer"&gt;Look Repository&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;But here is the thing, people don't really think in keywords. They think in questions. Like "that pdf i downloaded last week", or "what is 18% of 240", or "who painted Guernica". Normal keyword search can't really help with that.&lt;/p&gt;

&lt;p&gt;So I added a small AI layer to Look with Apple Intelligence. My one rule was simple: &lt;strong&gt;AI is best effort and it must never block or slow down the search.&lt;/strong&gt; If the model is not available, not ready, or it just fails, then Look works exactly like before. Nothing breaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why on-device and not a cloud LLM?
&lt;/h3&gt;

&lt;p&gt;For a launcher, three things matter a lot. It should be &lt;strong&gt;private&lt;/strong&gt; (your file names never leave the Mac), it should be &lt;strong&gt;free&lt;/strong&gt; (i hit this tool like 100 times a day, i can't pay per token), and it should &lt;strong&gt;start fast&lt;/strong&gt; (no network round trip). Apple's Foundation Models gives you a ~3B model running locally with a clean Swift API. For a launcher that is just the perfect shape.&lt;/p&gt;

&lt;p&gt;Ref: &lt;a href="https://developer.apple.com/documentation/foundationmodels" rel="noopener noreferrer"&gt;https://developer.apple.com/documentation/foundationmodels&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How i actually integrated it into Look
&lt;/h3&gt;

&lt;p&gt;One important decision first. In Look, ALL the Foundation Models code lives behind one provider type. The rest of the app never import &lt;code&gt;FoundationModels&lt;/code&gt; directly, it only talks to my own little abstraction. So the on-device model is just one swappable "tier", and later i can add a cloud model without touching everything.&lt;/p&gt;

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

&lt;h4&gt;
  
  
  1. Always check availability first
&lt;/h4&gt;

&lt;p&gt;Apple Intelligence is not always there. Maybe the user have old hardware, maybe they didn't turn it on, maybe the model is still downloading. You can read all of this from &lt;code&gt;SystemLanguageModel.default.availability&lt;/code&gt;, and you must handle every case.&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="cp"&gt;#if canImport(FoundationModels)&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;FoundationModels&lt;/span&gt;
&lt;span class="cp"&gt;#endif&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;availability&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AIProviderAvailability&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cp"&gt;#if canImport(FoundationModels)&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;#available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;macOS&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requiresNewerOS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="kt"&gt;SystemLanguageModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;availability&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;available&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deviceNotEligible&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requiresNewerOS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appleIntelligenceNotEnabled&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appleIntelligenceNotEnabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// "Turn on Apple Intelligence in System Settings."&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modelNotReady&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modelNotReady&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                 &lt;span class="c1"&gt;// "The on-device model is still downloading."&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;other&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;other&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="kd"&gt;@unknown&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;other&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unknown availability state"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="cp"&gt;#else&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requiresNewerOS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="cp"&gt;#endif&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trick here is two layers. &lt;code&gt;#if canImport(FoundationModels)&lt;/code&gt; let the app &lt;em&gt;compile&lt;/em&gt; on a lower deployment target, and &lt;code&gt;#available(macOS 26, *)&lt;/code&gt; guard the &lt;em&gt;runtime&lt;/em&gt;. In Look every AI entry point check &lt;code&gt;availability.isAvailable&lt;/code&gt; and just return &lt;code&gt;nil&lt;/code&gt; if not. The AI toggle in Settings show a green check or a reason, but the search itself never break.&lt;/p&gt;

&lt;p&gt;Ref: &lt;a href="https://developer.apple.com/documentation/foundationmodels/systemlanguagemodel" rel="noopener noreferrer"&gt;https://developer.apple.com/documentation/foundationmodels/systemlanguagemodel&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Structured output with @Generable (this is the best part)
&lt;/h4&gt;

&lt;p&gt;Okay this is where Foundation Models really shine. Instead of parsing the model text by hand, you just describe the &lt;em&gt;shape&lt;/em&gt; of the output with &lt;code&gt;@Generable&lt;/code&gt; and &lt;code&gt;@Guide&lt;/code&gt;, and the framework give you back exactly that type. In Look i use it to turn a vague query like "the folder where i keep invoices" into a typed plan that my Rust engine can run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;macOS&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;@Generable&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;EngineQueryPlan&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@Guide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"The kind of thing the user wants to find."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PlanKind&lt;/span&gt;

    &lt;span class="kd"&gt;@Guide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Just the keywords to search for, with filler words removed."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;searchText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;

    &lt;span class="kd"&gt;@Generable&lt;/span&gt;
    &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;PlanKind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;any&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// guided generation, no string parsing. the result IS an EngineQueryPlan&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;LanguageModelSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="o"&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="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;to&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="nv"&gt;generating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;EngineQueryPlan&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asIntent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;// -&amp;gt; Rust prefix grammar, e.g. app -&amp;gt; a"..., file -&amp;gt; f"...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No JSON prompt engineering, no parsing, no begging the model to "please answer in valid JSON". You just get a typed and validated struct. This remove a whole category of bugs honestly.&lt;/p&gt;

&lt;p&gt;In Look this only run as a &lt;em&gt;rescue&lt;/em&gt;. When the fast local search return zero result, then i rewrite the natural language query into engine grammar and search again. If local search already found something, the model never run. So you never pay latency for the queries that were already fine.&lt;/p&gt;

&lt;p&gt;Ref: &lt;a href="https://developer.apple.com/documentation/foundationmodels/generating-swift-data-structures-with-guided-generation" rel="noopener noreferrer"&gt;https://developer.apple.com/documentation/foundationmodels/generating-swift-data-structures-with-guided-generation&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Streaming short answers
&lt;/h4&gt;

&lt;p&gt;For the inline answer card (like "what is the capital of Norway"), i stream the response so the text appear word by word, and i cap the length so the answer stay small and launcher sized.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;AsyncThrowingStream&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;AsyncThrowingStream&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;continuation&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;LanguageModelSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;answerInstructions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;GenerationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;maximumResponseTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;220&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;streamResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;to&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="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="kt"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isCancelled&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// snapshot.content is cumulative&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;throwing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;error&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="n"&gt;continuation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onTermination&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&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;Two small details worth stealing. Each streamed &lt;code&gt;snapshot.content&lt;/code&gt; is the &lt;em&gt;cumulative&lt;/em&gt; answer (not a delta), and the on-device model is the &lt;em&gt;last&lt;/em&gt; fallback in Look. Calculator and web sources are tried first, the model only run when nothing else hit.&lt;/p&gt;

&lt;p&gt;Ref: &lt;a href="https://developer.apple.com/documentation/foundationmodels/languagemodelsession" rel="noopener noreferrer"&gt;https://developer.apple.com/documentation/foundationmodels/languagemodelsession&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Prewarm to hide the latency
&lt;/h4&gt;

&lt;p&gt;The first call to the model have a noticeable startup cost. A launcher is opened and closed all the time, so i &lt;code&gt;prewarm()&lt;/code&gt; a resident session the moment the window open or the query start to look like a sentence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@MainActor&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;AppleIntelligenceWarmer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;shared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;AppleIntelligenceWarmer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;LanguageModelSession&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;prewarm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;warm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kt"&gt;LanguageModelSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;warm&lt;/span&gt;
        &lt;span class="n"&gt;warm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prewarm&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;Ref: &lt;a href="https://developer.apple.com/documentation/foundationmodels/languagemodelsession/prewarm()" rel="noopener noreferrer"&gt;https://developer.apple.com/documentation/foundationmodels/languagemodelsession/prewarm()&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The advantages (what is genuinely great)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private by default.&lt;/strong&gt; Everything run on the Mac. For a launcher that see every file name you type, this is not a nice to have, this is the whole reason i felt okay to ship AI at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero marginal cost.&lt;/strong&gt; No API key, no billing, no rate limit. I can let the model run on every qualifying query and not watch a meter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Really nice Swift API.&lt;/strong&gt; The &lt;code&gt;@Generable&lt;/code&gt; guided generation is the star. Getting a typed and validated struct straight from the model is so good.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is already on the device.&lt;/strong&gt; I don't bundle a multi gigabyte model in my app, the OS ship and update the weights.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean and composable.&lt;/strong&gt; Sessions, instructions, streaming, options, prewarm. The pieces just snap together into a real feature.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The disadvantages (what still hurt, honestly)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Availability is a moving target.&lt;/strong&gt; It need macOS 26 and eligible hardware, the user have to enable it, and the model can still be downloading. A big chunk of your users just won't have it. So you must design the no-AI path as the default, not the exception.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is a small model.&lt;/strong&gt; ~3B is great for classification, rewriting and short factual answers, but it is clearly weaker than big cloud models at reasoning, long writing or niche knowledge. Scope your feature to what a small model do well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First use latency.&lt;/strong&gt; Without prewarm the first token is slow. Even with it, on-device inference is not free. For a sub 100ms tool this is a real thing to budget around.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guardrails can surprise you.&lt;/strong&gt; Generation can get refused or interrupted by safety guardrails. You have to catch those error and degrade nicely. In Look it just fall back to the raw query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;macOS 26 only and the API is young.&lt;/strong&gt; You are building on a brand new surface that will keep changing. The &lt;code&gt;#if canImport&lt;/code&gt; plus &lt;code&gt;#available&lt;/code&gt; gymnastics is the price of being early.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why building on it now is a chance, not a risk
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgjjoi29p9qxctg653rcw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgjjoi29p9qxctg653rcw.jpg" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Okay here is the main point I wanted to make. &lt;strong&gt;The local model only get better, and my code don't have to change.&lt;/strong&gt; Apple ship and upgrade the on-device weights through OS updates. The same &lt;code&gt;LanguageModelSession&lt;/code&gt; call i write today will, one year from now, be backed by a smarter and faster model. For free. With zero work from me. The &lt;code&gt;@Generable&lt;/code&gt; plan i defined keep returning the same type, it just get filled in more accurate.&lt;/p&gt;

&lt;p&gt;That really change the math. Adopting Foundation Models early is not a bet that today's small model is good enough for everything, because it clearly is not. It is a bet that &lt;strong&gt;the floor rise under you&lt;/strong&gt;. The integration work (the provider abstraction, the availability handling, the prewarm, the fallback design) is the hard part, and it is a one time cost. Once Look speak fluent Foundation Models, every OS update become a silent capability upgrade.&lt;/p&gt;

&lt;p&gt;So I am shipping on-device AI in Look today. Scoped tight to what a 3B model do well, always optional, never blocking. And i treat it like infrastructure that i grow into, not a feature i have to perfect right now. If you are building a Mac app, that is the chance. Get the plumbing right while the API is still young, and ride the model improvements for free.&lt;/p&gt;

&lt;p&gt;Thanks for reading. If you wanna try it, go grab Look and turn on the AI toggle in Settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reference links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Foundation Models framework: &lt;a href="https://developer.apple.com/documentation/foundationmodels" rel="noopener noreferrer"&gt;https://developer.apple.com/documentation/foundationmodels&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SystemLanguageModel: &lt;a href="https://developer.apple.com/documentation/foundationmodels/systemlanguagemodel" rel="noopener noreferrer"&gt;https://developer.apple.com/documentation/foundationmodels/systemlanguagemodel&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LanguageModelSession: &lt;a href="https://developer.apple.com/documentation/foundationmodels/languagemodelsession" rel="noopener noreferrer"&gt;https://developer.apple.com/documentation/foundationmodels/languagemodelsession&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Guided generation (@Generable / @Guide): &lt;a href="https://developer.apple.com/documentation/foundationmodels/generating-swift-data-structures-with-guided-generation" rel="noopener noreferrer"&gt;https://developer.apple.com/documentation/foundationmodels/generating-swift-data-structures-with-guided-generation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HIG: Generative AI: &lt;a href="https://developer.apple.com/design/human-interface-guidelines/generative-ai" rel="noopener noreferrer"&gt;https://developer.apple.com/design/human-interface-guidelines/generative-ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;WWDC25, Meet the Foundation Models framework: &lt;a href="https://developer.apple.com/videos/play/wwdc2025/286/" rel="noopener noreferrer"&gt;https://developer.apple.com/videos/play/wwdc2025/286/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>appleintelligence</category>
      <category>ai</category>
      <category>rust</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I built look because Spotlight didn’t match how I work.</title>
      <dc:creator>(ハオ)</dc:creator>
      <pubDate>Wed, 08 Apr 2026 08:24:09 +0000</pubDate>
      <link>https://dev.to/kunkka19xx/i-built-look-because-spotlight-didnt-match-how-i-work-3bmh</link>
      <guid>https://dev.to/kunkka19xx/i-built-look-because-spotlight-didnt-match-how-i-work-3bmh</guid>
      <description>&lt;p&gt;&lt;em&gt;An open-source, community-driven launcher for macOS. A lightweight alternative to Spotlight and Raycast.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Github:&lt;/strong&gt; &lt;a href="https://github.com/kunkka19xx/look" rel="noopener noreferrer"&gt;Repository&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Some projects start from a big idea. This one started from a hundred tiny annoyances.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Open app -&amp;gt; Switch -&amp;gt; Search file -&amp;gt; Open Finder -&amp;gt; Copy something -&amp;gt; Paste the wrong thing -&amp;gt; Search again -&amp;gt; Open Terminal for one quick command. Go back.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nothing was broken, but my attention kept getting sliced into small pieces.&lt;br&gt;
After enough of those slices, even simple work feels heavy. I wanted one small window that could keep me in flow.&lt;br&gt;&lt;br&gt;
That window became &lt;strong&gt;Look&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And yes, I tried built-in tools first. Spotlight works for many people, but in my daily workflow I kept hitting the same friction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slower than I wanted when moving quickly&lt;/li&gt;
&lt;li&gt;ranking that often missed my actual intent&lt;/li&gt;
&lt;li&gt;no single place for app search, file flow, clipboard reuse, and quick utility actions
So this was not “reinvent for fun.”
It was a practical response to repeated friction.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  The feeling I was chasing
&lt;/h2&gt;

&lt;p&gt;I did not want the "most feature-rich launcher."&lt;/p&gt;

&lt;p&gt;I wanted a launcher that feels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;quiet&lt;/strong&gt;: does not pull me into UI complexity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fast&lt;/strong&gt;: responds quickly enough that I trust muscle memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;local-first&lt;/strong&gt;: does not depend on cloud round trips for basic actions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;honest&lt;/strong&gt;: predictable behavior, clear shortcuts, no mystery state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the core of &lt;code&gt;look&lt;/code&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  What &lt;code&gt;look&lt;/code&gt; does in real daily usage
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1) Search apps, files, and folders from one place
&lt;/h3&gt;

&lt;p&gt;The default flow is intentionally simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;open launcher&lt;/li&gt;
&lt;li&gt;type&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Enter&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When I need precision, I use scoped prefixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;a"term&lt;/code&gt; -&amp;gt; apps only (great when app names collide with file names)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;f"term&lt;/code&gt; -&amp;gt; files only (for docs, notes, exports)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;d"term&lt;/code&gt; -&amp;gt; folders only (for project navigation)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;r"pattern&lt;/code&gt; -&amp;gt; regex (for structured matching)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tiny prefix system sounds small, but in practice it saves a lot of "did I search the wrong thing" moments.&lt;/p&gt;

&lt;p&gt;Path-style search also works (&lt;code&gt;git/books-pc/readme&lt;/code&gt; style fragments), so navigation feels closer to how developers actually think.&lt;/p&gt;


&lt;h3&gt;
  
  
  2) Clipboard history that is practical, not gimmicky
&lt;/h3&gt;

&lt;p&gt;Clipboard history is one of those features that either becomes daily muscle memory or gets ignored.&lt;/p&gt;

&lt;p&gt;I built it to be useful in normal work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;c"&lt;/code&gt; -&amp;gt; show recent clipboard entries&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;c"word&lt;/code&gt; -&amp;gt; filter by remembered keyword&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Enter&lt;/code&gt; -&amp;gt; copy selected history item back to clipboard&lt;/li&gt;
&lt;li&gt;delete in preview -&amp;gt; remove sensitive item from look history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The right-side preview matters. I can inspect long clipboard content before reusing it, instead of guessing from a short line.&lt;/p&gt;
&lt;h2&gt;
  
  
  This sounds minor, but if you work across Slack/email/code/docs all day, it removes a lot of repeated re-copying.
&lt;/h2&gt;
&lt;h3&gt;
  
  
  3) Command mode for utility actions in the same flow
&lt;/h3&gt;

&lt;p&gt;Sometimes I do not want to open a new app for a quick operation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Cmd+/&lt;/code&gt; opens command mode.&lt;/p&gt;

&lt;p&gt;Current built-ins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;calc&lt;/code&gt; for quick expression checks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shell&lt;/code&gt; for short one-off command execution&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kill&lt;/code&gt; for force-killing a stuck app&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sys&lt;/code&gt; for a quick system snapshot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping these inside launcher means fewer context switches and less "open this app for one tiny action" behavior.&lt;/p&gt;

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

&lt;p&gt;These shortcuts are where the day-to-day value shows up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Cmd+F&lt;/code&gt;: reveal selected app/file/folder in Finder at exact location&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Cmd+C&lt;/code&gt;: copy selected file/folder to pasteboard so I can paste directly&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Cmd+Enter&lt;/code&gt;: send current query to browser search when local result is not enough&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Cmd+H&lt;/code&gt;: open full in-window help for quick reminder&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also made sure the launcher does not keep aggressively forcing itself to front after opening another app, because that behavior breaks trust quickly.&lt;/p&gt;

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

&lt;p&gt;One personal quality-of-life detail: accented input.&lt;/p&gt;

&lt;p&gt;I added normalization so matching is friendlier for Vietnamese typing patterns, including handling cases like &lt;code&gt;đ -&amp;gt; d&lt;/code&gt; and combining marks.&lt;/p&gt;

&lt;p&gt;Practical example: &lt;code&gt;tẻrminal&lt;/code&gt; still matches &lt;code&gt;Terminal&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is a small implementation detail, but it makes the launcher feel like a tool for real people, not just demo text.&lt;/p&gt;

&lt;p&gt;&lt;a href="/post_imgs/6.png" class="article-body-image-wrapper"&gt;&lt;img src="/post_imgs/6.png" alt="alt text"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Under the hood: why this architecture
&lt;/h2&gt;

&lt;p&gt;I wanted native UX quality and tight performance control. The project is split into three layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SwiftUI/AppKit shell&lt;/strong&gt; (macOS integration + interaction)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust core engine&lt;/strong&gt; (indexing, matching, ranking)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FFI bridge&lt;/strong&gt; (small stable boundary between the two)
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
├── apps/
│   └── macos/
│       └── LauncherApp/
├── core/
│   ├── engine/
│   ├── indexing/
│   ├── matching/
│   ├── ranking/
│   └── storage/
├── bridge/
│   └── ffi/
├── docs/
├── scripts/
└── assets/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This split helps in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI iteration stays quick on native macOS side&lt;/li&gt;
&lt;li&gt;ranking/matching logic is testable and deterministic in Rust&lt;/li&gt;
&lt;li&gt;bridge API remains narrow and easier to reason about (&lt;code&gt;search&lt;/code&gt;, &lt;code&gt;record_usage&lt;/code&gt;, &lt;code&gt;reload_config&lt;/code&gt;, &lt;code&gt;translate&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Ranking, relevance, and "why this result is on top"
&lt;/h2&gt;

&lt;p&gt;Search quality is always the hardest part of launcher UX.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;look&lt;/code&gt; currently combines multiple signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exact/prefix/fuzzy text matching&lt;/li&gt;
&lt;li&gt;contains/token matching&lt;/li&gt;
&lt;li&gt;path-aware scoring&lt;/li&gt;
&lt;li&gt;result kind bias (app/folder/file)&lt;/li&gt;
&lt;li&gt;usage frequency and recency influence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Blank query has dedicated browse scoring so launcher is useful before typing.&lt;/p&gt;

&lt;p&gt;One interesting real-world behavior: if a settings entry has been used heavily, it can stay high even when a new app was just opened. That is not random; it follows the current weighting model.&lt;/p&gt;

&lt;p&gt;I added regression tests around these ranking edge cases after seeing them in real usage data.&lt;/p&gt;


&lt;h2&gt;
  
  
  Clipboard tradeoffs (the honest version)
&lt;/h2&gt;

&lt;p&gt;Clipboard monitoring uses a lightweight polling strategy plus burst sampling for rapid copy sequences.&lt;/p&gt;

&lt;p&gt;This gives a good responsiveness/performance balance, but it is still a practical engineering tradeoff, not a perfect event-capture system under extreme bursts.&lt;/p&gt;

&lt;p&gt;I prefer this kind of explicit tradeoff over pretending something is "real-time perfect" when it is not.&lt;/p&gt;


&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Homebrew
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap kunkka19xx/tap
brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; look
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew upgrade &lt;span class="nt"&gt;--cask&lt;/span&gt; kunkka19xx/tap/look
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Common install and first-run issues (please read)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Gatekeeper blocks first launch
&lt;/h3&gt;

&lt;p&gt;If macOS blocks launch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;right-click &lt;code&gt;Look.app&lt;/code&gt; -&amp;gt; &lt;code&gt;Open&lt;/code&gt; -&amp;gt; confirm&lt;/li&gt;
&lt;li&gt;or &lt;code&gt;System Settings&lt;/code&gt; -&amp;gt; &lt;code&gt;Privacy &amp;amp; Security&lt;/code&gt; -&amp;gt; &lt;code&gt;Open Anyway&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[IMAGE_PLACEHOLDER_GATEKEEPER]&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;Cmd+Space&lt;/code&gt; conflict with Spotlight
&lt;/h3&gt;

&lt;p&gt;By default, Spotlight owns that shortcut.&lt;/p&gt;

&lt;p&gt;To use &lt;code&gt;Cmd+Space&lt;/code&gt; with &lt;code&gt;look&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;open &lt;code&gt;System Settings&lt;/code&gt; -&amp;gt; &lt;code&gt;Keyboard&lt;/code&gt; -&amp;gt; &lt;code&gt;Keyboard Shortcuts...&lt;/code&gt; -&amp;gt; &lt;code&gt;Spotlight&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;disable or rebind &lt;code&gt;Show Spotlight search&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Hotkey does not work after fully quitting app
&lt;/h3&gt;

&lt;p&gt;Relaunch once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open &lt;span class="s2"&gt;"/Applications/Look.app"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then hotkey behavior returns.&lt;/p&gt;




&lt;h2&gt;
  
  
  Configuration: users can tune a lot
&lt;/h2&gt;

&lt;p&gt;I did not want a "my defaults are your destiny" app.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;look&lt;/code&gt; supports both settings UI and config-file tuning.&lt;/p&gt;

&lt;p&gt;Config file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;~/.look.config&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reload command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Cmd+Shift+;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What users can configure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;indexing roots/depth/limits (&lt;code&gt;file_scan_roots&lt;/code&gt;, &lt;code&gt;file_scan_depth&lt;/code&gt;, &lt;code&gt;file_scan_limit&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;exclusion rules (&lt;code&gt;file_exclude_paths&lt;/code&gt;, &lt;code&gt;app_exclude_paths&lt;/code&gt;, &lt;code&gt;app_exclude_names&lt;/code&gt;, &lt;code&gt;skip_dir_names&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;privacy/network controls (&lt;code&gt;translate_allow_network&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;backend logging level (&lt;code&gt;backend_log_level&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;launch behavior (&lt;code&gt;launch_at_login&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;UI customization (tint, blur, opacity, font, border, background mode)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is important to me because people work differently. Some want strict minimalism, some want broader indexing, some care deeply about visual tuning.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Source, docs, and project links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/kunkka19xx/look" rel="noopener noreferrer"&gt;https://github.com/kunkka19xx/look&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;User guide: &lt;code&gt;docs/user-guide.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Backend guide: &lt;code&gt;docs/backend-guide.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Long-term goal (and honest constraint)
&lt;/h2&gt;

&lt;p&gt;If the project gets enough traction, one of my top priorities is proper Apple signing/notarization for a smoother install experience.&lt;/p&gt;

&lt;p&gt;Right now, users may need manual Gatekeeper "Open Anyway" steps. I want to remove that.&lt;/p&gt;

&lt;p&gt;But this requires real cost (Apple Developer program) and extra release pipeline work. If community adoption grows, that is where I will invest first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;I built &lt;code&gt;look&lt;/code&gt; because I was tired of losing focus to small, repeated friction.&lt;/p&gt;

&lt;p&gt;This is still evolving, but it already gives me what I hoped for: less switching, less noise, and more actual work done.&lt;/p&gt;

&lt;p&gt;If you try it, I would love candid feedback:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;where ranking feels great&lt;/li&gt;
&lt;li&gt;where ranking feels wrong&lt;/li&gt;
&lt;li&gt;what feature would make this your default launcher&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/kunkka19xx/look" rel="noopener noreferrer"&gt;https://github.com/kunkka19xx/look&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>macos</category>
      <category>rust</category>
      <category>swift</category>
    </item>
  </channel>
</rss>
