<rss xmlns:source="http://source.scripting.com/" version="2.0">
  <channel>
    <title>LLBBL Blog</title>
    <link>https://llbbl.blog/</link>
    <description></description>
    
    <language>en</language>
    
    <lastBuildDate>Sun, 19 Apr 2026 10:00:00 -0500</lastBuildDate>
    <item>
      <title>AI-Assisted vs AI-Agentic Coding</title>
      <link>https://llbbl.blog/2026/04/19/aiassisted-vs-aiagentic-coding.html</link>
      <pubDate>Sun, 19 Apr 2026 10:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/19/aiassisted-vs-aiagentic-coding.html</guid>
      <description>&lt;p&gt;There are two ways to work (c0de) with AI tools right now. I think most people know the other one exists, but they haven&amp;rsquo;t taken the time to try it. You should know how to do both. And when to do both.&lt;/p&gt;
&lt;h2 id=&#34;assisted-mode&#34;&gt;Assisted Mode&lt;/h2&gt;
&lt;p&gt;Everybody knows this one. You write some code, you get stuck, you ask a question.&lt;/p&gt;
&lt;p&gt;How does date parsing work in Python? What&amp;rsquo;s this function do? Haven&amp;rsquo;t we built this already? I need some fucking Regex again.&lt;/p&gt;
&lt;p&gt;The AI answers. You copy-paste or accept the suggestion. You keep going. You&amp;rsquo;re driving. The AI is in the passenger seat reading the map.&lt;/p&gt;
&lt;p&gt;I mean, this is really useful. I&amp;rsquo;m not going to pretend it isn&amp;rsquo;t. It&amp;rsquo;s also just autocomplete with opinions. Fancy autocomplete. Smart autocomplete.&lt;/p&gt;
&lt;p&gt;Great. You&amp;rsquo;re doing the thinking. You&amp;rsquo;re deciding what gets built and how to structure it and what order to do things in. You&amp;rsquo;re just asking for help on some of the blanks. That&amp;rsquo;s assisted mode.&lt;/p&gt;
&lt;h2 id=&#34;agentic-mode&#34;&gt;Agentic Mode&lt;/h2&gt;
&lt;p&gt;This is different.&lt;/p&gt;
&lt;p&gt;You describe what you want. You need to know how to describe what you want.&lt;/p&gt;
&lt;p&gt;That is extremely important. Let me say that again. You need to know how to describe what you want.&lt;/p&gt;
&lt;p&gt;You need to build an agent that understands how to interpret your description as what you want.&lt;/p&gt;
&lt;p&gt;Sometimes it&amp;rsquo;s going to get it correct and sometimes it&amp;rsquo;s not. It&amp;rsquo;s going to go in a different direction than you wanted and you&amp;rsquo;re going to have to correct it. That&amp;rsquo;s the job now. You&amp;rsquo;re reviewing the output, the code, and how it&amp;rsquo;s producing the code. What are the gaps? You have to find the gaps and improve the agent so that it understands you better.&lt;/p&gt;
&lt;h2 id=&#34;when-i-use-which&#34;&gt;When I Use Which&lt;/h2&gt;
&lt;p&gt;I wish I had a clean rule for this. I don&amp;rsquo;t. That&amp;rsquo;s the vibes part.&lt;/p&gt;
&lt;p&gt;Small or specific things can be assisted. Quick answers. Great. Easy. Move on.&lt;/p&gt;
&lt;p&gt;Once you start wanting to touch multiple files, agentic. Major features like commands or parser changes or handler rewrites, recipes or tests. I&amp;rsquo;m not writing all that by hand. I can describe what I want way better than I can autocomplete it.&lt;/p&gt;
&lt;p&gt;Bug fixes? Depends. If I already know where the bug is, assisted. If I don&amp;rsquo;t, agentic. Let the agent grep around and figure it out. It&amp;rsquo;s better at reading a whole codebase quickly than I am. Not better at understanding it. Better at reading it.&lt;/p&gt;
&lt;p&gt;New features? Almost always agentic. I describe the feature, point it at similar code in the repo, and let it go.&lt;/p&gt;
&lt;p&gt;Again, review is super important. Sometimes you have to send it back or start over or change major portions of it. And if you build a system that learns, it&amp;rsquo;ll get better along the way.&lt;/p&gt;
&lt;h2 id=&#34;the-review-problem&#34;&gt;The Review Problem&lt;/h2&gt;
&lt;p&gt;Switching to agentic mode, your entire job is code review. All day, all the time, constant. That&amp;rsquo;s the human&amp;rsquo;s job. Code review.&lt;/p&gt;
&lt;p&gt;Are you good at code review? You should get better at it. You need to get better at it.&lt;/p&gt;
&lt;p&gt;This is not whether or not the tests pass. You need to identify possible issues and then describe tests that can check for those issues.&lt;/p&gt;
&lt;p&gt;The nuanced bugs are the worst. And if those make it to production, you&amp;rsquo;re going to have problems.&lt;/p&gt;
&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Don&amp;rsquo;t skim the diff.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;That should be the new motto. Read the code. Get better at code comprehension. It&amp;rsquo;s extremely important. You may be writing less code but you need to sure as shit understand what the code is doing and how it can be bad.&lt;/p&gt;
&lt;h2 id=&#34;the-hybrid-reality&#34;&gt;The Hybrid Reality&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s totally fine to switch between modes depending on what you&amp;rsquo;re doing or your work session. Agentic can be way more impactful, but assisted mode is way better at helping you understand what the code is doing because you can select code blocks and easily ask questions about it.&lt;/p&gt;
&lt;p&gt;So it&amp;rsquo;s not a toggle, it&amp;rsquo;s a spectrum. Now isn&amp;rsquo;t that funny? I&amp;rsquo;m on the spectrum of agentic development.&lt;/p&gt;
&lt;p&gt;Where are you on the spectrum of agentic development?&lt;/p&gt;
&lt;h2 id=&#34;so-which-is-better&#34;&gt;So Which Is Better?&lt;/h2&gt;
&lt;p&gt;Neither. Both. It depends. Whatever, just build stuff.&lt;/p&gt;
&lt;p&gt;Is assisted mode safer? Really? Like, does the human actually write better code this way? I don&amp;rsquo;t know. Agentic mode can be faster and you need to be super careful that it&amp;rsquo;s not gaslighting you into thinking it knows what it&amp;rsquo;s doing.&lt;/p&gt;
&lt;p&gt;Build software for you. And when it makes sense, help out with the community stuff. Support open source.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re a developer, I&amp;rsquo;d appreciate a follow. You can subscribe with your email below. The emails go out once a week. Or you can find me on Mastodon at &lt;a href=&#34;https://micro.blog/llbbl?remote_follow=1&#34;&gt;@logan@llbbl.blog&lt;/a&gt;.&lt;/p&gt;
</description>
      <source:markdown>There are two ways to work (c0de) with AI tools right now. I think most people know the other one exists, but they haven&#39;t taken the time to try it. You should know how to do both. And when to do both.

## Assisted Mode

Everybody knows this one. You write some code, you get stuck, you ask a question.

How does date parsing work in Python? What&#39;s this function do? Haven&#39;t we built this already? I need some fucking Regex again.

The AI answers. You copy-paste or accept the suggestion. You keep going. You&#39;re driving. The AI is in the passenger seat reading the map.

I mean, this is really useful. I&#39;m not going to pretend it isn&#39;t. It&#39;s also just autocomplete with opinions. Fancy autocomplete. Smart autocomplete.

Great. You&#39;re doing the thinking. You&#39;re deciding what gets built and how to structure it and what order to do things in. You&#39;re just asking for help on some of the blanks. That&#39;s assisted mode.

## Agentic Mode

This is different.

You describe what you want. You need to know how to describe what you want.

That is extremely important. Let me say that again. You need to know how to describe what you want.

You need to build an agent that understands how to interpret your description as what you want.

Sometimes it&#39;s going to get it correct and sometimes it&#39;s not. It&#39;s going to go in a different direction than you wanted and you&#39;re going to have to correct it. That&#39;s the job now. You&#39;re reviewing the output, the code, and how it&#39;s producing the code. What are the gaps? You have to find the gaps and improve the agent so that it understands you better.

## When I Use Which

I wish I had a clean rule for this. I don&#39;t. That&#39;s the vibes part.

Small or specific things can be assisted. Quick answers. Great. Easy. Move on.

Once you start wanting to touch multiple files, agentic. Major features like commands or parser changes or handler rewrites, recipes or tests. I&#39;m not writing all that by hand. I can describe what I want way better than I can autocomplete it.

Bug fixes? Depends. If I already know where the bug is, assisted. If I don&#39;t, agentic. Let the agent grep around and figure it out. It&#39;s better at reading a whole codebase quickly than I am. Not better at understanding it. Better at reading it.

New features? Almost always agentic. I describe the feature, point it at similar code in the repo, and let it go.

Again, review is super important. Sometimes you have to send it back or start over or change major portions of it. And if you build a system that learns, it&#39;ll get better along the way.

## The Review Problem

Switching to agentic mode, your entire job is code review. All day, all the time, constant. That&#39;s the human&#39;s job. Code review.

Are you good at code review? You should get better at it. You need to get better at it.

This is not whether or not the tests pass. You need to identify possible issues and then describe tests that can check for those issues.

The nuanced bugs are the worst. And if those make it to production, you&#39;re going to have problems.

&gt;&gt; Don&#39;t skim the diff.

That should be the new motto. Read the code. Get better at code comprehension. It&#39;s extremely important. You may be writing less code but you need to sure as shit understand what the code is doing and how it can be bad.

## The Hybrid Reality

It&#39;s totally fine to switch between modes depending on what you&#39;re doing or your work session. Agentic can be way more impactful, but assisted mode is way better at helping you understand what the code is doing because you can select code blocks and easily ask questions about it.

So it&#39;s not a toggle, it&#39;s a spectrum. Now isn&#39;t that funny? I&#39;m on the spectrum of agentic development.

Where are you on the spectrum of agentic development?

## So Which Is Better?

Neither. Both. It depends. Whatever, just build stuff.

Is assisted mode safer? Really? Like, does the human actually write better code this way? I don&#39;t know. Agentic mode can be faster and you need to be super careful that it&#39;s not gaslighting you into thinking it knows what it&#39;s doing.

Build software for you. And when it makes sense, help out with the community stuff. Support open source.

If you&#39;re a developer, I&#39;d appreciate a follow. You can subscribe with your email below. The emails go out once a week. Or you can find me on Mastodon at [@logan@llbbl.blog](https://micro.blog/llbbl?remote_follow=1).
</source:markdown>
    </item>
    
    <item>
      <title>Most Dev Blogs Die</title>
      <link>https://llbbl.blog/2026/04/18/most-dev-blogs-die.html</link>
      <pubDate>Sat, 18 Apr 2026 14:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/18/most-dev-blogs-die.html</guid>
      <description>&lt;p&gt;#MDBD&lt;/p&gt;
&lt;p&gt;Most developers have started a blog at some point. Almost none of them are still writing.&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t start a blog. You already know the reasons why you should.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s good for your career,&lt;/li&gt;
&lt;li&gt;it helps you learn,&lt;/li&gt;
&lt;li&gt;it builds an audience,&lt;/li&gt;
&lt;li&gt;whatever.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Maybe the interesting question isn&amp;rsquo;t &amp;ldquo;why should I blog&amp;rdquo; but &amp;ldquo;why did I stop.&amp;rdquo;&lt;/p&gt;
&lt;h2 id=&#34;the-work-nobody-talks-about&#34;&gt;The Work Nobody Talks About&lt;/h2&gt;
&lt;p&gt;Writing at 30% the effort. NO wait. I mean Writing is just a small part of it. Yea, right.&lt;/p&gt;
&lt;p&gt;You might, have you considered, possibly, that you perhaps, need a topic.&lt;/p&gt;
&lt;p&gt;You need to know what you are talking about, you need to research it.&lt;/p&gt;
&lt;p&gt;Truth matters, I guess, probably still right?&lt;/p&gt;
&lt;p&gt;I mean, certainly add to the conversation, that that matters.&lt;/p&gt;
&lt;p&gt;Publishing is the easy part. Maintaining a unique presence on multiple platforms is not.&lt;/p&gt;
&lt;p&gt;What started as &amp;ldquo;I should write about this thing I learned&amp;rdquo; quickly turns into a quarter-day or half-day ordeal.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s one post. Now there is tomorrow. And after that, another day.&lt;/p&gt;
&lt;p&gt;You need a topic.
You need a topic.
You need a topic.
You need a topic.&lt;/p&gt;
&lt;h2 id=&#34;it-becomes-work&#34;&gt;It Becomes Work&lt;/h2&gt;
&lt;p&gt;At some point, blogging stops being fun and interesting and becomes something you have to do. It feels like an obligation. You&amp;rsquo;re forcing yourself to sit down and write.&lt;/p&gt;
&lt;p&gt;This matters to me because I&amp;rsquo;m currently on a daily publishing streak. Every day is a struggle on what to talk about.&lt;/p&gt;
&lt;p&gt;You know what&amp;rsquo;s fun? Side projects. Building things. Tinkering with a new language on a Monday morning because you saw someone post about it. Writing about that? Easy. Staring at a blank editor trying to come up with something insightful about React server components? Sometimes fun.&lt;/p&gt;
&lt;p&gt;Write about the fun stuff. The rest will follow. Or it won&amp;rsquo;t. That&amp;rsquo;s fine too.&lt;/p&gt;
&lt;h2 id=&#34;what-actually-keeps-one-going&#34;&gt;What Actually Keeps One Going&lt;/h2&gt;
&lt;p&gt;I don&amp;rsquo;t have all the answers. I barely have some of the answers. But here&amp;rsquo;s what I&amp;rsquo;m doing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I built a system.&lt;/strong&gt; A whole content pipeline. CLI tools. Posts go through a lifecycle: draft, schedule, push. The system is not responsible for inspiration. It just managed those things. Markdown.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I lowered the bar.&lt;/strong&gt; Way down. Not everything has to be a certain number of words. I don&amp;rsquo;t even know what my best-performing posts are. Isn&amp;rsquo;t that crazy? I have no idea. I don&amp;rsquo;t check. I&amp;rsquo;ll care later, maybe.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I write about what I&amp;rsquo;m already doing.&lt;/strong&gt; What are you working on right now? That&amp;rsquo;s your next post. What are you interested in? What are you using that could be better? There. Done. You have a topic.&lt;/p&gt;
&lt;p&gt;No wait, nevermind. Ah crap, I need another topic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I stopped optimizing.&lt;/strong&gt; You don&amp;rsquo;t have to figure out all the things you should be doing. Keywords. Thumbnails. Social media strategy. Just get the words out. The words are the hard part. Everything else is a distraction disguised as productivity.&lt;/p&gt;
&lt;h2 id=&#34;the-commitment-problem&#34;&gt;The Commitment Problem&lt;/h2&gt;
&lt;p&gt;Blogging is a commitment. That&amp;rsquo;s the whole problem. Nobody is going to fire you for not posting this week. Nobody is likley two notice. :D&lt;/p&gt;
&lt;p&gt;Streaks work for some people and not others. I&amp;rsquo;m on day 102 of one. Don&amp;rsquo;t ask me why I doing this to myself.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re a developer, I&amp;rsquo;d appreciate a follow. You can subscribe with your email below. The emails go out once a week. Or you can find me on Mastodon at &lt;a href=&#34;https://micro.blog/llbbl?remote_follow=1&#34;&gt;@logan@llbbl.blog&lt;/a&gt;.&lt;/p&gt;
</description>
      <source:markdown>#MDBD

Most developers have started a blog at some point. Almost none of them are still writing.

Don&#39;t start a blog. You already know the reasons why you should.

 - It&#39;s good for your career, 
 - it helps you learn, 
 - it builds an audience, 
 - whatever. 
 
Maybe the interesting question isn&#39;t &#34;why should I blog&#34; but &#34;why did I stop.&#34;

## The Work Nobody Talks About

Writing at 30% the effort. NO wait. I mean Writing is just a small part of it. Yea, right.

You might, have you considered, possibly, that you perhaps, need a topic. 

You need to know what you are talking about, you need to research it. 

Truth matters, I guess, probably still right?

I mean, certainly add to the conversation, that that matters.

Publishing is the easy part. Maintaining a unique presence on multiple platforms is not. 

What started as &#34;I should write about this thing I learned&#34; quickly turns into a quarter-day or half-day ordeal.

And that&#39;s one post. Now there is tomorrow. And after that, another day.

You need a topic.
You need a topic.
You need a topic.
You need a topic.

## It Becomes Work

At some point, blogging stops being fun and interesting and becomes something you have to do. It feels like an obligation. You&#39;re forcing yourself to sit down and write.

This matters to me because I&#39;m currently on a daily publishing streak. Every day is a struggle on what to talk about.

You know what&#39;s fun? Side projects. Building things. Tinkering with a new language on a Monday morning because you saw someone post about it. Writing about that? Easy. Staring at a blank editor trying to come up with something insightful about React server components? Sometimes fun.

Write about the fun stuff. The rest will follow. Or it won&#39;t. That&#39;s fine too.

## What Actually Keeps One Going

I don&#39;t have all the answers. I barely have some of the answers. But here&#39;s what I&#39;m doing.

**I built a system.** A whole content pipeline. CLI tools. Posts go through a lifecycle: draft, schedule, push. The system is not responsible for inspiration. It just managed those things. Markdown.

**I lowered the bar.** Way down. Not everything has to be a certain number of words. I don&#39;t even know what my best-performing posts are. Isn&#39;t that crazy? I have no idea. I don&#39;t check. I&#39;ll care later, maybe.

