<?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: Deian</title>
    <description>The latest articles on DEV Community by Deian (@tofuchicken).</description>
    <link>https://dev.to/tofuchicken</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%2F4104552%2F6bfd3738-bde0-45d2-8b79-dc44c3ee36b2.png</url>
      <title>DEV Community: Deian</title>
      <link>https://dev.to/tofuchicken</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tofuchicken"/>
    <language>en</language>
    <item>
      <title>my agents loved writing to notion. none of them ever updated anything.
so i took the pen away. they read sources, they leave 30-day notes, and i triage. wrote it up here:
https://dev.to/tofuchicken/human-in-the-loop-knowledge-base-for-ai-agents-51lf</title>
      <dc:creator>Deian</dc:creator>
      <pubDate>Sun, 27 Sep 2026 09:25:26 +0000</pubDate>
      <link>https://dev.to/tofuchicken/my-agents-loved-writing-to-notion-none-of-them-ever-updated-anything-so-i-took-the-pen-away-2gbl</link>
      <guid>https://dev.to/tofuchicken/my-agents-loved-writing-to-notion-none-of-them-ever-updated-anything-so-i-took-the-pen-away-2gbl</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/tofuchicken/human-in-the-loop-knowledge-base-for-ai-agents-51lf" class="crayons-story__hidden-navigation-link"&gt;Human-in-the-loop knowledge base for AI agents&lt;/a&gt;
    &lt;div class="crayons-article__cover crayons-article__cover__image__feed"&gt;
      &lt;iframe src="https://www.youtube.com/embed/HKo3CFiuqug" title="Human-in-the-loop knowledge base for AI agents"&gt;&lt;/iframe&gt;
    &lt;/div&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/tofuchicken" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F4104552%2F6bfd3738-bde0-45d2-8b79-dc44c3ee36b2.png" alt="tofuchicken profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/tofuchicken" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Deian
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Deian
                
                
              
              &lt;div id="story-author-preview-content-4754914" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/tofuchicken" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F4104552%2F6bfd3738-bde0-45d2-8b79-dc44c3ee36b2.png" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Deian&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/tofuchicken/human-in-the-loop-knowledge-base-for-ai-agents-51lf" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 27&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/tofuchicken/human-in-the-loop-knowledge-base-for-ai-agents-51lf" id="article-link-4754914"&gt;
          Human-in-the-loop knowledge base for AI agents
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/agents"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;agents&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/llm"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;llm&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/productivity"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;productivity&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/tofuchicken/human-in-the-loop-knowledge-base-for-ai-agents-51lf#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>agents</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Human-in-the-loop knowledge base for AI agents</title>
      <dc:creator>Deian</dc:creator>
      <pubDate>Sun, 27 Sep 2026 09:23:53 +0000</pubDate>
      <link>https://dev.to/tofuchicken/human-in-the-loop-knowledge-base-for-ai-agents-51lf</link>
      <guid>https://dev.to/tofuchicken/human-in-the-loop-knowledge-base-for-ai-agents-51lf</guid>
      <description>&lt;p&gt;&lt;strong&gt;I stopped letting my agents write to my knowledge base. They can read it; they can leave notes next to it; but the library itself is mine. That split exists because every agent I run loves to create, and none of them want to update.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For months I gave my agents a shared Notion workspace. The idea was simple: they work, they write down what they learned, the next agent reads it. What I got was a one-way street. Pages appeared. Nothing got revised, nothing cited a source, and every handoff depended on whatever the previous agent had bothered to write down, in its own words.&lt;/p&gt;

&lt;p&gt;The moment it got on my nerves: I was working on a client's new feature pages when my agents pulled outdated information from Notion and built a page that wasn't in line with my specs. The specs were right. The page wasn't. The agents had read the wrong thing and written with full confidence.&lt;/p&gt;

&lt;p&gt;Who writes? Everybody. Who updates? Nobody. Who checks what's current? Me, by hand, which was the job I had tried to automate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The split
&lt;/h2&gt;

&lt;p&gt;So I drew a line. On one side: sources. Documents I chose, organized into projects and folders, permanent until I say otherwise. On the other side: notes. My agents can leave these while they work — the problem, what they tried, what worked, what to check next time.&lt;/p&gt;

