<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:source="https://source.scripting.com/">
	<channel>
		<title>Martin Emde</title>
		<description>Blog posts by Martin Emde</description>
		<link>https://martinemde.com</link>
		<atom:link href="https://martinemde.com/rss.xml" rel="self" type="application/rss+xml" />
		<source:self>https://martinemde.com/rss.xml</source:self>
		<pubDate>Sun, 04 Oct 2026 04:19:43 GMT</pubDate>
		<lastBuildDate>Sun, 04 Oct 2026 15:40:53 GMT</lastBuildDate>
    <item>
    <title>OpenAI Is Winning and It Doesn&apos;t Matter</title>
    <description>&lt;p&gt;I like OpenAI&apos;s GPT Astra and GPT Sol better than Anthropic&apos;s Claude, but it&apos;s all based on a vibes and a couple of &lt;a href=&quot;https://x.com/sashikantsingh_/status/2105110972784586933&quot; title=&quot;look closely at the y-axis&quot;&gt;questionable graphs&lt;/a&gt;. I was Team Claude for a while, but after switching I no longer feel attached to any provider. This is good for me but bad for the business models of expensive &quot;luxury&quot; token providers like Anthropic and OpenAI.&lt;/p&gt;
&lt;p&gt;What kept me with Claude at first was comfort with the model: I know how Claude codes. But the questionable graphs kept coming. &quot;Try GPT Sol,&quot; the graphs said. I dabbled with a $20 subscription and many more tokens fit into the plan than I expected. Adaptation was easy. I stuck with the GPT family and only dabble in Claude (which remains better at chat).&lt;/p&gt;
&lt;p&gt;Anthropic held the cutting edge from November 2025, often called &quot;the inflection point&quot;. OpenAI beat them at best coding agent per dollar with GPT 5.6 Sol and expanded their lead up through GPT 6.1 Sol.&lt;/p&gt;
&lt;p&gt;Claude Fable, on the other hand, reinforces the vibes-beat-quality story and the questionable graphs back me up. I was left with a bad taste because of the price, the overzealous security safeguards, and the spat with the government regarding export controls. It&apos;s become an expensive pariah for me. No one ever got fired for not using Fable.&lt;/p&gt;
&lt;p&gt;Both subscriptions are vastly cheaper than the raw API price. Compare price per million tokens, or price per task, and the advantage over open weight models evaporates. Anthropic and OpenAI are effectively paying me to stay on their subscriptions. They have no moat and the frontier labs know it. The choice between models is all vibes and, ironically, the products they sell are built to help you switch. They&apos;re built to do everything.&lt;/p&gt;
&lt;p&gt;The frontier labs lack pricing power over individuals. An exodus of personal subscribers would spell doom for their narrative. Consumer intolerance for restrictive policies has already made them &lt;a href=&quot;https://news.ycombinator.com/item?id=47844269&quot;&gt;back down&lt;/a&gt;, and the encroachment of open weight models into the questionable graphs is continuing to exert pressure. They&apos;ll keep subsidizing until they can&apos;t or don&apos;t need to.&lt;/p&gt;
&lt;p&gt;&quot;But what if there was a way for the AI companies to get government permission to violate antitrust law and cease to compete with one another, &lt;em&gt;and&lt;/em&gt; secure a ban on the use of Chinese open weight models?&quot; &lt;a href=&quot;https://pluralistic.net/2026/09/16/beggar-thy-neighbor/#red-queens-race&quot; title=&quot;one of my favorite bloggers&quot;&gt;asked Cory Doctorow&lt;/a&gt; recently. Monopolies do make it difficult to find a better competitor. So far this seems unlikely.&lt;/p&gt;
&lt;p&gt;For now, I will use the luxury tokens from the current best provider while they last, but my loyalty is gone. If I&apos;m a representative power user, and if that&apos;s the source of most consumer spend, then it could pose a tricky problem for AI companies trying to support their valuations. The commoditization of intelligence will continually force subsidization of loyalty in order to maintain the narrative that keeps their training runs funded.&lt;/p&gt;
&lt;p&gt;Anthropic &lt;a href=&quot;https://techcrunch.com/2026/09/28/anthropics-prospectus-details-losses-growth-and-yes-a-warning-that-its-ai-could-end-humanity/&quot;&gt;anticipated&lt;/a&gt; more than half a trillion dollars in spending in their potential upcoming IPO filing. It also notes that revenue is growing quarter over quarter, and that they &lt;a href=&quot;https://futurism.com/future-society/anthropic-claude-profit-ai-safety-development-finances&quot;&gt;would be profitable&lt;/a&gt; if you ignore how much it costs. Rising revenue may not even be enough. As &lt;a href=&quot;https://www.groundbrkr.com/p/the-second-derivative-why-no-one&quot;&gt;the second derivative&lt;/a&gt; argues, &quot;I do not need demand to fail. I need the rate of capex growth to flatten - and a structure this levered and dependent on perpetual acceleration breaks on the flattening alone.&quot; Even with revenue growing, can these subsidies hold in the face of a product that has near zero lock-in and questionable consumer loyalty? They both have to and, I think, can&apos;t afford to.&lt;/p&gt;
&lt;p&gt;The math for enterprises is different. Most of them pay full API prices and the terms of their lock-in are different, often self-inflicted. I&apos;d like to examine that soon.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2026/10/03/openai-is-winning-and-it-doesnt-matter</link>
    <guid isPermaLink="true">https://martinemde.com/2026/10/03/openai-is-winning-and-it-doesnt-matter</guid>
    <pubDate>Sun, 04 Oct 2026 04:19:43 GMT</pubDate>
    <category>openai</category><category>anthropic</category><category>tokens</category>
    <content:encoded><![CDATA[<p>I like OpenAI's GPT Astra and GPT Sol better than Anthropic's Claude, but it's all based on a vibes and a couple of <a href="https://x.com/sashikantsingh_/status/2105110972784586933" title="look closely at the y-axis">questionable graphs</a>. I was Team Claude for a while, but after switching I no longer feel attached to any provider. This is good for me but bad for the business models of expensive "luxury" token providers like Anthropic and OpenAI.</p>
<p>What kept me with Claude at first was comfort with the model: I know how Claude codes. But the questionable graphs kept coming. "Try GPT Sol," the graphs said. I dabbled with a $20 subscription and many more tokens fit into the plan than I expected. Adaptation was easy. I stuck with the GPT family and only dabble in Claude (which remains better at chat).</p>
<p>Anthropic held the cutting edge from November 2025, often called "the inflection point". OpenAI beat them at best coding agent per dollar with GPT 5.6 Sol and expanded their lead up through GPT 6.1 Sol.</p>
<p>Claude Fable, on the other hand, reinforces the vibes-beat-quality story and the questionable graphs back me up. I was left with a bad taste because of the price, the overzealous security safeguards, and the spat with the government regarding export controls. It's become an expensive pariah for me. No one ever got fired for not using Fable.</p>
<p>Both subscriptions are vastly cheaper than the raw API price. Compare price per million tokens, or price per task, and the advantage over open weight models evaporates. Anthropic and OpenAI are effectively paying me to stay on their subscriptions. They have no moat and the frontier labs know it. The choice between models is all vibes and, ironically, the products they sell are built to help you switch. They're built to do everything.</p>
<p>The frontier labs lack pricing power over individuals. An exodus of personal subscribers would spell doom for their narrative. Consumer intolerance for restrictive policies has already made them <a href="https://news.ycombinator.com/item?id=47844269">back down</a>, and the encroachment of open weight models into the questionable graphs is continuing to exert pressure. They'll keep subsidizing until they can't or don't need to.</p>
<p>"But what if there was a way for the AI companies to get government permission to violate antitrust law and cease to compete with one another, <em>and</em> secure a ban on the use of Chinese open weight models?" <a href="https://pluralistic.net/2026/09/16/beggar-thy-neighbor/#red-queens-race" title="one of my favorite bloggers">asked Cory Doctorow</a> recently. Monopolies do make it difficult to find a better competitor. So far this seems unlikely.</p>
<p>For now, I will use the luxury tokens from the current best provider while they last, but my loyalty is gone. If I'm a representative power user, and if that's the source of most consumer spend, then it could pose a tricky problem for AI companies trying to support their valuations. The commoditization of intelligence will continually force subsidization of loyalty in order to maintain the narrative that keeps their training runs funded.</p>
<p>Anthropic <a href="https://techcrunch.com/2026/09/28/anthropics-prospectus-details-losses-growth-and-yes-a-warning-that-its-ai-could-end-humanity/">anticipated</a> more than half a trillion dollars in spending in their potential upcoming IPO filing. It also notes that revenue is growing quarter over quarter, and that they <a href="https://futurism.com/future-society/anthropic-claude-profit-ai-safety-development-finances">would be profitable</a> if you ignore how much it costs. Rising revenue may not even be enough. As <a href="https://www.groundbrkr.com/p/the-second-derivative-why-no-one">the second derivative</a> argues, "I do not need demand to fail. I need the rate of capex growth to flatten - and a structure this levered and dependent on perpetual acceleration breaks on the flattening alone." Even with revenue growing, can these subsidies hold in the face of a product that has near zero lock-in and questionable consumer loyalty? They both have to and, I think, can't afford to.</p>
<p>The math for enterprises is different. Most of them pay full API prices and the terms of their lock-in are different, often self-inflicted. I'd like to examine that soon.</p>
]]></content:encoded>
    <source:markdown><![CDATA[I like OpenAI's GPT Astra and GPT Sol better than Anthropic's Claude, but it's all based on a vibes and a couple of [questionable graphs](https://x.com/sashikantsingh_/status/2105110972784586933 "look closely at the y-axis"). I was Team Claude for a while, but after switching I no longer feel attached to any provider. This is good for me but bad for the business models of expensive "luxury" token providers like Anthropic and OpenAI.

What kept me with Claude at first was comfort with the model: I know how Claude codes. But the questionable graphs kept coming. "Try GPT Sol," the graphs said. I dabbled with a $20 subscription and many more tokens fit into the plan than I expected. Adaptation was easy. I stuck with the GPT family and only dabble in Claude (which remains better at chat).

Anthropic held the cutting edge from November 2025, often called "the inflection point". OpenAI beat them at best coding agent per dollar with GPT 5.6 Sol and expanded their lead up through GPT 6.1 Sol.

Claude Fable, on the other hand, reinforces the vibes-beat-quality story and the questionable graphs back me up. I was left with a bad taste because of the price, the overzealous security safeguards, and the spat with the government regarding export controls. It's become an expensive pariah for me. No one ever got fired for not using Fable.

Both subscriptions are vastly cheaper than the raw API price. Compare price per million tokens, or price per task, and the advantage over open weight models evaporates. Anthropic and OpenAI are effectively paying me to stay on their subscriptions. They have no moat and the frontier labs know it. The choice between models is all vibes and, ironically, the products they sell are built to help you switch. They're built to do everything.

The frontier labs lack pricing power over individuals. An exodus of personal subscribers would spell doom for their narrative. Consumer intolerance for restrictive policies has already made them [back down](https://news.ycombinator.com/item?id=47844269), and the encroachment of open weight models into the questionable graphs is continuing to exert pressure. They'll keep subsidizing until they can't or don't need to.

"But what if there was a way for the AI companies to get government permission to violate antitrust law and cease to compete with one another, _and_ secure a ban on the use of Chinese open weight models?" [asked Cory Doctorow](https://pluralistic.net/2026/09/16/beggar-thy-neighbor/#red-queens-race "one of my favorite bloggers") recently. Monopolies do make it difficult to find a better competitor. So far this seems unlikely.

For now, I will use the luxury tokens from the current best provider while they last, but my loyalty is gone. If I'm a representative power user, and if that's the source of most consumer spend, then it could pose a tricky problem for AI companies trying to support their valuations. The commoditization of intelligence will continually force subsidization of loyalty in order to maintain the narrative that keeps their training runs funded.

Anthropic [anticipated](https://techcrunch.com/2026/09/28/anthropics-prospectus-details-losses-growth-and-yes-a-warning-that-its-ai-could-end-humanity/) more than half a trillion dollars in spending in their potential upcoming IPO filing. It also notes that revenue is growing quarter over quarter, and that they [would be profitable](https://futurism.com/future-society/anthropic-claude-profit-ai-safety-development-finances) if you ignore how much it costs. Rising revenue may not even be enough. As [the second derivative](https://www.groundbrkr.com/p/the-second-derivative-why-no-one) argues, "I do not need demand to fail. I need the rate of capex growth to flatten - and a structure this levered and dependent on perpetual acceleration breaks on the flattening alone." Even with revenue growing, can these subsidies hold in the face of a product that has near zero lock-in and questionable consumer loyalty? They both have to and, I think, can't afford to.
 
The math for enterprises is different. Most of them pay full API prices and the terms of their lock-in are different, often self-inflicted. I'd like to examine that soon.]]></source:markdown>
  </item><item>
    <title>Halloween Flamingo Wicker Bag</title>
    <description>&lt;p&gt;A bag made of wicker in the shape of a black flamingo. I think this is my favorite.&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;u-bookmark-of&quot; href=&quot;https://wickerdarling.com/collections/shop-all/products/spook-halloween-flamingo-handbag&quot;&gt;https://wickerdarling.com/collections/shop-all/products/spook-halloween-flamingo-handbag&lt;/a&gt;&lt;/p&gt;
</description>
    <link>https://martinemde.com/2026/09/23/halloween-flamingo-wicker-bag</link>
    <guid isPermaLink="true">https://martinemde.com/2026/09/23/halloween-flamingo-wicker-bag</guid>
    <pubDate>Wed, 23 Sep 2026 21:33:12 GMT</pubDate>
    
    <content:encoded><![CDATA[<p>A bag made of wicker in the shape of a black flamingo. I think this is my favorite.</p>
<p><a class="u-bookmark-of" href="https://wickerdarling.com/collections/shop-all/products/spook-halloween-flamingo-handbag">https://wickerdarling.com/collections/shop-all/products/spook-halloween-flamingo-handbag</a></p>
]]></content:encoded>
    <source:markdown><![CDATA[A bag made of wicker in the shape of a black flamingo. I think this is my favorite.

<a class="u-bookmark-of" href="https://wickerdarling.com/collections/shop-all/products/spook-halloween-flamingo-handbag">https://wickerdarling.com/collections/shop-all/products/spook-halloween-flamingo-handbag</a>]]></source:markdown>
  </item><item>
    
    <description>&lt;img src=&quot;https://martinemde.com/images/blog/35bc52d8-2a33-455d-b72c-25731551c834.png&quot; alt=&quot;NetNewsWire RSS reader with a coding agent session up, the title of the visible post is &amp;quot;The new LED firmware is flashed and running, and it compiled cleanly.&amp;quot; and there are user and agent replies in the sidebar.&quot; /&gt;
&lt;p&gt;What would it look like if we viewed agent sessions through RSS? I think a lot of the primitives transfer, and if you use &lt;a href=&quot;https://rss.chat&quot;&gt;https://rss.chat&lt;/a&gt; for the feed primitives, with threaded replies and &lt;code&gt;source:markdown&lt;/code&gt;, you get a conversation with the agent that is readable in any feed reader and easy to extend. So far, it feels a lot better than all the transcript webpage creators I&apos;ve used.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2026/09/22/174833</link>
    <guid isPermaLink="true">https://martinemde.com/blog/174833</guid>
    <pubDate>Wed, 23 Sep 2026 00:48:33 GMT</pubDate>
    <category>rss</category><category>agents</category>
    <content:encoded><![CDATA[<img src="https://martinemde.com/images/blog/35bc52d8-2a33-455d-b72c-25731551c834.png" alt="NetNewsWire RSS reader with a coding agent session up, the title of the visible post is &quot;The new LED firmware is flashed and running, and it compiled cleanly.&quot; and there are user and agent replies in the sidebar." />
<p>What would it look like if we viewed agent sessions through RSS? I think a lot of the primitives transfer, and if you use <a href="https://rss.chat">https://rss.chat</a> for the feed primitives, with threaded replies and <code>source:markdown</code>, you get a conversation with the agent that is readable in any feed reader and easy to extend. So far, it feels a lot better than all the transcript webpage creators I've used.</p>
]]></content:encoded>
    <source:markdown><![CDATA[<img src="https://martinemde.com/images/blog/35bc52d8-2a33-455d-b72c-25731551c834.png" alt="NetNewsWire RSS reader with a coding agent session up, the title of the visible post is &quot;The new LED firmware is flashed and running, and it compiled cleanly.&quot; and there are user and agent replies in the sidebar." />

What would it look like if we viewed agent sessions through RSS? I think a lot of the primitives transfer, and if you use <https://rss.chat> for the feed primitives, with threaded replies and `source:markdown`, you get a conversation with the agent that is readable in any feed reader and easy to extend. So far, it feels a lot better than all the transcript webpage creators I've used.]]></source:markdown>
  </item><item>
    <title>Apple Upgrade Broken Down - Compare iPhone Upgrade Paths</title>
    <description>&lt;p&gt;The Apple Upgrade program is more complicated than it looks. I&apos;ve seen many attempts to explain it away and I don&apos;t think they captured the nuances.&lt;/p&gt;
&lt;p&gt;The best way for me to understand what&apos;s going on is to build a tool to help myself. This gives me a clear understanding of the dynamics at play in a system. As we approach the iPhone Duo purchasing frenzy, I hope it helps you decide what works for you.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://martinemde.com/apple-upgrade&quot;&gt;Apple Upgrade Broken Down&lt;/a&gt; - My interactive exploration of the new Apple lease program.&lt;/p&gt;
&lt;p&gt;After many iterations, I&apos;m happy enough to release it into the wild. I&apos;ll warn you though, the Apple Upgrade program is not easy to understand, and so while I did my best to make sense of it, it&apos;s still a lot of data that will take a moment to absorb. If you want the short answer, the difference between plans is small and the levers for saving money are the obvious ones.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Simplest answer: pick the longest plan you&apos;re willing to live with: the 24 month lease is the best if you want optionality, the 36 month carrier deal is the best if you want the absolute cheapest no matter the downsides.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We all know all the obvious stuff already, and we usually pick the worse deal anyway:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Cheaper phones usually beat any other optimization.&lt;/li&gt;
&lt;li&gt;The longer you keep your phone, the more money you save.&lt;/li&gt;
&lt;li&gt;Better trade-in values are the next best saver.&lt;/li&gt;
&lt;li&gt;After the above, the best plan is the one that lets you hold on to more of your money longer.&lt;/li&gt;
&lt;li&gt;Private party sale is the highest trade in per phone, but you might get burned like I have.&lt;/li&gt;
&lt;li&gt;Carrier financing in the slowest cycle, but the best deal for trading in old phones that aren&apos;t worth much.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In short, using crappier products for longer obviously saves money but at the cost of some tax on your day every day. For this reason, this tool is not about picking the cheapest phone or the cheapest upgrade cycle. This tool is here to help you understand which button to press on the iPhone Duo order page so you can stop thinking wondering if you took a bad deal.&lt;/p&gt;
&lt;p&gt;If you&apos;re interested in this stuff, like me, then here&apos;s the tidbits this tool helped me figure out:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Both Apple Upgrade leases are maybe the best deal for people with liquid wealth that want to upgrade frequently.&lt;/li&gt;
&lt;li&gt;If you can&apos;t afford a sudden $400 charge, then you may get caught in a lease cycle trap that will be difficult to exit. That last charge to own the phone seems intentionally designed to make you want to avoid it by handing over the phone for a new lease.&lt;/li&gt;
&lt;li&gt;The lease will never cost more than the cash price of the phone (but that&apos;s not what makes it risky). The risk is overextending yourself because the payment is artificially low at first, especially when you have equity in you phone that discounts the initial payments to nearly zero. You&apos;re using up your equity to lower the price temporarily.&lt;/li&gt;
&lt;li&gt;AppleCare is really expensive. If you break your screen less than once per year, especially if you and your partner or family can distribute the cost of an occasional broken screen, then you&apos;re much better off without AppleCare. Even a full price fix every other year is a better deal, but 2 fixes, not a good deal. This may change with the iPhone Duo, which seems hard to protect and unpleasant to put a case on.&lt;/li&gt;
&lt;li&gt;If you upgrade every year, as many people do, surprisingly the best deal seems to be the 2 year lease, paid in full every year. If you pay the 2 year lease in full after 1 year, and then trade in right away you might get a reliably better deal than the lease hand-off. This is marginally better in the long run.&lt;/li&gt;
&lt;li&gt;Apple&apos;s lease trade-in, where you give your device back for free, is a luxury you pay a lot of money for. Right now a 1 year old iPhone 17 Pro Max will fetch 74% of its purchase price on trade-in. If this were an Apple Upgrade lease, I&apos;d hand it back for 50% of the original purchase price, &lt;strong&gt;a $335 loss&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There&apos;s no getting away from this: &lt;em&gt;This tool is complicated.&lt;/em&gt; Despite my efforts, there&apos;s some irreducible complexity here. I chose to model each month to give you a sense of your payment per month on each plan, and the months where a big charge hits, but it clutters the overall picture. &lt;a href=&quot;https://github.com/martinemde/martinemde.com&quot;&gt;Send me an issue on GitHub&lt;/a&gt; if you want to nerd-snipe me into fixing it some other way.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://martinemde.com/apple-upgrade&quot;&gt;Apple Upgrade Broken Down&lt;/a&gt; is built to compare financing options across the same approach, but doesn&apos;t make it super easy to compare different devices or different refresh periods. I figure it&apos;s pretty obvious when something costs more, and buying a more expensive phone more often costs more. Anyone with the liquid wealth to pay for a full price phone repair should consider dropping AppleCare unless they know they use it. Unless you raw dog your phone every day (in which case your willingness to break your phone is a signifier of your wealth) then a good case is a better investment than AppleCare.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2026/09/20/apple-upgrade-broken-down-compare-iphone-upgrade-paths</link>
    <guid isPermaLink="true">https://martinemde.com/blog/apple-upgrade-broken-down-compare-iphone-upgrade-paths</guid>
    <pubDate>Sun, 20 Sep 2026 12:00:00 GMT</pubDate>
    <category>iphone</category><category>apple</category><category>finance</category>
    <content:encoded><![CDATA[<p>The Apple Upgrade program is more complicated than it looks. I've seen many attempts to explain it away and I don't think they captured the nuances.</p>
<p>The best way for me to understand what's going on is to build a tool to help myself. This gives me a clear understanding of the dynamics at play in a system. As we approach the iPhone Duo purchasing frenzy, I hope it helps you decide what works for you.</p>
<p><a href="https://martinemde.com/apple-upgrade">Apple Upgrade Broken Down</a> - My interactive exploration of the new Apple lease program.</p>
<p>After many iterations, I'm happy enough to release it into the wild. I'll warn you though, the Apple Upgrade program is not easy to understand, and so while I did my best to make sense of it, it's still a lot of data that will take a moment to absorb. If you want the short answer, the difference between plans is small and the levers for saving money are the obvious ones.</p>
<p><strong>Simplest answer: pick the longest plan you're willing to live with: the 24 month lease is the best if you want optionality, the 36 month carrier deal is the best if you want the absolute cheapest no matter the downsides.</strong></p>
<p>We all know all the obvious stuff already, and we usually pick the worse deal anyway:</p>
<ol>
<li>Cheaper phones usually beat any other optimization.</li>
<li>The longer you keep your phone, the more money you save.</li>
<li>Better trade-in values are the next best saver.</li>
<li>After the above, the best plan is the one that lets you hold on to more of your money longer.</li>
<li>Private party sale is the highest trade in per phone, but you might get burned like I have.</li>
<li>Carrier financing in the slowest cycle, but the best deal for trading in old phones that aren't worth much.</li>
</ol>
<p>In short, using crappier products for longer obviously saves money but at the cost of some tax on your day every day. For this reason, this tool is not about picking the cheapest phone or the cheapest upgrade cycle. This tool is here to help you understand which button to press on the iPhone Duo order page so you can stop thinking wondering if you took a bad deal.</p>
<p>If you're interested in this stuff, like me, then here's the tidbits this tool helped me figure out:</p>
<ol>
<li>Both Apple Upgrade leases are maybe the best deal for people with liquid wealth that want to upgrade frequently.</li>
<li>If you can't afford a sudden $400 charge, then you may get caught in a lease cycle trap that will be difficult to exit. That last charge to own the phone seems intentionally designed to make you want to avoid it by handing over the phone for a new lease.</li>
<li>The lease will never cost more than the cash price of the phone (but that's not what makes it risky). The risk is overextending yourself because the payment is artificially low at first, especially when you have equity in you phone that discounts the initial payments to nearly zero. You're using up your equity to lower the price temporarily.</li>
<li>AppleCare is really expensive. If you break your screen less than once per year, especially if you and your partner or family can distribute the cost of an occasional broken screen, then you're much better off without AppleCare. Even a full price fix every other year is a better deal, but 2 fixes, not a good deal. This may change with the iPhone Duo, which seems hard to protect and unpleasant to put a case on.</li>
<li>If you upgrade every year, as many people do, surprisingly the best deal seems to be the 2 year lease, paid in full every year. If you pay the 2 year lease in full after 1 year, and then trade in right away you might get a reliably better deal than the lease hand-off. This is marginally better in the long run.</li>
<li>Apple's lease trade-in, where you give your device back for free, is a luxury you pay a lot of money for. Right now a 1 year old iPhone 17 Pro Max will fetch 74% of its purchase price on trade-in. If this were an Apple Upgrade lease, I'd hand it back for 50% of the original purchase price, <strong>a $335 loss</strong>.</li>
</ol>
<p>There's no getting away from this: <em>This tool is complicated.</em> Despite my efforts, there's some irreducible complexity here. I chose to model each month to give you a sense of your payment per month on each plan, and the months where a big charge hits, but it clutters the overall picture. <a href="https://github.com/martinemde/martinemde.com">Send me an issue on GitHub</a> if you want to nerd-snipe me into fixing it some other way.</p>
<p><a href="https://martinemde.com/apple-upgrade">Apple Upgrade Broken Down</a> is built to compare financing options across the same approach, but doesn't make it super easy to compare different devices or different refresh periods. I figure it's pretty obvious when something costs more, and buying a more expensive phone more often costs more. Anyone with the liquid wealth to pay for a full price phone repair should consider dropping AppleCare unless they know they use it. Unless you raw dog your phone every day (in which case your willingness to break your phone is a signifier of your wealth) then a good case is a better investment than AppleCare.</p>
]]></content:encoded>
    <source:markdown><![CDATA[The Apple Upgrade program is more complicated than it looks. I've seen many attempts to explain it away and I don't think they captured the nuances. 

The best way for me to understand what's going on is to build a tool to help myself. This gives me a clear understanding of the dynamics at play in a system. As we approach the iPhone Duo purchasing frenzy, I hope it helps you decide what works for you.

[Apple Upgrade Broken Down](https://martinemde.com/apple-upgrade) - My interactive exploration of the new Apple lease program.

After many iterations, I'm happy enough to release it into the wild. I'll warn you though, the Apple Upgrade program is not easy to understand, and so while I did my best to make sense of it, it's still a lot of data that will take a moment to absorb. If you want the short answer, the difference between plans is small and the levers for saving money are the obvious ones.

**Simplest answer: pick the longest plan you're willing to live with: the 24 month lease is the best if you want optionality, the 36 month carrier deal is the best if you want the absolute cheapest no matter the downsides.**

We all know all the obvious stuff already, and we usually pick the worse deal anyway:

1. Cheaper phones usually beat any other optimization.
2. The longer you keep your phone, the more money you save.
3. Better trade-in values are the next best saver.
4. After the above, the best plan is the one that lets you hold on to more of your money longer.
5. Private party sale is the highest trade in per phone, but you might get burned like I have.
6. Carrier financing in the slowest cycle, but the best deal for trading in old phones that aren't worth much.

In short, using crappier products for longer obviously saves money but at the cost of some tax on your day every day. For this reason, this tool is not about picking the cheapest phone or the cheapest upgrade cycle. This tool is here to help you understand which button to press on the iPhone Duo order page so you can stop thinking wondering if you took a bad deal.

If you're interested in this stuff, like me, then here's the tidbits this tool helped me figure out:

1. Both Apple Upgrade leases are maybe the best deal for people with liquid wealth that want to upgrade frequently.
2. If you can't afford a sudden $400 charge, then you may get caught in a lease cycle trap that will be difficult to exit. That last charge to own the phone seems intentionally designed to make you want to avoid it by handing over the phone for a new lease.
3. The lease will never cost more than the cash price of the phone (but that's not what makes it risky). The risk is overextending yourself because the payment is artificially low at first, especially when you have equity in you phone that discounts the initial payments to nearly zero. You're using up your equity to lower the price temporarily.
4. AppleCare is really expensive. If you break your screen less than once per year, especially if you and your partner or family can distribute the cost of an occasional broken screen, then you're much better off without AppleCare. Even a full price fix every other year is a better deal, but 2 fixes, not a good deal. This may change with the iPhone Duo, which seems hard to protect and unpleasant to put a case on.
5. If you upgrade every year, as many people do, surprisingly the best deal seems to be the 2 year lease, paid in full every year. If you pay the 2 year lease in full after 1 year, and then trade in right away you might get a reliably better deal than the lease hand-off. This is marginally better in the long run.
6. Apple's lease trade-in, where you give your device back for free, is a luxury you pay a lot of money for. Right now a 1 year old iPhone 17 Pro Max will fetch 74% of its purchase price on trade-in. If this were an Apple Upgrade lease, I'd hand it back for 50% of the original purchase price, **a $335 loss**.
   
There's no getting away from this: *This tool is complicated.* Despite my efforts, there's some irreducible complexity here. I chose to model each month to give you a sense of your payment per month on each plan, and the months where a big charge hits, but it clutters the overall picture. [Send me an issue on GitHub](https://github.com/martinemde/martinemde.com) if you want to nerd-snipe me into fixing it some other way.

[Apple Upgrade Broken Down](https://martinemde.com/apple-upgrade) is built to compare financing options across the same approach, but doesn't make it super easy to compare different devices or different refresh periods. I figure it's pretty obvious when something costs more, and buying a more expensive phone more often costs more. Anyone with the liquid wealth to pay for a full price phone repair should consider dropping AppleCare unless they know they use it. Unless you raw dog your phone every day (in which case your willingness to break your phone is a signifier of your wealth) then a good case is a better investment than AppleCare.]]></source:markdown>
  </item><item>
    
    <description>&lt;p&gt;&lt;a class=&quot;u-bookmark-of&quot; href=&quot;https://martinemde.com/trillion&quot;&gt;Give Away Elon&apos;s Money&lt;/a&gt; — A trillion dollars, a thousand squares, and a list of real sourced prices for fixing things. Fund what you like and watch how little the pile moves.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2026/08/12/sharing-trillion</link>
    <guid isPermaLink="true">https://martinemde.com/blog/sharing-trillion</guid>
    <pubDate>Wed, 12 Aug 2026 21:03:07 GMT</pubDate>
    
    <content:encoded><![CDATA[<p><a class="u-bookmark-of" href="https://martinemde.com/trillion">Give Away Elon's Money</a> — A trillion dollars, a thousand squares, and a list of real sourced prices for fixing things. Fund what you like and watch how little the pile moves.</p>
]]></content:encoded>
    <source:markdown><![CDATA[<a class="u-bookmark-of" href="https://martinemde.com/trillion">Give Away Elon's Money</a> — A trillion dollars, a thousand squares, and a list of real sourced prices for fixing things. Fund what you like and watch how little the pile moves.]]></source:markdown>
  </item><item>
    
    <description>&lt;p&gt;&lt;a class=&quot;u-bookmark-of&quot; href=&quot;https://martinemde.com/apple-upgrade&quot;&gt;Apple Upgrade Broken Down&lt;/a&gt; — Pick a phone, choose your trade-in, and watch the costs of leasing, buying, and financing add up as you make upgrade and repair decisions.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2026/07/30/sharing-apple-upgrade</link>
    <guid isPermaLink="true">https://martinemde.com/blog/sharing-apple-upgrade</guid>
    <pubDate>Fri, 31 Jul 2026 03:48:31 GMT</pubDate>
    
    <content:encoded><![CDATA[<p><a class="u-bookmark-of" href="https://martinemde.com/apple-upgrade">Apple Upgrade Broken Down</a> — Pick a phone, choose your trade-in, and watch the costs of leasing, buying, and financing add up as you make upgrade and repair decisions.</p>
]]></content:encoded>
    <source:markdown><![CDATA[<a class="u-bookmark-of" href="https://martinemde.com/apple-upgrade">Apple Upgrade Broken Down</a> — Pick a phone, choose your trade-in, and watch the costs of leasing, buying, and financing add up as you make upgrade and repair decisions.]]></source:markdown>
  </item><item>
    <title>Vestigial Mastery</title>
    <description>&lt;p&gt;I used to be really good at editing code.&lt;/p&gt;
&lt;p&gt;I was 12 when I learned VIm. I asked my dad &quot;how do I jump to a line number in a file?&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you open it in vi [instead of pico] I can show you. It&apos;s just &lt;code&gt;:123&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Amazing! So I learned VIm and I used it for 30 years. It&apos;s the only editor I ever got good at.&lt;/p&gt;
&lt;p&gt;It&apos;s almost useless now.&lt;/p&gt;
&lt;p&gt;Thirty years of editing code. I typed every character by hand until Co-Pilot. Instead of a single word, the ghost sometimes typed the rest of the line. I switched (with the VIm plugin, of course). Amazing!&lt;/p&gt;
&lt;p&gt;Progressively less and less of my code was typed character by character. What used to be a macro or a clever motion became &lt;code&gt;Tab Tab Tab&lt;/code&gt;. Coding became comments for the ghost to fill in the rest. Inevitably it was a little wrong, but I was good at editing code. &lt;code&gt;AlmostRight&lt;/code&gt; turned to &lt;code&gt;Quality&lt;/code&gt; with a few VIm motions. I was a lot faster this way.&lt;/p&gt;
&lt;p&gt;Then the chat on the side got better. The end was near. It was more efficient to ask for the fix than find and edit the code. My wrists, always a little sore, savored the fewer key presses.&lt;/p&gt;
&lt;p&gt;I typed prose into the chat.&lt;/p&gt;
&lt;p&gt;I stopped writing code.&lt;/p&gt;
&lt;p&gt;I stopped editing code.&lt;/p&gt;
&lt;p&gt;I stopped using VIm.&lt;/p&gt;
&lt;p&gt;Mastery intact and barely a reason to use it.&lt;/p&gt;
&lt;p&gt;I still use it sometimes. I go through the VIm motions of editing code. Obsidian, bless its code, and the lovingly thorough emulation in Claude Code, are now my outlet for VIm. The skills and mastery of 30 years of &lt;code&gt;hjkl&lt;/code&gt; and &lt;code&gt;:%s/vim/claude/&lt;/code&gt; have become a simple trick now that comforts my muscle memory. The motions are a permanent part of my brain, but they don&apos;t make me better at my job. Hours of modal efficiency are no more than a few minor minutes of savings now.&lt;/p&gt;
&lt;p&gt;That era of hand editing has ended. I wouldn&apos;t trade my new powers for the sore wrists of the past. I was good at text editing. I still am. It feels good to have mastery. It just doesn&apos;t matter anymore.&lt;/p&gt;
&lt;p&gt;My mastery of VIm has become vestigial.&lt;/p&gt;
&lt;p&gt;:wq&lt;/p&gt;
</description>
    <link>https://martinemde.com/2026/03/21/vestigial-mastery</link>
    <guid isPermaLink="true">https://martinemde.com/blog/vestigial-mastery</guid>
    <pubDate>Sat, 21 Mar 2026 12:00:00 GMT</pubDate>
    <category>vim</category><category>agents</category>
    <content:encoded><![CDATA[<p>I used to be really good at editing code.</p>
<p>I was 12 when I learned VIm. I asked my dad "how do I jump to a line number in a file?"</p>
<blockquote>
<p>If you open it in vi [instead of pico] I can show you. It's just <code>:123</code>.</p>
</blockquote>
<p>Amazing! So I learned VIm and I used it for 30 years. It's the only editor I ever got good at.</p>
<p>It's almost useless now.</p>
<p>Thirty years of editing code. I typed every character by hand until Co-Pilot. Instead of a single word, the ghost sometimes typed the rest of the line. I switched (with the VIm plugin, of course). Amazing!</p>
<p>Progressively less and less of my code was typed character by character. What used to be a macro or a clever motion became <code>Tab Tab Tab</code>. Coding became comments for the ghost to fill in the rest. Inevitably it was a little wrong, but I was good at editing code. <code>AlmostRight</code> turned to <code>Quality</code> with a few VIm motions. I was a lot faster this way.</p>
<p>Then the chat on the side got better. The end was near. It was more efficient to ask for the fix than find and edit the code. My wrists, always a little sore, savored the fewer key presses.</p>
<p>I typed prose into the chat.</p>
<p>I stopped writing code.</p>
<p>I stopped editing code.</p>
<p>I stopped using VIm.</p>
<p>Mastery intact and barely a reason to use it.</p>
<p>I still use it sometimes. I go through the VIm motions of editing code. Obsidian, bless its code, and the lovingly thorough emulation in Claude Code, are now my outlet for VIm. The skills and mastery of 30 years of <code>hjkl</code> and <code>:%s/vim/claude/</code> have become a simple trick now that comforts my muscle memory. The motions are a permanent part of my brain, but they don't make me better at my job. Hours of modal efficiency are no more than a few minor minutes of savings now.</p>
<p>That era of hand editing has ended. I wouldn't trade my new powers for the sore wrists of the past. I was good at text editing. I still am. It feels good to have mastery. It just doesn't matter anymore.</p>
<p>My mastery of VIm has become vestigial.</p>
<p>:wq</p>
]]></content:encoded>
    <source:markdown><![CDATA[I used to be really good at editing code.

I was 12 when I learned VIm. I asked my dad "how do I jump to a line number in a file?"

> If you open it in vi [instead of pico] I can show you. It's just `:123`.

Amazing! So I learned VIm and I used it for 30 years. It's the only editor I ever got good at.

It's almost useless now.

Thirty years of editing code. I typed every character by hand until Co-Pilot. Instead of a single word, the ghost sometimes typed the rest of the line. I switched (with the VIm plugin, of course). Amazing!

Progressively less and less of my code was typed character by character. What used to be a macro or a clever motion became `Tab Tab Tab`. Coding became comments for the ghost to fill in the rest. Inevitably it was a little wrong, but I was good at editing code. `AlmostRight` turned to `Quality` with a few VIm motions. I was a lot faster this way.

Then the chat on the side got better. The end was near. It was more efficient to ask for the fix than find and edit the code. My wrists, always a little sore, savored the fewer key presses.

I typed prose into the chat.

I stopped writing code.

I stopped editing code.

I stopped using VIm.

Mastery intact and barely a reason to use it.

I still use it sometimes. I go through the VIm motions of editing code. Obsidian, bless its code, and the lovingly thorough emulation in Claude Code, are now my outlet for VIm. The skills and mastery of 30 years of `hjkl` and `:%s/vim/claude/` have become a simple trick now that comforts my muscle memory. The motions are a permanent part of my brain, but they don't make me better at my job. Hours of modal efficiency are no more than a few minor minutes of savings now.

That era of hand editing has ended. I wouldn't trade my new powers for the sore wrists of the past. I was good at text editing. I still am. It feels good to have mastery. It just doesn't matter anymore.

My mastery of VIm has become vestigial.

:wq]]></source:markdown>
  </item><item>
    
    <description>&lt;p&gt;&lt;a class=&quot;u-bookmark-of&quot; href=&quot;https://martinemde.com/dimsum&quot;&gt;Dim Sum Scorer&lt;/a&gt; — Score tracker for Sushi Go! Spin Some for Dim Sum. Supports 2-6 players with automatic scoring for all dim sum cards.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2026/03/19/sharing-dimsum</link>
    <guid isPermaLink="true">https://martinemde.com/blog/sharing-dimsum</guid>
    <pubDate>Fri, 20 Mar 2026 06:43:47 GMT</pubDate>
    
    <content:encoded><![CDATA[<p><a class="u-bookmark-of" href="https://martinemde.com/dimsum">Dim Sum Scorer</a> — Score tracker for Sushi Go! Spin Some for Dim Sum. Supports 2-6 players with automatic scoring for all dim sum cards.</p>
]]></content:encoded>
    <source:markdown><![CDATA[<a class="u-bookmark-of" href="https://martinemde.com/dimsum">Dim Sum Scorer</a> — Score tracker for Sushi Go! Spin Some for Dim Sum. Supports 2-6 players with automatic scoring for all dim sum cards.]]></source:markdown>
  </item><item>
    
    <description>&lt;p&gt;&lt;a class=&quot;u-bookmark-of&quot; href=&quot;https://martinemde.com/loans&quot;&gt;Loan Calculator&lt;/a&gt; — Compare loans side by side. Calculate monthly payments, total interest, and grand totals.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2026/03/10/sharing-loans</link>
    <guid isPermaLink="true">https://martinemde.com/blog/sharing-loans</guid>
    <pubDate>Wed, 11 Mar 2026 03:56:42 GMT</pubDate>
    
    <content:encoded><![CDATA[<p><a class="u-bookmark-of" href="https://martinemde.com/loans">Loan Calculator</a> — Compare loans side by side. Calculate monthly payments, total interest, and grand totals.</p>
]]></content:encoded>
    <source:markdown><![CDATA[<a class="u-bookmark-of" href="https://martinemde.com/loans">Loan Calculator</a> — Compare loans side by side. Calculate monthly payments, total interest, and grand totals.]]></source:markdown>
  </item><item>
    <title>Claude Code commands deprecated in favor of skills</title>
    <description>&lt;p&gt;Today, I noticed I could no longer find the &lt;a href=&quot;https://code.claude.com/docs/en/slash-commands&quot; title=&quot;The old link to slash command documentation&quot;&gt;Slash commands&lt;/a&gt; documentation that I was using.
The link is still named Slash commands, but it renders the &lt;a href=&quot;https://code.claude.com/docs/en/skills&quot;&gt;Skills&lt;/a&gt; docs.&lt;/p&gt;
&lt;p&gt;I had been looking closely at Claude Code&apos;s Skills and Commands lately as I build &lt;a href=&quot;https://github.com/martinemde/skillet&quot; title=&quot;Run claude skills as beautiful shell scripts&quot;&gt;skillet&lt;/a&gt;, and all today I kept trying to remind myself what was different between them. Sure enough, if you read the big blue box at the top of the docs, it says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE] For built-in commands like /help and /compact, see &lt;a href=&quot;https://code.claude.com/docs/en/interactive-mode#built-in-commands&quot;&gt;interactive mode&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Custom slash commands have been merged into skills. A file at .claude/commands/review.md and a skill at .claude/skills/review/SKILL.md both create /review and work the same way. Your existing .claude/commands/ files keep working. Skills add optional features: a directory for supporting files, frontmatter to &lt;a href=&quot;https://code.claude.com/docs/en/skills#control-who-invokes-a-skill&quot;&gt;control whether you or Claude invokes them&lt;/a&gt;, and the ability for Claude to load them automatically when relevant.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They merged some of the command-only features into skills.
You can now use &lt;code&gt;hooks&lt;/code&gt; in skills which were previously command only, and
they added support for injecting command output into the prompt.
Check out this example from the docs (note the &lt;code&gt;!&lt;/code&gt; preceding the inline commands).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-md&quot;&gt;---
name: pr-summary
description: Summarize changes in a pull request
context: fork
agent: Explore
allowed-tools: Bash(gh:*)
---

## Pull request context

- PR diff: !`gh pr diff`
- PR comments: !`gh pr view --comments`
- Changed files: !`gh pr diff --name-only`

## Your task

Summarize this pull request...!`gh pr diff`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The inline commands will be processed and interpolated before evaluating the skill.&lt;/p&gt;
&lt;p&gt;One thing that is still missing is support for numbered arguments, like &lt;code&gt;$1&lt;/code&gt; and &lt;code&gt;$2&lt;/code&gt;.
These were complicated, and may have been under-utilized.
I chose to skip it myself when I went to implement command support.&lt;/p&gt;
&lt;p&gt;I&apos;m excited to see this merger. It seemed inevitable and their timing, at least for me, is impeccable.
Skillet will keep command support for now, as commands still exist, but I suggest
migrating all your commands now to take advantage of the newly combined set of features.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2026/01/22/claude-code-commands-deprecated</link>
    <guid isPermaLink="true">https://martinemde.com/blog/claude-code-commands-deprecated</guid>
    <pubDate>Thu, 22 Jan 2026 12:00:00 GMT</pubDate>
    <category>claude code</category><category>skills</category><category>commands</category>
    <content:encoded><![CDATA[<p>Today, I noticed I could no longer find the <a href="https://code.claude.com/docs/en/slash-commands" title="The old link to slash command documentation">Slash commands</a> documentation that I was using.
The link is still named Slash commands, but it renders the <a href="https://code.claude.com/docs/en/skills">Skills</a> docs.</p>
<p>I had been looking closely at Claude Code's Skills and Commands lately as I build <a href="https://github.com/martinemde/skillet" title="Run claude skills as beautiful shell scripts">skillet</a>, and all today I kept trying to remind myself what was different between them. Sure enough, if you read the big blue box at the top of the docs, it says:</p>
<blockquote>
<p>[!NOTE] For built-in commands like /help and /compact, see <a href="https://code.claude.com/docs/en/interactive-mode#built-in-commands">interactive mode</a>.</p>
<p>Custom slash commands have been merged into skills. A file at .claude/commands/review.md and a skill at .claude/skills/review/SKILL.md both create /review and work the same way. Your existing .claude/commands/ files keep working. Skills add optional features: a directory for supporting files, frontmatter to <a href="https://code.claude.com/docs/en/skills#control-who-invokes-a-skill">control whether you or Claude invokes them</a>, and the ability for Claude to load them automatically when relevant.</p>
</blockquote>
<p>They merged some of the command-only features into skills.
You can now use <code>hooks</code> in skills which were previously command only, and
they added support for injecting command output into the prompt.
Check out this example from the docs (note the <code>!</code> preceding the inline commands).</p>
<pre><code class="language-md">---
name: pr-summary
description: Summarize changes in a pull request
context: fork
agent: Explore
allowed-tools: Bash(gh:*)
---

## Pull request context

- PR diff: !`gh pr diff`
- PR comments: !`gh pr view --comments`
- Changed files: !`gh pr diff --name-only`

## Your task

Summarize this pull request...!`gh pr diff`
</code></pre>
<p>The inline commands will be processed and interpolated before evaluating the skill.</p>
<p>One thing that is still missing is support for numbered arguments, like <code>$1</code> and <code>$2</code>.
These were complicated, and may have been under-utilized.
I chose to skip it myself when I went to implement command support.</p>
<p>I'm excited to see this merger. It seemed inevitable and their timing, at least for me, is impeccable.
Skillet will keep command support for now, as commands still exist, but I suggest
migrating all your commands now to take advantage of the newly combined set of features.</p>
]]></content:encoded>
    <source:markdown><![CDATA[Today, I noticed I could no longer find the [Slash commands][slash-commands] documentation that I was using.
The link is still named Slash commands, but it renders the [Skills](https://code.claude.com/docs/en/skills) docs.

I had been looking closely at Claude Code's Skills and Commands lately as I build [skillet][skillet], and all today I kept trying to remind myself what was different between them. Sure enough, if you read the big blue box at the top of the docs, it says:

> [!NOTE] For built-in commands like /help and /compact, see [interactive mode](https://code.claude.com/docs/en/interactive-mode#built-in-commands).
>
> Custom slash commands have been merged into skills. A file at .claude/commands/review.md and a skill at .claude/skills/review/SKILL.md both create /review and work the same way. Your existing .claude/commands/ files keep working. Skills add optional features: a directory for supporting files, frontmatter to [control whether you or Claude invokes them](https://code.claude.com/docs/en/skills#control-who-invokes-a-skill), and the ability for Claude to load them automatically when relevant.

They merged some of the command-only features into skills.
You can now use `hooks` in skills which were previously command only, and
they added support for injecting command output into the prompt.
Check out this example from the docs (note the `!` preceding the inline commands).

```md
---
name: pr-summary
description: Summarize changes in a pull request
context: fork
agent: Explore
allowed-tools: Bash(gh:*)
---

## Pull request context

- PR diff: !`gh pr diff`
- PR comments: !`gh pr view --comments`
- Changed files: !`gh pr diff --name-only`

## Your task

Summarize this pull request...!`gh pr diff`
```

The inline commands will be processed and interpolated before evaluating the skill.

One thing that is still missing is support for numbered arguments, like `$1` and `$2`.
These were complicated, and may have been under-utilized.
I chose to skip it myself when I went to implement command support.

I'm excited to see this merger. It seemed inevitable and their timing, at least for me, is impeccable.
Skillet will keep command support for now, as commands still exist, but I suggest
migrating all your commands now to take advantage of the newly combined set of features.

[skillet]: https://github.com/martinemde/skillet 'Run claude skills as beautiful shell scripts'
[slash-commands]: https://code.claude.com/docs/en/slash-commands 'The old link to slash command documentation'
[skills]: https://code.claude.com/docs/en/skills 'The new skills documentation']]></source:markdown>
  </item><item>
    <title>Fast claude @file suggestion in BIG repositories</title>
    <description>&lt;p&gt;At &lt;a href=&quot;https://gusto.com&quot; title=&quot;Gusto - #1 Rated HR Platform - (also where I work)&quot;&gt;Gusto&lt;/a&gt; we have some &lt;em&gt;big&lt;/em&gt; repositories.
Many tools struggle with large codebases and Claude Code is no exception.&lt;/p&gt;
&lt;p&gt;File suggestion, when you type &lt;code&gt;@rea&lt;/code&gt; and it suggests &lt;code&gt;@README.md&lt;/code&gt;, is one area where the number of files in a project has an outsize impact on a UI interaction. Filtering 110,000 files every time you type a character can make claude choppy and slow.&lt;/p&gt;
&lt;p&gt;Anthropic has offered &lt;a href=&quot;https://code.claude.com/docs/en/settings#file-suggestion-settings&quot; title=&quot;Anthropic Help: Claude Code File Suggestion settings&quot;&gt;a worksround&lt;/a&gt; for this problem by providing the &lt;code&gt;fileSuggestion&lt;/code&gt; setting to allow customization for big repositories.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The built-in file suggestion uses fast filesystem traversal, but large monorepos may benefit from project-specific indexing such as a pre-built file index or custom tooling.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I hacked my own solution for our giant repo with claude:
&lt;a href=&quot;https://raw.githubusercontent.com/martinemde/dotfiles/main/home/dot_claude/executable_file-suggestion.sh&quot; title=&quot;Link to current version&quot;&gt;file-suggestion.sh&lt;/a&gt; is a custom file-suggestion command which builds a cache from &lt;code&gt;git ls-files&lt;/code&gt;, if available.
It falls back to &lt;code&gt;fd&lt;/code&gt; or &lt;code&gt;find&lt;/code&gt; outside of a git repository.
These results are filtered with &lt;code&gt;ripgrep&lt;/code&gt; and &lt;code&gt;fzf&lt;/code&gt;, allowing fast fuzzy searching.&lt;/p&gt;
&lt;p&gt;As a surprise bonus, using a custom script seems to fix the choppiness and UI lag that plagues big repos.
My guess is that the need to spawn a custom script forces the app to run the filtering async.
In a repo like &lt;a href=&quot;https://gusto.com&quot; title=&quot;Gusto - #1 Rated HR Platform - (also where I work)&quot;&gt;Gusto&lt;/a&gt;&apos;s core product, this can lag input considerably to where your keypresses stop rendering. Deleting characters is even worse, slowing to a crawl as the search space expands.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Hey, claude code devs, if you&apos;re reading:&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Move the file suggestion filtering off the main
UI thread so suggestions are returned async,
following an approach similar to spawning a
process for a custom fileSuggestion command.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to try my script, make sure you have &lt;code&gt;ripgrep&lt;/code&gt;, &lt;code&gt;fzf&lt;/code&gt; and optionally &lt;code&gt;fd&lt;/code&gt; installed.&lt;/p&gt;
&lt;p&gt;Grab the script below (or point your own claude at it and ask for a version that works for you). You can also view the &lt;a href=&quot;https://raw.githubusercontent.com/martinemde/dotfiles/main/home/dot_claude/executable_file-suggestion.sh&quot; title=&quot;Link to current version&quot;&gt;latest version&lt;/a&gt; if I didn&apos;t already break the link by the time you get here.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -o ~/.claude/file-suggestion.sh https://raw.githubusercontent.com/martinemde/dotfiles/36f670bda583065f634f1e83c4a195b9ac39c17b/home/dot_claude/executable_file-suggestion.sh
chmod +x ~/.claude/file-suggestion.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then add the following to &lt;code&gt;~/.claude/settings.json&lt;/code&gt; (ensure the path matches and the file is executable).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;  &quot;fileSuggestion&quot;: {
    &quot;type&quot;: &quot;command&quot;,
    &quot;command&quot;: &quot;~/.claude/file-suggestion.sh&quot;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart Claude Code, then test out the search by typing an &lt;code&gt;@&lt;/code&gt; and the start of a file. Fuzzy matching should let you match any fuzzy path after the first slash.&lt;/p&gt;
&lt;p&gt;On our biggest repo, this drops the search time to about &lt;strong&gt;62ms&lt;/strong&gt; compared to around &lt;strong&gt;1000ms&lt;/strong&gt; without the index (one full second per bounce).
The script watches for the &lt;code&gt;.git/index&lt;/code&gt; or &lt;code&gt;.git/HEAD&lt;/code&gt; to be newer than the cache file and automatically refreshes the cache.&lt;/p&gt;
&lt;p&gt;The cache is stored in the project&apos;s &lt;code&gt;.claude/&lt;/code&gt; directory, so you&apos;ll want to ignore the &lt;code&gt;.claude/file-suggestions.cache&lt;/code&gt; in your global gitignore file.&lt;/p&gt;
&lt;p&gt;In order to attain speed and retain fuzzy matching, it pre-filters based on the first directory segment using ripgrep to cut down on results.
This allows you to type &lt;code&gt;pac/payroll&lt;/code&gt; to find &lt;code&gt;packs/products/payroll/...&lt;/code&gt; but if you type
&lt;code&gt;pks/payroll&lt;/code&gt; or &lt;code&gt;papay&lt;/code&gt; it may not return any results.
This is a trade-off for speed that I&apos;d don&apos;t love since I was expecting neovim level fuzzy find.&lt;/p&gt;
&lt;p&gt;I may experiment with dropping the pre-filter and accepting the slower results just to get more reliable fuzziness.
Please let me know if you find a better way around it.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2026/01/13/fast-claude-file-suggestion-in-big-repos</link>
    <guid isPermaLink="true">https://martinemde.com/blog/fast-claude-file-suggestion-in-big-repos</guid>
    <pubDate>Tue, 13 Jan 2026 12:00:00 GMT</pubDate>
    <category>claude code</category>
    <content:encoded><![CDATA[<p>At <a href="https://gusto.com" title="Gusto - #1 Rated HR Platform - (also where I work)">Gusto</a> we have some <em>big</em> repositories.
Many tools struggle with large codebases and Claude Code is no exception.</p>
<p>File suggestion, when you type <code>@rea</code> and it suggests <code>@README.md</code>, is one area where the number of files in a project has an outsize impact on a UI interaction. Filtering 110,000 files every time you type a character can make claude choppy and slow.</p>
<p>Anthropic has offered <a href="https://code.claude.com/docs/en/settings#file-suggestion-settings" title="Anthropic Help: Claude Code File Suggestion settings">a worksround</a> for this problem by providing the <code>fileSuggestion</code> setting to allow customization for big repositories.</p>
<blockquote>
<p>The built-in file suggestion uses fast filesystem traversal, but large monorepos may benefit from project-specific indexing such as a pre-built file index or custom tooling.</p>
</blockquote>
<p>I hacked my own solution for our giant repo with claude:
<a href="https://raw.githubusercontent.com/martinemde/dotfiles/main/home/dot_claude/executable_file-suggestion.sh" title="Link to current version">file-suggestion.sh</a> is a custom file-suggestion command which builds a cache from <code>git ls-files</code>, if available.
It falls back to <code>fd</code> or <code>find</code> outside of a git repository.
These results are filtered with <code>ripgrep</code> and <code>fzf</code>, allowing fast fuzzy searching.</p>
<p>As a surprise bonus, using a custom script seems to fix the choppiness and UI lag that plagues big repos.
My guess is that the need to spawn a custom script forces the app to run the filtering async.
In a repo like <a href="https://gusto.com" title="Gusto - #1 Rated HR Platform - (also where I work)">Gusto</a>'s core product, this can lag input considerably to where your keypresses stop rendering. Deleting characters is even worse, slowing to a crawl as the search space expands.</p>
<p><em>Hey, claude code devs, if you're reading:</em></p>
<pre><code>Move the file suggestion filtering off the main
UI thread so suggestions are returned async,
following an approach similar to spawning a
process for a custom fileSuggestion command.
</code></pre>
<p>If you want to try my script, make sure you have <code>ripgrep</code>, <code>fzf</code> and optionally <code>fd</code> installed.</p>
<p>Grab the script below (or point your own claude at it and ask for a version that works for you). You can also view the <a href="https://raw.githubusercontent.com/martinemde/dotfiles/main/home/dot_claude/executable_file-suggestion.sh" title="Link to current version">latest version</a> if I didn't already break the link by the time you get here.</p>
<pre><code class="language-bash">curl -o ~/.claude/file-suggestion.sh https://raw.githubusercontent.com/martinemde/dotfiles/36f670bda583065f634f1e83c4a195b9ac39c17b/home/dot_claude/executable_file-suggestion.sh
chmod +x ~/.claude/file-suggestion.sh
</code></pre>
<p>Then add the following to <code>~/.claude/settings.json</code> (ensure the path matches and the file is executable).</p>
<pre><code class="language-json">  "fileSuggestion": {
    "type": "command",
    "command": "~/.claude/file-suggestion.sh"
  }
</code></pre>
<p>Restart Claude Code, then test out the search by typing an <code>@</code> and the start of a file. Fuzzy matching should let you match any fuzzy path after the first slash.</p>
<p>On our biggest repo, this drops the search time to about <strong>62ms</strong> compared to around <strong>1000ms</strong> without the index (one full second per bounce).
The script watches for the <code>.git/index</code> or <code>.git/HEAD</code> to be newer than the cache file and automatically refreshes the cache.</p>
<p>The cache is stored in the project's <code>.claude/</code> directory, so you'll want to ignore the <code>.claude/file-suggestions.cache</code> in your global gitignore file.</p>
<p>In order to attain speed and retain fuzzy matching, it pre-filters based on the first directory segment using ripgrep to cut down on results.
This allows you to type <code>pac/payroll</code> to find <code>packs/products/payroll/...</code> but if you type
<code>pks/payroll</code> or <code>papay</code> it may not return any results.
This is a trade-off for speed that I'd don't love since I was expecting neovim level fuzzy find.</p>
<p>I may experiment with dropping the pre-filter and accepting the slower results just to get more reliable fuzziness.
Please let me know if you find a better way around it.</p>
]]></content:encoded>
    <source:markdown><![CDATA[At [Gusto][gusto] we have some _big_ repositories.
Many tools struggle with large codebases and Claude Code is no exception.

File suggestion, when you type `@rea` and it suggests `@README.md`, is one area where the number of files in a project has an outsize impact on a UI interaction. Filtering 110,000 files every time you type a character can make claude choppy and slow.

Anthropic has offered [a worksround][file-suggestion] for this problem by providing the `fileSuggestion` setting to allow customization for big repositories.

> The built-in file suggestion uses fast filesystem traversal, but large monorepos may benefit from project-specific indexing such as a pre-built file index or custom tooling.

I hacked my own solution for our giant repo with claude:
[file-suggestion.sh][latest] is a custom file-suggestion command which builds a cache from `git ls-files`, if available.
It falls back to `fd` or `find` outside of a git repository.
These results are filtered with `ripgrep` and `fzf`, allowing fast fuzzy searching.

As a surprise bonus, using a custom script seems to fix the choppiness and UI lag that plagues big repos.
My guess is that the need to spawn a custom script forces the app to run the filtering async.
In a repo like [Gusto][gusto]'s core product, this can lag input considerably to where your keypresses stop rendering. Deleting characters is even worse, slowing to a crawl as the search space expands.

_Hey, claude code devs, if you're reading:_

```
Move the file suggestion filtering off the main
UI thread so suggestions are returned async,
following an approach similar to spawning a
process for a custom fileSuggestion command.
```

If you want to try my script, make sure you have `ripgrep`, `fzf` and optionally `fd` installed.

Grab the script below (or point your own claude at it and ask for a version that works for you). You can also view the [latest version][latest] if I didn't already break the link by the time you get here.

```bash
curl -o ~/.claude/file-suggestion.sh https://raw.githubusercontent.com/martinemde/dotfiles/36f670bda583065f634f1e83c4a195b9ac39c17b/home/dot_claude/executable_file-suggestion.sh
chmod +x ~/.claude/file-suggestion.sh
```

Then add the following to `~/.claude/settings.json` (ensure the path matches and the file is executable).

```json
  "fileSuggestion": {
    "type": "command",
    "command": "~/.claude/file-suggestion.sh"
  }
```

Restart Claude Code, then test out the search by typing an `@` and the start of a file. Fuzzy matching should let you match any fuzzy path after the first slash.

On our biggest repo, this drops the search time to about **62ms** compared to around **1000ms** without the index (one full second per bounce).
The script watches for the `.git/index` or `.git/HEAD` to be newer than the cache file and automatically refreshes the cache.

The cache is stored in the project's `.claude/` directory, so you'll want to ignore the `.claude/file-suggestions.cache` in your global gitignore file.

In order to attain speed and retain fuzzy matching, it pre-filters based on the first directory segment using ripgrep to cut down on results.
This allows you to type `pac/payroll` to find `packs/products/payroll/...` but if you type
`pks/payroll` or `papay` it may not return any results.
This is a trade-off for speed that I'd don't love since I was expecting neovim level fuzzy find.

I may experiment with dropping the pre-filter and accepting the slower results just to get more reliable fuzziness.
Please let me know if you find a better way around it.

[gusto]: https://gusto.com 'Gusto - #1 Rated HR Platform - (also where I work)'
[file-suggestion]: https://code.claude.com/docs/en/settings#file-suggestion-settings 'Anthropic Help: Claude Code File Suggestion settings'
[latest]: https://raw.githubusercontent.com/martinemde/dotfiles/main/home/dot_claude/executable_file-suggestion.sh 'Link to current version']]></source:markdown>
  </item><item>
    <title>Ghostty Focus and Blur Shaders</title>
    <description>&lt;p&gt;&lt;img src=&quot;https://martinemde.com/images/blog/ghostty-focus-shaders.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;What if you could have that sick Ghostty CRT effect but only on unfocused windows,
or a rad flaming cursor highlight but only once when you focused the window?&lt;/p&gt;
&lt;h2&gt;Introducing the iFocus and iTimeFocus uniforms&lt;/h2&gt;
&lt;p&gt;My recent &lt;a href=&quot;https://github.com/ghostty-org/ghostty/pull/10130&quot; title=&quot;Add iFocus and iTimeFocus shader uniforms&quot;&gt;contribution&lt;/a&gt; to Ghostty enables new focus-based shaders.
You can use these new input variables, &lt;code&gt;iFocus&lt;/code&gt; and &lt;code&gt;iTimeFocus&lt;/code&gt;, known as &quot;uniforms&quot; in Shadertoy GLSL, to allow shaders to react to focus and blur events.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: Until the next Ghostty release, you&apos;ll need to &lt;a href=&quot;https://ghostty.org/docs/install/pre&quot; title=&quot;Docs: Install Ghostty Prerelease&quot;&gt;install a recent Ghostty prerelease version&lt;/a&gt; to try this out.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Example: CRT Shader on Blur&lt;/h2&gt;
&lt;p&gt;Here&apos;s the shader I use in Ghostty for unfocused surfaces.&lt;/p&gt;
&lt;p&gt;Try clicking into and then outside of the &quot;terminal&quot; (yes it&apos;s upside down).
You should see a CRT-style effect that clears when you focus the terminal.&lt;/p&gt;
&lt;p&gt;{#if blurDemoShaders.length &gt; 0}
&lt;ShaderCanvas
 bind:shaders={blurDemoShaders}
 width={800}
 height={600}
 swapColorsOnClick={true}
 bind:cursorColor
 bind:prevCursorColor
 className=&quot;rounded-lg border-2 border-surface-400-600&quot;
/&gt;
{:else}&lt;/p&gt;
  &lt;div class=&quot;flex h-[600px] w-full items-center justify-center rounded-lg border-2 border-surface-400-600 bg-surface-100-900&quot;&gt;
    &lt;p class=&quot;text-surface-600-400&quot;&gt;Loading shader...&lt;/p&gt;
  &lt;/div&gt;
{/if}
&lt;p&gt;Here I&apos;m emulating Ghostty&apos;s &lt;code&gt;iFocus&lt;/code&gt; uniform to apply a CRT shader only when the canvas is unfocused.
It may not render in every browser.
Try it directly on my site with a modern browser or zoom in really close on your phone.&lt;/p&gt;
&lt;h2&gt;Example: Focus Animations&lt;/h2&gt;
&lt;p&gt;Here we emulate the &lt;code&gt;iTimeFocus&lt;/code&gt; and &lt;code&gt;iFocus&lt;/code&gt; uniforms to create a cursor highlight effect.&lt;/p&gt;
&lt;p&gt;Click into the &quot;terminal&quot; and a cursor focus zoom effect triggers.
Click around more, the effect only plays once.
Defocus the &quot;terminal&quot; then focus it again and you&apos;ll replay the focus animation.&lt;/p&gt;
&lt;p&gt;{#if focusDemoShaders.length &gt; 0}
&lt;ShaderCanvas
 bind:shaders={focusDemoShaders}
 width={800}
 height={600}
 swapColorsOnClick={true}
 bind:cursorColor
 bind:prevCursorColor
 className=&quot;rounded-lg border-2 border-surface-400-600&quot;
/&gt;
{:else}&lt;/p&gt;
  &lt;div class=&quot;flex h-[600px] w-full items-center justify-center rounded-lg border-2 border-surface-400-600 bg-surface-100-900&quot;&gt;
    &lt;p class=&quot;text-surface-600-400&quot;&gt;Loading shader...&lt;/p&gt;
  &lt;/div&gt;
{/if}
&lt;p&gt;If you&apos;d like to play with these shaders more, check out my &lt;a href=&quot;https://martinemde.com/shaders&quot; title=&quot;Martin&amp;#x27;s Ghostty Shaders&quot;&gt;Ghosty Shaders&lt;/a&gt; demo page.&lt;/p&gt;
&lt;p&gt;Toggle between the shaders using the menu button in the top-left of the demo canvas, or look at the debug overlay (bottom right) for uniform details.&lt;/p&gt;
&lt;h2&gt;How it works: Focus/Blur Effects&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;iFocus&lt;/code&gt; uniform is an &lt;code&gt;int&lt;/code&gt; set to either &lt;code&gt;0&lt;/code&gt; (blurred) or &lt;code&gt;1&lt;/code&gt; (focused).
This uniform is useful for controlling whether a shader renders on focused or blurred surfaces (panes or windows).&lt;/p&gt;
&lt;p&gt;To save resources, Ghostty shaders do not run on every frame for unfocused surface.
This is generally a good thing and saves resources, except sometimes unfocused surfaces receive frames anyway.
This can cause stutters and skips in a paused animation or cursor effect on blurred surfaces.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;iFocus&lt;/code&gt; we can intentionally apply blurred styles to unfocused surfaces or run shaders only on focused frames.
This fixes the stutters and skips by allowing the shader to ignoring these deceptive frames.&lt;/p&gt;
&lt;h3&gt;Example Usage&lt;/h3&gt;
&lt;p&gt;The following &lt;code&gt;if&lt;/code&gt; statement can be added to any shader to make it render only when blurred. You can
check &lt;code&gt;if (iFocus == 1)&lt;/code&gt; to run something only when focused instead.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-glsl&quot;&gt;void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord.xy / iResolution.xy;

    // Early exit when focused
    if (iFocus == 0) {
        // Render normal texture when
        fragColor = texture(iChannel0, uv);
        return;
    }

    // The rest of the shader goes here...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example disables all shader effects when the surface is focused, allowing unfocused states such as CRT scanlines, vignettes, shadows, or faded colors that render only on the blurred surfaces.&lt;/p&gt;
&lt;p&gt;On the other hand, if you invert the logic you can run something only when the surface is focused,
which I have used to make my cursor effects only appear on focused surfaces.&lt;/p&gt;
&lt;h2&gt;Focus Resume Animations&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;iTimeFocus&lt;/code&gt; uniform is a &lt;code&gt;float&lt;/code&gt;, set to the &lt;code&gt;iTime&lt;/code&gt; when the surface last received focus.
We can use this to render effects that need to animate when the surface is focused.&lt;/p&gt;
&lt;p&gt;Here&apos;s an example of how to use &lt;code&gt;iTimeFocus&lt;/code&gt; to create a resume animation.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-glsl&quot;&gt;const float PULSE_DURATION = 0.15;  // Animation duration in seconds

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;

    // Quick exit: only run during active focus animation
    float timeSinceFocus = iTime - iTimeFocus;
    if (iFocus == 0 || timeSinceFocus &amp;#x3C; 0.0 || timeSinceFocus &gt; PULSE_DURATION) {
        fragColor = texture(iChannel0, uv);
        return;
    }

    // Render focus resume animation...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, the shader checks how much time has passed since the surface received focus.
If the surface is blurred, or if the focus animation duration has passed, the shader exits early to
save resources.&lt;/p&gt;
&lt;p&gt;You can see and install my &lt;a href=&quot;https://github.com/martinemde/dotfiles/blob/main/home/dot_config/ghostty/shaders/focus_cursor.glsl&quot; title=&quot;A shader that zoom fades the cursor on focus&quot;&gt;focus-cursor&lt;/a&gt; shader for a complete example.&lt;/p&gt;
&lt;h2&gt;Rendering shaders on blurred surfaces&lt;/h2&gt;
&lt;p&gt;Ghostty shaders mostly don&apos;t receive frames when blurred.
This is for performance reasons and can be disabled with the &lt;code&gt;custom-shader-animation=always&lt;/code&gt; config option.&lt;/p&gt;
&lt;p&gt;However, it&apos;s inconsistent. Sometimes defocused surfaces get frames when a mod key is press (like
command or alt) or when the mouse moves over a link.
With &lt;code&gt;iFocus&lt;/code&gt; you can intentionally filter these frames out to avoid ugly stutters.&lt;/p&gt;
&lt;p&gt;So far it seems blurred surfaces reliably receive at least one defocused frame to render the defocused state.
While you can&apos;t ensure that an animation could complete on blur (without &lt;code&gt;custom-shader-animation=always&lt;/code&gt;), you can at least ensure that the defocused state is rendered once.&lt;/p&gt;
&lt;p&gt;Therefore, I would suggest that all shaders should include this gating &lt;code&gt;if&lt;/code&gt; statement unless they are intended to run &lt;code&gt;always&lt;/code&gt;.
It improves performance and polish on both focused state cursor animations and blurred state rendering.&lt;/p&gt;
&lt;h2&gt;Installing Ghostty prerelease&lt;/h2&gt;
&lt;p&gt;Again, if you want to use these shaders, you&apos;ll need to &lt;a href=&quot;https://ghostty.org/docs/install/pre&quot; title=&quot;Docs: Install Ghostty Prerelease&quot;&gt;install the prerelease&lt;/a&gt; version of Ghostty.
It might be glitchy (I immediately submitted another bug fix after this feature because the dev build had a problem)&lt;/p&gt;
</description>
    <link>https://martinemde.com/2026/01/10/ghostty-focus-shaders</link>
    <guid isPermaLink="true">https://martinemde.com/blog/ghostty-focus-shaders</guid>
    <pubDate>Sat, 10 Jan 2026 23:38:00 GMT</pubDate>
    
    <content:encoded><![CDATA[<p><img src="https://martinemde.com/images/blog/ghostty-focus-shaders.png" alt=""></p>
<p>What if you could have that sick Ghostty CRT effect but only on unfocused windows,
or a rad flaming cursor highlight but only once when you focused the window?</p>
<h2>Introducing the iFocus and iTimeFocus uniforms</h2>
<p>My recent <a href="https://github.com/ghostty-org/ghostty/pull/10130" title="Add iFocus and iTimeFocus shader uniforms">contribution</a> to Ghostty enables new focus-based shaders.
You can use these new input variables, <code>iFocus</code> and <code>iTimeFocus</code>, known as "uniforms" in Shadertoy GLSL, to allow shaders to react to focus and blur events.</p>
<p><em>Note: Until the next Ghostty release, you'll need to <a href="https://ghostty.org/docs/install/pre" title="Docs: Install Ghostty Prerelease">install a recent Ghostty prerelease version</a> to try this out.</em></p>
<h2>Example: CRT Shader on Blur</h2>
<p>Here's the shader I use in Ghostty for unfocused surfaces.</p>
<p>Try clicking into and then outside of the "terminal" (yes it's upside down).
You should see a CRT-style effect that clears when you focus the terminal.</p>
<p>{#if blurDemoShaders.length > 0}
<ShaderCanvas
 bind:shaders={blurDemoShaders}
 width={800}
 height={600}
 swapColorsOnClick={true}
 bind:cursorColor
 bind:prevCursorColor
 className="rounded-lg border-2 border-surface-400-600"
/>
{:else}</p>
  <div class="flex h-[600px] w-full items-center justify-center rounded-lg border-2 border-surface-400-600 bg-surface-100-900">
    <p class="text-surface-600-400">Loading shader...</p>
  </div>
{/if}
<p>Here I'm emulating Ghostty's <code>iFocus</code> uniform to apply a CRT shader only when the canvas is unfocused.
It may not render in every browser.
Try it directly on my site with a modern browser or zoom in really close on your phone.</p>
<h2>Example: Focus Animations</h2>
<p>Here we emulate the <code>iTimeFocus</code> and <code>iFocus</code> uniforms to create a cursor highlight effect.</p>
<p>Click into the "terminal" and a cursor focus zoom effect triggers.
Click around more, the effect only plays once.
Defocus the "terminal" then focus it again and you'll replay the focus animation.</p>
<p>{#if focusDemoShaders.length > 0}
<ShaderCanvas
 bind:shaders={focusDemoShaders}
 width={800}
 height={600}
 swapColorsOnClick={true}
 bind:cursorColor
 bind:prevCursorColor
 className="rounded-lg border-2 border-surface-400-600"
/>
{:else}</p>
  <div class="flex h-[600px] w-full items-center justify-center rounded-lg border-2 border-surface-400-600 bg-surface-100-900">
    <p class="text-surface-600-400">Loading shader...</p>
  </div>
{/if}
<p>If you'd like to play with these shaders more, check out my <a href="https://martinemde.com/shaders" title="Martin&#x27;s Ghostty Shaders">Ghosty Shaders</a> demo page.</p>
<p>Toggle between the shaders using the menu button in the top-left of the demo canvas, or look at the debug overlay (bottom right) for uniform details.</p>
<h2>How it works: Focus/Blur Effects</h2>
<p>The <code>iFocus</code> uniform is an <code>int</code> set to either <code>0</code> (blurred) or <code>1</code> (focused).
This uniform is useful for controlling whether a shader renders on focused or blurred surfaces (panes or windows).</p>
<p>To save resources, Ghostty shaders do not run on every frame for unfocused surface.
This is generally a good thing and saves resources, except sometimes unfocused surfaces receive frames anyway.
This can cause stutters and skips in a paused animation or cursor effect on blurred surfaces.</p>
<p>With <code>iFocus</code> we can intentionally apply blurred styles to unfocused surfaces or run shaders only on focused frames.
This fixes the stutters and skips by allowing the shader to ignoring these deceptive frames.</p>
<h3>Example Usage</h3>
<p>The following <code>if</code> statement can be added to any shader to make it render only when blurred. You can
check <code>if (iFocus == 1)</code> to run something only when focused instead.</p>
<pre><code class="language-glsl">void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord.xy / iResolution.xy;

    // Early exit when focused
    if (iFocus == 0) {
        // Render normal texture when
        fragColor = texture(iChannel0, uv);
        return;
    }

    // The rest of the shader goes here...
}
</code></pre>
<p>This example disables all shader effects when the surface is focused, allowing unfocused states such as CRT scanlines, vignettes, shadows, or faded colors that render only on the blurred surfaces.</p>
<p>On the other hand, if you invert the logic you can run something only when the surface is focused,
which I have used to make my cursor effects only appear on focused surfaces.</p>
<h2>Focus Resume Animations</h2>
<p>The <code>iTimeFocus</code> uniform is a <code>float</code>, set to the <code>iTime</code> when the surface last received focus.
We can use this to render effects that need to animate when the surface is focused.</p>
<p>Here's an example of how to use <code>iTimeFocus</code> to create a resume animation.</p>
<pre><code class="language-glsl">const float PULSE_DURATION = 0.15;  // Animation duration in seconds

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;

    // Quick exit: only run during active focus animation
    float timeSinceFocus = iTime - iTimeFocus;
    if (iFocus == 0 || timeSinceFocus &#x3C; 0.0 || timeSinceFocus > PULSE_DURATION) {
        fragColor = texture(iChannel0, uv);
        return;
    }

    // Render focus resume animation...
}
</code></pre>
<p>In this example, the shader checks how much time has passed since the surface received focus.
If the surface is blurred, or if the focus animation duration has passed, the shader exits early to
save resources.</p>
<p>You can see and install my <a href="https://github.com/martinemde/dotfiles/blob/main/home/dot_config/ghostty/shaders/focus_cursor.glsl" title="A shader that zoom fades the cursor on focus">focus-cursor</a> shader for a complete example.</p>
<h2>Rendering shaders on blurred surfaces</h2>
<p>Ghostty shaders mostly don't receive frames when blurred.
This is for performance reasons and can be disabled with the <code>custom-shader-animation=always</code> config option.</p>
<p>However, it's inconsistent. Sometimes defocused surfaces get frames when a mod key is press (like
command or alt) or when the mouse moves over a link.
With <code>iFocus</code> you can intentionally filter these frames out to avoid ugly stutters.</p>
<p>So far it seems blurred surfaces reliably receive at least one defocused frame to render the defocused state.
While you can't ensure that an animation could complete on blur (without <code>custom-shader-animation=always</code>), you can at least ensure that the defocused state is rendered once.</p>
<p>Therefore, I would suggest that all shaders should include this gating <code>if</code> statement unless they are intended to run <code>always</code>.
It improves performance and polish on both focused state cursor animations and blurred state rendering.</p>
<h2>Installing Ghostty prerelease</h2>
<p>Again, if you want to use these shaders, you'll need to <a href="https://ghostty.org/docs/install/pre" title="Docs: Install Ghostty Prerelease">install the prerelease</a> version of Ghostty.
It might be glitchy (I immediately submitted another bug fix after this feature because the dev build had a problem)</p>
]]></content:encoded>
    <source:markdown><![CDATA[![](https://martinemde.com/images/blog/ghostty-focus-shaders.png)

What if you could have that sick Ghostty CRT effect but only on unfocused windows,
or a rad flaming cursor highlight but only once when you focused the window?

## Introducing the iFocus and iTimeFocus uniforms

My recent [contribution][pr] to Ghostty enables new focus-based shaders.
You can use these new input variables, `iFocus` and `iTimeFocus`, known as "uniforms" in Shadertoy GLSL, to allow shaders to react to focus and blur events.

_Note: Until the next Ghostty release, you'll need to [install a recent Ghostty prerelease version][prerelease] to try this out._

## Example: CRT Shader on Blur

Here's the shader I use in Ghostty for unfocused surfaces.

Try clicking into and then outside of the "terminal" (yes it's upside down).
You should see a CRT-style effect that clears when you focus the terminal.

{#if blurDemoShaders.length > 0}
  <ShaderCanvas
    bind:shaders={blurDemoShaders}
    width={800}
    height={600}
    swapColorsOnClick={true}
    bind:cursorColor
    bind:prevCursorColor
    className="rounded-lg border-2 border-surface-400-600"
  />
{:else}
  <div class="flex h-[600px] w-full items-center justify-center rounded-lg border-2 border-surface-400-600 bg-surface-100-900">
    <p class="text-surface-600-400">Loading shader...</p>
  </div>
{/if}

Here I'm emulating Ghostty's `iFocus` uniform to apply a CRT shader only when the canvas is unfocused.
It may not render in every browser.
Try it directly on my site with a modern browser or zoom in really close on your phone.

## Example: Focus Animations

Here we emulate the `iTimeFocus` and `iFocus` uniforms to create a cursor highlight effect.

Click into the "terminal" and a cursor focus zoom effect triggers.
Click around more, the effect only plays once.
Defocus the "terminal" then focus it again and you'll replay the focus animation.

{#if focusDemoShaders.length > 0}
  <ShaderCanvas
    bind:shaders={focusDemoShaders}
    width={800}
    height={600}
    swapColorsOnClick={true}
    bind:cursorColor
    bind:prevCursorColor
    className="rounded-lg border-2 border-surface-400-600"
  />
{:else}
  <div class="flex h-[600px] w-full items-center justify-center rounded-lg border-2 border-surface-400-600 bg-surface-100-900">
    <p class="text-surface-600-400">Loading shader...</p>
  </div>
{/if}

If you'd like to play with these shaders more, check out my [Ghosty Shaders][shaders] demo page.

Toggle between the shaders using the menu button in the top-left of the demo canvas, or look at the debug overlay (bottom right) for uniform details.

## How it works: Focus/Blur Effects

The `iFocus` uniform is an `int` set to either `0` (blurred) or `1` (focused).
This uniform is useful for controlling whether a shader renders on focused or blurred surfaces (panes or windows).

To save resources, Ghostty shaders do not run on every frame for unfocused surface.
This is generally a good thing and saves resources, except sometimes unfocused surfaces receive frames anyway.
This can cause stutters and skips in a paused animation or cursor effect on blurred surfaces.

With `iFocus` we can intentionally apply blurred styles to unfocused surfaces or run shaders only on focused frames.
This fixes the stutters and skips by allowing the shader to ignoring these deceptive frames.

### Example Usage

The following `if` statement can be added to any shader to make it render only when blurred. You can
check `if (iFocus == 1)` to run something only when focused instead.

```glsl
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord.xy / iResolution.xy;

    // Early exit when focused
    if (iFocus == 0) {
        // Render normal texture when
        fragColor = texture(iChannel0, uv);
        return;
    }

    // The rest of the shader goes here...
}
```

This example disables all shader effects when the surface is focused, allowing unfocused states such as CRT scanlines, vignettes, shadows, or faded colors that render only on the blurred surfaces.

On the other hand, if you invert the logic you can run something only when the surface is focused,
which I have used to make my cursor effects only appear on focused surfaces.

## Focus Resume Animations

The `iTimeFocus` uniform is a `float`, set to the `iTime` when the surface last received focus.
We can use this to render effects that need to animate when the surface is focused.

Here's an example of how to use `iTimeFocus` to create a resume animation.

```glsl
const float PULSE_DURATION = 0.15;  // Animation duration in seconds

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;

    // Quick exit: only run during active focus animation
    float timeSinceFocus = iTime - iTimeFocus;
    if (iFocus == 0 || timeSinceFocus < 0.0 || timeSinceFocus > PULSE_DURATION) {
        fragColor = texture(iChannel0, uv);
        return;
    }

    // Render focus resume animation...
}
```

In this example, the shader checks how much time has passed since the surface received focus.
If the surface is blurred, or if the focus animation duration has passed, the shader exits early to
save resources.

You can see and install my [focus-cursor][focus-cursor] shader for a complete example.

## Rendering shaders on blurred surfaces

Ghostty shaders mostly don't receive frames when blurred.
This is for performance reasons and can be disabled with the `custom-shader-animation=always` config option.

However, it's inconsistent. Sometimes defocused surfaces get frames when a mod key is press (like
command or alt) or when the mouse moves over a link.
With `iFocus` you can intentionally filter these frames out to avoid ugly stutters.

So far it seems blurred surfaces reliably receive at least one defocused frame to render the defocused state.
While you can't ensure that an animation could complete on blur (without `custom-shader-animation=always`), you can at least ensure that the defocused state is rendered once.

Therefore, I would suggest that all shaders should include this gating `if` statement unless they are intended to run `always`.
It improves performance and polish on both focused state cursor animations and blurred state rendering.

## Installing Ghostty prerelease

Again, if you want to use these shaders, you'll need to [install the prerelease][prerelease] version of Ghostty.
It might be glitchy (I immediately submitted another bug fix after this feature because the dev build had a problem)

[prerelease]: https://ghostty.org/docs/install/pre 'Docs: Install Ghostty Prerelease'
[fun-with-shaders]: https://catskull.net/fun-with-ghostty-shaders.html 'Fun with Ghostty Shaders'
[pr]: https://github.com/ghostty-org/ghostty/pull/10130 'Add iFocus and iTimeFocus shader uniforms'
[focus-cursor]: https://github.com/martinemde/dotfiles/blob/main/home/dot_config/ghostty/shaders/focus_cursor.glsl 'A shader that zoom fades the cursor on focus'
[shaders]: /shaders "Martin's Ghostty Shaders"]]></source:markdown>
  </item><item>
    
    <description>&lt;p&gt;&lt;a class=&quot;u-bookmark-of&quot; href=&quot;https://martinemde.com/shaders&quot;&gt;Ghostty Shaders&lt;/a&gt; — A Ghostty shader emulator using WebGL. Built to help preview shaders for Ghostty on the web.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2026/01/02/sharing-shaders</link>
    <guid isPermaLink="true">https://martinemde.com/blog/sharing-shaders</guid>
    <pubDate>Sat, 03 Jan 2026 04:37:57 GMT</pubDate>
    
    <content:encoded><![CDATA[<p><a class="u-bookmark-of" href="https://martinemde.com/shaders">Ghostty Shaders</a> — A Ghostty shader emulator using WebGL. Built to help preview shaders for Ghostty on the web.</p>
]]></content:encoded>
    <source:markdown><![CDATA[<a class="u-bookmark-of" href="https://martinemde.com/shaders">Ghostty Shaders</a> — A Ghostty shader emulator using WebGL. Built to help preview shaders for Ghostty on the web.]]></source:markdown>
  </item><item>
    <title>Progress Bars in Ghostty Terminals</title>
    <description>&lt;p&gt;I notice that the blue bar at the top of the Ghostty terminal can do more than just bounce back and forth (like it does in Claude Code). I was tinkering with Ghostty (to add additional shader uniforms for focus change events) and when Zig builds, it updates the Ghostty progress bar as it builds, turning red if it fails.&lt;/p&gt;
&lt;p&gt;I &lt;a href=&quot;https://github.com/ghostty-org/ghostty/pull/8477#issuecomment-3302958474&quot;&gt;found this &quot;one-liner&quot;&lt;/a&gt; (it&apos;s a &lt;em&gt;long&lt;/em&gt; line) by &lt;a href=&quot;https://github.com/jcollie&quot;&gt;@jcollie&lt;/a&gt; that outputs the OSC9;4 progress bar sequences:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; counter=1; while [ $counter -le 25 ]; do printf &quot;\x1b]9;4;2;${counter}\x07&quot;; ((counter++)); sleep 0.1; done; while [ $counter -le 50 ]; do printf &quot;\x1b]9;4;1;${counter}\x07&quot;; ((counter++)); sleep 0.1; done; while [ $counter -le 75 ]; do printf &quot;\x1b]9;4;3\x07&quot;; ((counter++)); sleep 0.1; done; while [ $counter -le 100 ]; do printf &quot;\x1b]9;4;1;${counter}\x07&quot;; ((counter++)); sleep 0.1; done; printf &quot;\x1b]9;4;0\x07&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here&apos;s the breakdown of the escape sequence (quoting from the &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences&quot;&gt;Windows Terminal docs of all places&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;ESC ] 9 ; 4 ; &amp;#x3C;state&gt; ; &amp;#x3C;progress&gt; BEL
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ESC&lt;/code&gt; is the escape character, ASCII 27.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BEL&lt;/code&gt; is the bell character, ASCII 7.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;#x3C;state&gt;&lt;/code&gt; is one of &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;, &lt;code&gt;3&lt;/code&gt;, or &lt;code&gt;4&lt;/code&gt;.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt; i s the default state, and indicates that the progress bar should be hidden. Use this state when the command is complete, to clear out any progress state.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1&lt;/code&gt;: set progress value to &lt;code&gt;&amp;#x3C;progress&gt;&lt;/code&gt;, in the &quot;default&quot; state.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2&lt;/code&gt;: set progress value to &lt;code&gt;&amp;#x3C;progress&gt;&lt;/code&gt;, in the &quot;Error&quot; state&lt;/li&gt;
&lt;li&gt;&lt;code&gt;3&lt;/code&gt;: set the taskbar to the &quot;Indeterminate&quot; state. This is useful for commands that don&apos;t have a progress value, but are still running. This state ignores the &lt;code&gt;&amp;#x3C;progress&gt;&lt;/code&gt; value.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;4&lt;/code&gt;: set progress value to &lt;code&gt;&amp;#x3C;progress&gt;&lt;/code&gt;, in the &quot;Warning&quot; state&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;#x3C;progress&gt;&lt;/code&gt; is a number between 0 and 100, inclusive.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also refer to the ghostty documentation for &lt;a href=&quot;https://ghostty.org/docs/vt/osc/conemu&quot;&gt;ConEmu OSC 9;n Escape
Sequences&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;How great would it be to add that to other tools that compile or churn for a while? It&apos;s no replacement for other feedback since not all terminals implement this, but it seems like a good way to give extra information without an excess of printed characters that get picked up by logs.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2025/12/31/ghostty-progress-bars</link>
    <guid isPermaLink="true">https://martinemde.com/blog/ghostty-progress-bars</guid>
    <pubDate>Wed, 31 Dec 2025 22:00:09 GMT</pubDate>
    
    <content:encoded><![CDATA[<p>I notice that the blue bar at the top of the Ghostty terminal can do more than just bounce back and forth (like it does in Claude Code). I was tinkering with Ghostty (to add additional shader uniforms for focus change events) and when Zig builds, it updates the Ghostty progress bar as it builds, turning red if it fails.</p>
<p>I <a href="https://github.com/ghostty-org/ghostty/pull/8477#issuecomment-3302958474">found this "one-liner"</a> (it's a <em>long</em> line) by <a href="https://github.com/jcollie">@jcollie</a> that outputs the OSC9;4 progress bar sequences:</p>
<pre><code class="language-bash"> counter=1; while [ $counter -le 25 ]; do printf "\x1b]9;4;2;${counter}\x07"; ((counter++)); sleep 0.1; done; while [ $counter -le 50 ]; do printf "\x1b]9;4;1;${counter}\x07"; ((counter++)); sleep 0.1; done; while [ $counter -le 75 ]; do printf "\x1b]9;4;3\x07"; ((counter++)); sleep 0.1; done; while [ $counter -le 100 ]; do printf "\x1b]9;4;1;${counter}\x07"; ((counter++)); sleep 0.1; done; printf "\x1b]9;4;0\x07"
</code></pre>
<p>And here's the breakdown of the escape sequence (quoting from the <a href="https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences">Windows Terminal docs of all places</a>):</p>
<pre><code class="language-sh">ESC ] 9 ; 4 ; &#x3C;state> ; &#x3C;progress> BEL
</code></pre>
<ul>
<li><code>ESC</code> is the escape character, ASCII 27.</li>
<li><code>BEL</code> is the bell character, ASCII 7.</li>
<li><code>&#x3C;state></code> is one of <code>0</code>, <code>1</code>, <code>2</code>, <code>3</code>, or <code>4</code>.
<ul>
<li><code>0</code> i s the default state, and indicates that the progress bar should be hidden. Use this state when the command is complete, to clear out any progress state.</li>
<li><code>1</code>: set progress value to <code>&#x3C;progress></code>, in the "default" state.</li>
<li><code>2</code>: set progress value to <code>&#x3C;progress></code>, in the "Error" state</li>
<li><code>3</code>: set the taskbar to the "Indeterminate" state. This is useful for commands that don't have a progress value, but are still running. This state ignores the <code>&#x3C;progress></code> value.</li>
<li><code>4</code>: set progress value to <code>&#x3C;progress></code>, in the "Warning" state</li>
</ul>
</li>
<li><code>&#x3C;progress></code> is a number between 0 and 100, inclusive.</li>
</ul>
<p>You can also refer to the ghostty documentation for <a href="https://ghostty.org/docs/vt/osc/conemu">ConEmu OSC 9;n Escape
Sequences</a></p>
<p>How great would it be to add that to other tools that compile or churn for a while? It's no replacement for other feedback since not all terminals implement this, but it seems like a good way to give extra information without an excess of printed characters that get picked up by logs.</p>
]]></content:encoded>
    <source:markdown><![CDATA[I notice that the blue bar at the top of the Ghostty terminal can do more than just bounce back and forth (like it does in Claude Code). I was tinkering with Ghostty (to add additional shader uniforms for focus change events) and when Zig builds, it updates the Ghostty progress bar as it builds, turning red if it fails.

I [found this "one-liner"](https://github.com/ghostty-org/ghostty/pull/8477#issuecomment-3302958474) (it's a _long_ line) by [@jcollie](https://github.com/jcollie) that outputs the OSC9;4 progress bar sequences:

```bash
 counter=1; while [ $counter -le 25 ]; do printf "\x1b]9;4;2;${counter}\x07"; ((counter++)); sleep 0.1; done; while [ $counter -le 50 ]; do printf "\x1b]9;4;1;${counter}\x07"; ((counter++)); sleep 0.1; done; while [ $counter -le 75 ]; do printf "\x1b]9;4;3\x07"; ((counter++)); sleep 0.1; done; while [ $counter -le 100 ]; do printf "\x1b]9;4;1;${counter}\x07"; ((counter++)); sleep 0.1; done; printf "\x1b]9;4;0\x07"
```

And here's the breakdown of the escape sequence (quoting from the [Windows Terminal docs of all places](https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences)):

```sh
ESC ] 9 ; 4 ; <state> ; <progress> BEL
```

- `ESC` is the escape character, ASCII 27.
- `BEL` is the bell character, ASCII 7.
- `<state>` is one of `0`, `1`, `2`, `3`, or `4`.
  - `0` i s the default state, and indicates that the progress bar should be hidden. Use this state when the command is complete, to clear out any progress state.
  - `1`: set progress value to `<progress>`, in the "default" state.
  - `2`: set progress value to `<progress>`, in the "Error" state
  - `3`: set the taskbar to the "Indeterminate" state. This is useful for commands that don't have a progress value, but are still running. This state ignores the `<progress>` value.
  - `4`: set progress value to `<progress>`, in the "Warning" state
- `<progress>` is a number between 0 and 100, inclusive.

You can also refer to the ghostty documentation for [ConEmu OSC 9;n Escape
Sequences](https://ghostty.org/docs/vt/osc/conemu)

How great would it be to add that to other tools that compile or churn for a while? It's no replacement for other feedback since not all terminals implement this, but it seems like a good way to give extra information without an excess of printed characters that get picked up by logs.]]></source:markdown>
  </item><item>
    
    <description>&lt;p&gt;&lt;a class=&quot;u-bookmark-of&quot; href=&quot;https://martinemde.com/models&quot;&gt;LLM FIM Tester&lt;/a&gt; — A Fill-In-The-Middle tester for large language models powered by OpenRouter.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2025/12/20/sharing-models</link>
    <guid isPermaLink="true">https://martinemde.com/blog/sharing-models</guid>
    <pubDate>Sat, 20 Dec 2025 19:14:28 GMT</pubDate>
    
    <content:encoded><![CDATA[<p><a class="u-bookmark-of" href="https://martinemde.com/models">LLM FIM Tester</a> — A Fill-In-The-Middle tester for large language models powered by OpenRouter.</p>
]]></content:encoded>
    <source:markdown><![CDATA[<a class="u-bookmark-of" href="https://martinemde.com/models">LLM FIM Tester</a> — A Fill-In-The-Middle tester for large language models powered by OpenRouter.]]></source:markdown>
  </item><item>
    
    <description>&lt;p&gt;&lt;a class=&quot;u-bookmark-of&quot; href=&quot;https://martinemde.com/toy&quot;&gt;Toy LLM&lt;/a&gt; — A Toy LLM proof-of-concept that uses only canned phrases to respond safely but still coherently.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2025/12/20/sharing-toy</link>
    <guid isPermaLink="true">https://martinemde.com/blog/sharing-toy</guid>
    <pubDate>Sat, 20 Dec 2025 19:14:28 GMT</pubDate>
    
    <content:encoded><![CDATA[<p><a class="u-bookmark-of" href="https://martinemde.com/toy">Toy LLM</a> — A Toy LLM proof-of-concept that uses only canned phrases to respond safely but still coherently.</p>
]]></content:encoded>
    <source:markdown><![CDATA[<a class="u-bookmark-of" href="https://martinemde.com/toy">Toy LLM</a> — A Toy LLM proof-of-concept that uses only canned phrases to respond safely but still coherently.]]></source:markdown>
  </item><item>
    <title>Show Your Work: How to write reviewable code</title>
    <description>&lt;p&gt;I still remember a code review I received early in my career. I had just started a new job and my new coworker taught me something in a code review I haven&apos;t forgotten. I think the lesson applies more than ever now that agents are writing more (most?) of our code.&lt;/p&gt;
&lt;p&gt;Having freshly joined, I made an commit to address excess white space and improper indentation that were bugging me (painstakingly, by hand, it was the 2000s) and along the way I made a few refactors that seemed obvious to me. I committed this all with the message &quot;White space fixes&quot; and submitted for review.&lt;/p&gt;
&lt;p&gt;My colleague quickly called me out. &quot;You said these were just white space fixes, why did this code here change?&quot; My explanation was something about not wasting the overhead of splitting it out. &quot;If I&apos;m scanning for white space changes, I don&apos;t want to suddenly have to review for code correctness.&quot; OK. I reset my commit, added back only the white space changes, and resubmitted them each separately. Both were commits accepted easily.&lt;/p&gt;
&lt;p&gt;However, the lesson stuck with me. Even though the code was fine, I had made it much harder for him to review. White space changes are easy to scroll through. Your only job is to catch accidental code changes, which is exactly what I inserted. I had broken his expectation about how long this code review would take, what level of care he needed to use, and how big an interruption this would be to his workflow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No one has to merge your code. They got shit to do.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The burden is on the author to submit code that can be reviewed easily. This is an often unspoken part of the senior engineer skill set, &lt;a href=&quot;https://obie.medium.com/what-happens-when-the-coding-becomes-the-least-interesting-part-of-the-work-ab10c213c660&quot; title=&quot;Teams rarely standardize or document senior thinking&quot;&gt;transferred by working together&lt;/a&gt; rather than explicitly taught. Before you learn this skill, you&apos;ll sometimes notice than your larger commits and clever refactors take a long time to get reviewed. You may even think this is the fault of your team or your process. Maybe it is, or maybe the whole team assumes that code review always takes forever so they should do it in big batches.&lt;/p&gt;
&lt;p&gt;The requirement to make your code reviewable does not change when agents write your code. Your responsibility for making your code reviewable is more important than ever. And what makes code reviewable? Put simply, &lt;strong&gt;show your work!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before agents, your work was a stream of reasonably concise commits, comments, tests, and code, submitted as incremental updates. Now, while all of the old stuff still matters, your work includes the specs, prompts, decisions, and references that you used to supply context to your agent. Combined these artifacts &lt;a href=&quot;https://simonwillison.net/2025/Dec/18/code-proven-to-work/&quot;&gt;prove&lt;/a&gt; that your change does what you claim it does.&lt;/p&gt;
&lt;p&gt;When a reviewer tries to understand the code, they need to assess whether you effectively achieved your goal. The larger your change the harder it is to understand all the various goals you had while making it. As the author, it&apos;s also much harder to prove, since there&apos;s so much more code to justify.&lt;/p&gt;
&lt;p&gt;Share your &lt;code&gt;PLAN.md&lt;/code&gt; early and seek feedback. &lt;strong&gt;I&apos;d much rather critique 2 pages of &lt;code&gt;PLAN.md&lt;/code&gt; rather than 20 pages of code.&lt;/strong&gt; You&apos;ll have a easier time generating code that can be reviewed effectively when someone has already seen what you&apos;re trying to achieve.&lt;/p&gt;
&lt;p&gt;Reviewing the plan, and including it with your code for review, also give the reviewer a chance to see your words and approach, and to offer real feedback about the work you’re actually doing. Resist the urge to add more than one plan to a unit of reviewable code. If you want to keep building after completing a plan, wait! &lt;a href=&quot;https://martinemde.com/blog/its-just-their-tokens&quot; title=&quot;Reviewing AI agent produced code should change how we code review&quot;&gt;Go review someone else&apos;s code&lt;/a&gt; first, then develop your next plan.&lt;/p&gt;
&lt;p&gt;Prove that you did what you set out to do, show it clearly in your code, tests, and commits. Separate refactors from features and cleanup. If something you find out you need to do after can be done before, reorder the commits. Break code into smaller chunks to make them easier to review, build them in layers of abstraction and keep changes focused.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2025/12/19/show-your-work</link>
    <guid isPermaLink="true">https://martinemde.com/blog/show-your-work</guid>
    <pubDate>Fri, 19 Dec 2025 00:00:00 GMT</pubDate>
    
    <content:encoded><![CDATA[<p>I still remember a code review I received early in my career. I had just started a new job and my new coworker taught me something in a code review I haven't forgotten. I think the lesson applies more than ever now that agents are writing more (most?) of our code.</p>
<p>Having freshly joined, I made an commit to address excess white space and improper indentation that were bugging me (painstakingly, by hand, it was the 2000s) and along the way I made a few refactors that seemed obvious to me. I committed this all with the message "White space fixes" and submitted for review.</p>
<p>My colleague quickly called me out. "You said these were just white space fixes, why did this code here change?" My explanation was something about not wasting the overhead of splitting it out. "If I'm scanning for white space changes, I don't want to suddenly have to review for code correctness." OK. I reset my commit, added back only the white space changes, and resubmitted them each separately. Both were commits accepted easily.</p>
<p>However, the lesson stuck with me. Even though the code was fine, I had made it much harder for him to review. White space changes are easy to scroll through. Your only job is to catch accidental code changes, which is exactly what I inserted. I had broken his expectation about how long this code review would take, what level of care he needed to use, and how big an interruption this would be to his workflow.</p>
<p><strong>No one has to merge your code. They got shit to do.</strong></p>
<p>The burden is on the author to submit code that can be reviewed easily. This is an often unspoken part of the senior engineer skill set, <a href="https://obie.medium.com/what-happens-when-the-coding-becomes-the-least-interesting-part-of-the-work-ab10c213c660" title="Teams rarely standardize or document senior thinking">transferred by working together</a> rather than explicitly taught. Before you learn this skill, you'll sometimes notice than your larger commits and clever refactors take a long time to get reviewed. You may even think this is the fault of your team or your process. Maybe it is, or maybe the whole team assumes that code review always takes forever so they should do it in big batches.</p>
<p>The requirement to make your code reviewable does not change when agents write your code. Your responsibility for making your code reviewable is more important than ever. And what makes code reviewable? Put simply, <strong>show your work!</strong></p>
<p>Before agents, your work was a stream of reasonably concise commits, comments, tests, and code, submitted as incremental updates. Now, while all of the old stuff still matters, your work includes the specs, prompts, decisions, and references that you used to supply context to your agent. Combined these artifacts <a href="https://simonwillison.net/2025/Dec/18/code-proven-to-work/">prove</a> that your change does what you claim it does.</p>
<p>When a reviewer tries to understand the code, they need to assess whether you effectively achieved your goal. The larger your change the harder it is to understand all the various goals you had while making it. As the author, it's also much harder to prove, since there's so much more code to justify.</p>
<p>Share your <code>PLAN.md</code> early and seek feedback. <strong>I'd much rather critique 2 pages of <code>PLAN.md</code> rather than 20 pages of code.</strong> You'll have a easier time generating code that can be reviewed effectively when someone has already seen what you're trying to achieve.</p>
<p>Reviewing the plan, and including it with your code for review, also give the reviewer a chance to see your words and approach, and to offer real feedback about the work you’re actually doing. Resist the urge to add more than one plan to a unit of reviewable code. If you want to keep building after completing a plan, wait! <a href="https://martinemde.com/blog/its-just-their-tokens" title="Reviewing AI agent produced code should change how we code review">Go review someone else's code</a> first, then develop your next plan.</p>
<p>Prove that you did what you set out to do, show it clearly in your code, tests, and commits. Separate refactors from features and cleanup. If something you find out you need to do after can be done before, reorder the commits. Break code into smaller chunks to make them easier to review, build them in layers of abstraction and keep changes focused.</p>
]]></content:encoded>
    <source:markdown><![CDATA[I still remember a code review I received early in my career. I had just started a new job and my new coworker taught me something in a code review I haven't forgotten. I think the lesson applies more than ever now that agents are writing more (most?) of our code.

Having freshly joined, I made an commit to address excess white space and improper indentation that were bugging me (painstakingly, by hand, it was the 2000s) and along the way I made a few refactors that seemed obvious to me. I committed this all with the message "White space fixes" and submitted for review.

My colleague quickly called me out. "You said these were just white space fixes, why did this code here change?" My explanation was something about not wasting the overhead of splitting it out. "If I'm scanning for white space changes, I don't want to suddenly have to review for code correctness." OK. I reset my commit, added back only the white space changes, and resubmitted them each separately. Both were commits accepted easily.

However, the lesson stuck with me. Even though the code was fine, I had made it much harder for him to review. White space changes are easy to scroll through. Your only job is to catch accidental code changes, which is exactly what I inserted. I had broken his expectation about how long this code review would take, what level of care he needed to use, and how big an interruption this would be to his workflow.

**No one has to merge your code. They got shit to do.**

The burden is on the author to submit code that can be reviewed easily. This is an often unspoken part of the senior engineer skill set, [transferred by working together][senior] rather than explicitly taught. Before you learn this skill, you'll sometimes notice than your larger commits and clever refactors take a long time to get reviewed. You may even think this is the fault of your team or your process. Maybe it is, or maybe the whole team assumes that code review always takes forever so they should do it in big batches.

The requirement to make your code reviewable does not change when agents write your code. Your responsibility for making your code reviewable is more important than ever. And what makes code reviewable? Put simply, **show your work!**

Before agents, your work was a stream of reasonably concise commits, comments, tests, and code, submitted as incremental updates. Now, while all of the old stuff still matters, your work includes the specs, prompts, decisions, and references that you used to supply context to your agent. Combined these artifacts [prove][prove] that your change does what you claim it does.

When a reviewer tries to understand the code, they need to assess whether you effectively achieved your goal. The larger your change the harder it is to understand all the various goals you had while making it. As the author, it's also much harder to prove, since there's so much more code to justify.

Share your `PLAN.md` early and seek feedback. **I'd much rather critique 2 pages of `PLAN.md` rather than 20 pages of code.** You'll have a easier time generating code that can be reviewed effectively when someone has already seen what you're trying to achieve.

Reviewing the plan, and including it with your code for review, also give the reviewer a chance to see your words and approach, and to offer real feedback about the work you’re actually doing. Resist the urge to add more than one plan to a unit of reviewable code. If you want to keep building after completing a plan, wait! [Go review someone else's code][review] first, then develop your next plan.

Prove that you did what you set out to do, show it clearly in your code, tests, and commits. Separate refactors from features and cleanup. If something you find out you need to do after can be done before, reorder the commits. Break code into smaller chunks to make them easier to review, build them in layers of abstraction and keep changes focused.

[senior]: https://obie.medium.com/what-happens-when-the-coding-becomes-the-least-interesting-part-of-the-work-ab10c213c660 'Teams rarely standardize or document senior thinking'
[review]: /blog/its-just-their-tokens 'Reviewing AI agent produced code should change how we code review'
[prove]: https://simonwillison.net/2025/Dec/18/code-proven-to-work/]]></source:markdown>
  </item><item>
    <title>It&apos;s Just Their Tokens: Code Review Etiquette in the Vibe Era</title>
    <description>&lt;p&gt;A common complaint about the usage of AI coding agents is the burden it places on code reviewers. Engineers often resist switching their mental context to review code. Finding out that you&apos;re now expected to review someone else&apos;s low-effort slop can almost feel like an insult.&lt;/p&gt;
&lt;p&gt;Code review has long been a bottle neck and agents are making it worse. More code, produced faster and with less oversight increases the burden on reviewers. And yet, code reviews are an essential part of sharing knowledge in software engineering. They follow an etiquette that is meant to respect the effort of the author and encourage sharing of knowledge.&lt;/p&gt;
&lt;p&gt;Meanwhile, the expectations placed on the code reviewer, and the bleak future where all we do is review the output of coding agents, feels impossibly unbalanced. Much of this etiquette, and the expectations placed on the reviewer, assumes that writing code is slow and hard. This poses a problem if we want engineers to be effective with AI agents. We need code review to teach, learn, and ensure quality.&lt;/p&gt;
&lt;p&gt;How can we address the increasing demand for review without destroying code review or creating new bottlenecks?&lt;/p&gt;
&lt;h2&gt;Review Etiquette Must Change&lt;/h2&gt;
&lt;p&gt;The length of a PR is no longer a good predictor of effort. If you&apos;re staring down an angry bowl of vibe soup, &lt;strong&gt;you&apos;ll do the author a service by explaining the words they need to say to their agent to produce the quality you&apos;re expecting.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Normally reviewers might shy away from asking for a big architectural change in code reviews. Don&apos;t. Big architecture changes are not as difficult as they once were and vibe code can be re-vibed easily with better requirements. &lt;strong&gt;Reviewers should reject sloppy code as long as they make their expectations clear.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;How do we balance the effort to review the code with the effort to generate the code? My advice, keep the effort proportional. If a day of work in the before times took 20 minutes to review, then an hour of work should take only a few minutes. Allowing that hour to take 20 minutes threatens the balance of work between author and reviewer. This means the burden of quality, and the obligation to prove that quality, must remain on the author.&lt;/p&gt;
&lt;p&gt;If the author submitted a big mess, ask for a big solution. If the slop is high, simply scan to understand their goal and then respond with the architecture or solution you expected and why. Leave high level comments for code that lacks deep consideration. Ask questions that help you both understand the problem and the possible solutions. Focus on what would make their code easier to review.&lt;/p&gt;
&lt;p&gt;This isn&apos;t a blank check to be a jerk. Kindness and respect are still table stakes. If something doesn&apos;t meet your quality standards, nitpicking every problem line-by-line is counterproductive. Be direct about what you expect overall, ask questions about their goals, and learn how they arrived at the solution. Your input may help improve the quality and readability of the author&apos;s future code and maybe you&apos;ll even learn something yourself.&lt;/p&gt;
&lt;p&gt;If an author drops a giant review on your lap without warning, vibe coded or not, don&apos;t be afraid push back. Careful code review is limited resource. It’s OK to expect the author to make code review easier. You&apos;re not imposing on the author. This isn&apos;t their blood, sweat, and tears, it&apos;s just their tokens.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;My first draft of this blog post was about 4x longer and half as good. Thanks to my teammate Denis and my “infinitely patient with me wife” Kewe for their reviews. You both helped me merge a better blog post.&lt;/em&gt;&lt;/p&gt;
</description>
    <link>https://martinemde.com/2025/12/18/its-just-their-tokens</link>
    <guid isPermaLink="true">https://martinemde.com/blog/its-just-their-tokens</guid>
    <pubDate>Thu, 18 Dec 2025 00:00:00 GMT</pubDate>
    
    <content:encoded><![CDATA[<p>A common complaint about the usage of AI coding agents is the burden it places on code reviewers. Engineers often resist switching their mental context to review code. Finding out that you're now expected to review someone else's low-effort slop can almost feel like an insult.</p>
<p>Code review has long been a bottle neck and agents are making it worse. More code, produced faster and with less oversight increases the burden on reviewers. And yet, code reviews are an essential part of sharing knowledge in software engineering. They follow an etiquette that is meant to respect the effort of the author and encourage sharing of knowledge.</p>
<p>Meanwhile, the expectations placed on the code reviewer, and the bleak future where all we do is review the output of coding agents, feels impossibly unbalanced. Much of this etiquette, and the expectations placed on the reviewer, assumes that writing code is slow and hard. This poses a problem if we want engineers to be effective with AI agents. We need code review to teach, learn, and ensure quality.</p>
<p>How can we address the increasing demand for review without destroying code review or creating new bottlenecks?</p>
<h2>Review Etiquette Must Change</h2>
<p>The length of a PR is no longer a good predictor of effort. If you're staring down an angry bowl of vibe soup, <strong>you'll do the author a service by explaining the words they need to say to their agent to produce the quality you're expecting.</strong></p>
<p>Normally reviewers might shy away from asking for a big architectural change in code reviews. Don't. Big architecture changes are not as difficult as they once were and vibe code can be re-vibed easily with better requirements. <strong>Reviewers should reject sloppy code as long as they make their expectations clear.</strong></p>
<p>How do we balance the effort to review the code with the effort to generate the code? My advice, keep the effort proportional. If a day of work in the before times took 20 minutes to review, then an hour of work should take only a few minutes. Allowing that hour to take 20 minutes threatens the balance of work between author and reviewer. This means the burden of quality, and the obligation to prove that quality, must remain on the author.</p>
<p>If the author submitted a big mess, ask for a big solution. If the slop is high, simply scan to understand their goal and then respond with the architecture or solution you expected and why. Leave high level comments for code that lacks deep consideration. Ask questions that help you both understand the problem and the possible solutions. Focus on what would make their code easier to review.</p>
<p>This isn't a blank check to be a jerk. Kindness and respect are still table stakes. If something doesn't meet your quality standards, nitpicking every problem line-by-line is counterproductive. Be direct about what you expect overall, ask questions about their goals, and learn how they arrived at the solution. Your input may help improve the quality and readability of the author's future code and maybe you'll even learn something yourself.</p>
<p>If an author drops a giant review on your lap without warning, vibe coded or not, don't be afraid push back. Careful code review is limited resource. It’s OK to expect the author to make code review easier. You're not imposing on the author. This isn't their blood, sweat, and tears, it's just their tokens.</p>
<hr>
<p><em>My first draft of this blog post was about 4x longer and half as good. Thanks to my teammate Denis and my “infinitely patient with me wife” Kewe for their reviews. You both helped me merge a better blog post.</em></p>
]]></content:encoded>
    <source:markdown><![CDATA[A common complaint about the usage of AI coding agents is the burden it places on code reviewers. Engineers often resist switching their mental context to review code. Finding out that you're now expected to review someone else's low-effort slop can almost feel like an insult.

Code review has long been a bottle neck and agents are making it worse. More code, produced faster and with less oversight increases the burden on reviewers. And yet, code reviews are an essential part of sharing knowledge in software engineering. They follow an etiquette that is meant to respect the effort of the author and encourage sharing of knowledge.

Meanwhile, the expectations placed on the code reviewer, and the bleak future where all we do is review the output of coding agents, feels impossibly unbalanced. Much of this etiquette, and the expectations placed on the reviewer, assumes that writing code is slow and hard. This poses a problem if we want engineers to be effective with AI agents. We need code review to teach, learn, and ensure quality.

How can we address the increasing demand for review without destroying code review or creating new bottlenecks?

## Review Etiquette Must Change

The length of a PR is no longer a good predictor of effort. If you're staring down an angry bowl of vibe soup, **you'll do the author a service by explaining the words they need to say to their agent to produce the quality you're expecting.**

Normally reviewers might shy away from asking for a big architectural change in code reviews. Don't. Big architecture changes are not as difficult as they once were and vibe code can be re-vibed easily with better requirements. **Reviewers should reject sloppy code as long as they make their expectations clear.**

How do we balance the effort to review the code with the effort to generate the code? My advice, keep the effort proportional. If a day of work in the before times took 20 minutes to review, then an hour of work should take only a few minutes. Allowing that hour to take 20 minutes threatens the balance of work between author and reviewer. This means the burden of quality, and the obligation to prove that quality, must remain on the author.

If the author submitted a big mess, ask for a big solution. If the slop is high, simply scan to understand their goal and then respond with the architecture or solution you expected and why. Leave high level comments for code that lacks deep consideration. Ask questions that help you both understand the problem and the possible solutions. Focus on what would make their code easier to review.

This isn't a blank check to be a jerk. Kindness and respect are still table stakes. If something doesn't meet your quality standards, nitpicking every problem line-by-line is counterproductive. Be direct about what you expect overall, ask questions about their goals, and learn how they arrived at the solution. Your input may help improve the quality and readability of the author's future code and maybe you'll even learn something yourself.

If an author drops a giant review on your lap without warning, vibe coded or not, don't be afraid push back. Careful code review is limited resource. It’s OK to expect the author to make code review easier. You're not imposing on the author. This isn't their blood, sweat, and tears, it's just their tokens.

---

_My first draft of this blog post was about 4x longer and half as good. Thanks to my teammate Denis and my “infinitely patient with me wife” Kewe for their reviews. You both helped me merge a better blog post._]]></source:markdown>
  </item><item>
    
    <description>&lt;p&gt;&lt;a class=&quot;u-bookmark-of&quot; href=&quot;https://banchobox.com&quot;&gt;BanchoBox&lt;/a&gt; — A companion app for Dave the Diver. Lists all the dishes and fishes in the game so you can sort, filter, and min-max every tiny detail.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2025/12/14/sharing-banchobox</link>
    <guid isPermaLink="true">https://martinemde.com/blog/sharing-banchobox</guid>
    <pubDate>Sun, 14 Dec 2025 23:36:53 GMT</pubDate>
    
    <content:encoded><![CDATA[<p><a class="u-bookmark-of" href="https://banchobox.com">BanchoBox</a> — A companion app for Dave the Diver. Lists all the dishes and fishes in the game so you can sort, filter, and min-max every tiny detail.</p>
]]></content:encoded>
    <source:markdown><![CDATA[<a class="u-bookmark-of" href="https://banchobox.com">BanchoBox</a> — A companion app for Dave the Diver. Lists all the dishes and fishes in the game so you can sort, filter, and min-max every tiny detail.]]></source:markdown>
  </item><item>
    
    <description>&lt;p&gt;&lt;a class=&quot;u-bookmark-of&quot; href=&quot;https://github.com/martinemde/dotfiles&quot;&gt;dotfiles&lt;/a&gt; — I got nerdsniped into caring about my dotfiles and changed my whole dev environment. Now managed by chezmoi and Claude.&lt;/p&gt;
</description>
    <link>https://martinemde.com/2025/12/14/sharing-dotfiles</link>
    <guid isPermaLink="true">https://martinemde.com/blog/sharing-dotfiles</guid>
    <pubDate>Sun, 14 Dec 2025 23:36:53 GMT</pubDate>
    
    <content:encoded><![CDATA[<p><a class="u-bookmark-of" href="https://github.com/martinemde/dotfiles">dotfiles</a> — I got nerdsniped into caring about my dotfiles and changed my whole dev environment. Now managed by chezmoi and Claude.</p>
]]></content:encoded>
    <source:markdown><![CDATA[<a class="u-bookmark-of" href="https://github.com/martinemde/dotfiles">dotfiles</a> — I got nerdsniped into caring about my dotfiles and changed my whole dev environment. Now managed by chezmoi and Claude.]]></source:markdown>
  </item>
	</channel>
</rss>