**I write about what I&#39;m already doing.** What are you working on right now? That&#39;s your next post. What are you interested in? What are you using that could be better? There. Done. You have a topic.

No wait, nevermind. Ah crap, I need another topic.

**I stopped optimizing.** You don&#39;t have to figure out all the things you should be doing. Keywords. Thumbnails. Social media strategy. Just get the words out. The words are the hard part. Everything else is a distraction disguised as productivity.

## The Commitment Problem

Blogging is a commitment. That&#39;s the whole problem. Nobody is going to fire you for not posting this week. Nobody is likley two notice. :D 

Streaks work for some people and not others. I&#39;m on day 102 of one. Don&#39;t ask me why I doing this to myself.

If you&#39;re a developer, I&#39;d appreciate a follow. You can subscribe with your email below. The emails go out once a week. Or you can find me on Mastodon at [@logan@llbbl.blog](https://micro.blog/llbbl?remote_follow=1).
</source:markdown>
    </item>
    
    <item>
      <title></title>
      <link>https://llbbl.blog/2026/04/18/reply-to-this-with-blog.html</link>
      <pubDate>Sat, 18 Apr 2026 11:58:17 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/18/reply-to-this-with-blog.html</guid>
      <description>&lt;p&gt;reply to this with blog post ideas. your neighborhood tech blogger thanks you.&lt;/p&gt;
</description>
      <source:markdown>reply to this with blog post ideas. your neighborhood tech blogger thanks you.
</source:markdown>
    </item>
    
    <item>
      <title>Claude Opus 4.7 Is Here</title>
      <link>https://llbbl.blog/2026/04/17/claude-opus-is-here.html</link>
      <pubDate>Fri, 17 Apr 2026 10:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/17/claude-opus-is-here.html</guid>
      <description>&lt;p&gt;Anthropic just announced Claude Opus 4.7 yesterday, and here is my take on the new model after reading the blog post and doing a bit of research on their rollout plans from previous models.&lt;/p&gt;
&lt;h2 id=&#34;whats-new&#34;&gt;What&amp;rsquo;s New&lt;/h2&gt;
&lt;p&gt;The headline is a 13% improvement on a 93-task coding benchmark over Opus 4.6. Rakuten&amp;rsquo;s SWE-Bench saw 3x more production tasks resolved, which is the kind of real-world metric that actually matters. Benchmarks are one thing, but &amp;ldquo;can it handle my actual codebase&amp;rdquo; is another.&lt;/p&gt;
&lt;p&gt;The big quality-of-life improvement is that Opus 4.7 is better at verifying its own output before telling you it&amp;rsquo;s done. If you&amp;rsquo;ve ever had a model confidently hand you broken code and say &amp;ldquo;there you go,&amp;rdquo; you know why this matters. It handles long-running tasks with more precision, and the instruction following is noticeably tighter.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also a major vision upgrade. The new model accepts images up to 2,576 pixels on the long edge, which is more than 3x the resolution of previous Claude models. If you&amp;rsquo;re working with technical diagrams, architecture charts, or screenshots of code, that&amp;rsquo;s a real improvement.&lt;/p&gt;
&lt;h2 id=&#34;when-can-you-actually-use-it&#34;&gt;When Can You Actually Use It?&lt;/h2&gt;
&lt;p&gt;For enterprise customers, Anthropic says Opus 4.7 is available from your cloud vendor: the API, Amazon Bedrock, Google Cloud Vertex AI, and Microsoft Foundry. But most of us aren&amp;rsquo;t using the API directly.&lt;/p&gt;
&lt;p&gt;As of right now, Opus 4.7 is &lt;strong&gt;not yet available in Claude Code&lt;/strong&gt; or the desktop app. It&amp;rsquo;s also not showing up in the model picker on claude.ai for Pro plan users. Anthropic&amp;rsquo;s announcement says &amp;ldquo;available today across all Claude products,&amp;rdquo; but that doesn&amp;rsquo;t seem to have fully rolled out yet for consumer plans.&lt;/p&gt;
&lt;p&gt;Looking at previous releases, Opus 4.6 launched on February 5th and was accessible on claude.ai and the API the same day. Historically, Anthropic hasn&amp;rsquo;t gated new Opus models behind higher tiers, so there&amp;rsquo;s no reason to think Pro, Max, Team, and Enterprise won&amp;rsquo;t all get access. The question is just when. If past patterns hold, it should show up within a few days. Keep checking your model picker.&lt;/p&gt;
&lt;h2 id=&#34;claude-code-users&#34;&gt;Claude Code Users&lt;/h2&gt;
&lt;p&gt;As of today, Claude Code on the stable release is still on Opus 4.6. I&amp;rsquo;m not sure if it&amp;rsquo;s available on the bleeding edge builds, but for most people it&amp;rsquo;s not there yet.&lt;/p&gt;
&lt;p&gt;The announcement mentions a few Claude Code features coming with 4.7:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/ultrareview&lt;/code&gt;&lt;/strong&gt; is a new slash command for dedicated code review sessions. Pro and Max users get three free ultrareviews to try it out.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto mode&lt;/strong&gt; has been extended to Max plan users, letting Claude make more decisions autonomously.&lt;/li&gt;
&lt;li&gt;The default effort level is being bumped to &lt;code&gt;xhigh&lt;/code&gt; (a new level between &lt;code&gt;high&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt;), which means the model will spend more time reasoning through harder problems.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once Opus 4.7 does show up in Claude Code, remember to check any custom agents or skills that have a model hardcoded in the frontmatter. If you&amp;rsquo;ve got &lt;code&gt;claude-opus-4-6&lt;/code&gt; specified in your &lt;code&gt;.claude/commands/&lt;/code&gt; directory or agent configurations, those will keep using the old model until you update them.&lt;/p&gt;
&lt;p&gt;Anthropic also notes that Opus 4.7 follows instructions more literally than previous models. Prompts written for earlier models can sometimes produce unexpected results. So if something feels off after switching, it&amp;rsquo;s worth re-tuning your prompts.&lt;/p&gt;
&lt;h2 id=&#34;the-tokenizer-and-cost-changes&#34;&gt;The Tokenizer and Cost Changes&lt;/h2&gt;
&lt;p&gt;One thing to be aware of: the tokenizer has been updated. The same input text will produce 1.0 to 1.35x more tokens than before. That means your costs could go up slightly even at the same per-token pricing ($5/million input, $25/million output, unchanged from 4.6). Not a dealbreaker, but worth watching if you&amp;rsquo;re running high-volume workloads.&lt;/p&gt;
&lt;p&gt;Pricing hasn&amp;rsquo;t changed, the coding improvements look useful, and important to know that the model ID is &lt;code&gt;claude-opus-4-7&lt;/code&gt;. Keep an eye on your model picker over the next few days.&lt;/p&gt;
</description>
      <source:markdown>Anthropic just announced Claude Opus 4.7 yesterday, and here is my take on the new model after reading the blog post and doing a bit of research on their rollout plans from previous models.

## What&#39;s New

The headline is a 13% improvement on a 93-task coding benchmark over Opus 4.6. Rakuten&#39;s SWE-Bench saw 3x more production tasks resolved, which is the kind of real-world metric that actually matters. Benchmarks are one thing, but &#34;can it handle my actual codebase&#34; is another.

The big quality-of-life improvement is that Opus 4.7 is better at verifying its own output before telling you it&#39;s done. If you&#39;ve ever had a model confidently hand you broken code and say &#34;there you go,&#34; you know why this matters. It handles long-running tasks with more precision, and the instruction following is noticeably tighter.

There&#39;s also a major vision upgrade. The new model accepts images up to 2,576 pixels on the long edge, which is more than 3x the resolution of previous Claude models. If you&#39;re working with technical diagrams, architecture charts, or screenshots of code, that&#39;s a real improvement.

## When Can You Actually Use It?

For enterprise customers, Anthropic says Opus 4.7 is available from your cloud vendor: the API, Amazon Bedrock, Google Cloud Vertex AI, and Microsoft Foundry. But most of us aren&#39;t using the API directly.

As of right now, Opus 4.7 is **not yet available in Claude Code** or the desktop app. It&#39;s also not showing up in the model picker on claude.ai for Pro plan users. Anthropic&#39;s announcement says &#34;available today across all Claude products,&#34; but that doesn&#39;t seem to have fully rolled out yet for consumer plans.

Looking at previous releases, Opus 4.6 launched on February 5th and was accessible on claude.ai and the API the same day. Historically, Anthropic hasn&#39;t gated new Opus models behind higher tiers, so there&#39;s no reason to think Pro, Max, Team, and Enterprise won&#39;t all get access. The question is just when. If past patterns hold, it should show up within a few days. Keep checking your model picker.

## Claude Code Users

As of today, Claude Code on the stable release is still on Opus 4.6. I&#39;m not sure if it&#39;s available on the bleeding edge builds, but for most people it&#39;s not there yet.

The announcement mentions a few Claude Code features coming with 4.7:

- **`/ultrareview`** is a new slash command for dedicated code review sessions. Pro and Max users get three free ultrareviews to try it out.
- **Auto mode** has been extended to Max plan users, letting Claude make more decisions autonomously.
- The default effort level is being bumped to `xhigh` (a new level between `high` and `max`), which means the model will spend more time reasoning through harder problems.

Once Opus 4.7 does show up in Claude Code, remember to check any custom agents or skills that have a model hardcoded in the frontmatter. If you&#39;ve got `claude-opus-4-6` specified in your `.claude/commands/` directory or agent configurations, those will keep using the old model until you update them.

Anthropic also notes that Opus 4.7 follows instructions more literally than previous models. Prompts written for earlier models can sometimes produce unexpected results. So if something feels off after switching, it&#39;s worth re-tuning your prompts.

## The Tokenizer and Cost Changes

One thing to be aware of: the tokenizer has been updated. The same input text will produce 1.0 to 1.35x more tokens than before. That means your costs could go up slightly even at the same per-token pricing ($5/million input, $25/million output, unchanged from 4.6). Not a dealbreaker, but worth watching if you&#39;re running high-volume workloads.

Pricing hasn&#39;t changed, the coding improvements look useful, and important to know that the model ID is `claude-opus-4-7`. Keep an eye on your model picker over the next few days.
</source:markdown>
    </item>
    
    <item>
      <title>Agentic Development Trends: What&#39;s Changed in Early 2026</title>
      <link>https://llbbl.blog/2026/04/16/agentic-development-trends-whats-changed.html</link>
      <pubDate>Thu, 16 Apr 2026 14:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/16/agentic-development-trends-whats-changed.html</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been following the agentic development space around Claude Code and similar tools and the last couple months have been interesting. Here&amp;rsquo;s what I&amp;rsquo;m seeing as we move through March and April 2026.&lt;/p&gt;
&lt;h2 id=&#34;from-solo-agents-to-coordinated-teams&#34;&gt;From Solo Agents to Coordinated Teams&lt;/h2&gt;
&lt;p&gt;The biggest shift is that more people are moving away from trying to build one agent that does everything. Instead, we&amp;rsquo;re seeing coordinated teams of specialized agents managed by an orchestrator, often running tasks in parallel. I think this is the more proper use of these systems, and it&amp;rsquo;s great to see the community arriving here.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re curious about the different levels of working with agentic software development, I created an &lt;a href=&#34;https://github.com/agentic-tooling/maturity-model&#34;&gt;agentic maturity model&lt;/a&gt; on GitHub that goes into more detail on this progression.&lt;/p&gt;
&lt;h2 id=&#34;long-running-autonomous-workflows&#34;&gt;Long-Running Autonomous Workflows&lt;/h2&gt;
&lt;p&gt;Early on, agents handled what were essentially one-shot tasks. Now in 2026, agents can be configured to work for days at a time, requiring only strategic oversight at key decision points. Doesn&amp;rsquo;t that sound fun? You&amp;rsquo;re still the bottleneck, but at least now you&amp;rsquo;re a &lt;em&gt;strategic&lt;/em&gt; bottleneck.&lt;/p&gt;
&lt;h2 id=&#34;graph-based-orchestration&#34;&gt;Graph-Based Orchestration&lt;/h2&gt;
&lt;p&gt;Frameworks like LangGraph and AutoGen are converging on graph-based state management to handle the complex logic of multi-agent workflows. I think this makes sense when you consider the branching and conditional logic of real-world tasks could map naturally to graphs.&lt;/p&gt;
&lt;h2 id=&#34;mcp-is-everywhere&#34;&gt;MCP Is Everywhere&lt;/h2&gt;
&lt;p&gt;MCP (Model Context Protocol) has become the industry standard for tool integration. All vendors fully support it, and there&amp;rsquo;s no sign of slowing down. Every week there are new MCP servers popping up for connecting agents to different services and tools.&lt;/p&gt;
&lt;h2 id=&#34;unified-agentic-stacks&#34;&gt;Unified Agentic Stacks&lt;/h2&gt;
&lt;p&gt;The developer tooling is becoming more consistent. Cursor is becoming more like Claude Code, and Codex is becoming more like Claude Code. Maybe you see a pattern there&amp;hellip; might tell you something about who&amp;rsquo;s setting the pace.&lt;/p&gt;
&lt;p&gt;What is also noteable, people are experimenting with using different tools for different parts of the workflow. You might use Cursor to build the interface, Claude Code for the reasoning and main logic, and Codex for specific isolated tasks. Mix and match based on strengths.&lt;/p&gt;
&lt;h2 id=&#34;scheduled-agents-and-routines&#34;&gt;Scheduled Agents and Routines&lt;/h2&gt;
&lt;p&gt;Claude Code recently released routines or scheduled or trigger-based automations that can run 24/7 on cloud infrastructure without needing your laptop. Microsoft with GitHub Copilot are working on similar capabilities? Cursor had something like this a while back too.&lt;/p&gt;
&lt;h2 id=&#34;security-gets-serious&#34;&gt;Security Gets Serious&lt;/h2&gt;
&lt;p&gt;Two things happening here. First, people are getting better at leveraging agents for security reviews and monitoring. Tasks that previously required highly specialized InfoSec expertise. You no longer need to be a hacker to find vulnerabilities; you can let your AI try to hack you.&lt;/p&gt;
&lt;p&gt;However, the same capabilities that harden defenses can also be used for offensive attacks. We&amp;rsquo;re seeing a major push for security-first architecture as a requirement for all new applications, specifically to defend against the rise of agentic offensive attacks. Red team and blue team are both getting AI-pilled.&lt;/p&gt;
&lt;h2 id=&#34;finops-watching-the-bill&#34;&gt;FinOps: Watching the Bill&lt;/h2&gt;
&lt;p&gt;Last on the list is financial operations. Inference costs now account for over half of AI cloud spending according to recent estimates. Organizations are prioritizing frameworks that offer explicit cost monitoring and cost-per-task alerts. Getting granular about how much you&amp;rsquo;re spending to solve specific problems and optimizing at the task level. I think that&amp;rsquo;s pretty interesting and something we&amp;rsquo;ll see a lot more tooling around.&lt;/p&gt;
&lt;p&gt;The common thread across all of these trends is maturity. We&amp;rsquo;re past the &amp;ldquo;wow, an AI wrote code&amp;rdquo; phase and into &amp;ldquo;how do we make this reliable, secure, and cost-effective at scale.&amp;rdquo; That&amp;rsquo;s a good place to be.&lt;/p&gt;
</description>
      <source:markdown>I&#39;ve been following the agentic development space around Claude Code and similar tools and the last couple months have been interesting. Here&#39;s what I&#39;m seeing as we move through March and April 2026.

## From Solo Agents to Coordinated Teams

The biggest shift is that more people are moving away from trying to build one agent that does everything. Instead, we&#39;re seeing coordinated teams of specialized agents managed by an orchestrator, often running tasks in parallel. I think this is the more proper use of these systems, and it&#39;s great to see the community arriving here.

If you&#39;re curious about the different levels of working with agentic software development, I created an [agentic maturity model](https://github.com/agentic-tooling/maturity-model) on GitHub that goes into more detail on this progression.

## Long-Running Autonomous Workflows

Early on, agents handled what were essentially one-shot tasks. Now in 2026, agents can be configured to work for days at a time, requiring only strategic oversight at key decision points. Doesn&#39;t that sound fun? You&#39;re still the bottleneck, but at least now you&#39;re a *strategic* bottleneck.

## Graph-Based Orchestration

Frameworks like LangGraph and AutoGen are converging on graph-based state management to handle the complex logic of multi-agent workflows. I think this makes sense when you consider the branching and conditional logic of real-world tasks could map naturally to graphs.

## MCP Is Everywhere

MCP (Model Context Protocol) has become the industry standard for tool integration. All vendors fully support it, and there&#39;s no sign of slowing down. Every week there are new MCP servers popping up for connecting agents to different services and tools.

## Unified Agentic Stacks

The developer tooling is becoming more consistent. Cursor is becoming more like Claude Code, and Codex is becoming more like Claude Code. Maybe you see a pattern there... might tell you something about who&#39;s setting the pace.

What is also noteable, people are experimenting with using different tools for different parts of the workflow. You might use Cursor to build the interface, Claude Code for the reasoning and main logic, and Codex for specific isolated tasks. Mix and match based on strengths.

## Scheduled Agents and Routines

Claude Code recently released routines or scheduled or trigger-based automations that can run 24/7 on cloud infrastructure without needing your laptop. Microsoft with GitHub Copilot are working on similar capabilities? Cursor had something like this a while back too.

## Security Gets Serious

Two things happening here. First, people are getting better at leveraging agents for security reviews and monitoring. Tasks that previously required highly specialized InfoSec expertise. You no longer need to be a hacker to find vulnerabilities; you can let your AI try to hack you.