&lt;p&gt;Notes are untrusted by design. They live for 30 days and then they're gone, even the ones I kept. They can't be edited in place (append a new one, remove the old one), they never cross between Profiles, and nothing in them ever becomes a source on its own. A note does not become a fact by being saved.&lt;/p&gt;

&lt;p&gt;The triage is mine. I keep a review queue and each note gets one of three verdicts: Keep, Add to draft, or Remove. Useful ones get collected into a draft, and before that draft joins the library I check its claims and edit it myself. Material enters the source library through one door, and I'm standing in it.&lt;/p&gt;

&lt;p&gt;Does it work? My library holds 87 docs right now, and I trust all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop
&lt;/h2&gt;

&lt;p&gt;When an agent needs knowledge, it asks through the CLI or API. What comes back is short: verbatim passages, each carrying a citation I can open, plus a single support score. Takibi never writes answer prose. If nothing matches, it returns no passages, and my agents are instructed to say the evidence isn't there instead of filling the gap.&lt;/p&gt;

&lt;p&gt;When sources disagree, it flags the conflict and returns both sides. I don't want my retrieval picking a winner behind my back. I'd rather see the disagreement and decide.&lt;/p&gt;

&lt;p&gt;That citation is the whole point. I can follow it and read what the source actually said, including the qualifications the agent left out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The permissions
&lt;/h2&gt;

&lt;p&gt;Each job gets its own Profile, which is just a defined view over the same workspace. The writing agent reads approved product folders. Nothing else. The transcript uploader files Capkiku captures into each client's Uploads folder and remembers what it already sent — that workflow can add transcripts but can't edit or delete the library, and Uploads permission is off by default everywhere else.&lt;/p&gt;

&lt;p&gt;Keys rotate or revoke individually. Pause a Profile and all its keys stop. Least privilege isn't a posture here, it's the only reason I sleep: a confused agent can only ever touch what its job needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tradeoff
&lt;/h2&gt;

&lt;p&gt;Honest version: I'm the bottleneck now, on purpose. The triage queue is real work, and when the library is thin on a topic the move is still to broaden the question, fall back to search, or read the full document. Sometimes I just haven't added the source yet.&lt;/p&gt;

&lt;p&gt;What I'd change? The queue could rank itself better. Right now every note waits equally, and the obviously stale ones should sink on their own. That's next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steal this pattern
&lt;/h2&gt;

&lt;p&gt;You don't need my stack for the core idea. Two folders, a TTL habit, and a weekly triage pass get you most of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# notes older than 30 days get deleted. no archive.&lt;/span&gt;
&lt;span class="c"&gt;# if it mattered, it would be a source by now.&lt;/span&gt;
find ./agent-notes &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.md'&lt;/span&gt; &lt;span class="nt"&gt;-mtime&lt;/span&gt; +30 &lt;span class="nt"&gt;-delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rules that matter: agents append, never edit. Nothing graduates without a human reading it. And the day a note feels too important to expire is the day you promote it, not the day you extend the TTL.&lt;/p&gt;

&lt;p&gt;My library is small and I trust all of it. My scratch is large and I trust none of it. That sentence took me one dead product to learn.&lt;/p&gt;

&lt;p&gt;I built this into &lt;a href="https://takibibase.com" rel="noopener noreferrer"&gt;Takibi Base&lt;/a&gt; — a shared knowledge base where agents retrieve cited passages from sources I control. If your agents write more than they update, the pattern above works with or without it.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The 34 pixels that made PageSpeed think my static site was slow</title>
      <dc:creator>Deian</dc:creator>
      <pubDate>Thu, 03 Sep 2026 13:07:33 +0000</pubDate>
      <link>https://dev.to/tofuchicken/the-34-pixels-that-made-pagespeed-think-my-static-site-was-slow-37pp</link>
      <guid>https://dev.to/tofuchicken/the-34-pixels-that-made-pagespeed-think-my-static-site-was-slow-37pp</guid>
      <description>&lt;p&gt;Originally published at &lt;a href="https://deianisac.com/writing/pagespeed-speed-index/" rel="noopener noreferrer"&gt;deianisac.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;On deianisac.com, Speed Index stayed near four seconds after the page looked finished. Lighthouse scores a video of the load, not Largest Contentful Paint. 34 one-pixel flecks on Deian Isac's arrival canvas kept the first screen changing.&lt;/p&gt;

