<?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>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>