However, the same capabilities that harden defenses can also be used for offensive attacks. We&#39;re seeing a major push for security-first architecture as a requirement for all new applications, specifically to defend against the rise of agentic offensive attacks. Red team and blue team are both getting AI-pilled.

## FinOps: Watching the Bill

Last on the list is financial operations. Inference costs now account for over half of AI cloud spending according to recent estimates. Organizations are prioritizing frameworks that offer explicit cost monitoring and cost-per-task alerts. Getting granular about how much you&#39;re spending to solve specific problems and optimizing at the task level. I think that&#39;s pretty interesting and something we&#39;ll see a lot more tooling around.

The common thread across all of these trends is maturity. We&#39;re past the &#34;wow, an AI wrote code&#34; phase and into &#34;how do we make this reliable, secure, and cost-effective at scale.&#34; That&#39;s a good place to be.
</source:markdown>
    </item>
    
    <item>
      <title>Why javascript:void(0) Needs to Stay in the Past</title>
      <link>https://llbbl.blog/2026/04/15/why-javascriptvoid-needs-to-stay.html</link>
      <pubDate>Wed, 15 Apr 2026 22:35:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/15/why-javascriptvoid-needs-to-stay.html</guid>
      <description>&lt;p&gt;A few months ago, I ran across a &lt;code&gt;javascript:void(0)&lt;/code&gt; in the wild. I&amp;rsquo;m not going to get into the specific context because it doesn&amp;rsquo;t really matter, also it gets a bit too personal for this blog post. But I took a screenshot, thought &amp;ldquo;huh, that&amp;rsquo;s weird,&amp;rdquo; and promptly forgot about it.&lt;/p&gt;
&lt;p&gt;Then I was scrolling back through my screenshots and found it again. So here we are. Let&amp;rsquo;s talk about &lt;code&gt;javascript:void(0)&lt;/code&gt; and why you shouldn&amp;rsquo;t be using it in 2026.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the screenshot that started this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/70990/2026/cleanshot-2026-02-20-at-17.16.17.jpg&#34; alt=&#34;Screenshot of javascript:void(0) in the wild&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;what-does-it-actually-do&#34;&gt;What Does It Actually Do?&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;ve never encountered this pattern before, here&amp;rsquo;s the quick version. &lt;code&gt;void&lt;/code&gt; is a JavaScript operator, so not a function, and its only job is to evaluate the expression next to it, throw away the result, and return &lt;code&gt;undefined&lt;/code&gt;. That&amp;rsquo;s it. That&amp;rsquo;s the whole job.&lt;/p&gt;
&lt;p&gt;When a browser receives &lt;code&gt;undefined&lt;/code&gt; from clicking a link, it does nothing. No navigation, no page refresh. It&amp;rsquo;s a way to override the browser&amp;rsquo;s default behavior for an anchor tag.&lt;/p&gt;
&lt;p&gt;In practice, it looked like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;javascript:void(0);&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;onclick&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;openModal()&amp;#34;&lt;/span&gt;&amp;gt;Click Me&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;a&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;href&lt;/code&gt; prevents the browser from doing anything, and the &lt;code&gt;onclick&lt;/code&gt; fires whatever JavaScript you actually wanted to run. Clever? Sure. A good idea today? No.&lt;/p&gt;
&lt;h2 id=&#34;why-did-we-use-it&#34;&gt;Why Did We Use It?&lt;/h2&gt;
&lt;p&gt;Back in the day, we wanted to stop the browser from doing its default thing, following a link, so we could trigger events and make web pages more interactive. This was typically done on anchor tags because, well, that&amp;rsquo;s what we had. JavaScript didn&amp;rsquo;t give us a better way to handle it at the time, so &lt;code&gt;javascript:void(0)&lt;/code&gt; became the go-to pattern.&lt;/p&gt;
&lt;p&gt;It worked. But &amp;ldquo;it works&amp;rdquo; and &amp;ldquo;it&amp;rsquo;s a good idea&amp;rdquo; are two very different things.&lt;/p&gt;
&lt;h2 id=&#34;three-reasons-to-stop-using-it&#34;&gt;Three Reasons to Stop Using It&lt;/h2&gt;
&lt;h3 id=&#34;1-it-breaks-the-anchor-tags-purpose&#34;&gt;1. It Breaks the Anchor Tag&amp;rsquo;s Purpose&lt;/h3&gt;
&lt;p&gt;The biggest issue is that &lt;code&gt;javascript:void(0)&lt;/code&gt; completely overrides what an anchor tag is supposed to do. An &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag exists to link to things. When you stuff JavaScript into the &lt;code&gt;href&lt;/code&gt;, you&amp;rsquo;re hijacking the element&amp;rsquo;s entire reason for existing.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve moved on from needing to do this. If you want something clickable that triggers behavior, use a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;. If you want a link that also has JavaScript behavior, give it a real URL as a fallback.&lt;/p&gt;
&lt;h3 id=&#34;2-separation-of-concerns&#34;&gt;2. Separation of Concerns&lt;/h3&gt;
&lt;p&gt;Modern best practices tell us that HTML should define the structure of the page, and JavaScript should define the behavior. When you&amp;rsquo;ve got JavaScript living inside an &lt;code&gt;href&lt;/code&gt; attribute or relying on inline &lt;code&gt;onclick&lt;/code&gt; handlers, you&amp;rsquo;re mixing the two in ways that make code harder to maintain and reason about.&lt;/p&gt;
&lt;p&gt;The better approach? Use &lt;code&gt;event.preventDefault()&lt;/code&gt; in your JavaScript:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/fallback-page&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;myLink&amp;#34;&lt;/span&gt;&amp;gt;Click Me&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;a&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;document.&lt;span style=&#34;color:#a6e22e&#34;&gt;getElementById&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;myLink&amp;#39;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;addEventListener&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;click&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;event&lt;/span&gt;) {
  &lt;span style=&#34;color:#a6e22e&#34;&gt;event&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;preventDefault&lt;/span&gt;();
  &lt;span style=&#34;color:#a6e22e&#34;&gt;openModal&lt;/span&gt;();
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This way, if JavaScript is disabled or fails to load, the link still works. There&amp;rsquo;s a fallback behavior, which matters for accessibility and backwards compatibility. The HTML stays clean, and the behavior lives where it belongs, in your JavaScript files.&lt;/p&gt;
&lt;p&gt;Now, I will say that plenty of modern front-end frameworks add their own semantic patterns and play pretty loosey-goosey with this separation of concerns rule. But even React&amp;rsquo;s &lt;code&gt;onClick&lt;/code&gt; handlers and Vue&amp;rsquo;s &lt;code&gt;@click&lt;/code&gt; directives are compiled and managed in a way that&amp;rsquo;s fundamentally different from jamming raw JavaScript into an HTML attribute.&lt;/p&gt;
&lt;h3 id=&#34;3-content-security-policy-will-block-it&#34;&gt;3. Content Security Policy Will Block It&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;d like to believe Security still matters in 2026 so lets talk about the Content Security Policy (CSP).&lt;/p&gt;
&lt;p&gt;CSP is a set of rules that a web server sends to the browser via HTTP headers, telling the browser what resources the page is allowed to load or execute. Before CSP, browsers just assumed that if code was in the HTML document, it was meant to be there. Web pages were incredibly vulnerable to cross-site scripting (XSS) attacks.&lt;/p&gt;
&lt;p&gt;With CSP, the server tells the browser: &amp;ldquo;Only execute JavaScript if it comes from my own domain. Do not execute any code written directly inside the HTML file.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;A proper CSP header looks something like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Content-Security-Policy: default-src &#39;self&#39;; script-src &#39;self&#39;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is great for security. But guess what &lt;code&gt;javascript:void(0)&lt;/code&gt; is? Inline JavaScript. A strict CSP will block it.&lt;/p&gt;
&lt;p&gt;So if you see a site still using &lt;code&gt;javascript:void(0)&lt;/code&gt;, check the response headers. Chances are you&amp;rsquo;ll find something like:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Content-Security-Policy: default-src &#39;self&#39;; script-src &#39;self&#39; &#39;unsafe-inline&#39;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;See that &lt;code&gt;&#39;unsafe-inline&#39;&lt;/code&gt; addition? That&amp;rsquo;s the security risk. By adding &lt;code&gt;unsafe-inline&lt;/code&gt;, the developer is telling the browser to trust &lt;strong&gt;all&lt;/strong&gt; inline scripts. Every single one. So if an attacker manages to inject JavaScript onto the page, the browser will execute it without hesitation.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;re weakening your entire site&amp;rsquo;s security posture just to keep a legacy pattern alive. That&amp;rsquo;s not a tradeoff worth making.&lt;/p&gt;
&lt;h2 id=&#34;you-probably-dont-even-need-to-think-about-this&#34;&gt;You Probably Don&amp;rsquo;t Even Need to Think About This&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re working with any modern JavaScript framework, this problem is already solved for you.&lt;/p&gt;
&lt;p&gt;React, Svelte, Vue, Solid, whatever you&amp;rsquo;re using, they all ship components that handle default browser behavior the right way. Take forms as an example. The raw HTML &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element will, by default, submit and trigger a full page navigation. That&amp;rsquo;s why developers used to manually call &lt;code&gt;event.preventDefault()&lt;/code&gt; everywhere. But now, frameworks like Next.js, Remix, and SvelteKit give you a &lt;code&gt;&amp;lt;Form&amp;gt;&lt;/code&gt; component (or equivalent) that overrides that default behavior for you. No page reload. No manual prevention.&lt;/p&gt;
&lt;p&gt;The same applies to links, buttons, and pretty much any interactive element. The framework&amp;rsquo;s component handles the wiring so you don&amp;rsquo;t have to remember the low-level browser quirks. You import the component, use it, and move on.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the real reason &lt;code&gt;javascript:void(0)&lt;/code&gt; feels so out of place in 2026. It&amp;rsquo;s not just that we have better patterns available, but the tooling has abstracted the problem away entirely. The history is worth knowing, because understanding &lt;em&gt;why&lt;/em&gt; things work the way they do makes you a better developer!&lt;/p&gt;
</description>
      <source:markdown>A few months ago, I ran across a `javascript:void(0)` in the wild. I&#39;m not going to get into the specific context because it doesn&#39;t really matter, also it gets a bit too personal for this blog post. But I took a screenshot, thought &#34;huh, that&#39;s weird,&#34; and promptly forgot about it.

Then I was scrolling back through my screenshots and found it again. So here we are. Let&#39;s talk about `javascript:void(0)` and why you shouldn&#39;t be using it in 2026.

Here&#39;s the screenshot that started this:

![Screenshot of javascript:void(0) in the wild](https://cdn.uploads.micro.blog/70990/2026/cleanshot-2026-02-20-at-17.16.17.jpg)

## What Does It Actually Do?

If you&#39;ve never encountered this pattern before, here&#39;s the quick version. `void` is a JavaScript operator, so not a function, and its only job is to evaluate the expression next to it, throw away the result, and return `undefined`. That&#39;s it. That&#39;s the whole job.

When a browser receives `undefined` from clicking a link, it does nothing. No navigation, no page refresh. It&#39;s a way to override the browser&#39;s default behavior for an anchor tag.

In practice, it looked like this:

```html
&lt;a href=&#34;javascript:void(0);&#34; onclick=&#34;openModal()&#34;&gt;Click Me&lt;/a&gt;
```

The `href` prevents the browser from doing anything, and the `onclick` fires whatever JavaScript you actually wanted to run. Clever? Sure. A good idea today? No.

## Why Did We Use It?

Back in the day, we wanted to stop the browser from doing its default thing, following a link, so we could trigger events and make web pages more interactive. This was typically done on anchor tags because, well, that&#39;s what we had. JavaScript didn&#39;t give us a better way to handle it at the time, so `javascript:void(0)` became the go-to pattern.

It worked. But &#34;it works&#34; and &#34;it&#39;s a good idea&#34; are two very different things.

## Three Reasons to Stop Using It

### 1. It Breaks the Anchor Tag&#39;s Purpose

The biggest issue is that `javascript:void(0)` completely overrides what an anchor tag is supposed to do. An `&lt;a&gt;` tag exists to link to things. When you stuff JavaScript into the `href`, you&#39;re hijacking the element&#39;s entire reason for existing.

We&#39;ve moved on from needing to do this. If you want something clickable that triggers behavior, use a `&lt;button&gt;`. If you want a link that also has JavaScript behavior, give it a real URL as a fallback.

### 2. Separation of Concerns

Modern best practices tell us that HTML should define the structure of the page, and JavaScript should define the behavior. When you&#39;ve got JavaScript living inside an `href` attribute or relying on inline `onclick` handlers, you&#39;re mixing the two in ways that make code harder to maintain and reason about.

The better approach? Use `event.preventDefault()` in your JavaScript:

```html
&lt;a href=&#34;https://llbbl.blog/fallback-page&#34; id=&#34;myLink&#34;&gt;Click Me&lt;/a&gt;
```

```javascript
document.getElementById(&#39;myLink&#39;).addEventListener(&#39;click&#39;, function(event) {
  event.preventDefault();
  openModal();
});
```

This way, if JavaScript is disabled or fails to load, the link still works. There&#39;s a fallback behavior, which matters for accessibility and backwards compatibility. The HTML stays clean, and the behavior lives where it belongs, in your JavaScript files.

Now, I will say that plenty of modern front-end frameworks add their own semantic patterns and play pretty loosey-goosey with this separation of concerns rule. But even React&#39;s `onClick` handlers and Vue&#39;s `@click` directives are compiled and managed in a way that&#39;s fundamentally different from jamming raw JavaScript into an HTML attribute.

### 3. Content Security Policy Will Block It

I&#39;d like to believe Security still matters in 2026 so lets talk about the Content Security Policy (CSP).

CSP is a set of rules that a web server sends to the browser via HTTP headers, telling the browser what resources the page is allowed to load or execute. Before CSP, browsers just assumed that if code was in the HTML document, it was meant to be there. Web pages were incredibly vulnerable to cross-site scripting (XSS) attacks.

With CSP, the server tells the browser: &#34;Only execute JavaScript if it comes from my own domain. Do not execute any code written directly inside the HTML file.&#34;

A proper CSP header looks something like this:

```
Content-Security-Policy: default-src &#39;self&#39;; script-src &#39;self&#39;;
```

This is great for security. But guess what `javascript:void(0)` is? Inline JavaScript. A strict CSP will block it.

So if you see a site still using `javascript:void(0)`, check the response headers. Chances are you&#39;ll find something like:

```
Content-Security-Policy: default-src &#39;self&#39;; script-src &#39;self&#39; &#39;unsafe-inline&#39;;
```

See that `&#39;unsafe-inline&#39;` addition? That&#39;s the security risk. By adding `unsafe-inline`, the developer is telling the browser to trust **all** inline scripts. Every single one. So if an attacker manages to inject JavaScript onto the page, the browser will execute it without hesitation.

You&#39;re weakening your entire site&#39;s security posture just to keep a legacy pattern alive. That&#39;s not a tradeoff worth making.

## You Probably Don&#39;t Even Need to Think About This

If you&#39;re working with any modern JavaScript framework, this problem is already solved for you.

React, Svelte, Vue, Solid, whatever you&#39;re using, they all ship components that handle default browser behavior the right way. Take forms as an example. The raw HTML `&lt;form&gt;` element will, by default, submit and trigger a full page navigation. That&#39;s why developers used to manually call `event.preventDefault()` everywhere. But now, frameworks like Next.js, Remix, and SvelteKit give you a `&lt;Form&gt;` component (or equivalent) that overrides that default behavior for you. No page reload. No manual prevention.

The same applies to links, buttons, and pretty much any interactive element. The framework&#39;s component handles the wiring so you don&#39;t have to remember the low-level browser quirks. You import the component, use it, and move on.

That&#39;s the real reason `javascript:void(0)` feels so out of place in 2026. It&#39;s not just that we have better patterns available, but the tooling has abstracted the problem away entirely. The history is worth knowing, because understanding *why* things work the way they do makes you a better developer!
</source:markdown>
    </item>
    
    <item>
      <title>The Human&#39;s Guide to the Command Line: From Script to System</title>
      <link>https://llbbl.blog/2026/04/15/the-humans-guide-to-the.html</link>
      <pubDate>Wed, 15 Apr 2026 10:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/15/the-humans-guide-to-the.html</guid>
      <description>&lt;p&gt;This is Part 3 of The Human&amp;rsquo;s Guide to the Command Line. If you missed &lt;a href=&#34;https://llbbl.blog/2026/04/02/the-humans-guide-to-the.html&#34;&gt;Part 1&lt;/a&gt; or &lt;a href=&#34;https://llbbl.blog/2026/04/03/the-humans-guide-to-the.html&#34;&gt;Part 2&lt;/a&gt;, go check those out first, you&amp;rsquo;ll need the setup from both.&lt;/p&gt;
&lt;p&gt;In Part 2, we built a note-taking app. It works. But running it looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;uv run ~/code/note/note.py add &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;call the dentist&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s&amp;hellip; not great. Real command-line tools don&amp;rsquo;t make you remember where a Python file lives. You just type the name and it works. By the end of this post, you&amp;rsquo;ll be able to type:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;note add &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;call the dentist&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From anywhere on your computer. Let&amp;rsquo;s make that happen.&lt;/p&gt;
&lt;h2 id=&#34;why-cant-i-just-type-note&#34;&gt;Why Can&amp;rsquo;t I Just Type &lt;code&gt;note&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;When you type a command like &lt;code&gt;ls&lt;/code&gt; or &lt;code&gt;brew&lt;/code&gt;, your shell doesn&amp;rsquo;t search your entire computer for it. It checks a specific list of directories, one by one, looking for a program with that name. That list is called your &lt;strong&gt;PATH&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You can see yours right now:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;echo $PATH
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You&amp;rsquo;ll get a long string of directories separated by colons. Something like &lt;code&gt;/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin&lt;/code&gt;. When you type &lt;code&gt;brew&lt;/code&gt;, the shell checks each of those directories until it finds a match.&lt;/p&gt;
&lt;p&gt;Right now, &lt;code&gt;note.py&lt;/code&gt; is sitting in &lt;code&gt;~/code/note/&lt;/code&gt;. That folder isn&amp;rsquo;t in your PATH, so the shell has no idea it exists. We need to turn our script into something installable, and put it somewhere the shell knows to look.&lt;/p&gt;
&lt;h2 id=&#34;restructuring-your-project&#34;&gt;Restructuring Your Project&lt;/h2&gt;
&lt;p&gt;Python has specific expectations about how a project needs to be organized before it can be installed as a tool. Right now your project looks like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;note/
  pyproject.toml
  note.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We need to reorganize it into a package. Here&amp;rsquo;s what we&amp;rsquo;re aiming for:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;note/
  pyproject.toml
  src/
    note/
      __init__.py
      cli.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s do it step by step. Open Ghostty and navigate to your project:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;cd ~/code/note
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;create-the-new-directories&#34;&gt;Create the new directories&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;mkdir -p src/note
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;-p&lt;/code&gt; flag means &amp;ldquo;create parent directories too.&amp;rdquo; This creates both &lt;code&gt;src/&lt;/code&gt; and &lt;code&gt;note/&lt;/code&gt; inside it in one shot.&lt;/p&gt;
&lt;h3 id=&#34;move-your-script&#34;&gt;Move your script&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;mv note.py src/note/cli.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Your note-taking code now lives at &lt;code&gt;src/note/cli.py&lt;/code&gt; instead of &lt;code&gt;note.py&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;create-the-package-marker&#34;&gt;Create the package marker&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;touch src/note/__init__.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This creates an empty file. Python uses &lt;code&gt;__init__.py&lt;/code&gt; to recognize a folder as a package — it can be completely empty, it just needs to exist.&lt;/p&gt;
&lt;h3 id=&#34;update-pyprojecttoml&#34;&gt;Update pyproject.toml&lt;/h3&gt;
&lt;p&gt;Open your project in Zed:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;zed .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Find &lt;code&gt;pyproject.toml&lt;/code&gt; in the sidebar and open it. You need to add two things. First, tell uv where your code lives by adding this section:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;tool&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;uv&lt;/span&gt;]
&lt;span style=&#34;color:#a6e22e&#34;&gt;package&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then add this section to define your command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;project&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;scripts&lt;/span&gt;]
&lt;span style=&#34;color:#a6e22e&#34;&gt;note&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;note.cli:main&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That line is doing something specific: it&amp;rsquo;s saying &amp;ldquo;when someone types &lt;code&gt;note&lt;/code&gt;, find the &lt;code&gt;note&lt;/code&gt; package, go into the &lt;code&gt;cli&lt;/code&gt; module, and call the &lt;code&gt;main&lt;/code&gt; function.&amp;rdquo; That&amp;rsquo;s the same &lt;code&gt;main()&lt;/code&gt; function at the bottom of the code we wrote in Part 2.&lt;/p&gt;
&lt;p&gt;Your full &lt;code&gt;pyproject.toml&lt;/code&gt; should look something like this (the exact version numbers may differ):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;project&lt;/span&gt;]
&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;note&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;version&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0.1.0&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;description&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;A simple command-line note taker&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;requires-python&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;gt;=3.12&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;dependencies&lt;/span&gt; = []