&lt;p&gt;That canvas now waits for a mouse move, tap, or key, not idle time. Clean Speed Index runs landed next to first paint. There is no Lighthouse user-agent check.&lt;/p&gt;

&lt;p&gt;My static site looked finished after about 1.1 seconds. PageSpeed sometimes said it took four.&lt;/p&gt;

&lt;p&gt;I looked for a huge hero, a JavaScript framework, and layout shift. It was 34 &lt;strong&gt;one-pixel flecks&lt;/strong&gt; on a canvas.&lt;/p&gt;

&lt;p&gt;This happened while I was rebuilding deianisac.com with Astro 7.2.10. The site ships static HTML, hand-written CSS, two small module scripts, and a few AVIF stills. I expected that to be boring to optimize.&lt;/p&gt;

&lt;p&gt;It wasn’t.&lt;/p&gt;

&lt;h2&gt;
  
  
  The page was already done
&lt;/h2&gt;

&lt;p&gt;The DebugBear &lt;strong&gt;filmstrip, not the score&lt;/strong&gt;, showed the page visually complete at 883 ms. First Contentful Paint and Largest Contentful Paint both landed at 846 ms. The full TTFB was 404 ms, with no layout shift.&lt;/p&gt;

&lt;p&gt;PageSpeed was less consistent. I saw Speed Index land at 1.1 s, 2.3 s, 4.1 s, then 3.9 s on the same deployed build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.chrome.com/docs/lighthouse/performance/speed-index/" rel="noopener noreferrer"&gt;Lighthouse calculates Speed Index from a video of the page loading&lt;/a&gt;. It compares the visual progression between frames. The browser can finish the largest element, satisfy LCP, and keep changing enough small pixels to drag out Speed Index.&lt;/p&gt;

&lt;p&gt;My network waterfall was mostly innocent. The screenshots were not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 34 pixels
&lt;/h2&gt;

&lt;p&gt;The arrival scene has a full-screen canvas over a static coast image. It draws 24 short dashes on the water and 34 tiny flecks in the air, about 30 times a second.&lt;/p&gt;

&lt;p&gt;A person sees a little weather. Lighthouse sees a &lt;strong&gt;first screen that keeps changing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I had deferred the canvas until the browser was idle. That made the result unpredictable. If the main thread became idle early, the animation started inside the measurement window. If it became idle later, Speed Index finished before the first fleck appeared.&lt;/p&gt;

&lt;p&gt;Idle was the wrong trigger. The canvas now waits for &lt;strong&gt;real interaction&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pointermove&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onPointerMove&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;passive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pointerdown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;passive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is &lt;strong&gt;no Lighthouse user-agent check&lt;/strong&gt;. A mouse move starts it on desktop. A tap starts it on a phone. A key press covers keyboard use. Visitors who only read the first screen do not spend CPU on decorative weather, and &lt;code&gt;prefers-reduced-motion&lt;/code&gt; still skips it entirely.&lt;/p&gt;

&lt;p&gt;Once started, the flecks ease in from the left instead of snapping into view.&lt;/p&gt;

&lt;p&gt;That change pulled the clean Speed Index runs back beside first paint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The images were still too big
&lt;/h2&gt;

&lt;p&gt;The canvas explained the four-second result. Phones were downloading 1,672-pixel stills for much smaller slots.&lt;/p&gt;

&lt;p&gt;I added 400, 800, 1,200, and native-width variants, with AVIF first and WebP as the next choice. Each `&lt;code&gt;now has a&lt;/code&gt;sizes` value that matches its layout, and the map stays lazy while the arrival image is eager and preloaded.&lt;/p&gt;

&lt;p&gt;I also compressed the 800-pixel arrival AVIF from 30.0 KiB to 19.4 KiB. That removed the image-delivery warning without producing an obvious visual loss. Going lower did.&lt;/p&gt;

&lt;p&gt;Changing the hero from synchronous to asynchronous decoding did not help. Across controlled runs, the median Speed Index moved by about 2 ms. That is noise, so I reverted it.&lt;/p&gt;

