<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>The VMUNIX Blues</title>
    <description>A blog about software engineering, operating systems, the web, and tech.</description>
    <link>https://www.vmunix.com</link>
    <atom:link href="https://www.vmunix.com/rss.xml" rel="self" type="application/rss+xml"/>
    <language>en</language>
    <lastBuildDate>Wed, 08 Apr 2026 00:00:00 GMT</lastBuildDate>
    <item>
      <title>ed25519, woah</title>
      <link>https://www.vmunix.com/posts/ed25519-woah</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/ed25519-woah</guid>
      <pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p>Was doing some work in a Rust project and realized I’d somehow ended up with both <code>ed25519-compact</code> and <code>ed25519-dalek</code> in the mix. Which.. didn’t feel ideal,
and then <a href="https://www.scs.stanford.edu/~geoff/" rel="nofollow">Geoff</a> sent me Henry de Valence’s <a href="https://hdevalence.ca/blog/2020-10-04-its-25519am/" rel="nofollow">It’s 255:19AM.
Do you know what your validation criteria
are?</a>.</p> <p>Umm. Woah.</p> <p>I don’t really have a smart conclusion here beyond that. Just one of those
posts where you get a few paragraphs in and your confidence in the shape of a
thing starts evaporating. “Ed25519” was feeling to me like a much more stable
and obvious bucket than maybe it deserves to be in practice.</p> <p>Anyways, <a href="https://docs.rs/ed25519-zebra/latest/ed25519_zebra/" rel="nofollow"><code>ed25519-zebra</code></a> it is.</p><!--]-->]]></description>
    </item>
    <item>
      <title>slugr - a tiny file renaming tool</title>
      <link>https://www.vmunix.com/posts/slugr</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/slugr</guid>
      <pubDate>Thu, 26 Feb 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p>I released <a href="https://github.com/vmunix/slugr" rel="nofollow">slugr</a> tonight. It’s a small Rust CLI with almost no deps that renames files and directories into clean, URL-safe slugs. <code>My Résumé (Final).pdf</code> becomes <code>my-resume-final.pdf</code>. That’s basically it.</p> <p>I built it because I kept running into the same annoyance. Downloads folder full of <code>Screen Shot 2024-01-15 at 3.42.17 PM.png</code> and <code>Invoice (2) FINAL final.pdf</code> and whatever other garbage macOS and the web conspire to produce. I sometimes want to work with these files in the shell and it’s a PITA to escape them, or if I wanted to cleanup a bunch with a sequence of shell one-liners it’s also a PITA since I can never quite remember the incantations and I’m terrified I’ll make a mistake or heaven forbid there’s a shell escape in a filename.. Seemed like a thing that should just exist as a tool.</p> <p>The core of it is a library crate called <a href="https://crates.io/crates/fileslug" rel="nofollow">fileslug</a> that understands filenames. Most slug libraries are built for URLs and will happily turn <code>archive.tar.gz</code> into <code>archive-tar-gz</code>, which is obviously not what you want. fileslug knows about compound extensions, dotfiles, version numbers (<code>app-1.2.3.dmg</code> stays <code>app-1.2.3.dmg</code>), and all the weird edge cases that I could come up when you’re actually renaming files on disk. It does unicode transliteration via <a href="https://crates.io/crates/any_ascii" rel="nofollow">any_ascii</a> so your CJK and accented filenames get sensible ASCII equivalents. Or pass <code>--keep-unicode</code> if you’d rather just normalize the separators.</p> <p>Dry-run is the default, which feels important for a tool that renames your files. You see what it <em>would</em> do, and then pass <code>-x</code> when you’re happy. It won’t clobber existing files (appends <code>-2</code>, <code>-3</code>, etc. instead), handles case-only renames correctly on macOS’s case-insensitive filesystem, and does recursive directory renames bottom-up so paths don’t break mid-traversal. The kind of stuff that’s boring to describe but annoying to get wrong.</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span>$ slugr "My Résumé (Final).pdf" "Photo 2024_01.JPG"</span></span>
<span class="line"><span>slugr: dry-run mode (use -x to execute)</span></span>
<span class="line"><span>My Résumé (Final).pdf -> my-resume-final.pdf</span></span>
<span class="line"><span>Photo 2024_01.JPG -> photo-2024-01.JPG</span></span></code></pre><!----> <p>It supports kebab-case (default), snake_case, and camelCase. The whole thing is about 3,000 lines of Rust across both crates with 250 tests. Available via <code>cargo install slugr</code>, <code>brew install vmunix/tap/slugr</code>, or <a href="https://github.com/vmunix/slugr/releases" rel="nofollow">pre-built binaries</a> for macOS and Linux.</p> <p>This was another fun project built almost entirely with Claude Code. The whole thing from first commit to published crate took an evening. I still think it’s crazy to rely on these tools too much for “real” projects where you want to be able to reason about the architecture and so on, but for stuff like this I love it.</p><!--]-->]]></description>
    </item>
    <item>
      <title>The Most Vexing Capture, in the Wild</title>
      <link>https://www.vmunix.com/posts/vexing-capture-in-the-wild</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/vexing-capture-in-the-wild</guid>
      <pubDate>Sat, 07 Feb 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p><strong>TL;DR:</strong> A coroutine function can return a value (like <code>Detached{}</code>) before
its body has finished executing. If that function is an unnamed lambda, its
captures — whether by reference or by value — live in the closure object, not
the coroutine frame. If the closure is a temporary, it’s destroyed while the
coroutine is still suspended, and all captures dangle. <code>[&amp;](this auto) -> Coro { ... }()</code> fixes it. David Mazières’ <a href="https://www.scs.stanford.edu/~dm/blog/vexing-capture.html" rel="nofollow">The Most Vexing
Capture</a> explains
the mechanics. This is a field report of getting bitten by it.</p> <p>I spent a couple of days chasing a coroutine frame data race in a C++23 async
RPC library. The fix ended up being a fairly small change to how we manage
coroutine handle ownership. Finding it, however, was not small — it was one of
those investigations where you end up reading compiler IR and staring at memory
orderings until things start making sense. That kind of deep dive puts you in a
particular headspace, and from that headspace we wrote a bunch of new stress
tests to exercise the paths we’d been worried about. One of the tests that I
wrote was a multi-threaded RPC write-buffer stress scenario.</p> <p>Looked good locally to me, but on the very first CI run ASAN lit up on it:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span>ERROR: AddressSanitizer: stack-use-after-scope on address 0x7a6600201388</span></span>
<span class="line"><span>READ of size 8 thread T5</span></span>
<span class="line"><span>    #0 operator() rpcstress.cc:473</span></span></code></pre><!----> <p>Stack-use-after-scope. The coroutine was reading from a stack frame that ASAN
considered out of scope. Confusing, because the enclosing function was still
running — blocked in a while loop right below the launch site. The stack frame
was alive. So what was out of scope?</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]() -> </span><span style="color:#D73A49;--shiki-dark:#F97583">Detached</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  UniqueFd fd{</span><span style="color:#6F42C1;--shiki-dark:#B392F0">socket</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(addr.ss_family, SOCK_STREAM, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)};</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">  if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="color:#D73A49;--shiki-dark:#F97583">!</span><span style="color:#24292E;--shiki-dark:#E1E4E8">fd)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    co_return</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">  if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="color:#D73A49;--shiki-dark:#F97583">auto</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> res </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#D73A49;--shiki-dark:#F97583"> co_await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> cp.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">connect</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(fd, </span><span style="color:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">addr); </span><span style="color:#D73A49;--shiki-dark:#F97583">!</span><span style="color:#24292E;--shiki-dark:#E1E4E8">res)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    co_return</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">  auto</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> c </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> rpc_stream</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(cp, fd.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">release</span><span style="color:#24292E;--shiki-dark:#E1E4E8">())-></span><span style="color:#6F42C1;--shiki-dark:#B392F0">client</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(rpc_v1{});</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">  // ... launch work on c ...</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}();</span></span></code></pre><!----> <p>That <code>[&amp;]() -> Detached { ... }()</code> is an immediately-invoked lambda coroutine capturing by reference. And it’s a temporary. The closure is created, <code>operator()</code> is called, the coroutine suspends, and a <code>Detached</code> is returned to the caller — before the coroutine body has finished executing. Then the temporary is destroyed. All in one expression. The coroutine body, meanwhile, is suspended at <code>co_await cp.connect(...)</code> and will resume on a worker thread — at which point it reaches through a dangling <code>this</code> to access its captures.</p> <p>This is exactly what the <a href="https://www.scs.stanford.edu/~dm/blog/vexing-capture.html" rel="nofollow">Most Vexing
Capture</a> describes.
Lambda captures — whether by reference or by value — live in the closure
object, not in the coroutine frame. If the closure is a temporary, it’s
destroyed before the coroutine finishes, and all captures dangle. It compiles
without a whisper. The lambda looks fine. The captures look fine. Everything
about it looks fine, right up until the coroutine resumes on a different thread
and the closure is gone.</p> <p>The humbling part is I wrote this bug myself, in brand new test code, while
actively investigating a different coroutine lifetime issue. I was already
thinking hard about frame lifetimes and destruction ordering. Didn’t matter.
The MVC is that invisible.</p> <h2><code>(this auto)</code></h2> <p>C++23 deducing this makes the fix a one-token change:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#D73A49;--shiki-dark:#F97583">&#x26;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">](</span><span style="color:#6F42C1;--shiki-dark:#B392F0">this</span><span style="color:#D73A49;--shiki-dark:#F97583"> auto</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) -> </span><span style="color:#D73A49;--shiki-dark:#F97583">Detached</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">  // captures now survive suspension</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">  // ...</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}();</span></span></code></pre><!----> <p><code>this auto</code> makes the closure an explicit object parameter — a <em>coroutine</em> parameter — so it gets copied into the coroutine frame. The temporary still
dies at the semicolon, but the coroutine has its own copy of the closure and
its captures.</p> <p>One subtlety: <code>this auto</code> copies the <em>entire closure</em> into the coroutine frame,
regardless of capture mode. Without it, both <code>[&amp;]</code> and <code>[=]</code> captures dangle —
value captures are just as dead as reference captures when the temporary is
destroyed. With <code>this auto</code>, <code>[=]</code> captures are fully owned by the frame copy
and safe. But <code>[&amp;]</code> captures survive as copied references that still point at
the original objects, so the enclosing scope must outlive the coroutine. If it
might not, use <code>[=](this auto)</code> or explicit parameters. I think?</p> <p>What sticks with me is how <em>latent</em> this class of bug is. Without ASAN in CI
I’d never have caught it — the memory was still there, it would’ve
kept working by accident. Undefined behavior the entire time. And any change to
suspension behavior — tweaking <code>initial_suspend</code>, adding a <code>co_await</code>,
refactoring a coroutine type — can reshuffle destruction ordering and expose
MVCs that were previously hidden by luck. We’ve had this in the production code,
mine just happened to only be in the test code designed to find other races!</p> <p>Coroutines were only introduced in C++20 to make asynchronous code easier and
safer to write. It’s my first time ever working with them, the only comparable
I have really are Go’s goroutines which I’ve come to appreciate. Better than
callback hell? Almost certainly. But they’re still brutally hard to reason
about, in my experience so far. The abstraction is thinner than it looks. Maybe
I’ll get better at it, we’ll see.</p> <p>David’s <a href="https://www.scs.stanford.edu/~dm/blog/vexing-capture.html" rel="nofollow">post</a> covers the language-level analysis and standardization path far better than I
can. This is just a +1 from the trenches for <code>(this auto)</code> as the right default
on any lambda coroutine with captures.</p><!--]-->]]></description>
    </item>
    <item>
      <title>Anatomy of an LLM-Generated Biquad EQ</title>
      <link>https://www.vmunix.com/posts/anatomy-of-an-llm-generated-biquad</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/anatomy-of-an-llm-generated-biquad</guid>
      <pubDate>Thu, 01 Jan 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p>I’ve been building a <a href="https://github.com/vmunix/glicol-verb" rel="nofollow">live-coding glicol VST</a> in Rust for