[&lt;span style=&#34;color:#a6e22e&#34;&gt;tool&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;uv&lt;/span&gt;]
&lt;span style=&#34;color:#a6e22e&#34;&gt;package&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;

[&lt;span style=&#34;color:#a6e22e&#34;&gt;project&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;scripts&lt;/span&gt;]
&lt;span style=&#34;color:#a6e22e&#34;&gt;note&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;note.cli:main&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;verify-the-structure&#34;&gt;Verify the structure&lt;/h3&gt;
&lt;p&gt;Run this in Ghostty to make sure everything looks right:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;ls -R src/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You should see:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;src/note:
__init__.py  cli.py
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;installing-it-for-real&#34;&gt;Installing It For Real&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the moment. From your project directory (&lt;code&gt;~/code/note&lt;/code&gt;), run:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;uv tool install .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That &lt;code&gt;.&lt;/code&gt; means &amp;ldquo;install the project in the current folder&amp;rdquo; — same dot from Part 2 when we ran &lt;code&gt;zed .&lt;/code&gt; to mean &amp;ldquo;open this folder.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;What just happened? uv created an isolated environment for your tool and dropped a link to it in &lt;code&gt;~/.local/bin/&lt;/code&gt;. That directory &lt;em&gt;is&lt;/em&gt; on your PATH (or should be), which means your shell can now find it.&lt;/p&gt;
&lt;p&gt;Try it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;note list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You should see your notes from Part 2 (or &amp;ldquo;No notes yet&amp;rdquo; if you cleared them). Try adding one:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;note add &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I installed my first CLI tool&amp;#34;&lt;/span&gt;
note list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That works from &lt;em&gt;anywhere&lt;/em&gt;. Open a new terminal tab, navigate to your home directory, your Desktop, wherever&amp;hellip; &lt;code&gt;note&lt;/code&gt; just works now.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If you get &amp;ldquo;command not found&amp;rdquo;:&lt;/strong&gt; Run &lt;code&gt;uv tool update-shell&lt;/code&gt; and then restart your terminal. This adds &lt;code&gt;~/.local/bin&lt;/code&gt; to your PATH in &lt;code&gt;.zshrc&lt;/code&gt; so your shell knows where to find tools installed by uv.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;one-thing-to-know-about-editing&#34;&gt;One thing to know about editing&lt;/h3&gt;
&lt;p&gt;When you ran &lt;code&gt;uv tool install .&lt;/code&gt;, uv copied your code into its own isolated environment. That means if you go back and edit &lt;code&gt;src/note/cli.py&lt;/code&gt;, your changes won&amp;rsquo;t show up until you reinstall:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;uv tool install --force .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;--force&lt;/code&gt; flag tells uv to overwrite the existing installation. During development, you could also use &lt;code&gt;uv tool install -e .&lt;/code&gt; (the &lt;code&gt;-e&lt;/code&gt; is for &amp;ldquo;editable&amp;rdquo;) which keeps a live link to your source code so changes show up immediately without reinstalling.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;You went from a Python script you had to run with &lt;code&gt;uv run note.py&lt;/code&gt; to a real command that works from anywhere on your machine.&lt;/p&gt;
&lt;p&gt;Welcome to your first CLI!&lt;/p&gt;
</description>
      <source:markdown>This is Part 3 of The Human&#39;s Guide to the Command Line. If you missed [Part 1](https://llbbl.blog/2026/04/02/the-humans-guide-to-the.html) or [Part 2](https://llbbl.blog/2026/04/03/the-humans-guide-to-the.html), go check those out first, you&#39;ll need the setup from both.

In Part 2, we built a note-taking app. It works. But running it looks like this:

```bash
uv run ~/code/note/note.py add &#34;call the dentist&#34;
```

That&#39;s... not great. Real command-line tools don&#39;t make you remember where a Python file lives. You just type the name and it works. By the end of this post, you&#39;ll be able to type:

```bash
note add &#34;call the dentist&#34;
```

From anywhere on your computer. Let&#39;s make that happen.

## Why Can&#39;t I Just Type `note`?

When you type a command like `ls` or `brew`, your shell doesn&#39;t search your entire computer for it. It checks a specific list of directories, one by one, looking for a program with that name. That list is called your **PATH**.

You can see yours right now:

```bash
echo $PATH
```

You&#39;ll get a long string of directories separated by colons. Something like `/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin`. When you type `brew`, the shell checks each of those directories until it finds a match.

Right now, `note.py` is sitting in `~/code/note/`. That folder isn&#39;t in your PATH, so the shell has no idea it exists. We need to turn our script into something installable, and put it somewhere the shell knows to look.

## Restructuring Your Project

Python has specific expectations about how a project needs to be organized before it can be installed as a tool. Right now your project looks like this:

```
note/
  pyproject.toml
  note.py
```

We need to reorganize it into a package. Here&#39;s what we&#39;re aiming for:

```
note/
  pyproject.toml
  src/
    note/
      __init__.py
      cli.py
```

Let&#39;s do it step by step. Open Ghostty and navigate to your project:

```bash
cd ~/code/note
```

### Create the new directories

```bash
mkdir -p src/note
```

The `-p` flag means &#34;create parent directories too.&#34; This creates both `src/` and `note/` inside it in one shot.

### Move your script

```bash
mv note.py src/note/cli.py
```

Your note-taking code now lives at `src/note/cli.py` instead of `note.py`.

### Create the package marker

```bash
touch src/note/__init__.py
```

This creates an empty file. Python uses `__init__.py` to recognize a folder as a package — it can be completely empty, it just needs to exist.

### Update pyproject.toml

Open your project in Zed:

```bash
zed .
```

Find `pyproject.toml` in the sidebar and open it. You need to add two things. First, tell uv where your code lives by adding this section:

```toml
[tool.uv]
package = true
```

Then add this section to define your command:

```toml
[project.scripts]
note = &#34;note.cli:main&#34;
```

That line is doing something specific: it&#39;s saying &#34;when someone types `note`, find the `note` package, go into the `cli` module, and call the `main` function.&#34; That&#39;s the same `main()` function at the bottom of the code we wrote in Part 2.

Your full `pyproject.toml` should look something like this (the exact version numbers may differ):

```toml
[project]
name = &#34;note&#34;
version = &#34;0.1.0&#34;
description = &#34;A simple command-line note taker&#34;
requires-python = &#34;&gt;=3.12&#34;
dependencies = []

[tool.uv]
package = true

[project.scripts]
note = &#34;note.cli:main&#34;
```

### Verify the structure

Run this in Ghostty to make sure everything looks right:

```bash
ls -R src/
```

You should see:

```
src/note:
__init__.py  cli.py
```

## Installing It For Real

Here&#39;s the moment. From your project directory (`~/code/note`), run:

```bash
uv tool install .
```

That `.` means &#34;install the project in the current folder&#34; — same dot from Part 2 when we ran `zed .` to mean &#34;open this folder.&#34;

What just happened? uv created an isolated environment for your tool and dropped a link to it in `~/.local/bin/`. That directory *is* on your PATH (or should be), which means your shell can now find it.

Try it:

```bash
note list
```

You should see your notes from Part 2 (or &#34;No notes yet&#34; if you cleared them). Try adding one:

```bash
note add &#34;I installed my first CLI tool&#34;
note list
```

That works from *anywhere*. Open a new terminal tab, navigate to your home directory, your Desktop, wherever... `note` just works now.

&gt; **If you get &#34;command not found&#34;:** Run `uv tool update-shell` and then restart your terminal. This adds `~/.local/bin` to your PATH in `.zshrc` so your shell knows where to find tools installed by uv.

### One thing to know about editing

When you ran `uv tool install .`, uv copied your code into its own isolated environment. That means if you go back and edit `src/note/cli.py`, your changes won&#39;t show up until you reinstall:

```bash
uv tool install --force .
```

The `--force` flag tells uv to overwrite the existing installation. During development, you could also use `uv tool install -e .` (the `-e` is for &#34;editable&#34;) which keeps a live link to your source code so changes show up immediately without reinstalling.

---

You went from a Python script you had to run with `uv run note.py` to a real command that works from anywhere on your machine. 

Welcome to your first CLI!
</source:markdown>
    </item>
    
    <item>
      <title>How Active Noise Canceling Actually Works</title>
      <link>https://llbbl.blog/2026/04/14/how-active-noise-canceling-actually.html</link>
      <pubDate>Tue, 14 Apr 2026 10:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/14/how-active-noise-canceling-actually.html</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been thinking about how headphones work. What&amp;rsquo;s actually happening in there? Let me try and break down how active noise canceling (ANC) works, and how pass-through mode decides what sounds to let in.&lt;/p&gt;
&lt;h2 id=&#34;its-all-about-destructive-interference&#34;&gt;It&amp;rsquo;s All About Destructive Interference&lt;/h2&gt;
&lt;p&gt;Sound is a pressure wave moving through air. And here&amp;rsquo;s the thing about waves: if you produce an identical wave that&amp;rsquo;s perfectly inverted, 180 degrees out of phase, the wo cancel each other out. That&amp;rsquo;s the entire principle behind ANC.&lt;/p&gt;
&lt;p&gt;Modern headphones have multiple external microphones on each ear cup that are continuously sampling ambient noise in real time. Those audio signals get fed into a digital signal processor (DSP) chip, which analyzes the waveforms and generates an inverted version. The inverted signal is then played through the drivers into your ear at the same time the real-world sound arrives.&lt;/p&gt;
&lt;p&gt;So the noise gets canceled before it hits your eardrum. Simple concept, incredibly complex the more you learn about it.&lt;/p&gt;
&lt;h2 id=&#34;speed-is-everything&#34;&gt;Speed Is Everything&lt;/h2&gt;
&lt;p&gt;The entire chain, from the mic capture to waveform inversion to playback has to happen in &lt;strong&gt;microseconds&lt;/strong&gt; in order to beat the incoming sound wave. This is why ANC works best on predictable, steady sounds like airplane engines, road rumble, or HVAC hum. Those low-frequency drones are easy to predict and easy to invert.&lt;/p&gt;
&lt;p&gt;Higher-frequency or more modulated sounds, like human voices, are harder to predict. The waveforms change too quickly and irregularly for the DSP to perfectly cancel them out. That&amp;rsquo;s why you can still hear someone talking to you even with ANC cranked up. The algorithm just can&amp;rsquo;t keep up with the complexity of speech.&lt;/p&gt;
&lt;h2 id=&#34;hybrid-anc-the-second-pass&#34;&gt;Hybrid ANC: The Second Pass&lt;/h2&gt;
&lt;p&gt;Modern headphones use what&amp;rsquo;s called &lt;strong&gt;hybrid ANC&lt;/strong&gt;, which adds microphones on the inside of the ear cups in addition to the external ones. This gives the DSP a second correction pass. The external mics handle the initial cancellation, and the internal mics pick up whatever noise leaked through the ear cups and apply another round of inversion.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s basically error correction for sound. Pretty clever.&lt;/p&gt;
&lt;h2 id=&#34;how-pass-through-mode-works&#34;&gt;How Pass-Through Mode Works&lt;/h2&gt;
&lt;p&gt;Pass-through (or transparency mode, or ambient sound, every brand calls it something different) is essentially ANC running in reverse. Instead of canceling out the world, you&amp;rsquo;re selectively letting it in.&lt;/p&gt;
&lt;p&gt;The external microphones are still sampling ambient audio and the DSP is still processing it. But now the headphones are reproducing a version of the outside world through the drivers, as you would have heard it without headphones on.&lt;/p&gt;
&lt;p&gt;The DSP can selectively &lt;strong&gt;boost certain frequency ranges&lt;/strong&gt;, like the range where human speech commonly occurs, while reducing others like wind noise or low-frequency rumble. So pass-through mode isn&amp;rsquo;t just &amp;ldquo;turning off&amp;rdquo; noise canceling. It&amp;rsquo;s actively shaping what you hear.&lt;/p&gt;
&lt;p&gt;In transparency mode, ANC is still running. It&amp;rsquo;s just operating at a reduced level. The system is always actively managing your audio environment.&lt;/p&gt;
&lt;p&gt;Now how much of the outside world you are letting in is maybe a different topic for another day.&lt;/p&gt;
</description>
      <source:markdown>I&#39;ve been thinking about how headphones work. What&#39;s actually happening in there? Let me try and break down how active noise canceling (ANC) works, and how pass-through mode decides what sounds to let in.

## It&#39;s All About Destructive Interference

Sound is a pressure wave moving through air. And here&#39;s the thing about waves: if you produce an identical wave that&#39;s perfectly inverted, 180 degrees out of phase, the wo cancel each other out. That&#39;s the entire principle behind ANC.

Modern headphones have multiple external microphones on each ear cup that are continuously sampling ambient noise in real time. Those audio signals get fed into a digital signal processor (DSP) chip, which analyzes the waveforms and generates an inverted version. The inverted signal is then played through the drivers into your ear at the same time the real-world sound arrives.

So the noise gets canceled before it hits your eardrum. Simple concept, incredibly complex the more you learn about it.

## Speed Is Everything

The entire chain, from the mic capture to waveform inversion to playback has to happen in **microseconds** in order to beat the incoming sound wave. This is why ANC works best on predictable, steady sounds like airplane engines, road rumble, or HVAC hum. Those low-frequency drones are easy to predict and easy to invert.

Higher-frequency or more modulated sounds, like human voices, are harder to predict. The waveforms change too quickly and irregularly for the DSP to perfectly cancel them out. That&#39;s why you can still hear someone talking to you even with ANC cranked up. The algorithm just can&#39;t keep up with the complexity of speech.

## Hybrid ANC: The Second Pass

Modern headphones use what&#39;s called **hybrid ANC**, which adds microphones on the inside of the ear cups in addition to the external ones. This gives the DSP a second correction pass. The external mics handle the initial cancellation, and the internal mics pick up whatever noise leaked through the ear cups and apply another round of inversion.

It&#39;s basically error correction for sound. Pretty clever.

## How Pass-Through Mode Works

Pass-through (or transparency mode, or ambient sound, every brand calls it something different) is essentially ANC running in reverse. Instead of canceling out the world, you&#39;re selectively letting it in.

The external microphones are still sampling ambient audio and the DSP is still processing it. But now the headphones are reproducing a version of the outside world through the drivers, as you would have heard it without headphones on.

The DSP can selectively **boost certain frequency ranges**, like the range where human speech commonly occurs, while reducing others like wind noise or low-frequency rumble. So pass-through mode isn&#39;t just &#34;turning off&#34; noise canceling. It&#39;s actively shaping what you hear.

In transparency mode, ANC is still running. It&#39;s just operating at a reduced level. The system is always actively managing your audio environment.

Now how much of the outside world you are letting in is maybe a different topic for another day.
</source:markdown>
    </item>
    
    <item>
      <title>What Would Minimum Wage Be If It Kept Up With Housing?</title>
      <link>https://llbbl.blog/2026/04/13/what-would-minimum-wage-be.html</link>
      <pubDate>Mon, 13 Apr 2026 14:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/13/what-would-minimum-wage-be.html</guid>
      <description>&lt;p&gt;I pulled the data and verified the math. The answer is&amp;hellip; not great.&lt;/p&gt;
&lt;h2 id=&#34;the-numbers-were-working-with&#34;&gt;The Numbers We&amp;rsquo;re Working With&lt;/h2&gt;
&lt;p&gt;In 1950, the federal minimum wage was &lt;strong&gt;$0.75 per hour&lt;/strong&gt;. A median owner-occupied single-family home cost &lt;strong&gt;$7,354&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In 2026, the federal minimum wage is &lt;strong&gt;$7.25 per hour&lt;/strong&gt; — unchanged since 2009. The median U.S. family home price is &lt;strong&gt;$429,129&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;These numbers come from multiple independent sources. Let&amp;rsquo;s see what happens when we put them side by side.&lt;/p&gt;
&lt;h2 id=&#34;the-home-labor-index&#34;&gt;The Home-Labor Index&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m using a simple metric here: how many hours of minimum-wage work does it take to buy a median home? No mortgages, no interest rates, no down payments; just raw labor hours versus home price.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1950:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$7,354 ÷ $0.75/hr = &lt;strong&gt;9,805 hours&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;At 40 hrs/wk × 52 wks = 2,080 hrs/yr&lt;/li&gt;
&lt;li&gt;That&amp;rsquo;s &lt;strong&gt;4.71 years&lt;/strong&gt; of full-time minimum-wage work&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2026:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$429,129 ÷ $7.25/hr = &lt;strong&gt;59,191 hours&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Same 2,080 hrs/yr&lt;/li&gt;
&lt;li&gt;That&amp;rsquo;s &lt;strong&gt;28.46 years&lt;/strong&gt; of full-time minimum-wage work&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In 1950, a minimum-wage worker needed under 5 years of gross income to cover a median home. In 2026, that same worker needs over 28 years. The ratio has gotten roughly &lt;strong&gt;six times worse&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&#34;so-what-should-minimum-wage-be&#34;&gt;So What Should Minimum Wage Be?&lt;/h2&gt;
&lt;p&gt;If we wanted to preserve the same home-purchasing power that a minimum-wage worker had in 1950, we can work backwards:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2026 Median Home Price ÷ 1950 Home-Labor Index&lt;/li&gt;
&lt;li&gt;$429,129 ÷ 9,805 hours = &lt;strong&gt;$43.77 per hour&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can verify this another way. The 1950 ratio was 4.714 years of income to buy a home. To maintain that ratio in 2026:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$429,129 ÷ 4.714 = $91,029/yr required income&lt;/li&gt;
&lt;li&gt;$91,029 ÷ 2,080 hours = &lt;strong&gt;$43.76/hr&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both methods land in the same place. To have the same relationship between minimum wage and housing that existed in 1950, the federal minimum wage would need to be roughly &lt;strong&gt;$43.77 per hour&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You don&amp;rsquo;t need a PhD to look at these numbers and see the wage gap disparity. The gap between wages at the bottom and the cost of the most basic economic asset, a home, has grown dramatically. That the gap exists isn&amp;rsquo;t debatable.&lt;/p&gt;
&lt;p&gt;The federal minimum wage has been $7.25 since 2009. That&amp;rsquo;s 17 years without an increase. Meanwhile, median home prices have roughly doubled in that same period.&lt;/p&gt;
&lt;p&gt;If we cared about the citizens, we&amp;rsquo;d need to 6x the minmum wage while also working at more affordable housing for the middle class.&lt;/p&gt;
</description>
      <source:markdown>I pulled the data and verified the math. The answer is... not great.

## The Numbers We&#39;re Working With

In 1950, the federal minimum wage was **$0.75 per hour**. A median owner-occupied single-family home cost **$7,354**.

In 2026, the federal minimum wage is **$7.25 per hour** — unchanged since 2009. The median U.S. family home price is **$429,129**.

These numbers come from multiple independent sources. Let&#39;s see what happens when we put them side by side.

## The Home-Labor Index

I&#39;m using a simple metric here: how many hours of minimum-wage work does it take to buy a median home? No mortgages, no interest rates, no down payments; just raw labor hours versus home price.

**1950:**

- $7,354 ÷ $0.75/hr = **9,805 hours**
- At 40 hrs/wk × 52 wks = 2,080 hrs/yr
- That&#39;s **4.71 years** of full-time minimum-wage work

**2026:**

- $429,129 ÷ $7.25/hr = **59,191 hours**
- Same 2,080 hrs/yr
- That&#39;s **28.46 years** of full-time minimum-wage work

In 1950, a minimum-wage worker needed under 5 years of gross income to cover a median home. In 2026, that same worker needs over 28 years. The ratio has gotten roughly **six times worse**.

## So What Should Minimum Wage Be?

If we wanted to preserve the same home-purchasing power that a minimum-wage worker had in 1950, we can work backwards:

- 2026 Median Home Price ÷ 1950 Home-Labor Index
- $429,129 ÷ 9,805 hours = **$43.77 per hour**

We can verify this another way. The 1950 ratio was 4.714 years of income to buy a home. To maintain that ratio in 2026:

- $429,129 ÷ 4.714 = $91,029/yr required income
- $91,029 ÷ 2,080 hours = **$43.76/hr**

Both methods land in the same place. To have the same relationship between minimum wage and housing that existed in 1950, the federal minimum wage would need to be roughly **$43.77 per hour**

You don&#39;t need a PhD to look at these numbers and see the wage gap disparity. The gap between wages at the bottom and the cost of the most basic economic asset, a home, has grown dramatically. That the gap exists isn&#39;t debatable.

The federal minimum wage has been $7.25 since 2009. That&#39;s 17 years without an increase. Meanwhile, median home prices have roughly doubled in that same period.

If we cared about the citizens, we&#39;d need to 6x the minmum wage while also working at more affordable housing for the middle class.
</source:markdown>
    </item>
    
    <item>
      <title></title>
      <link>https://llbbl.blog/2026/04/13/timeseries-postgresql-at-petabyte-scale.html</link>
      <pubDate>Mon, 13 Apr 2026 11:55:50 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/13/timeseries-postgresql-at-petabyte-scale.html</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://www.tigerdata.com/&#34;&gt;Time-Series PostgreSQL at Petabyte Scale&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;From the creators of TimescaleDB — the PostgreSQL platform trusted by enterprises processing trillions of metrics daily. Start a free trial or get a demo.&lt;/p&gt;
</description>
      <source:markdown>[Time-Series PostgreSQL at Petabyte Scale](https://www.tigerdata.com/)

From the creators of TimescaleDB — the PostgreSQL platform trusted by enterprises processing trillions of metrics daily. Start a free trial or get a demo.
</source:markdown>
    </item>
    
    <item>
      <title></title>
      <link>https://llbbl.blog/2026/04/13/ghost-the-first-database-designed.html</link>
      <pubDate>Mon, 13 Apr 2026 11:55:32 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/13/ghost-the-first-database-designed.html</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://ghost.build/&#34;&gt;Ghost — The first database designed for agents&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Postgres databases for agents. Unlimited forks. Hard caps. No surprise bills.&lt;/p&gt;
</description>
      <source:markdown>[Ghost — The first database designed for agents](https://ghost.build/)

Postgres databases for agents. Unlimited forks. Hard caps. No surprise bills.
</source:markdown>
    </item>
    
    <item>
      <title>What Is an AI Agent, Actually?</title>
      <link>https://llbbl.blog/2026/04/13/what-is-an-ai-agent.html</link>
      <pubDate>Mon, 13 Apr 2026 10:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/13/what-is-an-ai-agent.html</guid>
      <description>&lt;p&gt;We need some actual definitions. The word &amp;ldquo;agent&amp;rdquo; is getting slapped onto every product and service, and marketers aren&amp;rsquo;t doing anybody favors as they SEO-optimize for the new agentic world we live in. There&amp;rsquo;s a huge range in what these things can actually do. Here is my attempt at clarity.&lt;/p&gt;
&lt;h2 id=&#34;the-spectrum-of-ai-capabilities&#34;&gt;The Spectrum of AI Capabilities&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Chatbot / Assistant&lt;/strong&gt; — This is a single conversation with no persistent goals and no tool use. You ask it questions, it answers from a knowledge base. Think of the little chat widget on a product page that helps you find pricing info or troubleshoot a common issue. It talks &lt;em&gt;with&lt;/em&gt; you, and that&amp;rsquo;s about it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LLM with Tool Use&lt;/strong&gt; — This is what you get when you open &amp;ldquo;agent mode&amp;rdquo; in your IDE. Your LLM can read files, run commands, edit code. A lot of IDE vendors call this an agent, but it&amp;rsquo;s not really one. It&amp;rsquo;s a language model that can use tools when you ask it to. The key difference: &lt;em&gt;you&lt;/em&gt; are still driving. You give it a task, it does that task, you give it the next one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Agent&lt;/strong&gt; — Given a goal, it can plan and execute multi-step workflows autonomously. By &amp;ldquo;workflow&amp;rdquo; I mean a sequence of actions that depend on each other: read a file, decide what to change, make the edit, run the tests, fix what broke, repeat. It has reasoning, memory, and some degree of autonomy in completing an objective. You don&amp;rsquo;t hand it step-by-step instructions. You describe what you want done, and it figures out how to get there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sub-Agent&lt;/strong&gt; — An agent that gets dispatched by another agent to handle a specific piece of a larger task. If you&amp;rsquo;ve used Claude Code or Cursor, you know what I&amp;rsquo;m talking about. The main agent kicks off a sub-agent to go research something, review code, or run tests in parallel while it keeps working on the bigger picture. The sub-agent has its own context and tools, but it reports back to the parent. It&amp;rsquo;s not a separate autonomous agent with its own goals. It&amp;rsquo;s more like delegating a subtask.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multi-Agent System&lt;/strong&gt; — Multiple independent agents coordinating together, either directly or through an orchestrator. The key difference from sub-agents: these agents have their own goals and specialties. They negotiate, hand off work, and make decisions independently. Think of a system where one agent monitors your infrastructure, another handles incident response, and a third writes the postmortem. Each Agent is operating autonomously but aware of the others.&lt;/p&gt;
&lt;h2 id=&#34;so-how-is-something-like-openclaw-different-from-a-chatbot&#34;&gt;So How Is Something Like OpenClaw Different From a Chatbot?&lt;/h2&gt;
&lt;p&gt;A chatbot is designed to talk with you, similar to how you&amp;rsquo;d just talk with an LLM directly. OpenClaw is designed to &lt;em&gt;work for you&lt;/em&gt;. It has agency. It can take actions. It&amp;rsquo;s more than just a conversation.&lt;/p&gt;
&lt;p&gt;Obviously, how much it can do depends on what skills and plugins you enable, and what degree of risk you&amp;rsquo;re comfortable with. But here&amp;rsquo;s the interesting part: it&amp;rsquo;s proactive. It has a heartbeat mechanism that keeps it running continuously in the background. It&amp;rsquo;ll automatically check on things or take action on a schedule you specify, without you having to prompt it.&lt;/p&gt;
&lt;h2 id=&#34;a-few-misconceptions-worth-clearing-up&#34;&gt;A Few Misconceptions Worth Clearing Up&lt;/h2&gt;
&lt;p&gt;OpenClaw is just one specific framework for building and orchestrating agents, but the misconceptions around it apply broadly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;Agents have to run locally.&amp;quot;&lt;/strong&gt; That&amp;rsquo;s how OpenClaw works, sure. But in reality, the enterprise agents are running invisibly in the background all the time. Your agent doesn&amp;rsquo;t need to live on your laptop.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;Agents need a chat interface.&amp;quot;&lt;/strong&gt; Because you &lt;em&gt;can&lt;/em&gt; talk to an agent, people assume you &lt;em&gt;must&lt;/em&gt; have a chat interface for it to be an agent. But by definition, agents don&amp;rsquo;t require a conversation. They can just run in the background doing things. No chat window needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;Sub-agents are just function calls.&amp;quot;&lt;/strong&gt; This one trips up developers. When your agent spawns a sub-agent, it&amp;rsquo;s not the same as calling a function. The sub-agent gets its own context window, its own reasoning loop, its own tool access. It can make judgment calls the parent didn&amp;rsquo;t anticipate. That&amp;rsquo;s fundamentally different from passing arguments to a function and getting a return value.&lt;/p&gt;
&lt;h2 id=&#34;why-write-this-down&#34;&gt;Why Write This Down&lt;/h2&gt;
&lt;p&gt;I mainly wrote this for myself. I keep running into these terms and needing a mental model to put them in context, so as I&amp;rsquo;m thinking about building agentic systems and trying to decide what level of capability I actually need for a given problem. The process of writing it down makes those decisions somewhat easier.&lt;/p&gt;
</description>
      <source:markdown>We need some actual definitions. The word &#34;agent&#34; is getting slapped onto every product and service, and marketers aren&#39;t doing anybody favors as they SEO-optimize for the new agentic world we live in. There&#39;s a huge range in what these things can actually do. Here is my attempt at clarity. 

## The Spectrum of AI Capabilities

**Chatbot / Assistant** — This is a single conversation with no persistent goals and no tool use. You ask it questions, it answers from a knowledge base. Think of the little chat widget on a product page that helps you find pricing info or troubleshoot a common issue. It talks *with* you, and that&#39;s about it.

**LLM with Tool Use** — This is what you get when you open &#34;agent mode&#34; in your IDE. Your LLM can read files, run commands, edit code. A lot of IDE vendors call this an agent, but it&#39;s not really one. It&#39;s a language model that can use tools when you ask it to. The key difference: *you* are still driving. You give it a task, it does that task, you give it the next one.

**Agent** — Given a goal, it can plan and execute multi-step workflows autonomously. By &#34;workflow&#34; I mean a sequence of actions that depend on each other: read a file, decide what to change, make the edit, run the tests, fix what broke, repeat. It has reasoning, memory, and some degree of autonomy in completing an objective. You don&#39;t hand it step-by-step instructions. You describe what you want done, and it figures out how to get there.

**Sub-Agent** — An agent that gets dispatched by another agent to handle a specific piece of a larger task. If you&#39;ve used Claude Code or Cursor, you know what I&#39;m talking about. The main agent kicks off a sub-agent to go research something, review code, or run tests in parallel while it keeps working on the bigger picture. The sub-agent has its own context and tools, but it reports back to the parent. It&#39;s not a separate autonomous agent with its own goals. It&#39;s more like delegating a subtask.

**Multi-Agent System** — Multiple independent agents coordinating together, either directly or through an orchestrator. The key difference from sub-agents: these agents have their own goals and specialties. They negotiate, hand off work, and make decisions independently. Think of a system where one agent monitors your infrastructure, another handles incident response, and a third writes the postmortem. Each Agent is operating autonomously but aware of the others.

## So How Is Something Like OpenClaw Different From a Chatbot?

A chatbot is designed to talk with you, similar to how you&#39;d just talk with an LLM directly. OpenClaw is designed to *work for you*. It has agency. It can take actions. It&#39;s more than just a conversation.

Obviously, how much it can do depends on what skills and plugins you enable, and what degree of risk you&#39;re comfortable with. But here&#39;s the interesting part: it&#39;s proactive. It has a heartbeat mechanism that keeps it running continuously in the background. It&#39;ll automatically check on things or take action on a schedule you specify, without you having to prompt it.

## A Few Misconceptions Worth Clearing Up

OpenClaw is just one specific framework for building and orchestrating agents, but the misconceptions around it apply broadly.

**&#34;Agents have to run locally.&#34;** That&#39;s how OpenClaw works, sure. But in reality, the enterprise agents are running invisibly in the background all the time. Your agent doesn&#39;t need to live on your laptop.

**&#34;Agents need a chat interface.&#34;** Because you *can* talk to an agent, people assume you *must* have a chat interface for it to be an agent. But by definition, agents don&#39;t require a conversation. They can just run in the background doing things. No chat window needed.

**&#34;Sub-agents are just function calls.&#34;** This one trips up developers. When your agent spawns a sub-agent, it&#39;s not the same as calling a function. The sub-agent gets its own context window, its own reasoning loop, its own tool access. It can make judgment calls the parent didn&#39;t anticipate. That&#39;s fundamentally different from passing arguments to a function and getting a return value.

## Why Write This Down

I mainly wrote this for myself. I keep running into these terms and needing a mental model to put them in context, so as I&#39;m thinking about building agentic systems and trying to decide what level of capability I actually need for a given problem. The process of writing it down makes those decisions somewhat easier.
</source:markdown>
    </item>
    
    <item>
      <title></title>
      <link>https://llbbl.blog/2026/04/13/a-concrete-definition-of-an.html</link>
      <pubDate>Mon, 13 Apr 2026 09:56:37 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/13/a-concrete-definition-of-an.html</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://www.nngroup.com/articles/definition-ai-agent/&#34;&gt;A Concrete Definition of an AI Agent&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;An AI agent pursues a goal by iteratively taking actions, evaluating progress, and deciding next steps. Useful agents must be reliable, adaptive, and accurate.&lt;/p&gt;
</description>
      <source:markdown>[A Concrete Definition of an AI Agent](https://www.nngroup.com/articles/definition-ai-agent/)

An AI agent pursues a goal by iteratively taking actions, evaluating progress, and deciding next steps. Useful agents must be reliable, adaptive, and accurate.
</source:markdown>
    </item>
    
    <item>
      <title></title>
      <link>https://llbbl.blog/2026/04/12/card-games-today-with-boardgame.html</link>
      <pubDate>Sun, 12 Apr 2026 09:59:10 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/12/card-games-today-with-boardgame.html</guid>
      <description>&lt;p&gt;Card games today with BoardGame friend. Going to try Star Wars Unlimited and 5 minute dungeon. Have you tried either and what did you like about them?&lt;/p&gt;
</description>
      <source:markdown>Card games today with BoardGame friend. Going to try Star Wars Unlimited and 5 minute dungeon. Have you tried either and what did you like about them?
</source:markdown>
    </item>
    
    <item>
      <title>SvelteKit Forms: Back to Basics (And Why That&#39;s a Good Thing)</title>
      <link>https://llbbl.blog/2026/04/11/sveltekit-forms-back-to-basics.html</link>
      <pubDate>Sat, 11 Apr 2026 10:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/11/sveltekit-forms-back-to-basics.html</guid>
      <description>&lt;p&gt;Before we talk about SvelteKit, we should touch on how React form handling works. I&amp;rsquo;m writing this to remind myself of the &lt;code&gt;modern&lt;/code&gt; way to do it&amp;hellip;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You set up &lt;code&gt;useState&lt;/code&gt; for every input field&lt;/li&gt;
&lt;li&gt;Write &lt;code&gt;onChange&lt;/code&gt; handlers to update state on every keystroke&lt;/li&gt;
&lt;li&gt;Add an &lt;code&gt;onSubmit&lt;/code&gt; handler to prevent the browser&amp;rsquo;s default behavior, construct a JSON payload, fire off a &lt;code&gt;fetch&lt;/code&gt; request&lt;/li&gt;
&lt;li&gt;Manage loading state to disable the submit button&lt;/li&gt;
&lt;li&gt;Manually parse the response to show success or error messages.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;rsquo;s a lot jumps to be hooped.&lt;/p&gt;
&lt;p&gt;Frameworks like Next.js have improved this with server actions, but the fundamental approach remains the same: a cage match with the browsers native form handling.&lt;/p&gt;
&lt;p&gt;SvelteKit takes a completely different approach. It just uses regular HTML.&lt;/p&gt;
&lt;h2 id=&#34;how-sveltekit-forms-work&#34;&gt;How SvelteKit Forms Work&lt;/h2&gt;
&lt;p&gt;In SvelteKit, you write a standard &lt;code&gt;&amp;lt;form method=&amp;quot;POST&amp;quot;&amp;gt;&lt;/code&gt; in your &lt;code&gt;+page.svelte&lt;/code&gt; file. That&amp;rsquo;s your UI. Then, in a &lt;code&gt;+page.server.ts&lt;/code&gt; file in the same directory, you export an &lt;code&gt;actions&lt;/code&gt; object that handles the submission.&lt;/p&gt;
&lt;p&gt;The server-side action receives a request object, extracts the form data, runs your logic, and returns either a &lt;code&gt;redirect&lt;/code&gt; or a &lt;code&gt;fail&lt;/code&gt; with validation errors. No client-side state management, no manual fetch calls, no JSON serialization.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// +page.server.ts
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;actions&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
  &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; ({ &lt;span style=&#34;color:#a6e22e&#34;&gt;request&lt;/span&gt; }) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;request&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;formData&lt;/span&gt;();
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;email&amp;#39;&lt;/span&gt;);
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;password&amp;#39;&lt;/span&gt;);

    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;) {
      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fail&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;400&lt;/span&gt;, { &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;missing&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt; });
    }

    &lt;span style=&#34;color:#75715e&#34;&gt;// authenticate user...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;redirect&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;303&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/dashboard&amp;#39;&lt;/span&gt;);
  }
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you&amp;rsquo;re a backend developer coming from Python or Ruby, this should feel familiar. The browser collects the data, posts it to a URL, and the server processes it. Stateless, reliable, and native.&lt;/p&gt;
&lt;h2 id=&#34;progressive-enhancement-the-best-of-both-worlds&#34;&gt;Progressive Enhancement: The Best of Both Worlds&lt;/h2&gt;
&lt;p&gt;You might be thinking, &amp;ldquo;Okay, but I don&amp;rsquo;t want full page reloads every time someone submits a form.&amp;rdquo; Fair point. SvelteKit handles this with something called progressive enhancement. You add &lt;code&gt;use:enhance&lt;/code&gt; to your form tag:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-svelte&#34; data-lang=&#34;svelte&#34;&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;method&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;POST&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;use:enhance&lt;/span&gt;&amp;gt;
  &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!-- your inputs --&amp;gt;&lt;/span&gt;
&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;form&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That single directive tells SvelteKit to automatically intercept the submission, prevent the page reload, submit the data via fetch under the hood, and update the &lt;code&gt;form&lt;/code&gt; prop with any validation errors returned from the server.&lt;/p&gt;
&lt;p&gt;But wait there&amp;rsquo;s more.&lt;/p&gt;
&lt;p&gt;If JavaScript fails to load or the user is on a terrible connection, the form still works. It falls back to the browser&amp;rsquo;s native POST request.&lt;/p&gt;
&lt;p&gt;Your form doesn&amp;rsquo;t break just because a script didn&amp;rsquo;t load. That&amp;rsquo;s how the web is supposed to work.&lt;/p&gt;
&lt;p&gt;The React approach treats the browser as a rendering target and reimplements everything in JavaScript. SvelteKit treats the browser as a platform and builds on top of what&amp;rsquo;s already there.&lt;/p&gt;
&lt;p&gt;Now, don&amp;rsquo;t get me wrong. I&amp;rsquo;ll write React code any day of the week at the ole w2, but I am really liking how Svelte and SvelteKit has decided to approach these sorts of features.&lt;/p&gt;
</description>
      <source:markdown>Before we talk about SvelteKit, we should touch on how React form handling works. I&#39;m writing this to remind myself of the `modern` way to do it... 

- You set up `useState` for every input field
- Write `onChange` handlers to update state on every keystroke
- Add an `onSubmit` handler to prevent the browser&#39;s default behavior, construct a JSON payload, fire off a `fetch` request
- Manage loading state to disable the submit button
- Manually parse the response to show success or error messages. 
 
That&#39;s a lot jumps to be hooped.

Frameworks like Next.js have improved this with server actions, but the fundamental approach remains the same: a cage match with the browsers native form handling.

SvelteKit takes a completely different approach. It just uses regular HTML.

## How SvelteKit Forms Work

In SvelteKit, you write a standard `&lt;form method=&#34;POST&#34;&gt;` in your `+page.svelte` file. That&#39;s your UI. Then, in a `+page.server.ts` file in the same directory, you export an `actions` object that handles the submission.

The server-side action receives a request object, extracts the form data, runs your logic, and returns either a `redirect` or a `fail` with validation errors. No client-side state management, no manual fetch calls, no JSON serialization.

```typescript
// +page.server.ts
export const actions = {
  default: async ({ request }) =&gt; {
    const data = await request.formData();
    const email = data.get(&#39;email&#39;);
    const password = data.get(&#39;password&#39;);

    if (!email) {
      return fail(400, { email, missing: true });
    }

    // authenticate user...
    throw redirect(303, &#39;/dashboard&#39;);
  }
};
```

If you&#39;re a backend developer coming from Python or Ruby, this should feel familiar. The browser collects the data, posts it to a URL, and the server processes it. Stateless, reliable, and native.

## Progressive Enhancement: The Best of Both Worlds

You might be thinking, &#34;Okay, but I don&#39;t want full page reloads every time someone submits a form.&#34; Fair point. SvelteKit handles this with something called progressive enhancement. You add `use:enhance` to your form tag:

```svelte
&lt;form method=&#34;POST&#34; use:enhance&gt;
  &lt;!-- your inputs --&gt;
&lt;/form&gt;
```

That single directive tells SvelteKit to automatically intercept the submission, prevent the page reload, submit the data via fetch under the hood, and update the `form` prop with any validation errors returned from the server.

But wait there&#39;s more. 

If JavaScript fails to load or the user is on a terrible connection, the form still works. It falls back to the browser&#39;s native POST request. 

Your form doesn&#39;t break just because a script didn&#39;t load. That&#39;s how the web is supposed to work.

The React approach treats the browser as a rendering target and reimplements everything in JavaScript. SvelteKit treats the browser as a platform and builds on top of what&#39;s already there.

Now, don&#39;t get me wrong. I&#39;ll write React code any day of the week at the ole w2, but I am really liking how Svelte and SvelteKit has decided to approach these sorts of features.
</source:markdown>
    </item>
    
    <item>
      <title>Routing in SvelteKit vs Next.js vs Astro</title>
      <link>https://llbbl.blog/2026/04/10/routing-in-sveltekit-vs-nextjs.html</link>
      <pubDate>Fri, 10 Apr 2026 10:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/10/routing-in-sveltekit-vs-nextjs.html</guid>
      <description>&lt;p&gt;Continuing my series on Svelte, I want to dig into how SvelteKit handles routing and how it compares with Next.js and Astro. These are the frameworks I am most familiar with, so apologies if you wanted a different framework.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start with everybody&amp;rsquo;s favorite topic: routing. Okay, maybe I&amp;rsquo;m the only one excited about this, but stick with me.&lt;/p&gt;
&lt;h2 id=&#34;astro-the-traditional-approach&#34;&gt;Astro: The Traditional Approach&lt;/h2&gt;
&lt;p&gt;Astro&amp;rsquo;s routing feels the most traditional of the three. You have a &lt;code&gt;src/pages/&lt;/code&gt; directory, and the file name &lt;em&gt;is&lt;/em&gt; the route. So &lt;code&gt;src/pages/dashboard.astro&lt;/code&gt; maps to &lt;code&gt;/dashboard&lt;/code&gt;. If you&amp;rsquo;ve worked with PHP or other languages with file-based routing, this will feel immediately familiar.&lt;/p&gt;
&lt;p&gt;Inside the &lt;code&gt;.astro&lt;/code&gt; file, you separate your backend logic at the top of the file with &lt;code&gt;---&lt;/code&gt; fences. That code runs entirely on the server, and everything below is your HTML template. Clean and straightforward.&lt;/p&gt;
&lt;h2 id=&#34;nextjs-folder-based-with-the-app-router&#34;&gt;Next.js: Folder-Based with the App Router&lt;/h2&gt;
&lt;p&gt;Next.js (the current versions, at least) uses the App Router. The structure is folder-based: &lt;code&gt;app/dashboard/page.tsx&lt;/code&gt; maps to &lt;code&gt;/dashboard&lt;/code&gt;. The key difference from Astro is that the &lt;em&gt;folder&lt;/em&gt; name determines the route, but the file must always be called &lt;code&gt;page.tsx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;By default, everything in the &lt;code&gt;app&lt;/code&gt; directory is a server component, meaning it runs on the server. If you want client-side interactivity, you explicitly add &lt;code&gt;&amp;quot;use client&amp;quot;&lt;/code&gt; at the top of the file. You can also set &lt;code&gt;&amp;quot;use server&amp;quot;&lt;/code&gt; if you want to be explicit, but it&amp;rsquo;s the default. I think Next.js does a really good job of making it clear what runs where.&lt;/p&gt;
&lt;h2 id=&#34;sveltekit-convention-over-configuration&#34;&gt;SvelteKit: Convention Over Configuration&lt;/h2&gt;
&lt;p&gt;SvelteKit also uses folder-based routing, similar to Next.js. The structure is &lt;code&gt;src/routes/dashboard/&lt;/code&gt;, but instead of &lt;code&gt;page.tsx&lt;/code&gt;, SvelteKit uses a reserved &lt;code&gt;+&lt;/code&gt; prefix for its special files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;+page.svelte&lt;/code&gt;&lt;/strong&gt; — Renders on the server, then hydrates on the client&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;+page.server.ts&lt;/code&gt;&lt;/strong&gt; — Runs &lt;em&gt;only&lt;/em&gt; on the server (data loading, form actions)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;+server.ts&lt;/code&gt;&lt;/strong&gt; — A raw API endpoint with no UI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There&amp;rsquo;s no &lt;code&gt;&amp;quot;use client&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;use server&amp;quot;&lt;/code&gt; directive. The file naming convention itself tells SvelteKit what should run where. If you fetch a database record in &lt;code&gt;+page.server.ts&lt;/code&gt;, that data is returned as an object, fully typed, and available in your &lt;code&gt;+page.svelte&lt;/code&gt; via the &lt;code&gt;$props&lt;/code&gt; rune. It just works.&lt;/p&gt;
&lt;h2 id=&#34;the-case-for-standard-file-names&#34;&gt;The Case for Standard File Names&lt;/h2&gt;
&lt;p&gt;I can see if some people get annoyed that every route has files named &lt;code&gt;+page.svelte&lt;/code&gt; and &lt;code&gt;+page.server.ts&lt;/code&gt;. The files will all look the same in the IDE, but there&amp;rsquo;s a real advantage here: you can group all related components in the same route folder.&lt;/p&gt;
&lt;p&gt;For example, if you&amp;rsquo;re building a dashboard, you can keep your &lt;code&gt;DashboardChart.svelte&lt;/code&gt;, &lt;code&gt;DashboardFilters.svelte&lt;/code&gt;, and other components right alongside your &lt;code&gt;+page.svelte&lt;/code&gt; and &lt;code&gt;+page.server.ts&lt;/code&gt;. You always know which file is the route entry point, which handles server logic, and which are supporting components. It encourages logical grouping instead of scattering related files across the project.&lt;/p&gt;
&lt;h2 id=&#34;quick-comparison&#34;&gt;Quick Comparison&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Astro&lt;/th&gt;
&lt;th&gt;Next.js&lt;/th&gt;
&lt;th&gt;SvelteKit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Route structure&lt;/td&gt;
&lt;td&gt;File name = route&lt;/td&gt;
&lt;td&gt;Folder + &lt;code&gt;page.tsx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Folder + &lt;code&gt;+page.svelte&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server/client split&lt;/td&gt;
&lt;td&gt;&lt;code&gt;---&lt;/code&gt; fences&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;quot;use client&amp;quot;&lt;/code&gt; directive&lt;/td&gt;
&lt;td&gt;File naming convention&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API routes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/pages/api/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app/api/route.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;+server.ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default rendering&lt;/td&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;Server + hydration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All three frameworks use file-based routing, but they each have a slightly different philosophy about how to organize and separate concerns. Astro keeps it simple with traditional file mapping. Next.js gives you explicit directives. SvelteKit leans on naming conventions to keep things clean.&lt;/p&gt;
&lt;p&gt;I think I can get used to the &lt;code&gt;+&lt;/code&gt; prefix convention in SvelteKit. The type safety between your server file and your page component is nice.&lt;/p&gt;
&lt;p&gt;Next up in the series, I&amp;rsquo;ll dig into how each framework handles data loading and forms.&lt;/p&gt;
</description>
      <source:markdown>Continuing my series on Svelte, I want to dig into how SvelteKit handles routing and how it compares with Next.js and Astro. These are the frameworks I am most familiar with, so apologies if you wanted a different framework.

Let&#39;s start with everybody&#39;s favorite topic: routing. Okay, maybe I&#39;m the only one excited about this, but stick with me.

## Astro: The Traditional Approach

Astro&#39;s routing feels the most traditional of the three. You have a `src/pages/` directory, and the file name *is* the route. So `src/pages/dashboard.astro` maps to `/dashboard`. If you&#39;ve worked with PHP or other languages with file-based routing, this will feel immediately familiar.

Inside the `.astro` file, you separate your backend logic at the top of the file with `---` fences. That code runs entirely on the server, and everything below is your HTML template. Clean and straightforward.

## Next.js: Folder-Based with the App Router

Next.js (the current versions, at least) uses the App Router. The structure is folder-based: `app/dashboard/page.tsx` maps to `/dashboard`. The key difference from Astro is that the *folder* name determines the route, but the file must always be called `page.tsx`.

By default, everything in the `app` directory is a server component, meaning it runs on the server. If you want client-side interactivity, you explicitly add `&#34;use client&#34;` at the top of the file. You can also set `&#34;use server&#34;` if you want to be explicit, but it&#39;s the default. I think Next.js does a really good job of making it clear what runs where.

## SvelteKit: Convention Over Configuration

SvelteKit also uses folder-based routing, similar to Next.js. The structure is `src/routes/dashboard/`, but instead of `page.tsx`, SvelteKit uses a reserved `+` prefix for its special files:

- **`+page.svelte`** — Renders on the server, then hydrates on the client
- **`+page.server.ts`** — Runs *only* on the server (data loading, form actions)
- **`+server.ts`** — A raw API endpoint with no UI

There&#39;s no `&#34;use client&#34;` or `&#34;use server&#34;` directive. The file naming convention itself tells SvelteKit what should run where. If you fetch a database record in `+page.server.ts`, that data is returned as an object, fully typed, and available in your `+page.svelte` via the `$props` rune. It just works.

## The Case for Standard File Names

I can see if some people get annoyed that every route has files named `+page.svelte` and `+page.server.ts`. The files will all look the same in the IDE, but there&#39;s a real advantage here: you can group all related components in the same route folder.

For example, if you&#39;re building a dashboard, you can keep your `DashboardChart.svelte`, `DashboardFilters.svelte`, and other components right alongside your `+page.svelte` and `+page.server.ts`. You always know which file is the route entry point, which handles server logic, and which are supporting components. It encourages logical grouping instead of scattering related files across the project.

## Quick Comparison

| Feature | Astro | Next.js | SvelteKit |
|---|---|---|---|
| Route structure | File name = route | Folder + `page.tsx` | Folder + `+page.svelte` |
| Server/client split | `---` fences | `&#34;use client&#34;` directive | File naming convention |
| API routes | `src/pages/api/` | `app/api/route.ts` | `+server.ts` |
| Default rendering | Server | Server | Server + hydration |

All three frameworks use file-based routing, but they each have a slightly different philosophy about how to organize and separate concerns. Astro keeps it simple with traditional file mapping. Next.js gives you explicit directives. SvelteKit leans on naming conventions to keep things clean.

I think I can get used to the `+` prefix convention in SvelteKit. The type safety between your server file and your page component is nice.

Next up in the series, I&#39;ll dig into how each framework handles data loading and forms.
</source:markdown>
    </item>
    
    <item>
      <title></title>
      <link>https://llbbl.blog/2026/04/09/mergephp-stop-guessing-start-verifying.html</link>
      <pubDate>Thu, 09 Apr 2026 18:33:07 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/09/mergephp-stop-guessing-start-verifying.html</guid>
      <description>&lt;p&gt;MergePHP: Stop Guessing, Start Verifying: A Modern API Documentation and Testing Toolkit. -  &lt;a href=&#34;https://www.youtube.com/watch?v=YjF2aSd80ok&#34;&gt;www.youtube.com/watch&lt;/a&gt;
Context7 Mentioned? We will see. 😶‍🌫️&lt;/p&gt;
</description>
      <source:markdown>MergePHP: Stop Guessing, Start Verifying: A Modern API Documentation and Testing Toolkit. -  [www.youtube.com/watch](https://www.youtube.com/watch?v=YjF2aSd80ok)
Context7 Mentioned? We will see. 😶‍🌫️
</source:markdown>
    </item>
    
    <item>
      <title>Svelte 5 Runes: A React Developer&#39;s Guide to Reactivity</title>
      <link>https://llbbl.blog/2026/04/09/svelte-runes-a-react-developers.html</link>
      <pubDate>Thu, 09 Apr 2026 10:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/09/svelte-runes-a-react-developers.html</guid>
      <description>&lt;p&gt;Continuing my series on Svelte topics, today we&amp;rsquo;re talking about runes. If you&amp;rsquo;re coming from React, this is is going to be a different way to work with reactivity in modern JavaScript.&lt;/p&gt;
&lt;p&gt;These blog posts might be what is considered &lt;em&gt;the basics&lt;/em&gt;, but it helps me learn and think through the topics if I work on blog posts around the important things that every developer needs to know.&lt;/p&gt;
&lt;h2 id=&#34;what-are-runes&#34;&gt;What Are Runes?&lt;/h2&gt;
&lt;p&gt;In Svelte 5, runes are special symbols that start with the dollar sign (&lt;code&gt;$&lt;/code&gt;). They look like regular JavaScript functions, but they&amp;rsquo;re actually compiler directives, reserved keywords that tell the Svelte compiler how to wire up reactivity during the build step.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve used decorators in Python or macros in other languages, runes fill a similar role. They look like standard JavaScript, but the compiler transforms them into something more powerful behind the scenes.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s walk through the four runes you&amp;rsquo;ll use most.&lt;/p&gt;
&lt;h2 id=&#34;state--the-engine-of-reactivity&#34;&gt;&lt;code&gt;$state&lt;/code&gt; — The Engine of Reactivity&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$state&lt;/code&gt; is the foundation. It declares reactive state in your component.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-svelte&#34; data-lang=&#34;svelte&#34;&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;$state&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;

&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;onclick&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;}&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;gt;&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;}&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;lt;/button&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In React, &lt;code&gt;useState&lt;/code&gt; returns an immutable value and a setter function, so you always need that &lt;code&gt;setCount&lt;/code&gt; call. In Svelte, &lt;code&gt;$state&lt;/code&gt; returns a deeply reactive proxy. You just mutate the value directly, and the compiler handles the rest. No setter function, no spread operators for nested objects. It just works.&lt;/p&gt;
&lt;h2 id=&#34;derived--computed-values-without-dependency-arrays&#34;&gt;&lt;code&gt;$derived&lt;/code&gt; — Computed Values Without Dependency Arrays&lt;/h2&gt;
&lt;p&gt;In React, you&amp;rsquo;d reach for &lt;code&gt;useMemo&lt;/code&gt; here, and you&amp;rsquo;d need to explicitly declare a dependency array so React knows when to recalculate.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-svelte&#34; data-lang=&#34;svelte&#34;&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;$state&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;doubled&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;$derived&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);
&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dependency arrays are prone to human error. We forget what depends on what, and that leads to stale data or unnecessary recalculations. &lt;code&gt;$derived&lt;/code&gt; automatically tracks whatever state variables are used inside of it. No dependency array needed. It just reads what it reads, and recalculates when those values change.&lt;/p&gt;
&lt;h2 id=&#34;effect--side-effects-that-actually-make-sense&#34;&gt;&lt;code&gt;$effect&lt;/code&gt; — Side Effects That Actually Make Sense&lt;/h2&gt;
&lt;p&gt;This is the equivalent of &lt;code&gt;useEffect&lt;/code&gt; in React, which is notoriously tricky. Missing dependencies, stale closures, infinite loops&amp;hellip; all the big gotchas are in useEffect calls.&lt;/p&gt;
&lt;p&gt;In Svelte, &lt;code&gt;$effect&lt;/code&gt; is used to synchronize state with external systems, like writing to local storage or updating a canvas:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-svelte&#34; data-lang=&#34;svelte&#34;&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;theme&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;$state&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;dark&amp;#39;&lt;/span&gt;);

  &lt;span style=&#34;color:#a6e22e&#34;&gt;$effect&lt;/span&gt;(() =&amp;gt; {
    &lt;span style=&#34;color:#a6e22e&#34;&gt;localStorage&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;setItem&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;theme&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;theme&lt;/span&gt;);
  });
&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Just like &lt;code&gt;$derived&lt;/code&gt;, it automatically tracks its dependencies and only runs when the state it reads actually changes. No dependency array, no cleanup function gotchas. It runs when it needs to run. That&amp;rsquo;s it.&lt;/p&gt;
&lt;h2 id=&#34;props--clean-component-interfaces&#34;&gt;&lt;code&gt;$props&lt;/code&gt; — Clean Component Interfaces&lt;/h2&gt;
&lt;p&gt;Every framework needs a way to pass data into components. In Svelte 5, &lt;code&gt;$props&lt;/code&gt; makes this look like standard JavaScript object destructuring:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-svelte&#34; data-lang=&#34;svelte&#34;&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;age&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;role&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;viewer&amp;#39;&lt;/span&gt; } &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;$props&lt;/span&gt;();
&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;

&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;p&lt;/span&gt;&amp;gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;} ({&lt;span style=&#34;color:#a6e22e&#34;&gt;age&lt;/span&gt;}) - {&lt;span style=&#34;color:#a6e22e&#34;&gt;role&lt;/span&gt;}&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Default values, rest parameters, renaming &amp;hellip; it all works exactly how you&amp;rsquo;d expect from CommonJS. If you know destructuring, you already know &lt;code&gt;$props&lt;/code&gt;. It&amp;rsquo;s readable, predictable, and there&amp;rsquo;s nothing new to learn.&lt;/p&gt;
&lt;h2 id=&#34;runes-me-over&#34;&gt;Runes Me Over&lt;/h2&gt;
&lt;p&gt;You&amp;rsquo;ve probably noticed a theme. Svelte 5 runes eliminate a whole class of bugs that come from manually managing dependencies. React makes you think about &lt;em&gt;when&lt;/em&gt; things should update. Svelte&amp;rsquo;s goal is to figure it out for you at compile time.&lt;/p&gt;
</description>
      <source:markdown>Continuing my series on Svelte topics, today we&#39;re talking about runes. If you&#39;re coming from React, this is is going to be a different way to work with reactivity in modern JavaScript. 

These blog posts might be what is considered *the basics*, but it helps me learn and think through the topics if I work on blog posts around the important things that every developer needs to know.

## What Are Runes?

In Svelte 5, runes are special symbols that start with the dollar sign (`$`). They look like regular JavaScript functions, but they&#39;re actually compiler directives, reserved keywords that tell the Svelte compiler how to wire up reactivity during the build step.

If you&#39;ve used decorators in Python or macros in other languages, runes fill a similar role. They look like standard JavaScript, but the compiler transforms them into something more powerful behind the scenes.

Let&#39;s walk through the four runes you&#39;ll use most.

## `$state` — The Engine of Reactivity

`$state` is the foundation. It declares reactive state in your component.

```svelte
&lt;script&gt;
  let count = $state(0);
&lt;/script&gt;

&lt;button onclick={() =&gt; count++}&gt;{count}&lt;/button&gt;
```

In React, `useState` returns an immutable value and a setter function, so you always need that `setCount` call. In Svelte, `$state` returns a deeply reactive proxy. You just mutate the value directly, and the compiler handles the rest. No setter function, no spread operators for nested objects. It just works.

## `$derived` — Computed Values Without Dependency Arrays

In React, you&#39;d reach for `useMemo` here, and you&#39;d need to explicitly declare a dependency array so React knows when to recalculate.

```svelte
&lt;script&gt;
  let count = $state(0);
  let doubled = $derived(count * 2);
&lt;/script&gt;
```

Dependency arrays are prone to human error. We forget what depends on what, and that leads to stale data or unnecessary recalculations. `$derived` automatically tracks whatever state variables are used inside of it. No dependency array needed. It just reads what it reads, and recalculates when those values change.

## `$effect` — Side Effects That Actually Make Sense

This is the equivalent of `useEffect` in React, which is notoriously tricky. Missing dependencies, stale closures, infinite loops... all the big gotchas are in useEffect calls. 

In Svelte, `$effect` is used to synchronize state with external systems, like writing to local storage or updating a canvas:

```svelte
&lt;script&gt;
  let theme = $state(&#39;dark&#39;);

  $effect(() =&gt; {
    localStorage.setItem(&#39;theme&#39;, theme);
  });
&lt;/script&gt;
```

Just like `$derived`, it automatically tracks its dependencies and only runs when the state it reads actually changes. No dependency array, no cleanup function gotchas. It runs when it needs to run. That&#39;s it.

## `$props` — Clean Component Interfaces

Every framework needs a way to pass data into components. In Svelte 5, `$props` makes this look like standard JavaScript object destructuring:

```svelte
&lt;script&gt;
  let { name, age, role = &#39;viewer&#39; } = $props();
&lt;/script&gt;

&lt;p&gt;{name} ({age}) - {role}&lt;/p&gt;
```

Default values, rest parameters, renaming ... it all works exactly how you&#39;d expect from CommonJS. If you know destructuring, you already know `$props`. It&#39;s readable, predictable, and there&#39;s nothing new to learn.

## Runes Me Over

You&#39;ve probably noticed a theme. Svelte 5 runes eliminate a whole class of bugs that come from manually managing dependencies. React makes you think about *when* things should update. Svelte&#39;s goal is to figure it out for you at compile time.
</source:markdown>
    </item>
    
    <item>
      <title>Svelte vs React: State Management Without the Ceremony</title>
      <link>https://llbbl.blog/2026/04/08/svelte-vs-react-state-management.html</link>
      <pubDate>Wed, 08 Apr 2026 10:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/08/svelte-vs-react-state-management.html</guid>
      <description>&lt;p&gt;Continuing my Svelte deep-dive series, let&amp;rsquo;s talk about state management and reactivity. This is where the differences between React and Svelte can &amp;lsquo;feel&amp;rsquo; much different.&lt;/p&gt;
&lt;h2 id=&#34;reacts-state-ceremony&#34;&gt;React&amp;rsquo;s State Ceremony&lt;/h2&gt;
&lt;p&gt;In React, state requires a specific ritual. You declare state with &lt;code&gt;useState&lt;/code&gt;, which gives you a getter and a setter:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-jsx&#34; data-lang=&#34;jsx&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; [&lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;setCount&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;useState&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);

&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;increment&lt;/span&gt;() {
  &lt;span style=&#34;color:#a6e22e&#34;&gt;setCount&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Want to update a variable? You have to call a function. You can&amp;rsquo;t just reassign &lt;code&gt;count&lt;/code&gt;. React won&amp;rsquo;t know anything changed. This is fine once you internalize it, but it adds ceremony to what should be a simple operation.&lt;/p&gt;
&lt;p&gt;Then there&amp;rsquo;s &lt;code&gt;useEffect&lt;/code&gt;, which is where things get tricky. You need to understand dependency arrays, and if you get them wrong, you&amp;rsquo;re looking at infinite loops or stale data:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-jsx&#34; data-lang=&#34;jsx&#34;&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;useEffect&lt;/span&gt;(() =&amp;gt; {
  document.&lt;span style=&#34;color:#a6e22e&#34;&gt;title&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`Count: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;;
}, [&lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;]); &lt;span style=&#34;color:#75715e&#34;&gt;// forget this array and enjoy your infinite loop
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Some of &lt;code&gt;useEffect&lt;/code&gt; usage is actually unnecessary and likely using it wrong. If you&amp;rsquo;re using it for data transformations, derived values from state or props, or responding to user events, you&amp;rsquo;re probably reaching for the wrong tool.&lt;/p&gt;
&lt;p&gt;The React docs themselves will tell you that &lt;a href=&#34;https://react.dev/learn/you-might-not-need-an-effect&#34;&gt;you might not need an effect&lt;/a&gt;. It&amp;rsquo;s a common source of bugs and confusion, especially for developers who are still building their mental model of React&amp;rsquo;s render cycle.&lt;/p&gt;
&lt;h2 id=&#34;svelte-reactivity-through-the-language-itself&#34;&gt;Svelte: Reactivity Through the Language Itself&lt;/h2&gt;
&lt;p&gt;Svelte takes a fundamentally different approach. Reactivity is baked into the language semantics. Want to declare state? Just declare a variable:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-svelte&#34; data-lang=&#34;svelte&#34;&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;$state&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);

  &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;increment&lt;/span&gt;() {
    &lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
  }
&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;

&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;onclick&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;increment&lt;/span&gt;}&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;gt;&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;}&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;lt;/button&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s it. You assign a new value, and the DOM updates. The Svelte compiler sees your assignments and automatically generates the code to update exactly the parts of the DOM that depend on that variable. No virtual DOM diffing, no setter functions, no dependency arrays to manage.&lt;/p&gt;
&lt;p&gt;Need a derived value? Svelte has you covered with &lt;code&gt;$derived&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-svelte&#34; data-lang=&#34;svelte&#34;&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;$state&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;doubled&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;$derived&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);
&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;script&lt;/span&gt;&amp;gt;

&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;p&lt;/span&gt;&amp;gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;} doubled is {&lt;span style=&#34;color:#a6e22e&#34;&gt;doubled&lt;/span&gt;}&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In React, you&amp;rsquo;d either compute this inline, use &lt;code&gt;useMemo&lt;/code&gt; with a dependency array, or&amp;hellip; if you didn&amp;rsquo;t know better reach for &lt;code&gt;useEffect&lt;/code&gt; and a second piece of state (please don&amp;rsquo;t do this).&lt;/p&gt;
&lt;p&gt;Svelte&amp;rsquo;s &lt;code&gt;$effect&lt;/code&gt; rune exists for side effects like updating &lt;code&gt;document.title&lt;/code&gt; or logging, but you should reach for it far less often than &lt;code&gt;useEffect&lt;/code&gt; in React. The compiler handles most of what &lt;code&gt;useEffect&lt;/code&gt; gets used for automatically.&lt;/p&gt;
&lt;p&gt;More Svelte comparisons coming as I keep digging in. Thanks for Svelting with me.&lt;/p&gt;
</description>
      <source:markdown>Continuing my Svelte deep-dive series, let&#39;s talk about state management and reactivity. This is where the differences between React and Svelte can &#39;feel&#39; much different.

## React&#39;s State Ceremony

In React, state requires a specific ritual. You declare state with `useState`, which gives you a getter and a setter:

```jsx
const [count, setCount] = useState(0);

function increment() {
  setCount(count + 1);
}
```

Want to update a variable? You have to call a function. You can&#39;t just reassign `count`. React won&#39;t know anything changed. This is fine once you internalize it, but it adds ceremony to what should be a simple operation.

Then there&#39;s `useEffect`, which is where things get tricky. You need to understand dependency arrays, and if you get them wrong, you&#39;re looking at infinite loops or stale data:

```jsx
useEffect(() =&gt; {
  document.title = `Count: ${count}`;
}, [count]); // forget this array and enjoy your infinite loop
```

Some of `useEffect` usage is actually unnecessary and likely using it wrong. If you&#39;re using it for data transformations, derived values from state or props, or responding to user events, you&#39;re probably reaching for the wrong tool. 

The React docs themselves will tell you that [you might not need an effect](https://react.dev/learn/you-might-not-need-an-effect). It&#39;s a common source of bugs and confusion, especially for developers who are still building their mental model of React&#39;s render cycle.

## Svelte: Reactivity Through the Language Itself

Svelte takes a fundamentally different approach. Reactivity is baked into the language semantics. Want to declare state? Just declare a variable:

```svelte
&lt;script&gt;
  let count = $state(0);

  function increment() {
    count += 1;
  }
&lt;/script&gt;

&lt;button onclick={increment}&gt;{count}&lt;/button&gt;
```

That&#39;s it. You assign a new value, and the DOM updates. The Svelte compiler sees your assignments and automatically generates the code to update exactly the parts of the DOM that depend on that variable. No virtual DOM diffing, no setter functions, no dependency arrays to manage.

Need a derived value? Svelte has you covered with `$derived`:

```svelte
&lt;script&gt;
  let count = $state(0);
  let doubled = $derived(count * 2);
&lt;/script&gt;

&lt;p&gt;{count} doubled is {doubled}&lt;/p&gt;
```

In React, you&#39;d either compute this inline, use `useMemo` with a dependency array, or... if you didn&#39;t know better reach for `useEffect` and a second piece of state (please don&#39;t do this).

Svelte&#39;s `$effect` rune exists for side effects like updating `document.title` or logging, but you should reach for it far less often than `useEffect` in React. The compiler handles most of what `useEffect` gets used for automatically.

More Svelte comparisons coming as I keep digging in. Thanks for Svelting with me.
</source:markdown>
    </item>
    
    <item>
      <title>Svelte vs React: The Virtual DOM Tax You Might Not Need</title>
      <link>https://llbbl.blog/2026/04/07/svelte-vs-react-the-virtual.html</link>
      <pubDate>Tue, 07 Apr 2026 14:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/07/svelte-vs-react-the-virtual.html</guid>
      <description>&lt;p&gt;I&amp;rsquo;m diving more into Svelte and SvelteKit lately, and I&amp;rsquo;m going to be writing a few posts about it as I learn. Fair warning: some of these will be general knowledge posts, but writing things out helps me internalize the details.&lt;/p&gt;
&lt;h2 id=&#34;the-virtual-dom-question&#34;&gt;The Virtual DOM Question&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s well known that React relies on a virtual DOM. The basic idea is that React maintains a copy of the DOM in memory, diffs it against the actual DOM, and then batches the changes to update the real thing. This works, but having to maintain this virtual DOM can lead to complications and confusion around what triggers a render or a re-render. If you&amp;rsquo;ve ever stared at a &lt;code&gt;useEffect&lt;/code&gt; dependency array wondering why your component is re-rendering, you know what I mean.&lt;/p&gt;
&lt;p&gt;Svelte takes a completely different approach. It&amp;rsquo;s not a library you ship to the browser, it&amp;rsquo;s a compiler step. You write Svelte code, and it compiles down to highly optimized vanilla JavaScript that surgically updates the DOM directly. No virtual DOM to maintain. No diffing algorithm running in the background. The framework essentially disappears at build time, and what you&amp;rsquo;re left with is just&amp;hellip; JavaScript.&lt;/p&gt;
&lt;h2 id=&#34;templating-that-feels-like-the-web&#34;&gt;Templating That Feels Like the Web&lt;/h2&gt;
&lt;p&gt;I like how Svelte handles the relationship between HTML, CSS, and JavaScript. React forces you to write HTML inside JavaScript using JSX. You get used to it, sure, but it&amp;rsquo;s a specific way of thinking about your UI that can take some getting used to.&lt;/p&gt;
&lt;p&gt;Svelte flips this around. Your &lt;code&gt;.svelte&lt;/code&gt; files are structured more like traditional web pages — you&amp;rsquo;ve got &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags for your JavaScript, regular HTML for markup, and &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags for CSS. Everything lives in one file, but there&amp;rsquo;s a clear separation between the three concerns.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve ever worked with Django templates, Laravel Blade, or Ruby on Rails views, this will feel immediately familiar. It&amp;rsquo;s a lot closer to how the web actually works than JSX&amp;rsquo;s &amp;ldquo;everything is JavaScript&amp;rdquo; approach. For someone coming from those backgrounds, the learning curve is noticeably gentler.&lt;/p&gt;
&lt;p&gt;More Svelte posts coming as I dig deeper. That&amp;rsquo;s all for now!&lt;/p&gt;
</description>
      <source:markdown>I&#39;m diving more into Svelte and SvelteKit lately, and I&#39;m going to be writing a few posts about it as I learn. Fair warning: some of these will be general knowledge posts, but writing things out helps me internalize the details. 

## The Virtual DOM Question

It&#39;s well known that React relies on a virtual DOM. The basic idea is that React maintains a copy of the DOM in memory, diffs it against the actual DOM, and then batches the changes to update the real thing. This works, but having to maintain this virtual DOM can lead to complications and confusion around what triggers a render or a re-render. If you&#39;ve ever stared at a `useEffect` dependency array wondering why your component is re-rendering, you know what I mean.

Svelte takes a completely different approach. It&#39;s not a library you ship to the browser, it&#39;s a compiler step. You write Svelte code, and it compiles down to highly optimized vanilla JavaScript that surgically updates the DOM directly. No virtual DOM to maintain. No diffing algorithm running in the background. The framework essentially disappears at build time, and what you&#39;re left with is just... JavaScript.


## Templating That Feels Like the Web

I like how Svelte handles the relationship between HTML, CSS, and JavaScript. React forces you to write HTML inside JavaScript using JSX. You get used to it, sure, but it&#39;s a specific way of thinking about your UI that can take some getting used to.

Svelte flips this around. Your `.svelte` files are structured more like traditional web pages — you&#39;ve got `&lt;script&gt;` tags for your JavaScript, regular HTML for markup, and `&lt;style&gt;` tags for CSS. Everything lives in one file, but there&#39;s a clear separation between the three concerns.

If you&#39;ve ever worked with Django templates, Laravel Blade, or Ruby on Rails views, this will feel immediately familiar. It&#39;s a lot closer to how the web actually works than JSX&#39;s &#34;everything is JavaScript&#34; approach. For someone coming from those backgrounds, the learning curve is noticeably gentler.

More Svelte posts coming as I dig deeper. That&#39;s all for now!
</source:markdown>
    </item>
    
    <item>
      <title>Lisette is a New Rust-to-Go Language, So I Built It a Test Library</title>
      <link>https://llbbl.blog/2026/04/06/lisette-is-a-new-rusttogo.html</link>
      <pubDate>Mon, 06 Apr 2026 14:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/06/lisette-is-a-new-rusttogo.html</guid>
      <description>&lt;p&gt;This morning I dove into a new programming language called &lt;a href=&#34;https://github.com/ivov/lisette&#34;&gt;Lisette&lt;/a&gt;. I saw it from &lt;a href=&#34;https://lmika.org/2026/04/05/lisette-rust-syntax-go-a.html&#34;&gt;@lmika&lt;/a&gt; and had to take a look. It gives you Rust-like syntax but compiles down to Go, and you can import from the Go standard library directly.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s early in development, so a lot of things don&amp;rsquo;t exist yet. They have a &lt;a href=&#34;https://github.com/ivov/lisette/blob/main/docs/intro/roadmap.md&#34;&gt;roadmap&lt;/a&gt; posted with plans for third-party package support, a test runner, bitwise operators, and configurable diagnostics.&lt;/p&gt;
&lt;h2 id=&#34;so-naturally-i-built-a-test-library&#34;&gt;So Naturally, I Built a Test Library&lt;/h2&gt;
&lt;p&gt;Anyone who reads my blog knows I care a lot about testing. So when I saw &amp;ldquo;implement a test runner&amp;rdquo; sitting on the roadmap, I did what any reasonable person would do on a Monday morning. I built a testing library for Lisette called &lt;a href=&#34;https://github.com/llbbl/lisunit&#34;&gt;LisUnit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I wanted something that felt familiar if you&amp;rsquo;ve used Jest or PHPUnit. Test cases are closures that return a result, and assertions work the same way. Here&amp;rsquo;s what it looks like:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-lis&#34; data-lang=&#34;lis&#34;&gt;lisunit.Suite.new(&amp;quot;math&amp;quot;)
  .case(&amp;quot;add produces sum&amp;quot;, || {
    lisunit.assert_eq_int(add(2, 3), 5)?
    Ok(())
  })
  .case(&amp;quot;add is commutative&amp;quot;, || {
    lisunit.assert_eq_int(add(2, 3), add(3, 2))?
    Ok(())
  })
  .run()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Define a suite, chain your test cases, run it.&lt;/p&gt;
&lt;h2 id=&#34;why-bother&#34;&gt;Why Bother?&lt;/h2&gt;
&lt;p&gt;I don&amp;rsquo;t know exactly what direction the Lisette team is headed with their own test runner, so this is just a prototype. Building a test library turns out to fun way to try out a new language because you end up touching a lot of language constucts?&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll probably keep poking at it as Lisette evolves. Happy Monday.&lt;/p&gt;
</description>
      <source:markdown>This morning I dove into a new programming language called [Lisette](https://github.com/ivov/lisette). I saw it from [@lmika](https://lmika.org/2026/04/05/lisette-rust-syntax-go-a.html) and had to take a look. It gives you Rust-like syntax but compiles down to Go, and you can import from the Go standard library directly.

It&#39;s early in development, so a lot of things don&#39;t exist yet. They have a [roadmap](https://github.com/ivov/lisette/blob/main/docs/intro/roadmap.md) posted with plans for third-party package support, a test runner, bitwise operators, and configurable diagnostics.

## So Naturally, I Built a Test Library

Anyone who reads my blog knows I care a lot about testing. So when I saw &#34;implement a test runner&#34; sitting on the roadmap, I did what any reasonable person would do on a Monday morning. I built a testing library for Lisette called [LisUnit](https://github.com/llbbl/lisunit).

I wanted something that felt familiar if you&#39;ve used Jest or PHPUnit. Test cases are closures that return a result, and assertions work the same way. Here&#39;s what it looks like:

```lis
lisunit.Suite.new(&#34;math&#34;)
  .case(&#34;add produces sum&#34;, || {
    lisunit.assert_eq_int(add(2, 3), 5)?
    Ok(())
  })
  .case(&#34;add is commutative&#34;, || {
    lisunit.assert_eq_int(add(2, 3), add(3, 2))?
    Ok(())
  })
  .run()
```

Define a suite, chain your test cases, run it.

## Why Bother?

I don&#39;t know exactly what direction the Lisette team is headed with their own test runner, so this is just a prototype. Building a test library turns out to fun way to try out a new language because you end up touching a lot of language constucts? 

I&#39;ll probably keep poking at it as Lisette evolves. Happy Monday.
</source:markdown>
    </item>
    
    <item>
      <title></title>
      <link>https://llbbl.blog/2026/04/06/emdashcmsemdash-emdash-is-a-fullstack.html</link>
      <pubDate>Mon, 06 Apr 2026 08:57:33 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/06/emdashcmsemdash-emdash-is-a-fullstack.html</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/emdash-cms/emdash&#34;&gt;emdash-cms/emdash: EmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;EmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress - emdash-cms/emdash&lt;/p&gt;
</description>
      <source:markdown>[emdash-cms/emdash: EmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress](https://github.com/emdash-cms/emdash)

EmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress - emdash-cms/emdash
</source:markdown>
    </item>
    
    <item>
      <title>Hiding Poems Inside Images</title>
      <link>https://llbbl.blog/2026/04/05/hiding-poems-inside-images.html</link>
      <pubDate>Sun, 05 Apr 2026 10:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/05/hiding-poems-inside-images.html</guid>
      <description>&lt;p&gt;I built a tool that hides poems inside images. Not as metadata, not as a watermark. The actual text of the poem drives the visual pattern, and you can reconstruct the poem perfectly from the image alone.&lt;/p&gt;
&lt;h2 id=&#34;how-it-works&#34;&gt;How It Works&lt;/h2&gt;
&lt;p&gt;You give it a poem. It analyzes the syllable count, rhyme scheme, and stress patterns. Then it generates a visual pattern where those poetic features drive the aesthetics: spiral width, dot placement, block size, line weight.&lt;/p&gt;
&lt;p&gt;The text itself is encoded into the pattern in a variety of ways. The pattern isn&amp;rsquo;t just inspired by the poem, it IS the poem. Run the decoder on the image and you get back the original text, character for character, including whitespace and punctuation.&lt;/p&gt;
&lt;h2 id=&#34;seven-renderers-two-approaches&#34;&gt;Seven Renderers, Two Approaches&lt;/h2&gt;
&lt;p&gt;There are seven different visual styles, split into two categories.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;steganographic renderers&lt;/strong&gt; (geometric, concentric, waveform) hide text invisibly in pixel color channels using LSB encoding. The visual pattern is purely decorative. This is a well-known technique. nothing new there.&lt;/p&gt;
&lt;p&gt;I wanted to build something different, so I focused on &lt;strong&gt;visual encoding patterns&lt;/strong&gt;. The encoding is the art, and the art is the encoding. Everything about how the image is constructed follows repeatable algorithms so it can be decoded back:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nautilus&lt;/strong&gt; draws a golden spiral where line width carries the data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fibonacci&lt;/strong&gt; uses a sunflower phyllotaxis dot pattern&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mosaic&lt;/strong&gt; creates an adaptive block grid&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dotline&lt;/strong&gt; connects dots with varying line weight&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each has different capacity. The nautilus spiral can hold about 2,200 characters, enough for a full Whitman poem. A fibonacci pattern holds about 1,600. Even the smallest renderer handles a haiku easily.&lt;/p&gt;
&lt;p&gt;Right now I&amp;rsquo;m just having fun building different visuals, images that encode and decode. Eventually I might build an API around it, but for now it&amp;rsquo;s a side project&amp;hellip;&lt;/p&gt;
&lt;p&gt;A Basho&amp;rsquo;s haiku is here:
&lt;img src=&#34;https://cdn.uploads.micro.blog/70990/2026/tempest-square.png&#34; width=&#34;500&#34; height=&#34;500&#34; alt=&#34;Auto-generated description: A central square is surrounded by a series of radiating lines with red points, creating a geometric pattern.&#34;&gt;&lt;/p&gt;
</description>
      <source:markdown>I built a tool that hides poems inside images. Not as metadata, not as a watermark. The actual text of the poem drives the visual pattern, and you can reconstruct the poem perfectly from the image alone.

## How It Works

You give it a poem. It analyzes the syllable count, rhyme scheme, and stress patterns. Then it generates a visual pattern where those poetic features drive the aesthetics: spiral width, dot placement, block size, line weight.

The text itself is encoded into the pattern in a variety of ways. The pattern isn&#39;t just inspired by the poem, it IS the poem. Run the decoder on the image and you get back the original text, character for character, including whitespace and punctuation.

## Seven Renderers, Two Approaches

There are seven different visual styles, split into two categories.

The **steganographic renderers** (geometric, concentric, waveform) hide text invisibly in pixel color channels using LSB encoding. The visual pattern is purely decorative. This is a well-known technique. nothing new there.

I wanted to build something different, so I focused on **visual encoding patterns**. The encoding is the art, and the art is the encoding. Everything about how the image is constructed follows repeatable algorithms so it can be decoded back:

- **Nautilus** draws a golden spiral where line width carries the data
- **Fibonacci** uses a sunflower phyllotaxis dot pattern
- **Mosaic** creates an adaptive block grid
- **Dotline** connects dots with varying line weight

Each has different capacity. The nautilus spiral can hold about 2,200 characters, enough for a full Whitman poem. A fibonacci pattern holds about 1,600. Even the smallest renderer handles a haiku easily.

Right now I&#39;m just having fun building different visuals, images that encode and decode. Eventually I might build an API around it, but for now it&#39;s a side project... 

A Basho&#39;s haiku is here:
&lt;img src=&#34;https://cdn.uploads.micro.blog/70990/2026/tempest-square.png&#34; width=&#34;500&#34; height=&#34;500&#34; alt=&#34;Auto-generated description: A central square is surrounded by a series of radiating lines with red points, creating a geometric pattern.&#34;&gt;
</source:markdown>
    </item>
    
    <item>
      <title></title>
      <link>https://llbbl.blog/2026/04/04/we-can-explain-how-dreams.html</link>
      <pubDate>Sat, 04 Apr 2026 19:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/04/we-can-explain-how-dreams.html</guid>
      <description>&lt;p&gt;We can explain how dreams work but can an artificial system really ever understand them. What does being able to dream even mean to you the human?&lt;/p&gt;
</description>
      <source:markdown>We can explain how dreams work but can an artificial system really ever understand them. What does being able to dream even mean to you the human?
</source:markdown>
    </item>
    
    <item>
      <title></title>
      <link>https://llbbl.blog/2026/04/04/the-highest-rated-episode-in.html</link>
      <pubDate>Sat, 04 Apr 2026 11:02:10 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/04/04/the-highest-rated-episode-in.html</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://youtube.com/shorts/hCy9ohTPdvA?si=RqNxyI-5aRZuP3-j&#34;&gt;The Highest Rated Episode In Dimension 20 History&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t a joke, this is Katie&amp;rsquo;s life! 🚌🧝‍♀️ #shorts #dimension20 #dropoutTo watch EVERY season of Dimension 20, every live show, every Adventuring Party&amp;hellip;&lt;/p&gt;
</description>
      <source:markdown>[The Highest Rated Episode In Dimension 20 History](https://youtube.com/shorts/hCy9ohTPdvA?si=RqNxyI-5aRZuP3-j)

This isn&#39;t a joke, this is Katie&#39;s life! 🚌🧝‍♀️ #shorts #dimension20 #dropoutTo watch EVERY season of Dimension 20, every live show, every Adventuring Party...
</source:markdown>
    </item>
    
  </channel>
</rss>