&lt;p&gt;The page got cheaper to load. Bytes were never &lt;strong&gt;the whole problem&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 244-byte placeholder
&lt;/h2&gt;

&lt;p&gt;After the image work, the frames showed one small blemish. The text appeared over a flat blue fallback, then the coast arrived 37 ms later.&lt;/p&gt;

&lt;p&gt;I looked at a &lt;a href="https://pinelab.studio/blog/implementing-low-quality-image-placeholders-lqip-in-astro/" rel="noopener noreferrer"&gt;ThumbHash approach for Astro&lt;/a&gt; and at &lt;a href="https://github.com/felixicaza/astro-lqip" rel="noopener noreferrer"&gt;astro-lqip&lt;/a&gt;. Fine if you have a folder of images. I had one hero.&lt;/p&gt;

&lt;p&gt;The smallest thing that worked was a 48-pixel WebP generated from the same hero. It is 244 bytes. I put its base64 data URL behind the real `` as a CSS background, so it adds no request and no browser script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.arrival__picture&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--arrival-placeholder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt; &lt;span class="m"&gt;52%&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;cover&lt;/span&gt; &lt;span class="nb"&gt;no-repeat&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;A small local Lighthouse test produced this median across three simulated mobile runs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fallback&lt;/th&gt;
&lt;th&gt;Speed Index&lt;/th&gt;
&lt;th&gt;LCP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Solid sky color&lt;/td&gt;
&lt;td&gt;922 ms&lt;/td&gt;
&lt;td&gt;1,503 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inline 48 px WebP&lt;/td&gt;
&lt;td&gt;902 ms&lt;/td&gt;
&lt;td&gt;1,502 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Twenty milliseconds is small. It closes the visible gap for roughly 350 bytes of HTML, so I kept it. I did not add a dependency or a blur transition.&lt;/p&gt;

&lt;h2&gt;
  
  
  The optimization I threw away
&lt;/h2&gt;

&lt;p&gt;At one point I inlined some first-screen color, broadened font preloads to every page, and preloaded each inner-page hero. The dependency tree looked cleaner. The screenshots got worse, including one run that did not become visually complete until 8.56 s.&lt;/p&gt;

&lt;p&gt;I liked that cleaner tree. The pass did not improve repeated tests, so I reverted it. One ugly run is not evidence about request order or render-blocking CSS.&lt;/p&gt;

&lt;p&gt;An unscored warning is not a reason to rearrange a working critical path. My later tree was HTML, one small stylesheet, and two small module scripts. The stylesheet blocks first paint because stylesheets do that. The scripts did not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The remaining bad run was real
&lt;/h2&gt;

&lt;p&gt;After the canvas and image changes, I ran production Lighthouse three times.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Speed Index&lt;/th&gt;
&lt;th&gt;Blocking time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1.13 s&lt;/td&gt;
&lt;td&gt;25 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1.18 s&lt;/td&gt;
&lt;td&gt;19 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;68&lt;/td&gt;
&lt;td&gt;3.02 s&lt;/td&gt;
&lt;td&gt;1.36 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A managed &lt;strong&gt;Cloudflare challenge script&lt;/strong&gt; injected by the hosting layer spent 3.42 s on the main thread in that last run.&lt;/p&gt;

&lt;p&gt;My static host does not expose a switch for that script. I cannot compress an AVIF until third-party main-thread work disappears. The outlier is still part of the experience for a visitor who receives it, but it needs a hosting-layer fix, not another image tweak.&lt;/p&gt;

&lt;p&gt;A 68 is a bad score for a page that already looked finished. I stopped treating the integer score as the result. The frames, the trace, and the repeated runs tell me what changed. The badge does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next time
&lt;/h2&gt;

&lt;p&gt;I would open those frames first.&lt;/p&gt;

&lt;p&gt;If the first screen looks done while Speed Index keeps climbing, I would look for a canvas, font swap, transition, or anything else that continues to repaint. Then I would pause decorative work until interaction and test again.&lt;/p&gt;

&lt;p&gt;I would still do the boring file work. Responsive images, honest &lt;code&gt;sizes&lt;/code&gt;, preload only the actual LCP asset, keep below-the-fold art lazy.&lt;/p&gt;