shits and giggles and wanted a “native” EQ in the GUI because it’s convenient vs going back to the
DAW and adding a seperate EQ effect, and I couldn’t just farm it out to the Glicol engine because
that’s not what it’s for.</p> <p>I found myself staring at a filter implementation that I first wrote by hand, and then had an LLM
rewrite because mine clearly sucked and I hated it. I’m looking over the resulting LLM generated code,
fairly impressed tbh, and noted Claude hadn’t used a “WebSearch” tool at all to create it, it just
blasted it out “one shot”. Which had me wondering where it came from. Not in the existential sense —
I know I asked an LLM to <em>“make a 3-band parametric EQ that works withy my Mod abstraction, use best
practices the way an experienced audio engineer would”</em> — but in the archaeological sense. These
coefficients. This structure. This specific hardcoded <code>0.9</code> buried in an alpha calculation. <strong>Where
did this thing learn to do this?</strong> And is it any good?</p> <p>Queue the rabbit hole that’s been the last 24 hours trying to figure out how LLMs synthesize
“canonical” algorithms — and what that tells us about both the limits and the weirdly impressive
capabilities of statistical pattern matching.</p> <p>Turns out, the answer to “where did the LLM learn this” is:</p> <blockquote><p><strong>Everywhere. And nowhere. It’s complicated.</strong></p></blockquote> <h2>The Starting Point: Naive Me</h2> <p>Let’s be honest about where I started. I knew I wanted a 3-band EQ — low shelf, mid peak, high
shelf. I knew these were “biquad filters” from reading about audio DSP over the years. And I knew
enough to be dangerous, which meant my first attempt looked like this:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">pub</span><span style="color:#D73A49;--shiki-dark:#F97583"> fn</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> process_sample</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    input</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    freq</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    gain_db</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    sample_rate</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    x1</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#D73A49;--shiki-dark:#F97583"> &#x26;mut</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, x2</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#D73A49;--shiki-dark:#F97583"> &#x26;mut</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    y1</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#D73A49;--shiki-dark:#F97583"> &#x26;mut</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, y2</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#D73A49;--shiki-dark:#F97583"> &#x26;mut</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">-></span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    // Computing trig functions PER SAMPLE (44100 times per second!)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    let</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> a </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 10.0_</span><span style="color:#6F42C1;--shiki-dark:#B392F0">f32</span><span style="color:#D73A49;--shiki-dark:#F97583">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">powf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(gain_db </span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 40.0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    let</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> w0 </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 2.0</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> PI</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> freq </span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> sample_rate;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    let</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> cos_w0 </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> w0</span><span style="color:#D73A49;--shiki-dark:#F97583">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">cos</span><span style="color:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    let</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> sin_w0 </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> w0</span><span style="color:#D73A49;--shiki-dark:#F97583">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">sin</span><span style="color:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    // Giant wall of coefficient math adapted from code I found on the internet...</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    let</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> alpha </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> sin_w0 </span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 2.0</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ((a </span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1.0</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">a) </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1.0</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#005CC5;--shiki-dark:#79B8FF">0.9</span><span style="color:#D73A49;--shiki-dark:#F97583"> -</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1.0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 2.0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">sqrt</span><span style="color:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    // ... dozens more lines ...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    let</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> output </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> b0 </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> input </span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> b1 </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#24292E;--shiki-dark:#E1E4E8">x1 </span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> b2 </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#24292E;--shiki-dark:#E1E4E8">x2 </span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> a1 </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#24292E;--shiki-dark:#E1E4E8">y1 </span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> a2 </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#24292E;--shiki-dark:#E1E4E8">y2;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    output</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre><!----> <p>It worked! In the sense that audio came out and it sounded vaguely filtered. But I knew it was bad.
I just didn’t know <em>how</em> bad, or what “good” would even look like but I assumed examples on the
internet I looked at probably weren’t it. So I asked Claude to improve it. And what came back was… different.
Structured. Professional-looking. A separation between coefficients and state. A dirty flag for lazy
recomputation. Proper stereo handling. <a href="https://github.com/vmunix/glicol-verb/blob/main/src/dsp/eq.rs" rel="nofollow">All 300 lines of
it</a>.</p> <p>But here’s the thing that nagged at me: I recognized patterns in that code. Not because I wrote
them, but because they felt <em>familiar</em> in the way that all biquad implementations feel familiar if
you’ve looked at a few. Which made me wonder — <strong>did the LLM actually understand what it was doing,
or did it just really convincingly remix the collected wisdom of the internet?</strong></p> <h2>Robert Bristow-Johnson Posted a Thing to Usenet in 1998</h2> <p>To understand what the LLM produced, you have to understand the source material. And for biquad
filters, there is essentially one source: Robert Bristow-Johnson’s <a href="https://www.w3.org/TR/audio-eq-cookbook/" rel="nofollow">Audio EQ
Cookbook</a>.</p> <p>Nobody remembers the original usenet post, but <em>everybody</em> uses those formulas. They’re in the W3C
Web Audio spec now. They’re in every Rust audio crate I looked at. For example:
(<a href="https://github.com/korken89/biquad-rs" rel="nofollow">biquad-rs</a>, <a href="https://github.com/SamiPerttu/fundsp" rel="nofollow">fundsp</a>). They’re in Python scipy discussions. Every tutorial
on <a href="https://www.earlevel.com/main/2003/02/28/biquads/" rel="nofollow">EarLevel Engineering</a>. Every forum thread on
KVR. The same damned formulas, copy-pasted across every programming language humans have invented.
And when you ask an LLM for a biquad filter, you get… those formulas. Because that’s what “biquad
filter” <em>means</em> at this point. The training data isn’t biased toward RBJ — it <em>is</em> RBJ, wearing
different syntactic costumes.</p> <p>Compare the cookbook’s lowShelf formula:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span>b0 =    A*[ (A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha ]</span></span>
<span class="line"><span>b1 =  2*A*[ (A-1) - (A+1)*cos(w0)                   ]</span></span>
<span class="line"><span>a0 =        (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha</span></span></code></pre><!----> <p>To what the LLM produced in my <code>eq.rs</code>:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">let</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> a_plus_1 </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> a </span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1.0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">let</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> a_minus_1 </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> a </span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1.0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">let</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> two_sqrt_a_alpha </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 2.0</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> a</span><span style="color:#D73A49;--shiki-dark:#F97583">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">sqrt</span><span style="color:#24292E;--shiki-dark:#E1E4E8">() </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> alpha;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">BiquadCoeffs</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    b0</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (a </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (a_plus_1 </span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> a_minus_1 </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> cos_w0 </span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> two_sqrt_a_alpha)) </span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> a0,</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    // ...</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre><!----> <p>It’s the same formula. Factored slightly differently, with more verbose variable names, but
structurally identical. The LLM didn’t invent anything here. It transcribed.</p> <h2>Convergent Citation Creates Canonical Knowledge</h2> <p>Here’s where it gets interesting from an LLM-learning perspective. The RBJ cookbook doesn’t appear
once in the training data — it appears <em>everywhere</em>, in <em>every language</em>, with <em>every syntax</em>. This
creates what I’m calling <strong>convergent citation</strong>: a pattern so universally adopted that it becomes
statistically inevitable in any generated output.</p> <p>Think about what the LLM has seen:</p> <ul><li>The W3C Web Audio specification (which cites RBJ verbatim)</li> <li>The biquad-rs Rust crate (which implements RBJ formulas)</li> <li>Python scipy discussions (RBJ formulas in numpy)</li> <li>CCRMA academic materials (RBJ with derivations)</li> <li>Hundreds of Stack Overflow answers (RBJ, badly formatted)</li> <li>Thousands of personal blog posts (RBJ, with varying degrees of understanding)</li></ul> <p>The formulas aren’t just common — they’re <em>canonical</em>. And the LLM, being a statistical creature,
produces what is statistically dominant. This isn’t a bug. For well-established algorithms, <strong>this
is exactly what you want</strong>. The cookbook is battle-tested across millions of implementations.
Transcribing it faithfully is the <em>correct</em> choice. Innovation would introduce bugs.</p> <h2>The Fingerprints of Multi-Source Synthesis</h2> <p>But the LLM didn’t <em>just</em> transcribe the cookbook. It synthesized from multiple sources, and you can see the fingerprints if you look closely.</p> <p><strong>From RBJ Cookbook</strong>: The exact coefficient formulas. The <code>A = 10^(dBgain/40)</code> calculation (40, not
20, because we’re dealing with amplitude, not power). The hardcoded <code>S = 0.9</code> shelf slope buried in
that alpha calculation as <code>1.0 / 0.9 - 1.0</code>.</p> <p><strong>From Rust audio crates</strong>: The separation of <code>BiquadCoeffs</code> (static coefficients) from <code>BiquadState</code> (per-sample state). The <code>coeffs_dirty</code> flag for lazy recomputation. The <code>#[derive(Clone, Copy)]</code> idiom.</p> <p><strong>From tutorials</strong>: The Direct Form 1 implementation with explicit <code>x1</code>, <code>x2</code>, <code>y1</code>, <code>y2</code> state
variables. The verbose naming (<code>a_plus_1</code> rather than <code>ap1</code>).</p> <p><strong>From Rust best practices</strong>: The <code>impl Default</code> pattern. The <code>StereoSample</code> abstraction. The
separation of concerns that makes the code testable.</p> <p>What a direct C-to-Rust port might look like (and, frankly, where my brain went for my own first draft):</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">typedef</span><span style="color:#D73A49;--shiki-dark:#F97583"> struct</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#D73A49;--shiki-dark:#F97583">float</span><span style="color:#E36209;--shiki-dark:#FFAB70"> b</span><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#005CC5;--shiki-dark:#79B8FF">3</span><span style="color:#24292E;--shiki-dark:#E1E4E8">], </span><span style="color:#E36209;--shiki-dark:#FFAB70">a</span><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#005CC5;--shiki-dark:#79B8FF">3</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]; } Coeffs;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">typedef</span><span style="color:#D73A49;--shiki-dark:#F97583"> struct</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#D73A49;--shiki-dark:#F97583">float</span><span style="color:#E36209;--shiki-dark:#FFAB70"> x</span><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">], </span><span style="color:#E36209;--shiki-dark:#FFAB70">y</span><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]; } State;</span></span></code></pre><!----> <p>What the LLM produced:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">struct</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> BiquadCoeffs</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { b0</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, b1</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, b2</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, a1</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, a2</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">struct</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> BiquadState</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { x1_l</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, x2_l</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, y1_l</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, y2_l</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">...</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span></code></pre><!----> <p>The flattened struct with named fields (not arrays) is idiomatic Rust, I think, not a direct port
from any single source. The LLM synthesized a “greatest hits” compilation: formulas from the
cookbook, architecture from professional crates, idioms from Rust best practices, naming from
tutorial culture. I’m already taking this technology for granted, but when I just look at one
specific examples like this it’s hard for me to not be impressed. I certainly would struggle to
do as well, as my shit implementation clearly demonstrates.</p> <h2>When Naive is Fine (and the LLM Doesn’t Know the Difference)</h2> <p>Here’s the thing about naive implementations: sometimes they’re fine. How much does it <em>really</em> matter if you’re computing <code>sin()</code> and <code>cos()</code> 44,100 times per second instead of caching
the coefficients? For a guitar pedal where you maybe tweak the EQ once a song? Probably doesn’t
matter at all.</p> <p>But the LLM doesn’t know that. It’s seen enough “optimized” implementations that it knows the
pattern: separate your coefficients from your state, add a dirty flag, recompute only when
parameters change. It’s not thinking about <em>your</em> use case. It’s reproducing what people
generally do. And that’s usually fine! But it’s worth knowing the difference between “best practice”
and “necessary for my context.”</p> <p>The current implementation sits at what I’d call <strong>Level 1</strong> on an optimization spectrum:</p> <p><strong>Level 0 (Naive)</strong>: Recompute everything per sample. 44,100 trig calls per second. Probably fine for a hobby project. <em>raises hand</em></p> <p><strong>Level 1 (My eq.rs)</strong>: Cached coefficients with dirty flag. Clean, readable, maintainable. Good for most plugin scenarios.</p> <p><strong>Level 2 (DF2T)</strong>: Direct Form 2 Transposed. Uses 2 state variables instead of 4. Better for static filters but introduces artifacts on parameter changes. Tutorials teach DF1T but mention DF2T.</p> <p><strong>Level 3 (SIMD)</strong>: Process 4-8 channels simultaneously with AVX. Overkill for a mono-in/stereo-out guitar pedal. SIMD logic notoriously hard for humans to write bug free.</p> <p><strong>Level 4+ (Advanced)</strong>: IIRNet neural filter design, nonlinear biquads with waveshaping, fixed-point with noise shaping for embedded / no FP hardware.</p> <p><strong>Level 5 (Custom)</strong>: Tailored to specific use cases, such as guitar pedals that mimic classic analog circuits. Requires deep understanding of the hardware and software environment.</p> <p>Someone who’s an expert could certainly list 10 more levels, I’d wager. I came up with these after a
4hr research extravaganza last night. As someone without much direct knowledge in this particular
domain, but 30 years of generalalized experience in “why is this running so slow?” was enough for me to
intuit that SIMD approaches would of course by applicable. I’ve used paid plugins by
companies that emulate circuit designs so I know a custom algorithm that mimics old (incredibly
limited!) hardware, analog or digital, is a thing. DFT2 is talked about in tutorials that implement
DFT1 (but is left as an exercise for the reader), and there’s bespoke, clever implementations often
designed to run on low end hardware done by smart folks who get a thrill out of it. Like Airwindow’s <a href="https://www.airwindows.com/smootheq3/" rel="nofollow">SmoothEQ3</a>. Which, as an aside, is beautiful both in its
cleverness and simplicity, but also in how “natural” it sounds. Where natural simply means it’s what
my ears are used to hearing and therefore expect. That it both sounds better, and is significantly
faster, is really something.</p> <p>So there’s a bunch of possible approaches the LLM could have suggested. But when I asked it to build me a
better, optimized version it went for a very specific approach (Level 1) and when I questioned it
about futher optimizations it literally said:</p> <blockquote><p><strong><code>"this is a textbook example of a biquad filter implementation, it is well optimized for performance and accuracy."</code></strong></p></blockquote> <p>No fixed point optimizations for you! So.. the LLM wants to do it this way, which is… correct?
It’d be hard for me to argue that it’s not the “smart default” that works for most cases. But it
didn’t explain <em>why</em>, or help me understand when I might want to go higher or lower on that
spectrum.</p> <h2>Where This Breaks Down</h2> <p>Convergent citation is great when there’s a canonical answer. For biquad EQs, there is. But what
about things that <em>aren’t</em> statistically dominant in the training data?</p> <p>SIMD optimizations? Sure, there’s some example code out there. But it’s specialized, inconsistent,
poorly documented, and varies wildly between platforms. The LLM is much more likely to produce
something subtly wrong because there are fewer examples and they’re more diverse.</p> <p>Same for fixed-point implementations with noise shaping (embedded DSP without FPU). Same for
cache-aware optimizations. Same for anything that requires understanding your specific hardware or
use case. My project is very clearly aimed at guitar effects in particular, but the LLM was essentially
unwilling to move past “textbook RBJ”.</p> <p>The cookbook wins because everybody uses it. The edge cases lose because almost nobody documents them.</p> <p>Can we all just appreciate for a moment the impact of that <a href="https://music-dsp.music.columbia.narkive.com/ksCRrbQl/zoelzer-rbj-biquad-code-in-archives#post8" rel="nofollow">1998 RBJ usenet
post</a> that by now has been battle-tested across more implementations than any of us can count.</p> <p>It’s discussed to death that an LLM is fundamentally limited by what’s already been written.
Obvious, I know, but worth restating and this little exploration certainly reinforced this fact for
me. It’s excellent at <strong>convergent synthesis</strong> — combining canonical patterns from multiple sources
into idiomatic target code. It’s going to be <strong>much weaker</strong> at innovation, non-obvious trade-offs,
and anything that requires understanding your specific context.</p> <p>I got a solid, professional-looking implementation that I can now understand and maintain. The LLM
gave me a better starting point than my naive attempt, and the forensic exercise of understanding <em>where it came from</em> taught me more than I expected. So I’m pretty happy.</p> <p>Whether that’s enough depends on what you’re building. For most of us, most of the time, I suspect it is.</p> <p>More to come. I want to see if I can work with it to bang out more “interesting” EQ designs. I suspect
it will hinder more than help, but I’m constantly surprised by how much it can do. Regardless, I want
to try baning out my own SmoothEQ implementation because that airwindows code is so cool it makes
me want to get better at these DSP algos.</p> <hr/> <p><strong>Sources</strong>:</p> <ul><li><a href="https://www.w3.org/TR/audio-eq-cookbook/" rel="nofollow">RBJ Audio EQ Cookbook (W3C)</a></li> <li><a href="https://www.earlevel.com/main/2003/02/28/biquads/" rel="nofollow">EarLevel Engineering - Biquads</a></li> <li><a href="https://github.com/korken89/biquad-rs" rel="nofollow">biquad-rs</a></li> <li><a href="https://github.com/SamiPerttu/fundsp" rel="nofollow">fundsp</a></li> <li><a href="https://ccrma.stanford.edu/~jos/filters/" rel="nofollow">Julius Smith - Introduction to Digital Filters</a></li> <li><a href="https://en.wikipedia.org/wiki/Digital_biquad_filter" rel="nofollow">Wikipedia - Digital biquad filter</a></li></ul><!--]-->]]></description>
    </item>
    <item>
      <title>GlicolVerb - The Hard Part Was The UI</title>
      <link>https://www.vmunix.com/posts/glicol-verb-first-release</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/glicol-verb-first-release</guid>
      <pubDate>Tue, 30 Dec 2025 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p>I read a piece by Joan Westenberg a couple weeks ago — <a href="https://www.joanwestenberg.com/thin-desires-are-eating-your-life/?ref=westenberg-newsletter" rel="nofollow">Thin Desires are Eating Your