&lt;p&gt;I would change &lt;strong&gt;one thing at a time&lt;/strong&gt; and keep the failed runs. They are usually more useful than the screenshot with 100 in the corner.&lt;/p&gt;

&lt;p&gt;Speed Index was not wrong. I thought I had asked when the page looked done. It was asking when &lt;strong&gt;the pixels stop changing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Stop when &lt;strong&gt;the picture is done&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>astro</category>
      <category>webperf</category>
    </item>
    <item>
      <title>The browser used more CPU than the recorder</title>
      <dc:creator>Deian</dc:creator>
      <pubDate>Tue, 01 Sep 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/tofuchicken/the-browser-used-more-cpu-than-the-recorder-5ak7</link>
      <guid>https://dev.to/tofuchicken/the-browser-used-more-cpu-than-the-recorder-5ak7</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;strong&gt;I sampled &lt;a href="https://capkiku.com/" rel="noopener noreferrer"&gt;capkiku&lt;/a&gt; every second through a 65-minute call. 17% of one core, 85 MB of RAM, and Brave did the expensive work.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Meeting recorders have a reputation for cooking the laptop. Fans, battery, a guest in the call, something uploading in the background.&lt;/p&gt;

&lt;p&gt;I don't run that. As an SEO agency owner (&lt;a href="https://gobofu.com/" rel="noopener noreferrer"&gt;goBOFU&lt;/a&gt;), I need my device to stay lean. There’s tons of stuff constantly running, including multiple sessions of Grok and Codex.&lt;/p&gt;

&lt;p&gt;Before building capkiku, I used Audio Highjack, only for it to completely bog down my Mac and make it almost crash, eating away 4GB of RAM. What for?&lt;/p&gt;

&lt;p&gt;Anyway, now I just hit &lt;code&gt;Record Audio&lt;/code&gt; in the menu bar and leave the recording running.&lt;/p&gt;

&lt;p&gt;For my most recent session, a 65-minute call with my client, it recorded two tracks, the client in Brave (Google Meet) plus my Tula mic, on capkiku 1.0.19, an M3 MacBook Air with 16 GB, macOS 26, sampled every second.&lt;/p&gt;

&lt;p&gt;The recorder sat at 17% of one core, median 17.5%. Almost every second of the hour lived between 15 and 20%. Brave as a whole, every helper process included, sat around 73% of one core and spiked past three.&lt;/p&gt;

&lt;p&gt;If the laptop got warm, that was the meeting.&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%2Fc0sdo23v03y2rw4zok88.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%2Fc0sdo23v03y2rw4zok88.png" alt="CPU during a 65-minute call. capkiku holds 15–20% of one core. Brave spikes much higher." width="799" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What 65 minutes wrote
&lt;/h2&gt;

&lt;p&gt;The audio is 1 hour 5 minutes 42 seconds. The charts run closer to 67 minutes because that is the monitor window, from the files appearing to pressing the &lt;code&gt;Stop&lt;/code&gt; button. Others came out at 67.5 MiB and my own track at 72.0 MiB, both AAC at 48 kHz stereo. A 64 KB Markdown file sits next to them with an on-device transcript.&lt;/p&gt;

&lt;p&gt;That is about 2 MB per minute, written at 37 KB/s, in a straight line. It did not buffer the hour in RAM and dump it at the end.&lt;/p&gt;

&lt;p&gt;The app capkiku started around 97 MB idle, sat near 85 MB while recording, and peaked at 111 MB. RAM did not climb as the files grew. Idle used 16 threads, recording used 21, then it dropped back.&lt;/p&gt;

&lt;p&gt;CPU-time for the hour was 699 seconds, 540 user and 159 system, which integrates to 17.4% of one core.&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%2F8pku4yqt4tbvbs10w4dm.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%2F8pku4yqt4tbvbs10w4dm.png" alt="RAM stayed flat while 139 MB of audio landed on disk" width="799" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My app doesn’t need an account, and it doesn’t upload the captures. The files landed in a folder I already use as a knowledge base, and I review them before I save. Only thing it doesn’t do is diarization.&lt;/p&gt;

&lt;p&gt;Of course, there are some network requests for updates, and on macOS 26 the speech model can initiate a download.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop, then the transcript
&lt;/h2&gt;

&lt;p&gt;I hit &lt;code&gt;Stop&lt;/code&gt;, and &lt;code&gt;Review Audio&lt;/code&gt; showed up in about 12 seconds. The capkiku process barely moved: a few 7–26% flickers, RAM from 110 MB to 137 MB, then idle.&lt;/p&gt;

&lt;p&gt;12 seconds is what you need for the on-device model to transcribe 65 minutes times two files, and the menu bar app did not look busy.&lt;/p&gt;

&lt;p&gt;So where did the work go?&lt;/p&gt;

&lt;p&gt;I ran Apple's SpeechAnalyzer again on the same two &lt;code&gt;.m4a&lt;/code&gt; files from a small CLI, and sampled &lt;code&gt;localspeechrecognition.xpc&lt;/code&gt;. LocalCapture stayed at 0%, and the XPC sat at about 1.2 cores and 150 MB across two processes.&lt;/p&gt;

&lt;p&gt;That is the transcribe tax, it is Apple's process, not a second copy of the recorder.&lt;/p&gt;

&lt;p&gt;The CLI replay took 82 seconds, not 12, and the first file logged a speech-model download. Don't mix those clocks. Use 12 seconds for what I waited after the call. Use the XPC chart for who burned the CPU.&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%2Fffhd7y0ujaig3ysz2op8.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%2Fffhd7y0ujaig3ysz2op8.png" alt="Transcription CPU sits in localspeechrecognition.xpc, not in capkiku" width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also ran &lt;code&gt;powermetrics&lt;/code&gt; with sudo on that replay. Apple says these watts are estimates, so don't compare your Mac with them. The sample covers the first ~60 seconds of the 82-second replay.&lt;/p&gt;

&lt;p&gt;Before transcribe, ANE sat at 0 W and combined CPU+GPU+ANE around 3 W. During transcribe, ANE was about 1 W and peaked at 3.1 W. CPU was about 10 W and peaked at 15 W. Combined was about 11 W and peaked at 18 W. Thermal went from Nominal to Moderate for a bit; &lt;code&gt;corespeechd&lt;/code&gt; did nothing useful in the samples.&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%2Fhyroi3ddh9r9y0g7hsva.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%2Fhyroi3ddh9r9y0g7hsva.png" alt="Estimated SoC power. ANE around 1 W during transcribe. Combined CPU+GPU+ANE around 11 W." width="799" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I could not measure
&lt;/h2&gt;

&lt;p&gt;The processes &lt;code&gt;coreaudiod&lt;/code&gt;, &lt;code&gt;audiomxd&lt;/code&gt;, and &lt;code&gt;usbaudiod&lt;/code&gt; are SIP-blocked, so 17.5% is the capkiku process. The system audio tap may cost more inside Core Audio, and I don't have that number.&lt;/p&gt;

&lt;p&gt;I don't have watts for the live hour, only for the transcription replay. Apple Energy Impact on the XPC averaged about 887, which is a dimensionless score, not watts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The encode is more than voice needs
&lt;/h2&gt;

&lt;p&gt;The writer targets 192 kbps stereo at 48 kHz, and voice does not need that. The mic file came out at 151 kbps of stereo speech, so mono and a lower bitrate on my own track would cut CPU and disk, and the other track can drop too. I’m not sure yet it’s worth optimizing that far.&lt;/p&gt;

&lt;p&gt;The good news is that the session didn’t leak memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule
&lt;/h2&gt;

&lt;p&gt;If your Mac is loud on a call, look at the browser first. That’s usually the bottleneck. But if you use also an app for transcription that brings it to its knees, there’s a better solution.&lt;/p&gt;

&lt;p&gt;A recorder like capkiku can sit at 17% of one core for an hour. It writes two files into a folder you own, then gets out of the way.&lt;/p&gt;

&lt;p&gt;Boring is good. Boring means it gets the job done fast and efficiently. Also, as a user said to me yesterday, the design is great—so not boring!&lt;/p&gt;

&lt;p&gt;capkiku is free, menu bar, macOS 14+, Apple Silicon. &lt;a href="https://capkiku.com" rel="noopener noreferrer"&gt;capkiku.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>swift</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