Life</a> —
that resonated with me. In particular, this quote lodged in my brain and I couldnt get it out:</p> <blockquote><p>“Build something that will never scale, never be monetized, never attract users.”</p></blockquote> <p>With that in mind, I built <a href="https://github.com/vmunix/glicol-verb" rel="nofollow">GlicolVerb</a> this week. It’s a
VST3/CLAP plugin that lets you write <a href="https://glicol.org/" rel="nofollow">Glicol</a> code in a text editor, click
“Update”, and hear your guitar (or voice, keyboard, whatever) processed through whatever DSP chain
you just described. Live-coding for guitar pedals, basically. The whole thing was a learning
exercise: first time writing Rust for audio, first time building a VST plugin, first time poking
around the Rust audio ecosystem to see what’s actually out there.</p> <p>I went in expecting the hard part to be the audio processing. Real-time constraints! Buffer
management! Sample rates! DSP math! Surely getting audio to flow through a plugin without crackling,
dropping, or exploding would be the thing that ate all my time.</p> <p>Turns out I was completely wrong about where the pain would be.</p> <p><a href="https://github.com/robbert-vdh/nih-plug" rel="nofollow">NIH-plug</a> is excellent. Like, genuinely impressive. Here’s
the entire Glicol engine wrapper - the thing that does the actual audio processing:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">pub</span><span style="color:#D73A49;--shiki-dark:#F97583"> struct</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> GlicolWrapper</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    engine</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Engine</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&#x3C;128>,  </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 128-sample blocks</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    left_buffer</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="color:#6F42C1;--shiki-dark:#B392F0">f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#005CC5;--shiki-dark:#79B8FF">128</span><span style="color:#24292E;--shiki-dark:#E1E4E8">],</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    right_buffer</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="color:#6F42C1;--shiki-dark:#B392F0">f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#005CC5;--shiki-dark:#79B8FF">128</span><span style="color:#24292E;--shiki-dark:#E1E4E8">],</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">impl</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> GlicolWrapper</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    pub</span><span style="color:#D73A49;--shiki-dark:#F97583"> fn</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> new</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(sample_rate</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> f32</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">-></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> Self</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        let</span><span style="color:#D73A49;--shiki-dark:#F97583"> mut</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> engine </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Engine</span><span style="color:#D73A49;--shiki-dark:#F97583">::</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">128</span><span style="color:#24292E;--shiki-dark:#E1E4E8">></span><span style="color:#D73A49;--shiki-dark:#F97583">::</span><span style="color:#6F42C1;--shiki-dark:#B392F0">new</span><span style="color:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        engine</span><span style="color:#D73A49;--shiki-dark:#F97583">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">set_sr</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(sample_rate </span><span style="color:#D73A49;--shiki-dark:#F97583">as</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> usize</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        engine</span><span style="color:#D73A49;--shiki-dark:#F97583">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">update_with_code</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"out: ~input >> plate 0.5"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        Self</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { engine, left_buffer</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="color:#005CC5;--shiki-dark:#79B8FF">0.0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#005CC5;--shiki-dark:#79B8FF">128</span><span style="color:#24292E;--shiki-dark:#E1E4E8">], right_buffer</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="color:#005CC5;--shiki-dark:#79B8FF">0.0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#005CC5;--shiki-dark:#79B8FF">128</span><span style="color:#24292E;--shiki-dark:#E1E4E8">] }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    pub</span><span style="color:#D73A49;--shiki-dark:#F97583"> fn</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> update_code</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">&#x26;mut</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, code</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#D73A49;--shiki-dark:#F97583"> &#x26;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">str</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">-></span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Result</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&#x3C;(), </span><span style="color:#6F42C1;--shiki-dark:#B392F0">String</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        self</span><span style="color:#D73A49;--shiki-dark:#F97583">.</span><span style="color:#24292E;--shiki-dark:#E1E4E8">engine</span><span style="color:#D73A49;--shiki-dark:#F97583">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">update_with_code</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(code);</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">        Ok</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(())</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre><!----> <p>That’s it. Fifty lines for the whole file. Glicol’s <code>update_with_code()</code> hot-swaps your DSP graph
without audio glitches. NIH-plug handles parameter smoothing, buffer management, DAW integration.
Even writing a 3-band parametric EQ from scratch using biquad filters was less painful than
expected.</p> <p>So where did the time go? Looking back at the commit history, about a third of commits (9 out of 28)
are UI-related. And not just small tweaks. Here’s the <code>editor.rs</code> changelog:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span>c801c13: src/editor.rs | 764 ++++++---   (509 insertions, 255 deletions)</span></span>
<span class="line"><span>         "Add dark hardware theme and two-column layout"</span></span>
<span class="line"><span></span></span>
<span class="line"><span>1fac077: src/editor.rs | 649 ++++++---   (369 insertions, 293 deletions)</span></span>
<span class="line"><span>         "Redesign UI with compact Effects Lab and fix state persistence"</span></span>
<span class="line"><span></span></span>
<span class="line"><span>dfb43b7: src/editor.rs | 324 +++++---   (281 insertions, 55 deletions)</span></span>
<span class="line"><span>         "Improve UI layout and add new effect recipes"</span></span></code></pre><!----> <p>That’s over 1,700 lines of changes across just three commits. The UI file is 970 lines. The main
plugin logic is 350. The entire <a href="https://github.com/vmunix/glicol/blob/main/src/equalizer.rs" rel="nofollow">DSP
equalizer</a> is 359 lines. The “simple”
text editor became the central challenge. I made small tweaks myself to figure out what wasn’t
working, and then had Claude Code rewrite entire sets of UI definition at once like when switching
all the slider controls at once from one egui type to another. Huge time saver!</p> <p>The culprit: getting text input and panel layout to work reliably inside a plugin window. The stack
is <a href="https://www.egui.rs/" rel="nofollow">egui</a> (immediate-mode GUI) running on top of <a href="https://github.com/RustAudio/baseview" rel="nofollow">baseview</a> (cross-platform windowing for plugin UIs). This
combo is popular in the Rust audio world and mostly works. Except when it doesn’t. baseview has a <a href="https://github.com/RustAudio/baseview/issues/169" rel="nofollow">known issue</a> with keyboard handling in plugin
hosts - the host intercepts key events in ways that mess with text input. Some DAWs work fine,
others eat your keystrokes or send them twice. I ended up adding preset buttons as a workaround
because you can’t always reliably type code. I spent an embarrising amount of time debugging before
spotting the upstream issue..</p> <p>Then there was a macOS crash. baseview had a bug that would crash the plugin on certain setups.
There’s a <a href="https://github.com/RustAudio/baseview/pull/204" rel="nofollow">fix in a PR</a> that wasn’t merged yet, so I
vendored a patched version. Had to figure out how to do that with Rust modules. Claude Code helped
me with that. Had to fix submodule pointers when CI broke. Several commits just to
keep the UI from crashing.</p> <p>The irony isn’t lost on me. I wanted to learn about audio processing, and I did. But the lesson I
didn’t expect was: <strong>the Rust audio ecosystem is surprisingly mature for the hard stuff, and
surprisingly rough for the “easy” stuff</strong>. NIH-plug makes real-time audio accessible. Glicol makes
DSP fun. But putting a text box on screen that works reliably in every DAW? That’s still an
adventure. I suppose I shouldn’t be surprised, but I was.  :)</p> <p>Anyway, <a href="https://github.com/vmunix/glicol-verb/releases/tag/v0.1.2" rel="nofollow">v0.1.2 is out</a>. VST3 and CLAP
builds for macOS, Windows, and Linux. It works. I think? You can write <code>out: ~input >> lpf 1000.0 1.0 >> plate 0.5</code> and hear your guitar through a low-pass filter into a plate reverb. The text input
is janky in some hosts but the presets get you most of the way there. It’s still hobby project
class software, but it works and makes me smile, which I wasn’t sure I should expect when I started!</p> <p><img src="/images/glicol-verb-screenshot-v012.png" alt="Screenshot of Glicol Verb in action"/></p> <p>Final thoughts. The journey was the point. Building is fun, and I’m having a blast using LLM tech to
get from 0—>1 on a project and then deal with some of the tedious chores so I can spend my time
reading about the Robert Bristow-Johnson <a href="https://github.com/libaudioverse/libaudioverse/blob/master/audio%20eq%20cookbook.txt" rel="nofollow">cookbook formulas for EQ
filters</a>. Now
I’m curious what else is hiding in the Rust audio world. I’m also now curious to try building
something similar in <a href="https://juce.com/" rel="nofollow">JUCE</a>, which I gather is by far the most popular choice for
audio plugin development. C++. Good times!</p><!--]-->]]></description>
    </item>
    <item>
      <title>Helix Editor Cheatsheet</title>
      <link>https://www.vmunix.com/posts/helix-cheat-sheet</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/helix-cheat-sheet</guid>
      <pubDate>Mon, 08 Dec 2025 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p>Have been trying out the <a href="https://helix-editor.com/" rel="nofollow">Helix</a> editor before teaching the kid about modal editors. It’s almost impossible for me to break
my vim key binding muscle memory.. but I think for him, it might be a more intuitive “first contact” to modal editors.
Anyhow, couldn’t find a cheatsheet to print out for him, but Nano Banana made one that is worth sharing.</p> <p><img src="/images/hx-cheat-sheet-16_10.png" alt="Helix Cheat Sheet - 16:10"/></p> <p>Enjoy!</p><!--]-->]]></description>
    </item>
    <item>
      <title>Humour</title>
      <link>https://www.vmunix.com/posts/humour-me-jony-ive</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/humour-me-jony-ive</guid>
      <pubDate>Thu, 27 Nov 2025 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p>While the entire video is a little.. much.. I really like this
quote from Jony Ive in his <a href="https://www.emersoncollective.com/persons/sam-altman-and-jony-ive-dd-2025" rel="nofollow">Emerson Collective conversation</a> earlier this year:</p> <blockquote><p>There’s not a lot of humor in the products that are being designed and made, particularly in this area. I really sense very clearly there is a huge desire for us not to take ourselves quite so seriously, even though these are serious times.</p></blockquote><!--]-->]]></description>
    </item>
    <item>
      <title>The Claude Code WebSearch Black Box</title>
      <link>https://www.vmunix.com/posts/claude-websearch-blackbox</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/claude-websearch-blackbox</guid>
      <pubDate>Thu, 27 Nov 2025 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p>Absolutely nothing new here, move along, this is well known, but I didn’t know it and it was semi-interesting to look into
the details of how one small part of one of these Agentic Coding Computers (tm) work. So I wrote a few notes as I explored.</p> <p>In particular, I was investigating how Anthropic’s WebSearch tool works when the tool calling magic decides to go out and query the web
for info before proceeding. There are <a href="https://platform.claude.com/docs/en/build-with-claude/search-results" rel="nofollow">lots of details on the official Claude Docs site</a> but the summary is that you end up with a <strong>search_result</strong> object.</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "type"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"search_result"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "source"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"https://example.com/article"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,  </span><span style="color:#6A737D;--shiki-dark:#6A737D">// Required: Source URL or identifier</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "title"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Article Title"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,                  </span><span style="color:#6A737D;--shiki-dark:#6A737D">// Required: Title of the result</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "content"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: [                               </span><span style="color:#6A737D;--shiki-dark:#6A737D">// Required: Array of text blocks</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">      "type"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"text"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">      "text"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"The actual content of the search result..."</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  ],</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  "citations"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {                             </span><span style="color:#6A737D;--shiki-dark:#6A737D">// Optional: Citation configuration</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "enabled"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#6A737D;--shiki-dark:#6A737D">                          // Enable/disable citations for this result</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre><!----> <p>Ah cool, I can see what I’m burning up tokens on in the text field. Nice.</p> <p>Hold on! I’m actually getting a <strong>web_search_tool_result</strong> back, not a <strong>search_result</strong>, and these objects in Claude Code contain
almost the same stuff but not quite:</p> <ul><li><strong>title</strong> - page title</li> <li><strong>url</strong> - the source URL</li> <li><strong>encrypted_content</strong> - encrypted page content (not meant for you to read)</li> <li><strong>page_age</strong> - when the page was last updated</li></ul> <p>Example:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "type"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"web_search_tool_result"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "tool_use_id"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"srvtoolu_01WYG3ziw53XMcoyKL4XcZmE"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    "content"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">      {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        "type"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"web_search_result"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        "url"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"https://en.wikipedia.org/wiki/Claude_Shannon"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        "title"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Claude Shannon - Wikipedia"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        "encrypted_content"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"EqgfCioIARgBIiQ3YTAwMjY1Mi1mZjM5LTQ1NGUtODgxNC1kNjNjNTk1ZWI3Y..."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        "page_age"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"April 30, 2025"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">      },</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">      {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        "type"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"web_search_result"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        "url"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"https://example.com/another-page"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        "title"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Another Result"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        "encrypted_content"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        "page_age"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"May 1, 2025"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">      }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    ]</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span></code></pre><!----> <p>It’s always an <strong><em>encrypted_content</em></strong> field. Never a text field. Which made me wonder what I’m supposed to do with it?</p> <p>Answer: I don’t do anything with it. I (or this case Claude Code on my behalf) simply pass it back to Claude in subsequent turns of a multi-turn conversation. Claude uses this internally to reference the original search content, generate citations, and mostly to maintain context about what was found.</p> <p>Ok, I get it. So where is the public key stored to validate it? Is there a key to decrypt it hiding somewhere? I couldn’t find either. Because it’s not in the client, dummy! Of course..</p> <p><strong>The encryption is entirely server-side:</strong></p> <ul><li>Anthropic’s servers fetch web content</li> <li>Anthropic encrypts it before sending to you</li> <li>You pass the opaque blob back in subsequent requests</li> <li>Anthropic decrypts it on their end</li></ul> <p>You never decrypt anything. There’s no public key exposed to you, no client-side crypto, nothing to validate. The <strong>encrypted_content</strong> field is a black box by design.</p> <h2>The Security Model</h2> <p>Upon reflection this makes perfect sense (see Why? below). But it’s useful to write out the consequences
of what we’re dealing with:</p> <ul><li>You’re trusting Anthropic’s infrastructure entirely</li> <li>The “encryption” is essentially a signed/encrypted token that only Anthropic can read</li> <li>You can’t verify the content wasn’t tampered with</li> <li>You can’t verify what’s inside (or compare it to the cited source)</li> <li>You can’t verify the crypto is sound</li> <li><strong>You just pass blobs around and trust Anthropic</strong></li></ul> <p>Of course <em>the entire bloody LLM takes this shape as well</em>, and you’re trusting that with far less insight into its
inner workings. But for some reason not even being able to see the content of a web search struck me harder.</p> <h2>Alternative Methods</h2> <p>If you need to audit/verify what content is being used in your AI pipeline, you’d need to:</p> <ol><li>Fetch the URLs yourself - You can scrape/fetch those URLs independently to see what content likely came
from them. But you can’t compare it to what the model injested.</li> <li>Ask Claude to summarize what it found - In your prompt ask Claude to explicitly quote or describe the search results it’s using.</li> <li>Use your own RAG instead - If you need full control over context, skip web_search and its tool variant and implement your own search + retrieval pipeline. Good times!</li></ol> <p>Obviously this is all doable if you’re building your own agentic coding magic machine to compete with Claude Code, but
as simple end users of CC it’s just how it works we either don’t use the tool or live with it. I haven’t checked any
other products but I assume they all work the same way.</p> <h2>Why Encrypted</h2> <p>As anyone who’s used this stuff would already know, the encryption used here is a business/legal decision, not a technical one. The reason it works like this, I would assume, is:</p> <ul><li>Licensing/Copyright - Anthropic uses <a href="https://brave.com/search/api/" rel="nofollow">Brave Search</a> as the provider. The encryption prevents you from using Claude’s API as a free web
scraping service to extract content from websites.</li> <li>Content provider agreements - Publishers <a href="https://the-decoder.com/openai-thinks-it-knows-what-media-is-high-quality-and-thats-a-problem/" rel="nofollow">likely only allow their content to be used for AI context</a>, not redistributed in raw form.</li></ul> <p>So yes, you’re paying for tokens you can’t inspect but I can’t see how they can work around this without opening up
a world of scraping abuse (as if that’s not already a rampant problem..).</p> <p>My lesson learned? Learning new things is fun! Be curious about how things work! I now know just a little bit more about how
these new agentic coding tools works, and there’s a ton more info in the
WebSearch documentation about things like user flows that were helpful to broaden my knowledge.
I feel like this quick sidequest might not have been a total waste of time. Heh.</p><!--]-->]]></description>
    </item>
    <item>
      <title>Bye Ghost, hello SvelteKit!</title>
      <link>https://www.vmunix.com/posts/welcome</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/welcome</guid>
      <pubDate>Tue, 21 Oct 2025 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><h1>Welcome to the new VMUNIX Blues</h1> <p>It’s been about a year since I revived the old blog, and while Ghost has been just fine and I like supporting them,
I decided I really don’t need a CMS I just need a simple static site. I tried a few different tools and frameworks by
generating whole sites with
Claude Code and settled on SvelteKit in the end. I <em>almost</em> just went for handcrafted HTML and maybe a future version will
do that but for now it’s nice to not have to deal with CSS directly and instead let Svelte do the heavy lifting.</p> <p>The site does need the browser to download about 20KB of JS regardless of it’s going to be used or not, mostly for faster page
navigaton and
responsive views, but it should gracefully fall back to full page loads of the pre-rendered files if you don’t
have JS enabled.</p> <p>Posts are written in Markdown with full support for code blocks and syntax highlighting.</p> <h2>Code Examples</h2> <p>Here’s a simple shell script:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">#!/bin/bash</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Check system uptime</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">uptime</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -p</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Display disk usage</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">df</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -h</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> tmpfs</span></span></code></pre><!----> <p>And here’s some C code:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">#include</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> &#x3C;stdio.h></span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">int</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> main</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">void</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    printf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Hello from VMUNIX!</span><span style="color:#005CC5;--shiki-dark:#79B8FF">
</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    return</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre><!----> <h2>Configuration Files</h2> <p>The blog also handles configuration blocks nicely. Here’s a sample nginx configuration:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">server</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    listen </span><span style="color:#005CC5;--shiki-dark:#79B8FF">80</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    server_name </span><span style="color:#24292E;--shiki-dark:#E1E4E8">example.com;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    location</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> / </span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        root </span><span style="color:#24292E;--shiki-dark:#E1E4E8">/var/www/html;</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        index </span><span style="color:#24292E;--shiki-dark:#E1E4E8">index.html;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre><!----> <h2>Features</h2> <p>So in the end after much trial and error the blog is being built with:</p> <ul><li><strong>SvelteKit</strong> - Fast, modern framework</li> <li><strong>MDsveX</strong> - Markdown processing</li> <li><strong>Shiki</strong> - Beautiful syntax highlighting</li> <li><strong>Static generation</strong> - Deploy anywhere</li></ul> <p>The entire site is pre-rendered at build time, making it extremely fast to load with zero runtime JavaScript required for content display.</p> <h2>Writing Posts</h2> <p>To create a new post, I simply add a <code>.md</code> file to the <code>src/posts</code> directory with frontmatter containing:</p> <ul><li><code>title</code> - The post title</li> <li><code>date</code> - Publication date (YYYY-MM-DD)</li> <li><code>excerpt</code> - Optional short description</li> <li><code>slug</code> - optional slug URL</li></ul> <p>That’s it!</p><!--]-->]]></description>
    </item>
    <item>
      <title>Essential UNIX Configuration Tips</title>
      <link>https://www.vmunix.com/posts/unix-tips</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/unix-tips</guid>
      <pubDate>Mon, 20 Oct 2025 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><h1>Essential UNIX Configuration Tips</h1> <p>This was a test post I made with Claude as I was debugging the Svelte shit. Decided screw it, I’ll keep it. LOL</p> <h2>Shell Aliases</h2> <p>Add these to your <code>.bashrc</code> or <code>.zshrc</code>:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Better defaults</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">alias</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ll</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'ls -lah'</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">alias</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> grep</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'grep --color=auto'</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">alias</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> df</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'df -h'</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Quick navigation</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">alias</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ..='cd ..'</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">alias</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ...='cd ../..'</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">alias</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ....='cd ../../..'</span></span></code></pre><!----> <h2>SSH Configuration</h2> <p>Create <code>~/.ssh/config</code> to simplify SSH connections:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span>Host myserver</span></span>
<span class="line"><span>    HostName 192.168.1.100</span></span>
<span class="line"><span>    User admin</span></span>
<span class="line"><span>    Port 22</span></span>
<span class="line"><span>    IdentityFile ~/.ssh/id_rsa</span></span>
<span class="line"><span>    ServerAliveInterval 60</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Host *.internal</span></span>
<span class="line"><span>    ProxyJump bastion.example.com</span></span>
<span class="line"><span>    User deploy</span></span></code></pre><!----> <p>Now you can simply run <code>ssh myserver</code> instead of typing the full connection string.</p> <h2>tmux Configuration</h2> <p>A minimal <code>~/.tmux.conf</code> that improves the default experience:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Use Ctrl-A as prefix (more ergonomic than Ctrl-B)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">unbind</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> C-b</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">set</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> prefix</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> C-a</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">bind</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> C-a</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> send-prefix</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Start windows and panes at 1, not 0</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">set</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> base-index</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">setw</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> pane-base-index</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Enable mouse support</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">set</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> mouse</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> on</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Increase scrollback buffer size</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">set</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> history-limit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 10000</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Vi mode for copy mode</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">setw</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> mode-keys</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> vi</span></span></code></pre><!----> <h2>Git Configuration</h2> <p>Some useful Git aliases in <code>~/.gitconfig</code>:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">[alias]</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    st</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = status</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    co</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = checkout</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    br</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = branch</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    ci</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = commit</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    unstage</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = reset HEAD --</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    last</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = log -1 HEAD</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    visual</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = log --graph --oneline --all --decorate</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">[core]</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    editor</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = vim</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    autocrlf</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = input</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">[pull]</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    rebase</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> = false</span></span></code></pre><!----> <h2>Wrapping Up</h2> <p>The beauty of UNIX is that you can customize everything to match your workflow. Start small, add configurations as you discover what slows you down, and before long you’ll have a finely-tuned environment.</p><!--]-->]]></description>
    </item>
    <item>
      <title>Apple container setup notes</title>
      <link>https://www.vmunix.com/posts/apple-container-setup-notes</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/apple-container-setup-notes</guid>
      <pubDate>Sun, 19 Oct 2025 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p>I upgraded to MacOS 26 this week, and first thing I’ve wanted to try is the new <a href="https://github.com/apple/containerization" rel="nofollow">native container support</a>. I’ve been content with <a href="https://orbstack.dev/" rel="nofollow">OrbStack</a> but a lot of the time it’s overkill when I just want to test something on the MacBook. <a href="https://www.docker.com/" rel="nofollow">Docker</a> and <a href="https://podman.io/" rel="nofollow">Podman</a> even more so. From an initial hour playing around, it’s quick and easy and works fine if you don’t need <code>docker compose</code> in your testing workflow.</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">❯</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --cask</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> container</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">❯</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> container</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> system</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> start</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Verifying</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> apiserver</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> is</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> running...</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">No</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> default</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kernel</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> configured.</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> the</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> recommended</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> default</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kernel</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> from</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [https://github.com/kata-containers/kata-containers/releases/download/3.17.0/kata-static-3.17.0-arm64.tar.xz]</span><span style="color:#D73A49;--shiki-dark:#F97583">?</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [Y/n]:</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Installing</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kernel...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">~</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> took 32s</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">❯</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> container</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --rm</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> docker.io/library/alpine:latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> sh</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -c</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'echo hello world'</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">hello</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> world</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">~</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> took 10s</span></span></code></pre><!----> <p>That’s it for the install and getting the service running.</p> <p>All these tools use the standardized <a href="https://opencontainers.org/" rel="nofollow">OCI image spec</a>, which means I can just grab one from Docker Hub or another registry, and run it.  This example runs a command and then the <code>--rm</code> deletes the image when it’s done.</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">❯</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> container</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --detach</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --name</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> web</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> docker.io/library/nginx:alpine</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">web</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">~</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> took 3s</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">❯</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> container</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ls</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -a</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">ID</span><span style="color:#032F62;--shiki-dark:#9ECBFF">   IMAGE</span><span style="color:#032F62;--shiki-dark:#9ECBFF">                           OS</span><span style="color:#032F62;--shiki-dark:#9ECBFF">     ARCH</span><span style="color:#032F62;--shiki-dark:#9ECBFF">   STATE</span><span style="color:#032F62;--shiki-dark:#9ECBFF">    ADDR</span><span style="color:#032F62;--shiki-dark:#9ECBFF">          CPUS</span><span style="color:#032F62;--shiki-dark:#9ECBFF">  MEMORY</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">web</span><span style="color:#032F62;--shiki-dark:#9ECBFF">  docker.io/library/nginx:alpine</span><span style="color:#032F62;--shiki-dark:#9ECBFF">  linux</span><span style="color:#032F62;--shiki-dark:#9ECBFF">  arm64</span><span style="color:#032F62;--shiki-dark:#9ECBFF">  running</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  192.168.64.3</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  4</span><span style="color:#005CC5;--shiki-dark:#79B8FF">     1024</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> MB</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">❯</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> container</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> exec</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> web</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ps</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">PID</span><span style="color:#032F62;--shiki-dark:#9ECBFF">   USER</span><span style="color:#032F62;--shiki-dark:#9ECBFF">     TIME</span><span style="color:#032F62;--shiki-dark:#9ECBFF">  COMMAND</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">1</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> root</span><span style="color:#032F62;--shiki-dark:#9ECBFF">      0:00</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> nginx:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> master</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> process</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> nginx</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> daemon</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> off</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> nginx</span><span style="color:#032F62;--shiki-dark:#9ECBFF">     0:00</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> nginx:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> worker</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> process</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">26</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> nginx</span><span style="color:#032F62;--shiki-dark:#9ECBFF">     0:00</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> nginx:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> worker</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> process</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">27</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> nginx</span><span style="color:#032F62;--shiki-dark:#9ECBFF">     0:00</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> nginx:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> worker</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> process</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">28</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> nginx</span><span style="color:#032F62;--shiki-dark:#9ECBFF">     0:00</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> nginx:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> worker</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> process</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">29</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> root</span><span style="color:#032F62;--shiki-dark:#9ECBFF">      0:00</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ps</span></span></code></pre><!----> <p>I like that you get virtual IPs, and there’s even a DNS system which simplifies my type of testing where I routinely want to build and test a few hosts that need to talk to each other.</p> <p>And when I’m done?</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">❯</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> container</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> system</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> status</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">apiserver</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> is</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> running</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">application</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> data</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> root:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /Users/mmayo/Library/Application</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Support/com.apple.container/</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">application</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> root:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /usr/local/</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">container-apiserver</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> version:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> container-apiserver</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> version</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0.5.0</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (build: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">release,</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> commit:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 48230f3</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">container-apiserver</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> commit:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 48230f380499efecd663fdc6749a2cbaf2f8ed76</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">~</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">❯</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> container</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> system</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> stop</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">❯</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> container</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> system</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> status</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">apiserver</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> is</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> not</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> running</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> and</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> not</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> registered</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> with</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> launchd</span></span></code></pre><!----> <p>The whole thing is just gone. YASSSS.</p> <p>There’s lots of great examples and documentation on the <a href="https://github.com/apple/container/blob/main/docs/tutorial.md" rel="nofollow">container GitHub page</a>.</p> <p>It’s not a polished end product, but for me I like the simplicity, the firewalled networking and DNS, effective containment, and ease of firing the whole thing up or down when I need it without bloating my whole Mac. Pretty sweet.</p><!--]-->]]></description>
    </item>
    <item>
      <title>Gecko. Mozilla. Thoughts.</title>
      <link>https://www.vmunix.com/posts/gecko-mozilla-thoughts</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/gecko-mozilla-thoughts</guid>
      <pubDate>Fri, 28 Feb 2025 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p>Lots of talk on the socials re: Mozilla and Firefox/Gecko triggered by the recent <a href="https://www.reddit.com/r/privacy/comments/1j03omv/mozilla_changed_their_tos/" rel="nofollow">ToS changes</a>. Thought I’d write up some thoughts on the bigger picture “what to do about gecko” stuff.  Additions, corrections, giant gaping holes that need to be addressed.. all welcome feedback. :)</p> <p>The goal and awesomeness of having an implementation of the web is to be able to nudge the whole damned web towards or away from a behaviour you care about. Make a little dent in the world, as it were. Firefox and Gecko have been able to do that, repeatedly, in some big ways and in many many many small ways, affecting the experience of every single user of the web, <em>not just Firefox users</em>. That’s the allure, and incredible impact, and the reason why you’d even bother with the incredibly hard, expensive, mostly thankless job of making Gecko, SpiderMonkey, and Firefox and the vast array of supporting infrastructure needed to do it. <strong>Direct mission impact</strong>. It’s important to note that Firefox has done this over multiple decades while never having “winning” marketshare. Even in its glory days it was a ~20% player. That was enough, even into the more recent single digit marketshare era, to sometimes shift the implementations of the world’s largest web platforms. Pretty rad.</p> <p>With that uplifting note, I have so many thoughts on Mozilla and Firefox going forward that it’s hard to know where to even begin. I do think the only productive thing to spend any of my own mental energy on is to ignore past/current/future decisions from the org (can’t change them, and no insight into their context!) and focus only on <strong>“what do we do between now and when Firefox is over?”</strong>. Because it sure <em>feels</em> like the end is inevitable, it’s perhaps not too far off, and that’s gonna be a weird dangerous moment for the web. I’ve <a href="https://www.vmunix.com/posts/browsers-innovators-dilemma-and-project-tofino/" rel="nofollow">thought about this going back 10+ years</a> when I started to worry about Mozilla’s long term financial ability to maintain a full stack implementation of the web against the backdrop of slipping market share. Turns out Google kept paying anyways and the budget pressure never came. But for different reasons both <a href="https://blog.mozilla.org/en/mozilla/mozilla-leadership-growth-planning-updates/" rel="nofollow">Mozilla</a> and <a href="https://www.cnbc.com/2024/10/29/google-cfo-says-company-can-push-a-little-further-in-cost-savings.html" rel="nofollow">Google</a> appear far more focused on what will gain them relevance and influence in the AI wars to come, and far less on the browser wars of the past. Fair! We’re probably gonna need Mozilla.ai to be a force of nature! But I still don’t have a satisfying answer to what happens if Mozilla can no longer make Firefox/Gecko for <em>whatever</em> reason. Seems bad? Question one, I think, is do we actually care if it’s gone? If so, what do we really care about in particular? Different folks will have different “most important things”, but there’s a lot of good browsers out there so here’s what’s rattling around in my head.</p> <p>Some context to get out of the way first. There’s a couple open source engines we all know deployed at massive scale fighting the good fight of ensuring the web’s implementation is “open”. But, like Gecko, are so expensive to build and maintain that the fact that the code is open is nearly irrelevant. There’s a couple more under development (servo, ladybird) that are exciting and I’m cheering for, but it’s a very long road. Regardless, the primary reason Gecko was compelling as a platform choice in the last decade wasn’t the code, IMO, it was the nature and intent of its owner. A theoretical antidote of sorts that felt good to have exist and go to battle with anyone and everyone if necessary. Mozilla Corporation, as it slowly learns to be a real company, not a movement, is a less and less obvious counter bet to the culture and behaviours of the market winners. Which feels like a betrayal of sorts to us old farts who grew up with both the early web itself, and Mozilla’s rescue of it from the clutches of evil. Back then both had a tangible rebel alliance “fuck you I’ll do what I want to” vibe. They don’t now. The reality is it’s just an inevitable progression of an org and a web so well funded to pursue an ancient and incredibly broad mission for so long. Mozilla behaviour and interests sort of mirrors the web broadly, I guess I’d conclude. Not good, not bad, just natural evolution and shifting of priorities. Still… SHIT. The web is under attack! Where are its defenders?! The emotional part of this isn’t losing Gecko, it’s that the web’s rebel alliance disbanded and drifted away long ago. There was no top down conspiracy. It stopped being the most important fight for too many folks, I think. Tough pill to swallow.</p> <p>The market dynamics around the tech itself is also worth talking about. People have been saying for years that Gecko is “a dead engine walking” and they’ve sort of been right. They’ve been saying that for a decade at least, of course, so at a minimum it’s a remarkably resilient dead engine. The thing to note, IMO, is that Gecko’s fate is <em>exclusively</em> tied to Firefox’s. I would argue Apple’s WebKit isn’t quite as extreme, but is similar - very little deployment scale outside Apple products. You could make the argument that without Google Blink/Chromium/v8 are totally dead, and I think that’s largely true, but unlike Gecko or WebKit there are a LOT of downstream consumers of that stack with products deployed to many tens if not hundreds of millions of users. Several big browsers, yes, but also things like Electron, Node.js, a long list of notable projects, products, and companies that are dependent on Google’s implementation. Folks with a lot to lose. For this reason, I’ve long wondered if the web’s implementations would ultimately follow the path of the OS wars of the 90s. In the end all the Unixes died off, to be replaced by a single implementation: Linux. And a gaggle of Linux distros competing on forks of every magnitude imaginable! It’s messy, still expensive, and sort of resembles cold war mutually assured destruction dynamics at times.. but on the whole seemed to work better than the alternative. Of note, the BSDs didn’t die, and appear healthy, but they had a deeply distributed contributor base from day one and ultimately are very niche in terms of developer mindshare and deployment vs. Linux today. Still, I love my FreeBSD. Is Gecko on track to be a dead Unix, or is it a BSD? If we consider the parallels to the Unix wars, it’s entirely supported by a single vendor, and the conclusion gets pretty clear. <strong>We <em>want</em> Gecko to be a BSD, but I fear it’s soon to be a dead Unix..</strong></p> <p>So there’s both challenge and opportunity if we think of things this way, in my view. We desperately need organizations that deeply and primarily care about browsers and the web, who are going to do more than just skin Chromium (Linux). We need rebel alliances, and we need them to take on forks “of significance” because they are trying to influence how the web itself works for <em>everyone</em>, just like Mozilla did. Natural forces (fork cost) suggest these efforts are incentivized to merge over time and the web <em>could</em> continue to evolve and reflect multiple points of view on top of a single dominant code base this way. Microsoft Edge is the closet example of this already working, with the caveat that they continue to be heavy on bad taste and light on rebel alliance. Heh. But there are going to be those types of contributors, and they’re extremely valuable. Just like they were/are in Linux. There just can’t ONLY be those types for this to feel like it’s working and we didn’t lose the web entirely. Of import to Mozilla, this sort of outcome, if successful, doesn’t need a <em>fully</em> independent implementation of the web in any one of these organizations. Probably good news in terms of long term budget friendliness, but tbh I doubt the development costs to Mozilla would be wildly different than today regardless. The costs there are largely defined by ambition, not technology.  Personally, though, I think I’d be ok to see an empowered Mozilla browser team operating with a clear mission throwing its weight around inside the Chromium code base. Cool improvements for users could much more quickly adopted and shipped by other Chromium browsers, in aggregate perhaps providing the proof/pressure to drive change in Chrome/Edge. Might get interesting!</p> <p>Two asterisks worth calling out. First, we need to acknowledge that the distribution power of the established parties make the whole idea that smaller shops/browsers can still impact the web’s APIs incredibly difficult to realize. Fork costs are high. Distribution costs are insanely higher. It probably requires regulatory interventions beyond anything discussed today for any of this to end well. I don’t have a reason to believe otherwise, whether there’s one or ten browser engines. There’s zero incentive for Google to take a patch from Mozilla or Microsoft or an IC that advances the web for users but hurts their bottom line. You need momentum and heft in market to change that kind of behaviour by driving their user bases to demand it from them. Right now, and for quite some time I’d wager, Google alone decides what is de facto on the web by what they ship in Chrome, regardless of what plays out in open source code repos. Second, someone has to fund next generation engines, in my opinion. They’re long shots, but they’re invaluable in so many ways to show us what’s possible when we don’t have the weight of every single website ever made on our shoulders. I have no idea how to fund them except all the players should just shake hands and put 1% of their fuzzily defined browser budgets into a small pool that hands out grants to crazy people.</p> <p>So, impossibly difficult challenges on all fronts! Odds of success near zero! Will most certainly never play out like this! Really, perfect conditions for a Mozilla and their war chest to do the impossible yet again. They just have to want it to happen so badly they’re willing for it to all go wrong. Regardless, for those of us with the passion for building the web in some sort of way, we can’t wait for Mozilla to do anything. We’ve got to roll up our sleeves and get to work.</p> <p>More to come.</p><!--]-->]]></description>
    </item>
    <item>
      <title>Macbook Setup 2024 Edition</title>
      <link>https://www.vmunix.com/posts/macbook-setup-2024-edition</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/macbook-setup-2024-edition</guid>
      <pubDate>Wed, 22 May 2024 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p>It’s been a while since I’ve picked up a new laptop. I opted for an M3 Max with 36GB of RAM and a 2TB SSD which feels like a bit of a future proof sweet spot once you’re already crossed the rubicon into “omg that’s a lot of money for a laptop” territory..</p> <h2>Homebrew</h2> <p>Over the years I’ve increasingly leaned on <a href="https://brew.sh/" rel="nofollow">Homebrew</a> to make my life easier, to the point where it’s one of the tools I miss the most on my Linux desktop which is a crazy thing to say but I just did?! I’m going a step beyond this time and using it to install/manage everything I can including most of the non-opensource Mac .apps I use as casks.</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Assume Homebrew already install from link above</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># utlities</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --cask</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> shottr</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --cask</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> rectangle</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --cask</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> tailscale</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --cask</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> appcleaner</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --cask</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> font-hack-nerd-font</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --cask</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> font-monaspace</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --cask</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> itsycal</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># 3rd party apps</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --cask</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> slack</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --cask</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> whatsapp</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --cask</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> google-chrome</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># dev stuff</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --cask</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> visual-studio-code</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">#brew install --cask docker</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --cask</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> utm</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --cask</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> restfox</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># shell tools</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> git</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> btop</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> node</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> go</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gcc</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># AI junk</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ollama</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> pytorch</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> pyenv</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># shell upgrades</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> wezterm</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> zsh</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> starship</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> neovim</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> tree-sitter</span></span></code></pre><!----> <p>for me.. a giant copy-n-paste and I’m almost up and running just with brew!</p> <h2>Shell Rustification</h2> <p>I briefly flirted with the notion of changing my custom zsh setup and “throwing in the towel” by moving to <a href="https://ohmyz.sh/" rel="nofollow">oh-my-zsh</a> or <a href="https://zimfw.sh/" rel="nofollow">zim</a> but I’ve also thought about switching back to bash as that’s what on Linux servers. I tried out <a href="https://github.com/ohmybash/oh-my-bash" rel="nofollow">oh-my-bash</a> but in the end I’ve retreated back to an up-to-date zsh from homebrew and <a href="https://starship.rs/" rel="nofollow">starship.rs</a>.  Out of the box I can live with it, and it is cross-shell which is kinda rad. Best of both worlds? Maybe. Time will tell.</p> <h2>Terminal Rustification</h2> <p>I’m running zsh and all my command-line tools in Wez Furlong’s <a href="https://wezfurlong.org/wezterm/index.html" rel="nofollow">WezTerm</a> these days. It’s fast, reliable (for me so far at least), and once you have a nerd font installed, it “just works” with all the modern fancy fonts and glyphs and shit and did I mention it’s crazy fast?  Nearly bone stock config:</p> <!----><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">--</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Pull</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> in</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> the</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> wezterm</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> API</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">local</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> wezterm = require </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'wezterm'</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">--</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> This</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> table</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> will</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> hold</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> the</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> configuration.</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">local</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> config = {}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">--</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> In</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> newer</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> versions</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> of</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> wezterm,</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> use</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> the</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> config_builder</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> which</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> will</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">--</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> help</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> provide</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> clearer</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> error</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> messages</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">if</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> wezterm.config_builder</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> then</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  config</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> wezterm.config_builder</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">end</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">--</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> This</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> is</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> where</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> you</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> actually</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> apply</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> your</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> config</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> choices</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">--</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> For</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> example,</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> changing</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> the</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> color</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> scheme:</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">config.color_scheme</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'AdventureTime'</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">config.font_size</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> =</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 14.0</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">--</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> and</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> finally,</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> the</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> configuration</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> to</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> wezterm</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> config</span></span></code></pre><!----> <h2>Editor</h2> <p>Fancy terminal, fancy shell, fancy fonts.. you can see where this is going. <a href="https://neovim.io/" rel="nofollow">NeoVim</a>. I have clearly lost my mind and parked my Vim configuration of 20 years and ended the Visual Studio Code experiment, and am trying something new.  It’s vim, but with all the knobs turned up to 11.  I’m either in love, or.. about to have mental breakdown? <img src="/images/SCR-20240521-rbig.png" alt="fancy pants neovim"/></p> <h2>Virtual Machines</h2> <p>I try to not have to run VMs locally because it ends up just being slow and terrible on the Mac.. but sometimes it’s the only way..  You’ll see in the Homebrew script above I commented out both docker and <a href="https://github.com/abiosoft/colima" rel="nofollow">colima</a>. I really hate Docker.  colima makes running the occasional VM less awful than the full blown Docker Experience (tm), but lately I admit I’ve been using <a href="https://orbstack.dev/" rel="nofollow">OrbStack</a> and it’s nice GUI there’s just far less cursing and frustration involve. Has been reliable, too. <img src="https://orbstack.dev/_next/image?url=%2Fimg%2Fhero.png&amp;w=1920&amp;q=75" alt="OrbStack UI running a Compose project"/></p> <h2>Conclusion</h2> <p>My new zen laptop approach is quite simple:</p> <ol><li>Manage as much as I can in Homebrew.</li> <li>Embrace good tools with sensible default configs that don’t need hours of tweaking.</li> <li>Know that when docker and its irk ruin the party a complete re-glass of the computer is just a few easy steps away!</li></ol><!--]-->]]></description>
    </item>
    <item>
      <title>Let&apos;s build the fediverse</title>
      <link>https://www.vmunix.com/posts/build-the-fediverse</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/build-the-fediverse</guid>
      <pubDate>Mon, 27 Feb 2023 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p><strong>TL;DR: I’ve gone from skeptic to fan of Mastodon and the fediverse. To that end, I’ve been part of a small team that’s releasing a new iOS app today:</strong><a href="https://getmammoth.app/" rel="nofollow"><strong>Mammoth, a beautiful Mastodon app for the rest of us</strong></a><strong>. It’s free, it’s high quality, we’re doing some novel things to make the whole experience more friendly and fun for new users, and it’s also a deeply customizable app we think anyone will love. I hope you like it. 🙂</strong></p> <p><strong>We’ve already had a lot of amazing supporters who believe in the potential of the Fediverse and have been helping us start on our journey. A special mention goes to Mozilla who not only contributed financially but also with expertise and guidance. 🙏🙏</strong></p> <p>The story so far.</p> <p>It was back in October, on a rainy weekend, and my daughter and I ended up watching <a href="https://www.twitch.tv/krisnova" rel="nofollow">Kris Nova’s Twitch stream</a> as she and her band of merry ops peeps were hacking on the backend infrastructure for a Mastodon site called <a href="https://hachyderm.io/" rel="nofollow">hachyderm.io</a>. Curious name, we thought! More importantly, we were inspired by watching cool people working on something they loved, building something that mattered to them. A node on new kind of decentralized, community-at-the-core, network-of-social-networks. We created accounts and started exploring.</p> <p>I’d last looked at Mastodon in 2017 as part of a project at Mozilla to investigate feasibility of new p2p messaging platforms. Frankly, I hastily concluded it could never work: incredibly tricky usability challenges are the hallmark of federated consumer products, a lesson I’d learned trying to bring <a href="https://en.wikipedia.org/wiki/Mozilla_Persona" rel="nofollow">Persona</a> to market for Mozilla years earlier with <a href="https://ben.adida.net/" rel="nofollow">Ben Adida</a>, <a href="https://hachyderm.io/@michaelrhanson" rel="nofollow">Mike Hanson</a>, and many other talented folks. Yet the more I signed up on different servers, followed more folks, followed more hashtags, I was drawn in as I saw a whole community of people who wanted a place to call their own and just didn’t fucking care federation was never going to work. They needed this thing to exist, so they were building it anyway.</p> <p>I’ve seen this kind of dynamic first hand a few times now, as I approach 30 years of working with and on open source software. The first time for me was living through the rhetoric about how there was no way Linux on cheap PCs could ever replace the entrenched operating systems and expensive servers from Sun, HP, Digital, SCO. They owned the market! But we needed cheap servers and free operating systems to build the internet, so we worked on Linux and BSD regardless. 2nd time it was: “obviously nothing can beat Internet Explorer. Microsoft won.” Brendan and Ben and whole slew of people didn’t care — they built Firefox anyways because the web needed it. 3rd time it was a belief that there was no way anything could replace PHP as the de facto web development stack! Ryan Dahl didn’t care, and soon neither did a whole team of developers who wound up building Node.JS. <img src="https://miro.medium.com/max/1280/1*IzFO7R06XuxcY0EUVB7qWg.jpeg" alt="Eric Allman, Kirk McKusick, and myself sometime in the late 90s."/>Eric Allman (sendmail), myself, Kirk McKusick (BSD). Late 90s? Kirk loved a good margarita. :)
Sometimes, it just needs to be built. And that’s where we are today with “social networks”, I think. Reasons why decentralization won’t work far outweigh the reasons it might work, but I think the Fediverse gets it right by putting the power where it belongs: with passionate communities and developers building stuff because it’s cool, because it makes their community stronger, more connected.</p> <p>We’re at a point where there’s now enough people who believe decentralization <strong>**</strong>is<strong>**</strong> what’s next for the Internet and are willing to put their money where their mouth is, so to speak. The inertia of the current systems will indeed be overcome. *<strong>*My bet is that “the Fediverse” is our best shot at how to do a decentralized internet, here and now**</strong>.</p> <p>Today, <a href="https://mammoth.writeas.com/" rel="nofollow">along with the whole team</a>, I’m excited to introduce the first release of <a href="https://getmammoth.app/" rel="nofollow">Mammoth, a beautiful Mastodon app for the rest of us</a>. If you’ve already gone down the Mastodon rabbit hole, we think Mammoth is the only mobile app you’ll need — it’s fast, fun, and fully-featured and you can customize it to be just right for you. If you’re new to Mastodon, we take the headaches out of getting started with Mastodon: *<strong>*we’re</strong>one of<strong>first app</strong>s<strong>to offer simple, in-app account creation and a quick way to find your first set of hashtags and conversations to follow**</strong>.</p> <p>Mastodon is already wildly successful, and quickly becoming the place where many of the best conversations, especially around technology, start.</p> <p>We want to preserve what makes Mastodon special. The team is all in on the Fediverse, open source, and supporting the tens of thousands of instances and micro-communities. But we think we can help make Mastodon easier to approach for the next 100 million users so they too can enjoy the thrill of finding their place. By combining great client engineering with a backend team that operates our own instance &amp; can build our own backend services (as well as send patches upstream to the core dev team), we think we can help craft a better end-to-end experience.</p> <p>A big part of how we want to work is by being as completely open and considerate as we possibly can. We’ll be <a href="https://github.com/TheBLVD/moth.social" rel="nofollow">open sourcing our code</a>, of course, but more importantly we want to be part of the conversation around what behaviours are good to reinforce, what the performance implications of a neat feature might be on the network as a whole, what approaches might lead to good or bad outcomes across the system. Let’s get this right.</p> <p>Hack on,</p> <p>-Mark (<a href="https://hachyderm.io/@mmayo" rel="nofollow">https://hachyderm.io/@mmayo</a>)</p><!--]-->]]></description>
    </item>
    <item>
      <title>Introducing on-chain lotteries for KryptoSign</title>
      <link>https://www.vmunix.com/posts/introducing-on-chain-lotteries-for-kryptosign</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/introducing-on-chain-lotteries-for-kryptosign</guid>
      <pubDate>Wed, 01 Sep 2021 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p><em>* 2025 note: none of this exists anymore but it’s left here to look back at and point and smirk</em></p> <br/> TL;DR: we’re adding 3 new features to KryptoSign today! <ul><li><strong>CSV downloads</strong> of a document’s signers</li> <li><strong>Document Locking</strong> (prevent further signing)</li> <li><strong>Document Lotteries</strong> (pick a winner from list of signers)</li></ul> <p>Why? Well, you folks keep abusing this simple Ethereum-native document signing tool to run contests for airdrops and pre-sales, so we thought we’d make your lives a bit easier! :) <img src="https://cdn-images-1.medium.com/max/1600/1*BJmLzca4_5NXOGKrscm_OA.png" alt="up and to the right graph showing exponential growth of KS"/> We launched <a href="https://www.kryptosign.io/" rel="nofollow">KryptoSign</a> in May this year as tool for Kai, Bart, and I to do the lightest possible “contract signing” using our MetaMask wallets. Write down a simple scope of work with someone, both parties sign with their wallet to signal they agree. When the job is complete, their Ethereum address is right there to copy-n-paste into a wallet to send payment. Quick, easy, delightful. :)</p> <p><strong>But as often happens, users started showing up and using it for other things.</strong> Like guestbooks. And then guestbooks became a way to sign up users for NFT drops as part of contests and pre-sales, and so on. The organizer has everyone sign a KS doc, maybe link their Discord or Twitter, and then picks a winner and sends a NFT/token/etc. to their address in the signature block. Cool.</p> <p>As these NFT drops started getting really hot the feature you all wanted was pretty obvious: have folks sign a KS document as part of a pre-sales window, and <strong>have KS pick the winner automatically</strong>. Because the stakes on things like hot NFT pre-sales are high, we decided to <strong>implement the random winner using</strong> <a href="https://docs.chain.link/docs/chainlink-vrf/" rel="nofollow"><strong>Chainlink’s VRF </strong></a><strong>— verifiable random functions —</strong> which means everyone involved in a KryptoSign lottery can independently confirm how the random winner was picked. Transparency is nice!</p> <p>The UI for doing this is quite simple, as you’d hope and expect from <a href="https://www.kryptosign.io/" rel="nofollow">KryptoSign</a>. There’s an action icon on the document now: <img src="https://cdn-images-1.medium.com/max/1600/1*4mlk7t4nr2sW8S45PDdEdA.png" alt="screenshot of menu option to pick a winner from the signers of a document"/> When you’re ready to pick a winner, it’s pretty easy. Lock the document, and hit the button: <img src="https://cdn-images-1.medium.com/max/1600/1*3vI9CzLLLpocxOUmOBNKpQ.png" alt="UI of the pick a winner screen"/> Of note, <strong>to pick a winner we’re collecting to 0.05 ETH from you to cover the cost of the 2 LINK required to invoke the VRF on mainnet</strong>. You don’t need your own <a href="https://coinmarketcap.com/currencies/chainlink/" rel="nofollow">LINK</a> and all the gas-incurring swapping that would imply. Phew! The user approves a single transaction with their wallet (including gas to interact with the smart contract) and they’re done.</p> <p>Our initial users really wanted the on-chain trust of a VRF, and are willing to pay for it so their communities can trust the draw, but for other use cases you have in mind, maybe it’s overkill? Let us know! We’ll continue to build upon <a href="https://www.kryptosign.io/" rel="nofollow">KryptoSign</a> as long as people find useful things to do with it.</p> <p>Finally, big props to our team who worked through some rough patches with calling the Chainlink VRF contract. Blockchain is weird, yo! This release saw engineering contributions from <a href="https://twitter.com/neocho_" rel="nofollow">Neo Cho</a>, <a href="https://twitter.com/RyanOuyang_" rel="nofollow">Ryan Ouyang</a>, and <a href="https://twitter.com/joshpeters_" rel="nofollow">Josh Peters</a>. Thanks!</p> <p>— Mark</p><!--]-->]]></description>
    </item>
    <item>
      <title>How MeebitsDAO airdropped Polygon NFTs to members</title>
      <link>https://www.vmunix.com/posts/how-meebitsdao-airdropped-polygon-nfts-for-governance-voting</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/how-meebitsdao-airdropped-polygon-nfts-for-governance-voting</guid>
      <pubDate>Wed, 21 Jul 2021 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p>So what happened was that the 6th most rare <a href="https://meebits.larvalabs.com/" rel="nofollow">Meebit</a> was <a href="https://medium.com/meebitsdao/meebitsdao-dissect-the-dissected-giveaway-5b6646f732ee" rel="nofollow">fractionalized into 1M pieces</a>, and 30,000 (3%) of those fragments were graciously donated to <a href="https://meebitsdao.world/" rel="nofollow">MeebitsDAO</a> by <a href="https://www.div.vc/" rel="nofollow">Divergence.VC</a>. Kai proposed that a fun way to re-distribute those fractions would be to do a giveaway contest. Earn tickets for a raffle, have a shot at a chunk of a famous Meebit. Cool! There’s 3 different kinds of tickets, but for the 1st lottery <a href="https://kaigani.medium.com/" rel="nofollow">Kai</a> wanted to airdrop a raffle ticket in the form of an NFT — aka the “<a href="https://opensea.io/collection/meebitsdaotickets" rel="nofollow">Red Ticket</a>” — to every current Meebit holder so they could have a chance to win. Hype up the MeebitsDAO and have some fun! <img src="https://cdn-images-1.medium.com/max/1600/0*wXo_z4J2SxSS79zE.png" alt="red theatre ticket"/> The first question was <strong>“cool idea, but how do we not lose our shirts on gas fees minting 4700 NFTs!?”</strong>. There’s a bunch of low-gas alternative chains out there now, which, fortunately, we’d played around with quite a bit when we started doing community “Achievement” NFTs (<a href="https://opensea.io/collection/meebitsdaoachievements" rel="nofollow">see some here on OpenSea</a>) for MeebitsDAO. <a href="https://polygon.technology/" rel="nofollow">Polygon</a>, for the moment at least, had some really compelling advantages for this kind of “badge” NFT where there’s no/limited monetary value in the token:</p> <ul><li>Polygon is 100% ethereum compatible — same solidity smart contracts, same metamask, even the same explorer (the etherscan team built <a href="https://polygonscan.com/" rel="nofollow">polygonscan</a>). Easy!</li> <li>For end users Polygon addresses are the same as their Ethereum addresses. 1:1. Nice!</li> <li>OpenSea natively/automatically displays NFTs from a user’s matching polygon address in their collection! <strong><em>This was huge</em></strong>, because we knew 99% of wallets we wanted to drop a ticket on wouldn’t otherwise notice activity on a side-chain.</li> <li>Polygon assets can be moved back to Ethereum mainnet by folks if they so desire, which has a nice feeling.</li></ul> <p>Getting up and running on Polygon is <a href="https://medium.com/stakingbits/setting-up-metamask-for-polygon-matic-network-838058f6d844" rel="nofollow">covered elsewhere</a>, and is pretty simple:</p> <ul><li>Add a “custom RPC” network to metamask.</li> <li>Get some fake test MATIC (the native token on the polygon chain) on the “Mumbai” testnet <a href="https://faucet.matic.network/" rel="nofollow">from a faucet</a> and play around.</li> <li>Get some “real” MATIC on mainnet. Fees are super low on Polygon, so you don’t need much for minting, <strong>5 MATIC ($5!) is plenty to mint thousands of NFTs</strong>. I swapped Eth for MATIC on <a href="https://app.1inch.io/#/137/swap/ETH/MATIC" rel="nofollow">1inch</a>, and then <a href="https://wallet.matic.network/bridge/" rel="nofollow">bridged that MATIC to Polygon</a>. There are many other ways of doing it.</li></ul> <p>For MeebitsDAO, we create our own ERC721 smart contracts and mint from them instead of using a minting service. It gives us more control, and over time we’re <a href="https://github.com/BlockBlockInc/meebitsdao-tickets" rel="nofollow">building up a repo of code and scripts</a> that gets better and better and is <em>purpose-built to the needs of the MeebitsDAO community</em>. This maybe sounds like a lot of work vs using a site like Cargo, but if you have some Node.js experience tools like <a href="https://hardhat.org/" rel="nofollow">Hardhat</a> make deploying contracts and minting from them approachable.</p> <p>If you’re new to Ethereum and NFTs, the first thing you need to do know is that you 1st deploy your smart contract to the blockchain, at which point it will get an address, and then you call that smart contract on that address to mint NFT tokens. As you mint the tokens you need to supply a URI that contains the metadata for that particular token (almost everything we think of as “the NFT” — the description, image, etc. — actually lives in the metadata file <em>off-chain)</em>. We generate a JSON file for each ticket and upload it to IPFS via a <a href="https://pinata.cloud/" rel="nofollow">Pinata</a> gateway, and then pin the file with the <a href="https://github.com/PinataCloud/Pinata-SDK" rel="nofollow">Pinata SDK</a>. (pinning is the mechanism where you entice IPFS nodes to not discard your files.. ah, IPFS..)</p> <p>Like many projects, we lean on the heavily tested <a href="https://docs.openzeppelin.com/contracts/2.x/api/token/erc721" rel="nofollow">de facto ERC721 contracts published by the OpenZeppelin team</a>:</p> <p>The contract maybe looks a little daunting but really all it says is:</p> <ul><li>Be an ERC721 that has metadata in an external URI, and be burnable by an approved address on an access list.</li> <li>The Counters stuff lets the token be capped — i.e. a fixed supply, supplied at contract creation.</li> <li>safeMint/burn/tokenURI/supportedInterface are just boilerplate to allow above.</li> <li>totalSupply() lets block explorers like etherscan/polygonscan/etc. know what the cap on the token is so they can <a href="https://polygonscan.com/token/0xb3e7df09eb3f0a5e2150c75bd71f583fc83ee193" rel="nofollow">display it in their UIs</a>.</li></ul> <p>Once the contract is published, we have a helper script in the hardhat repo that simply reads a CSV files of addresses we want to airdop the ticket to and calls safeMint() on the contract. Here’s the core loop:</p> <p>Because blockchain calls don’t always finish quickly, or at all, we use a <code>try{}</code> block to wait for the transactions to succeed before moving onto the next ticket, logging success/failure. When we minted the 4689 NFTs it just happened to be on a morning when Polygon was quite busy, so it took a few hours to complete. We had logs of 2 mints that failed, so we just re-ran those to complete the drop.</p> <p>You can check out the <a href="https://github.com/BlockBlockInc/meebitsdao-tickets" rel="nofollow">full repo on GitHub</a>, but hopefully this gives you a quick view at the two “bespoke” pieces: the contract and the minting logic.</p> <p>Our goal is to share as much as we can about what goes on behind the scenes at block::block as we help the MeebitsDAO team launch fun ideas! Let us know if it’s helpful, what’s missing, what’s cool, and what else we could write about that help shine a light on “DAO Ops”. :)</p><!--]-->]]></description>
    </item>
    <item>
      <title>(Re)defining the Tofino Project</title>
      <link>https://www.vmunix.com/posts/re-defining-the-tofino-project</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/re-defining-the-tofino-project</guid>
      <pubDate>Tue, 15 Nov 2016 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p>Our original 3-month goal was to investigate what a browser designed in 2016 would look like.</p> <p><em>(Of note, this is different than exploring what an implementation of the web’s platform should look like — see</em><a href="https://medium.com/mozilla-tech/a-quantum-leap-for-the-web-a3b7174b3c12#.fas2nkric" rel="nofollow"><em>Project Quantum</em></a><em>and</em><a href="https://servo.org/" rel="nofollow"><em>Servo</em></a><em>for that.)</em></p> <p>In those three months, we experimented with lots of different parts of the browser: from the way you navigate, to the way you save things from the web, to the very way in which the front-end for a web browser is built.</p> <p>Now that the initial project is over (in fact,was over a few months ago…), it is time to look at if and how we move forward. The <a href="https://medium.com/project-tofino/engineering-update-on-tofino-8381d82398e8#.h5nj5hsv8" rel="nofollow">Engineering Update for Tofino</a> post by Joe Walker is live, and posts from the product and UX teams will follow, but at the high level this is what we learned:</p> <ul><li>User testing our concepts ahead of even completing a prototype was extremely valuable.</li> <li>Boy howdy do normal users have hard time with non-conventional browser interfaces!</li> <li>A User Agent Service is a good idea. We want one in Firefox, like, yesterday.</li> <li>Writing a new browser UI in React is flexible and easier to develop in than XUL. Mixing XUL and HTML in a single product/interface was harder than we thought.</li> <li>Electron is great for prototyping, but some work is needed if you want to build a full browser.</li> <li>Getting out of our Firefox developer bubble and engaging with new open source teams and projects was great. So much awesome feedback and energy!</li></ul> <p>So what is next?</p> <p>The ideas behind the user-agent service and a React UI sound good on paper, but do they work in practice, and are they useful to Mozilla? The <a href="https://github.com/mozilla/tofino/blob/master/docs/UA-service.md" rel="nofollow">User Agent Service</a> is designed to store a broader set of data than Firefox Sync can currently handle, but we’re not sure if can it scale up with large volumes of data or if it’s as flexible as we need.</p> <p>There could be lessons from our React based UI that we can take into mainline Firefox. To that end Dave Townsend is going to be starting with <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/tabbrowser" rel="nofollow">TabBrowser</a> and seeing if there are things that Firefox can learn and we’re going to continue to evolve what we’ve got to be more workable.</p> <p>We’re going to continue using an environment that makes it easy to prototype fast. We’re not wedded to Electron (far from it) but the fast prototyping aspects of that have been really nice.</p> <p>It’s also becoming clear that the name Tofino doesn’t really work with what we’re doing now; What we’re working on now is less of a product or user experience exploration, and more of a set of technologies that need testing out, so we’re renaming the group to the Browser Futures Group.</p> <p>We’re working on a roadmap.</p> <p>Tofino is dead — Long live the Browser Futures Group.</p> <p><em>A brief aside: We briefly called this team the Browser Research Group, and asked for help picking a new name. The internet provided! Background: Naming things is always hard.. so a brief clarification is required because I agonized over using the word Research. The timescale of our ‘research’ is short (months rather than years) and what we’re doing is closely aligned to the goals of the Firefox product organization — i.e. building and shipping a popular browser. The team does not do the kinds of research Mozilla Research in our Emerging Technologies group does, things looking years ahead like</em><a href="https://www.rust-lang.org/en-US/" rel="nofollow"><em>Rust</em></a><em>and</em><a href="https://servo.org/" rel="nofollow"><em>Servo</em></a><em>and</em><a href="https://hacks.mozilla.org/2016/10/webassembly-browser-preview/" rel="nofollow"><em>WebAssembly</em></a><em>. Calling the team the “Browser Innovation Group” felt even more awkward. Got a better name suggestion? Comment below!</em></p><!--]-->]]></description>
    </item>
    <item>
      <title>Browsers, Innovators Dilemma, and Project Tofino</title>
      <link>https://www.vmunix.com/posts/browsers-innovators-dilemma-and-project-tofino</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/browsers-innovators-dilemma-and-project-tofino</guid>
      <pubDate>Thu, 07 Apr 2016 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p><em>** update 08/04: I should have been clearer that Project Tofino is wholly focused on UX explorations and not the technology platform. We are working with the Platform team on technology platform futures too, and we’re excited about the Gecko and Servo-based futures being discussed! Also, don’t forget to check out the companion post from Philipp: <a href="https://medium.com/project-tofino/designing-a-browser-that-isn-t-a-browser-685b63c4b6f1#.bagvb8q4x" rel="nofollow">Designing a Browser that isn’t a Browser</a>. Finally, go straight to the <a href="https://github.com/mozilla/tofino" rel="nofollow">GitHub repo</a> for actual project details. Thx!</em></p> <p><a href="/posts/content-blocking-is-the-web-at-its-best">Last time I posted</a>, I talked about Content Blockers. We <a href="https://blog.mozilla.org/futurereleases/2015/12/08/announcing-focus-by-firefox-a-content-blocker-for-ios/" rel="nofollow">ended up making one</a>, and the world didn’t end. Yay! Blogging helped me sort it out in my own head, and that helped identify a path forward at <a href="https://www.mozilla.com" rel="nofollow">work</a>. So I’m going to do some more blogging, because I’m kicking off a new project which triggered a bunch of soul searching about products, innovator’s dilemma in particular, and what taking risk feels like. Maybe talking about it will help me sort some of this out. So, here we go.</p> <p>Let’s jump right in and say yes, the rumors are true, we’re <a href="https://github.com/mozilla/tofino" rel="nofollow">working on browser prototypes</a> that look and feel almost nothing like the current Firefox. The premise for these experiments couldn’t be simpler: what we need a browser to do for us —both on PCs and mobile devices — has changed a lot since Firefox 1.0, and we’re long overdue for some fresh approaches. It’s worth noting that we have a <a href="https://wiki.mozilla.org/Firefox/Go_Faster" rel="nofollow">ton</a> <a href="https://wiki.mozilla.org/Test_Pilot" rel="nofollow">of</a> <a href="https://wiki.mozilla.org/Firefox/Recipe_Server" rel="nofollow">work</a> underway on our flagship product, Firefox, that’s all about evolving the browser experience. Not to mention the huge bet we’re making this year that we can get the biggest changes to Gecko we’ve ever attempted (<a href="https://wiki.mozilla.org/E10s" rel="nofollow">e10s</a>) to land so that Firefox is on a healthy, competitive footing for years to come. But it’s not enough, when someone has a totally different idea they want to explore.</p> <p>We’ll blog on the <a href="https://medium.com/project-tofino" rel="nofollow">Project Tofino Medium channel</a> about the project itself, but what this and subsequent posts are mostly about is thoughts on what the business of doing “innovation” at an organization that has a lot to lose feels like. It feels hard. Up to this point in my 20 year career (20 years? right?!), I’ve never experienced anything quite so “textbook innovator’s dilemma” as my last year at Mozilla. Let me explain. What’s probably not surprising is that the team that builds our browser has a lot of great insights and ideas about how people actually use browsers and the kinds of problems people have that aren’t currently solved by <em>anybody’s</em> browser product. Also likely not surprising is that said team would be stoked to build an entirely different kind of browser focused on solving those problems. Focus. Freedom. Yes! What’s not intuitive to anybody that hasn’t experienced the backside of a very successful product run is that creating something new and different is incredibly difficult. Every little decision can bring crushing stop energy. At an academic level, this is fascinating. Everyone’s read about this in books. When you live it, it’s very personal. The reason new things at old shops is difficult, mostly, I think, isn’t because people are bad, or stupid, or actively sabotage new projects or any nonsense like that. It’s largely because doing anything that might conceivably impact the current product creates unavoidable tension. Nobody likes tension, so you replace it with that self-created stop energy. <a href="http://www.newyorker.com/magazine/2014/06/23/the-disruption-machine" rel="nofollow">Innovator’s dilemma is a real thing</a>.</p> <p>For example, the prototype we’re feeling good about right now is built with <a href="http://electron.atom.io/" rel="nofollow">Electron</a> and <a href="https://facebook.github.io/react/" rel="nofollow">React</a>, not <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Gecko" rel="nofollow">Gecko</a> and <a href="https://en.wikipedia.org/wiki/XUL" rel="nofollow">XUL</a> (our go-to technologies for building browsers). For a small team starting out pursuing a new product concept it’s a great choice — Electron is a wonderful tool for us to do prototyping with — but a simple decision like picking the right tool for the job becomes an epic FUD generator when it could be perceived as threatening to the existing product. Immediately, you worry. Does it signal we don’t believe in Gecko? What will the platform team think? Will they believe us that the project has nothing to do with technology selection, that’s what <a href="https://github.com/browserhtml/browserhtml" rel="nofollow">browser.html</a> is for? What if web developers think we’re abandoning Firefox, and Gecko? We’ll lose on web compat, and nothing will work in Firefox! Consumers will abandon Firefox in droves! We’ll lose our influence at the W3C! OMG the web is dead! I’m not making fun of that. I’ve had all of those thoughts. And spent countless hours thinking about how to deal with the others that will inevitably have them too. Before you know it <em>everybody involved</em> is spending countless hours worrying about about outcomes that we aren’t in control of anyway. Not having an awesome Firefox will kill Firefox. End of story.</p> <p>There’s a great deal we don’t know about creativity and innovation. But the research does suggest some patterns are more common than others. You can expect tension. No amount of “messaging” can work your way out of that. The tension will be there, you have to accept it, or you never move. There will be risk. Again, accept it or you never move. You should trust in small, cohesive teams. You should support them, and encourage risk taking. That’s often referred to as “creating space”. My personal experience has been that all these things are true, but I’ve also seen that if you make the innovation process “easy” whatever is being built ends up sucking. Especially at the front end of the process. Maybe it’s because ideas are free to create and valueless on their own. I don’t know.</p> <p>In that spirit, today I’m placing a bet on a small, cohesive team that for the last 6 weeks spent hours I know for a fact they didn’t have, hacking on an idea they simply refused to let die. God knows the last month of stop energy should have killed it. Years of fear of hurting Firefox, by rights, should have never even let the idea germinate. But somehow they held on and fought for it.</p> <p>We’re setting up the project a little differently too. The team is not open for internal transfer. The team no longer has access to their own calendars. They’re being unsubscribed from all email lists. They’re not going to attend even a single Mozilla meeting. We’re kicking off in person, everyone in a room, in Tofino on Vancouver Island where the idea for this UX direction originated last summer, and from there we’re going to co-locate the core team in Vancouver for 3 months. That’s how much time I’m giving the team to prove there’s something here that we could turn into a product. If not, we kill it. We’re not going to expect help from anyone at Mozilla and we’re not going to distract the other 95% of our team that’s doing hero work on Firefox.</p> <p><a href="https://www.youtube.com/watch?v=4Q7FTjhvZ7Y" rel="nofollow">Origin stories</a> have always fascinated me, and when I look back, a great deal of Firefox’s origin story is not well known. Whether a successful product or feature set comes from this project or not, I can’t say, but I’ll at least document the story of how we’re building new browser experiences inside one of the world’s most experienced browser teams. I’m looking forward to being their story teller!</p><!--]-->]]></description>
    </item>
    <item>
      <title>Content blocking is the web at its best</title>
      <link>https://www.vmunix.com/posts/content-blocking-is-the-web-at-its-best</link>
      <guid isPermaLink="true">https://www.vmunix.com/posts/content-blocking-is-the-web-at-its-best</guid>
      <pubDate>Tue, 22 Sep 2015 00:00:00 GMT</pubDate>
      <description><![CDATA[<!--[--><p><em>Disclaimer: I’m SVP of Firefox at Mozilla, I’m clearly biased, but I’m writing here just as me. IT’S ALL ABOUT ME, DAMNIT.</em></p> <p>Boy there’s been a lot of attention on content blocking in the last week! Apple entered the debate with the blunt instrument that is Safari / SafariViewController for iOS9, and all of a sudden everyone noticed. As they should. Tim Bray’s Money and Ads on the Web links to a bunch of writing worth reading, and I’d recommend Zeldman’s piece on <a href="https://medium.com/@zeldman/ad-blocking-and-the-future-of-the-web-78e44e57edb9" rel="nofollow">Ad Blocking and the Future of the Web</a> as a must read. But for all the reasonable, intelligent articles that try to wrestle with the fact that content blocking is a complex issue, it pretty much boils down to this:</p> <blockquote><p>Then: Users: Please DoNotTrack me ; AdTech+Publishers: Screw you
Now: AdTech+Publishers: Please DoNotAdBlock me ; Users: Screw you</p></blockquote> <p>I’ve spent a lot of time in the last year thinking about how to evolve the web’s relationship with ads responsibly, and you see that work shipping in Firefox’s <a href="https://support.mozilla.org/en-US/kb/tracking-protection-pbm" rel="nofollow">Tracking Protection for Private Browsing</a> mode, which is moving to <a href="https://www.mozilla.org/en-US/firefox/channel/#beta" rel="nofollow">Firefox Beta</a> tomorrow, and also in how we designed <a href="https://wiki.mozilla.org/Tiles" rel="nofollow">Tiles</a> for the new tab page to respect user choice and privacy. I’m really proud of the work we did, and I’ll talk more about it soon.</p> <p>Right now, though, in the middle of all this attention around content blocking, <strong>what I’m struck by is that the web’s model allows us to have a debate about content blockers at all</strong>. The web is still fairly unique in that its fundamental architecture is built around a “user agent” that sits between the publisher and consumer and that agent mediates the experience. Also of note, the <strong>web browser is the only agent the user chooses separately from content</strong>(thx for reminding me, <a href="https://medium.com/@osunick" rel="nofollow">@osunick</a>). It’s a pretty powerful model that has benefitted all parties involved over the last 15 years, and without that design tradeoff we wouldn’t be having this debate at all. The web’s kinda old, yeah, but it’s still got some fascinating properties that differentiate it from everything else!</p> <p>The other big thing I’ve been thinking about over the last week is <strong>whether I should have had my team build a Safari content blocker</strong>. We considered it, debated it, decided against it, and in retrospect, I suspect I held too tightly to an opinion that <a href="https://medium.com/@zeldman/ad-blocking-and-the-future-of-the-web-78e44e57edb9" rel="nofollow">Apple was being irresponsible in their pursuit to harm Google</a> by implementing content blocking with little to no user interaction capability. “We’d never ship something this crude!”, I argued. An opinion rooted in the fact we’ve had the technical ability in Firefox to do what Safari is effectively doing for over a year, but we decided that we didn’t have good enough answers to “well, what should the system work like, then? how and why do sites get on/off these lists?” and until we did we wouldn’t ship for general browsing. This is where Apple differs from a pure web browser vendor like Mozilla, I suppose; Apple has an answer for publishers to the often shitty experience of web advertising: don’t use the web, use apps with iAds, use Apple News, use Apple Music, use Apple… Apple’s answer isn’t a better web, really, it’s Apple’s iOS ecosystem.</p> <p>It’s not too late, of course. We <a href="https://github.com/mozilla/firefox-ios" rel="nofollow">know how to build for iOS</a>, have the policies/tech/lists we developed for Tracking Protection in Firefox, have no need to ever charge money (and therefore, arguably, no potential conflict of interest) for a content blocking app or list curation service, can credibly say we could operate in the open with transparency, and of course we have a running theory that users might want Mozilla to have a part in how this plays out on iOS.</p> <p>Whereas Marco <a href="http://www.marco.org/2015/09/18/just-doesnt-feel-good" rel="nofollow">just didn’t feel good</a> about the success of his #1 app, I think for many folks at Mozilla<strong>it just doesn’t feel good for us NOT to be there for our users</strong>, skin in the game, working to make the web better, despite the <a href="https://www.webkit.org/blog/3476/content-blockers-first-look/" rel="nofollow">severe limitations that Apple’s placed on developers</a> implementing content blocking.</p> <p>A final thought on Apple and the web. John Gruber argues that the outcomes, whatever they may be, should not be framed as “<a href="http://daringfireball.net/linked/2015/09/16/because-of-apple" rel="nofollow">Because of Apple</a>”, but as “because of us”, and that’s true but I think he is underestimating Apple’s influence and role in this somewhat. First with Flash, and now with content blocking, Apple is shaping the web as we know it. It’s almost ironic, then, that while the health of the web isn’t top of Apple’s mind (hurting Google and advancing their own ecosystem clearly is), they have an uncanny ability to create these watershed moments that force the web to adapt and get better.</p><!--]-->]]></description>
    </item>
  </channel>
</rss>