<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Aral Roca</title>
    <description>The latest articles on Forem by Aral Roca (@aralroca).</description>
    <link>https://forem.com/aralroca</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F51056%2Fe0e242b1-fc41-4f88-8163-629ee7692ecd.jpeg</url>
      <title>Forem: Aral Roca</title>
      <link>https://forem.com/aralroca</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/aralroca"/>
    <language>en</language>
    <item>
      <title>SAP Hybris Impex When None of Us on the Team Can Read It</title>
      <dc:creator>Aral Roca</dc:creator>
      <pubDate>Sat, 18 Apr 2026 15:31:01 +0000</pubDate>
      <link>https://forem.com/aralroca/sap-hybris-impex-when-none-of-us-on-the-team-can-read-it-4ok</link>
      <guid>https://forem.com/aralroca/sap-hybris-impex-when-none-of-us-on-the-team-can-read-it-4ok</guid>
      <description>&lt;p&gt;I'm currently embedded as an external on a team that uses the &lt;a href="https://help.sap.com/docs/SAP_COMMERCE" rel="noopener noreferrer"&gt;Hybris Management Console&lt;/a&gt; — HMC for short — to administer a SAP Commerce environment. Every few days the shared environment gets refreshed from production, which is exactly what you want for most of the data, and exactly what you don't want for half-finished configuration that hasn't made it to prod yet. A content type you registered on Tuesday, a workflow tweak from Wednesday, a test user you needed for a demo — gone by Friday morning. Everyone on the team has been through this loop enough times that they've stopped being surprised.&lt;/p&gt;

&lt;p&gt;Impex is the natural answer. If you can describe a configuration change as an Impex file, you can replay it after every refresh, commit it to the repo, code-review it, and treat it like the rest of the platform. That is the official playbook and it is the right one.&lt;/p&gt;

&lt;p&gt;The problem is more human than technical. Nobody on the team reads Impex fluently. The format predates most of the languages the team writes every day, the tooling around it is sparse, and a pull request that is sixty lines of semicolon-separated text doesn't exactly invite careful review. So people would rather click through the HMC, accept that the work will evaporate in a few days, and move on. Knowing this fixes itself with tooling, not with a training session, I spent a week building the eight browser tools this post is about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Impex is, in one paragraph
&lt;/h2&gt;

&lt;p&gt;Impex is a plain-text scripting format SAP Commerce uses to insert, update, or remove rows in the platform's type system. Every Hybris object — &lt;code&gt;Product&lt;/code&gt;, &lt;code&gt;Catalog&lt;/code&gt;, &lt;code&gt;Category&lt;/code&gt;, &lt;code&gt;PriceRow&lt;/code&gt;, &lt;code&gt;User&lt;/code&gt;, &lt;code&gt;Customer&lt;/code&gt;, &lt;code&gt;Order&lt;/code&gt; — is a Java-defined type, and Impex is how you push data at those types without writing Java. A file is a sequence of "blocks," each with a header line declaring a mode and type (&lt;code&gt;INSERT_UPDATE Product;code[unique=true];name&lt;/code&gt;) followed by semicolon-separated data rows (&lt;code&gt;;P-0001;My Product&lt;/code&gt;). Comments start with &lt;code&gt;#&lt;/code&gt;. Macros start with &lt;code&gt;$&lt;/code&gt;. Qualifier paths go inside parentheses: &lt;code&gt;catalogVersion(catalog(id),version)&lt;/code&gt;. The official &lt;a href="https://help.sap.com/docs/SAP_COMMERCE/d0224eca81e249cb821f2cdf45a82ace/1c8f5bebdc6e434782ff0cfdb0ca1847.html" rel="noopener noreferrer"&gt;Impex interpreter reference&lt;/a&gt; is the authoritative spec.&lt;/p&gt;

&lt;p&gt;That is the entire language. It is genuinely small. What makes it hard to read is not the grammar; it's that it's a tabular format displayed in a text editor, without column alignment, without syntax highlighting, without any of the affordances you'd want for reviewing a spreadsheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why HMC plus a periodic refresh forces this conversation
&lt;/h2&gt;

&lt;p&gt;There is a recurring pattern in SAP Commerce projects: you have one production-like environment shared across product, QA, and engineering. To keep it close to reality, someone runs a refresh from production on a schedule — weekly, bi-weekly, overnight. That refresh is non-negotiable; it's how bugs get reproduced. But it means every configuration change that lives only in HMC has a half-life.&lt;/p&gt;

&lt;p&gt;There are three ways out and only one of them scales.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option one:&lt;/strong&gt; stop doing the refresh. You immediately drift from production, QA starts missing real bugs, the refresh comes back, and you've lost a month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option two:&lt;/strong&gt; ask people not to click in HMC. This is a social contract that lasts about a sprint. The tool is there, it works, deadlines exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option three:&lt;/strong&gt; every change goes into version control as an Impex file and is replayed after each refresh. This is the answer the platform has always wanted you to pick. It's also the one that requires your team to be comfortable reading and writing Impex, which brings me back to the opening of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  The eight tools
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpmxosodyjzq5of2t3l2x.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpmxosodyjzq5of2t3l2x.webp" alt="Retail catalog shelves; Impex is the plumbing behind every product tile you see in a SAP Commerce storefront" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything below runs locally in your browser. Nothing is uploaded. The parser is a ~550-line TypeScript module that handles quoted fields, escaped semicolons, line continuations, macros, modifier blocks, and multi-block files. If you find real-world Impex this parser can't handle, I'd like to see it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dev.to/en/data/impex-table-editor"&gt;Impex Table Editor&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This is the one that does the heavy lifting on my team. Paste a block, get a spreadsheet-style grid, edit cells inline, add or remove rows, copy the regenerated Impex back out. When the person writing the change doesn't read Impex but does read Excel, this is the bridge. It is also the tool I reach for first when I need to make a ten-row change without scrolling through semicolons.&lt;/p&gt;

&lt;p&gt;If nothing else in this post sticks, the table editor is the one to bookmark.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dev.to/en/data/impex-to-csv"&gt;Impex to CSV Converter&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Turns any Impex block into CSV, one header per column. Useful when someone from content wants to see the current state of a catalog section in a spreadsheet without waiting for an engineer.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dev.to/en/data/csv-to-impex"&gt;CSV to Impex Converter&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The reverse: a CSV (often pasted from Google Sheets) becomes an Impex script, with the type name and unique column selected explicitly. Product people can draft the change in Sheets, the engineer converts it and commits. The review diff stays reviewable.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dev.to/en/data/impex-to-json"&gt;Impex to JSON&lt;/a&gt; and &lt;a href="https://dev.to/en/data/json-to-impex"&gt;JSON to Impex&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;JSON is the lingua franca of everything that isn't Impex. If you're scripting a migration, generating Impex from a source of truth, or diffing two versions, a structured representation is easier than regexing semicolons. Each block becomes &lt;code&gt;{mode, type, headers, rows}&lt;/code&gt;, macros are first-class, and the two tools round-trip so you can edit the JSON and convert back. A swap button at the top of each page flips the direction.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dev.to/en/data/impex-validator"&gt;Impex Validator / Linter&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Reports missing type names, unknown modifiers, duplicate unique keys across data rows, undefined macros, and column-count mismatches. None of these are exotic errors — they are all things SAP's interpreter will accept and then silently produce a bad import for. Adding this as a CI check on &lt;code&gt;.impex&lt;/code&gt; files is the single highest-leverage change a team using Impex in version control can make.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dev.to/en/data/impex-macro-expander"&gt;Impex Macro Expander&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Given an Impex file with &lt;code&gt;$macros&lt;/code&gt;, produces the same file with every reference inlined. Fixed-point expansion, so macros that reference macros resolve cleanly. This is what you commit into an audit artefact. Auditors are not going to chase &lt;code&gt;$macros&lt;/code&gt; through your repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dev.to/en/data/impex-to-sql"&gt;Impex to SQL (Approximate)&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Hybris abstracts over the database. Sometimes that abstraction leaks — a DBA asks what's actually being written, or you're reasoning about performance. This tool flattens Impex into approximate INSERT / UPDATE / DELETE in Postgres, MySQL, or ANSI-standard SQL, with &lt;code&gt;INSERT_UPDATE&lt;/code&gt; rendered as &lt;code&gt;ON CONFLICT&lt;/code&gt; / &lt;code&gt;ON DUPLICATE KEY&lt;/code&gt; / &lt;code&gt;MERGE&lt;/code&gt; depending on dialect. It is explicitly approximate; qualifier paths like &lt;code&gt;catalogVersion(catalog(id),version)&lt;/code&gt; can't be rendered faithfully as joins without your &lt;code&gt;items.xml&lt;/code&gt;. But for a mental model conversation, it's faster than spinning up a local Hybris and watching the query log.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcs60kkyo0fngvpv10i5s.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcs60kkyo0fngvpv10i5s.webp" alt="A developer's laptop showing terminal output; most Impex work happens at the boundary between code and the platform console" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow I'm pushing on my team
&lt;/h2&gt;

&lt;p&gt;Short version, ordered by ROI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Impex goes both ways: dump what you did in HMC into a &lt;code&gt;.impex&lt;/code&gt; file, or edit &lt;code&gt;.impex&lt;/code&gt; and import to skip HMC entirely.&lt;/strong&gt; HMC is slow for configuration — a twenty-field form is tedious the third time you touch it. For anything repetitive or bulk, writing the Impex directly and running the import is several times faster than clicking. And whatever you did have to do in HMC gets exported to &lt;code&gt;.impex&lt;/code&gt; so it survives the next refresh.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-trivial edits happen in the &lt;a href="https://dev.to/en/data/impex-table-editor"&gt;Table Editor&lt;/a&gt;.&lt;/strong&gt; Flat-editor Impex is fine for adding one column or flipping a flag. For anything with more than a handful of rows, the table editor is correct and the flat editor is not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run the &lt;a href="https://dev.to/en/data/impex-validator"&gt;Impex Validator&lt;/a&gt; before every import.&lt;/strong&gt; This is 90% of the value of the tooling in this post. It catches duplicate unique keys, unknown modifiers, and undefined macros before SAP's interpreter silently accepts them and produces a bad import. Right now we pass it by hand over each &lt;code&gt;.impex&lt;/code&gt; before running the import; not automated, but it catches most of the surprises.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diffs are done on JSON.&lt;/strong&gt; Convert both versions of a file to JSON via &lt;a href="https://dev.to/en/data/impex-to-json"&gt;Impex to JSON&lt;/a&gt; and diff that. Row-level semicolon diffs are unreadable; structured diffs are not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Macros are expanded in audit artefacts.&lt;/strong&gt; Commit the compact form. When compliance asks what the deployment actually did, hand them the expanded version.&lt;/p&gt;

&lt;h2&gt;
  
  
  What still bites
&lt;/h2&gt;

&lt;p&gt;Not every Impex problem has a browser-tool answer.&lt;/p&gt;

&lt;p&gt;Type-system evolution is the big one. When somebody renames a field in &lt;code&gt;items.xml&lt;/code&gt;, every Impex file referencing the old name silently starts writing to the wrong column or failing in confusing ways. No linter in the browser can catch this without access to your specific type definitions. A small project-local script that walks your &lt;code&gt;.impex&lt;/code&gt; files and cross-references attributes against the current type system is worth a weekend. I haven't shipped one because every team has a different setup.&lt;/p&gt;

&lt;p&gt;Performance is the other. Large Impex imports behave differently from bulk SQL loads — the interpreter validates every row, triggers platform events, and invalidates caches. If a million-row import takes six hours, the fix is almost never "tune the database." It's "split the file and import in parallel" or "disable cache eviction during the load." The &lt;a href="https://help.sap.com/docs/SAP_COMMERCE" rel="noopener noreferrer"&gt;SAP Commerce performance documentation&lt;/a&gt; covers this well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built this and not something else
&lt;/h2&gt;

&lt;p&gt;The honest answer: my team (myself included) was not going to learn to read Impex by reading more Impex. Documentation didn't fix it. Pair programming didn't fix it. The bet was that handing someone a &lt;a href="https://dev.to/en/data/impex-table-editor"&gt;spreadsheet UI&lt;/a&gt; over an Impex block and saying "edit this like you'd edit anything else, the tool will write the Impex for you" might lower the barrier enough for Impex to become a format the team treats like CSV — a transport representation that happens to be how SAP Commerce wants the data, not a language they need to master.&lt;/p&gt;

&lt;h2&gt;
  
  
  Will this actually work? I don't know yet.
&lt;/h2&gt;

&lt;p&gt;I want to be straight about where I am with this. These tools exist because my team is trying to answer a bigger question: &lt;strong&gt;can configuration-as-code via Impex replace clicking through HMC, long-term, for a team that isn't fluent in Impex?&lt;/strong&gt; The tools are a bet on "yes." They are not proof.&lt;/p&gt;

&lt;p&gt;There are a few ways this experiment could fall apart. Maybe the table editor is fine for 80% of edits but the remaining 20% — the ones involving nested qualifier paths, macros that reference other catalogs, modifier subtleties — still push people back to HMC and the discipline erodes. Maybe writing Impex is the easy part and the hard part is the CI pipeline that actually replays the scripts after a refresh, which is not what this post is about. Maybe the type system evolves faster than the tooling can keep up and the validator starts producing noise people learn to ignore. Any of these would mean "infrastructure-as-code in HMC via Impex" isn't really the right framing, and we need a different abstraction.&lt;/p&gt;

&lt;p&gt;I'll write a follow-up in a few months once there's real data. Either these tools become the default path and configuration-as-code works the way SAP always wanted it to, or we learn something interesting about why it doesn't, and that's its own post. I don't have a strong prior either way.&lt;/p&gt;

&lt;p&gt;If you're running SAP Commerce with the same HMC + refresh setup and you want to try something similar, the tools are free, local, and will keep working regardless of what I conclude. Bookmark the ones that fit your workflow. If you end up with your own data — positive or negative — I'd like to hear it.&lt;/p&gt;

&lt;p&gt;For the ecosystem context: the &lt;a href="https://help.sap.com/docs/SAP_COMMERCE/d0224eca81e249cb821f2cdf45a82ace/1c8f5bebdc6e434782ff0cfdb0ca1847.html" rel="noopener noreferrer"&gt;SAP Help ImpEx documentation&lt;/a&gt; is the canonical reference, the &lt;a href="https://community.sap.com/topics/commerce-cloud" rel="noopener noreferrer"&gt;SAP Commerce community forums&lt;/a&gt; are better than Stack Overflow for version-specific questions, and the &lt;a href="https://www.gartner.com/en/documents/magic-quadrant-for-digital-commerce" rel="noopener noreferrer"&gt;Gartner Magic Quadrant for Digital Commerce&lt;/a&gt; covers where SAP Commerce sits in the broader enterprise ecommerce landscape.&lt;/p&gt;

&lt;p&gt;More in a few months.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Was Paying Anthropic to Read CSS Class Names</title>
      <dc:creator>Aral Roca</dc:creator>
      <pubDate>Fri, 17 Apr 2026 14:04:33 +0000</pubDate>
      <link>https://forem.com/aralroca/i-was-paying-anthropic-to-read-css-class-names-o2c</link>
      <guid>https://forem.com/aralroca/i-was-paying-anthropic-to-read-css-class-names-o2c</guid>
      <description>&lt;p&gt;I burned through 176 million Anthropic tokens last Wednesday. You can see the spike in the screenshot further down. Most of that was productive Claude Code work, but when I tracked down the anomaly I found a batch job quietly shovelling raw HTML into Claude Sonnet 4.5 for summarization. The prompts worked; the token budget did not. When I finally looked at what my scraper was feeding the model, roughly 70% of every request was &lt;code&gt;&amp;lt;div class="css-1f2x"&amp;gt;&lt;/code&gt; soup. The actual article, the part I cared about, was maybe 25% of the payload.&lt;/p&gt;

&lt;p&gt;The fix was a five-line change: convert HTML to Markdown before sending it to the model. Token count dropped by 60%. Same output quality. I wish I had learned this sooner.&lt;/p&gt;

&lt;p&gt;This post is about why that works, when it matters, and a few adjacent problems that same trick solves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why HTML is a token disaster
&lt;/h2&gt;

&lt;p&gt;LLMs tokenize text. Anthropic exposes a &lt;a href="https://docs.claude.com/en/docs/build-with-claude/token-counting" rel="noopener noreferrer"&gt;token counting endpoint&lt;/a&gt; in the Claude API, and OpenAI's open-source &lt;a href="https://github.com/openai/tiktoken" rel="noopener noreferrer"&gt;tiktoken&lt;/a&gt; gives a similar view: most English prose splits into roughly one token per 3-4 characters. But HTML is not prose. It is a nested tree of tags, class names, inline styles, SVG attributes, and JSON blobs dumped into &lt;code&gt;data-*&lt;/code&gt; attributes by every modern framework.&lt;/p&gt;

&lt;p&gt;You can verify this yourself. Paste a paragraph of plain text into Anthropic's token counter and note the count. Then wrap it in a typical React-generated &lt;code&gt;&amp;lt;div class="prose dark:prose-invert max-w-none sm:px-6 lg:px-8"&amp;gt;&lt;/code&gt; and count again. The wrapper alone, that one line, can cost you 30-40 tokens. Multiply that across a scraped page with 500 nested divs and you see where the money goes.&lt;/p&gt;

&lt;p&gt;Markdown, by contrast, is nearly invisible to a tokenizer. A heading is two characters: &lt;code&gt;#&lt;/code&gt;. A bold span is four: &lt;code&gt;**x**&lt;/code&gt;. A link is &lt;code&gt;[text](url)&lt;/code&gt;, and the model handles it natively because its training corpus is saturated with Markdown from GitHub, Stack Overflow, and Reddit.&lt;/p&gt;

&lt;p&gt;I ran a quick test on the Wikipedia article for the &lt;a href="https://en.wikipedia.org/wiki/HTTP" rel="noopener noreferrer"&gt;HTTP protocol&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Raw HTML (saved from browser): ~48,000 tokens&lt;/li&gt;
&lt;li&gt;Cleaned HTML (boilerpipe style): ~22,000 tokens&lt;/li&gt;
&lt;li&gt;Markdown conversion: ~8,900 tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is an 81% reduction against raw, and 60% against even a cleaned HTML payload. On a model that charges per input token, this is not a micro-optimization. It is the difference between a viable product and a Stripe chargeback.&lt;/p&gt;

&lt;p&gt;Here is a week of my actual Claude Code usage for reference. Four days, 433 million tokens, about $306 at list prices (I am on a Max subscription, but the equivalent pay-as-you-go cost makes the scale legible). Most of that is productive coding. Imagine what shaving 10-15% off by not shipping structural noise would buy you over a year.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46uuu2geuu8nvc292feq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46uuu2geuu8nvc292feq.webp" alt="Claude Code token usage this week; 433M tokens across four days, roughly $306 in equivalent API cost" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The RAG pipeline case
&lt;/h2&gt;

&lt;p&gt;If you are building retrieval-augmented generation, this matters even more. A RAG system typically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Crawls or scrapes source documents&lt;/li&gt;
&lt;li&gt;Chunks them into pieces that fit the embedding model's context window&lt;/li&gt;
&lt;li&gt;Embeds each chunk into a vector database&lt;/li&gt;
&lt;li&gt;At query time, retrieves the top-K chunks and injects them into the LLM prompt&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of those steps gets worse with HTML in the loop. Embeddings drift because the model is trying to encode CSS class names alongside actual meaning. Chunking gets jagged; you either split mid-tag and confuse the retriever, or you use a naive character split that cuts a sentence in half. Retrieval latency grows because the vector space is polluted with structural noise.&lt;/p&gt;

&lt;p&gt;Converting to Markdown before chunking solves all three. The excellent &lt;a href="https://python.langchain.com/docs/concepts/text_splitters/" rel="noopener noreferrer"&gt;LangChain docs&lt;/a&gt; quietly recommend this: use &lt;code&gt;MarkdownHeaderTextSplitter&lt;/code&gt; or &lt;code&gt;RecursiveCharacterTextSplitter&lt;/code&gt; with Markdown separators. Their own examples strip HTML to Markdown first. There is a reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you cannot run Python on a server
&lt;/h2&gt;

&lt;p&gt;A lot of the HTML-to-Markdown literature assumes you have a backend with &lt;a href="https://www.crummy.com/software/BeautifulSoup/" rel="noopener noreferrer"&gt;BeautifulSoup&lt;/a&gt; and &lt;a href="https://github.com/matthewwpeters/markdownify" rel="noopener noreferrer"&gt;markdownify&lt;/a&gt; installed. That is fine if you are building a pipeline. It is painful if you are a content writer, a journalist archiving sources, or a developer doing a one-off migration at 11pm.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://kitmul.com/en/writing/html-to-markdown" rel="noopener noreferrer"&gt;Kitmul HTML to Markdown&lt;/a&gt; because I kept reaching for that exact workflow and hating every option. The tool runs in the browser. Paste the HTML source, get the Markdown. Nothing uploads, nothing phones home. The conversion happens with JavaScript on your machine, which means you can paste internal wiki exports, legal drafts, or client content without a second thought.&lt;/p&gt;

&lt;p&gt;If you have never inspected the HTML behind a page, open any article, right-click, choose "View Page Source", and copy the article body. Feed that into the tool. What comes back is something you can actually put into a prompt, paste into Notion, or commit to a Git repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seven places this actually helps
&lt;/h2&gt;

&lt;p&gt;It took me a while to recognize how many of my problems reduce to "I have HTML and I wish it were Markdown". A partial list:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feeding a long article to an LLM.&lt;/strong&gt; The single largest wins happen when you are summarizing, extracting entities, or asking questions about a long web page. Markdown strips out the chrome and leaves the substance. Prompt cost drops, and ironically the model does better on the cleaner input because it stops getting distracted by CSS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Migrating a blog off WordPress or Ghost.&lt;/strong&gt; If you are moving to a static site generator like &lt;a href="https://astro.build/" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;, &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt;, or Jekyll, every post needs to be a &lt;code&gt;.md&lt;/code&gt; file. Export your WordPress XML, feed each &lt;code&gt;&amp;lt;content:encoded&amp;gt;&lt;/code&gt; block through the converter, drop the result into &lt;code&gt;content/posts/&lt;/code&gt;. I moved 84 posts this way in an evening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Archiving research.&lt;/strong&gt; I keep a personal wiki of engineering articles I reference often. Pages disappear from the web all the time; &lt;a href="https://www.nature.com/articles/d41586-024-01095-4" rel="noopener noreferrer"&gt;the average web page lifespan is under 100 days&lt;/a&gt;. Markdown snapshots take milliseconds to read, diff cleanly in Git, and survive format churn in a way screenshots or PDFs do not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Preparing training data or few-shot examples.&lt;/strong&gt; If you are curating examples for fine-tuning or in-context learning, you want them consistent. Markdown is the consistent form. Every scraped source ends up the same shape, with the same heading hierarchy, and no random &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; wrappers confusing your template.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cleaning up content after visual editors.&lt;/strong&gt; Tools like Notion, Medium, or CKEditor export HTML that is technically correct but full of &lt;code&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;em&amp;gt;&amp;lt;u&amp;gt;&lt;/code&gt; nesting nobody wants. Round-tripping through Markdown collapses that to the minimum semantic form, and you get a diffable, portable source of truth out of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing documentation from scraped API references.&lt;/strong&gt; I have done this for three SDKs now. Grab the rendered HTML from the vendor's docs (many are built with MkDocs or Docusaurus and have no Markdown source available), convert it, and you have a starting point you can edit and commit. It is faster than copy-pasting each code block by hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reducing costs on the &lt;a href="https://docs.claude.com/en/home" rel="noopener noreferrer"&gt;Claude API&lt;/a&gt; and &lt;a href="https://ai.google.dev/" rel="noopener noreferrer"&gt;Gemini API&lt;/a&gt;.&lt;/strong&gt; Every frontier model bills input tokens. Anthropic's &lt;a href="https://docs.claude.com/en/docs/build-with-claude/prompt-caching" rel="noopener noreferrer"&gt;prompt caching&lt;/a&gt; helps, but caching 48K of HTML is still worse than caching 9K of Markdown. Caches also evict. The cheapest token is the one you never send.&lt;/p&gt;

&lt;h2&gt;
  
  
  What gets lost in the conversion
&lt;/h2&gt;

&lt;p&gt;Be honest about the trade-offs. A few things do not survive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visual styling.&lt;/strong&gt; Colors, fonts, custom CSS. If the visual layout is the content (infographics, landing pages), Markdown is the wrong target.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive components.&lt;/strong&gt; Embedded forms, iframes, JavaScript widgets. These become either plain links or they disappear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact table layouts.&lt;/strong&gt; Markdown tables handle rows and columns, but merged cells, colspans, and nested tables simplify or break. For data-heavy tables, CSV export is usually a better path; our &lt;a href="https://kitmul.com/en/data-converters/csv-to-json" rel="noopener noreferrer"&gt;CSV to JSON tool&lt;/a&gt; covers the adjacent case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image positioning.&lt;/strong&gt; Markdown images are inline, top-down. Float layouts do not translate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For 90% of text-driven web pages, none of this matters. For the remaining 10%, you probably want the HTML anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete workflow
&lt;/h2&gt;

&lt;p&gt;Here is the routine I have settled into for LLM work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scrape the page with &lt;code&gt;fetch&lt;/code&gt; or &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; and grab the innerHTML of the article body, not the full document. A decent CSS selector does half the cleanup for you.&lt;/li&gt;
&lt;li&gt;If it is a one-off, paste into the &lt;a href="https://kitmul.com/en/writing/html-to-markdown" rel="noopener noreferrer"&gt;HTML to Markdown converter&lt;/a&gt;. If it is a batch, use a library like &lt;code&gt;turndown&lt;/code&gt; (&lt;a href="https://github.com/mixmark-io/turndown" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;) or &lt;code&gt;markdownify&lt;/code&gt; in Python.&lt;/li&gt;
&lt;li&gt;Strip boilerplate (cookie banners, "related posts", newsletter CTAs). Markdown makes this easy: the noise tends to cluster at the top and bottom.&lt;/li&gt;
&lt;li&gt;Feed to the model. For long documents, chunk along Markdown headings rather than character counts.&lt;/li&gt;
&lt;li&gt;Cache the Markdown version locally. The next run should not repeat step 1.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole thing usually takes longer to describe than to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The broader point
&lt;/h2&gt;

&lt;p&gt;Format conversion sounds boring. It is, until you watch your API bill shrink by 60% because you stopped sending structural ASCII noise to a trillion-parameter model. LLMs are expensive not in absolute terms but per million tokens, and the number of pipelines where someone is shipping raw HTML into a context window is genuinely embarrassing.&lt;/p&gt;

&lt;p&gt;Markdown is the lingua franca of AI training data. That is a fact about the world, not an opinion. Working in Markdown aligns your inputs with what the model was trained to handle, and a plain-text intermediate form also gives you a version your Git history can meaningfully diff, which is something you will be grateful for in six months when you are trying to remember what the prompt looked like in Q3.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Open the &lt;a href="https://kitmul.com/en/writing/html-to-markdown" rel="noopener noreferrer"&gt;HTML to Markdown converter&lt;/a&gt;, paste any HTML, and see the token count drop. Everything runs in your browser, which means your client drafts, scraped sources, and internal docs never leave your machine. The code is inspectable in the DevTools network tab; there are zero outbound requests during conversion.&lt;/p&gt;

&lt;p&gt;If you want to publish the Markdown somewhere visual, the &lt;a href="https://kitmul.com/en/writing/markdown-to-medium" rel="noopener noreferrer"&gt;Markdown to Medium converter&lt;/a&gt; handles publication-ready output for Medium's editor.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Related reading: &lt;a href="https://kitmul.com/en/blog/markdown-to-medium-guide" rel="noopener noreferrer"&gt;How to Publish Markdown to Medium Without Losing Formatting&lt;/a&gt; · &lt;a href="https://kitmul.com/en/blog/why-client-side-tools-are-more-private" rel="noopener noreferrer"&gt;Why Client-Side Tools Are More Private Than the Cloud&lt;/a&gt; · &lt;a href="https://kitmul.com/en/blog/avif-image-format-complete-guide" rel="noopener noreferrer"&gt;AVIF in 2026: The Complete Guide&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Related tools: &lt;a href="https://kitmul.com/en/developer/json-formatter" rel="noopener noreferrer"&gt;JSON Formatter&lt;/a&gt; · &lt;a href="https://kitmul.com/en/data-converters/csv-to-json" rel="noopener noreferrer"&gt;CSV to JSON&lt;/a&gt; · &lt;a href="https://kitmul.com/en/writing/word-counter" rel="noopener noreferrer"&gt;Word Counter&lt;/a&gt; · &lt;a href="https://kitmul.com/en/writing/text-diff" rel="noopener noreferrer"&gt;Text Diff Checker&lt;/a&gt; · &lt;a href="https://kitmul.com/en/image-design/image-compressor" rel="noopener noreferrer"&gt;Image Compressor&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>markdown</category>
      <category>webdev</category>
      <category>llm</category>
      <category>ai</category>
    </item>
    <item>
      <title>WSJF, Cost of Delay and Why Your Loudest Stakeholder Keeps Winning the Backlog Argument</title>
      <dc:creator>Aral Roca</dc:creator>
      <pubDate>Thu, 16 Apr 2026 19:53:48 +0000</pubDate>
      <link>https://forem.com/aralroca/wsjf-cost-of-delay-and-why-your-loudest-stakeholder-keeps-winning-the-backlog-argument-2ggf</link>
      <guid>https://forem.com/aralroca/wsjf-cost-of-delay-and-why-your-loudest-stakeholder-keeps-winning-the-backlog-argument-2ggf</guid>
      <description>&lt;p&gt;A few months ago I sat in a PI planning session where a team of twelve argued for forty minutes about whether a "see product alternatives" button was more important than a "notify me" button for out-of-stock items. Both PMs had slide decks. Both had data. Both were convinced. The conversation ended the way those conversations always end; the loudest person in the room won, and everyone else went back to their laptops with a quiet grudge.&lt;/p&gt;

&lt;p&gt;That meeting is why I rebuilt our &lt;a href="https://kitmul.com/en/agile-project-management/wsjf-calculator" rel="noopener noreferrer"&gt;WSJF Calculator&lt;/a&gt;. Not because the world needed yet another backlog prioritization tool, but because I kept watching smart teams flush an entire quarter down the drain arguing about feelings when the math has existed since the 1980s.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trick nobody tells you about prioritization
&lt;/h2&gt;

&lt;p&gt;Most people think prioritization is about picking the most valuable thing. It isn't. It's about picking the thing where &lt;strong&gt;value divided by size&lt;/strong&gt; is highest. That distinction sounds pedantic until you realize it's the difference between shipping eight small wins in a quarter and shipping one lumbering "strategic initiative" that nobody really wanted.&lt;/p&gt;

&lt;p&gt;That's the whole thesis behind &lt;a href="https://scaledagileframework.com/wsjf/" rel="noopener noreferrer"&gt;Weighted Shortest Job First&lt;/a&gt;. You score each backlog item on two things; how expensive it is to &lt;em&gt;delay&lt;/em&gt;, and how big the job is. Then you divide. The highest score wins. That's it. No story point debates at 9am on a Monday, no "is login page a 3 or a 5", no pretending your gut feeling is a methodology.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WSJF = Cost of Delay / Job Size
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.productplan.com/glossary/weighted-shortest-job-first/" rel="noopener noreferrer"&gt;Don Reinertsen&lt;/a&gt; popularized this in &lt;em&gt;The Principles of Product Development Flow&lt;/em&gt;, which I'd call the best agile book ever written if the phrase "agile book" didn't immediately make most engineers reach for the nearest window. It's actually a &lt;a href="https://en.wikipedia.org/wiki/Queueing_theory" rel="noopener noreferrer"&gt;queueing theory&lt;/a&gt; book dressed up as a management book, which is why it's so good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost of Delay is three things, not one
&lt;/h2&gt;

&lt;p&gt;Here's where most WSJF spreadsheets get it wrong. &lt;a href="https://en.wikipedia.org/wiki/Cost_of_delay" rel="noopener noreferrer"&gt;Cost of Delay&lt;/a&gt; isn't a single number. It's the sum of three very different questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Business Value.&lt;/strong&gt; If we shipped this tomorrow, how much would users or the business care? Revenue, retention, a specific OKR. Concrete when you can, relative when you can't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time Criticality.&lt;/strong&gt; Does the value decay over time? A Black Friday feature shipped in December is worth approximately zero. A compliance deadline you miss is worth less than zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk Reduction / Opportunity Enablement.&lt;/strong&gt; Does this unlock something else, or take uncertainty off the table? Boring infrastructure work lives here and that's usually where it gets buried.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add those three together. That's Cost of Delay. Divide by Job Size. That's WSJF. The reason this works isn't because the numbers are accurate; it's because the &lt;em&gt;conversation&lt;/em&gt; is accurate. You can't pretend a risk-reduction spike is a revenue feature anymore. Every item has to justify itself on three axes before it gets a score.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why relative sizing beats hours every single time
&lt;/h2&gt;

&lt;p&gt;The other trap is how you size. People want to estimate in days. Don't. Human beings are terrible at estimating duration and we have decades of behavioral research showing it; &lt;a href="https://en.wikipedia.org/wiki/Parkinson%27s_law" rel="noopener noreferrer"&gt;Parkinson's law&lt;/a&gt; alone should be enough to scare you off. What humans are actually okay at is comparing two things and saying "that one's bigger."&lt;/p&gt;

&lt;p&gt;So score in &lt;a href="https://en.wikipedia.org/wiki/Fibonacci_number" rel="noopener noreferrer"&gt;Fibonacci&lt;/a&gt; (1, 2, 3, 5, 8, 13, 20) or T-shirt sizes (XS, S, M, L, XL, XXL). The gaps matter; a jump from 5 to 8 forces a conversation that "4 days vs 5 days" doesn't. Our tool supports both because teams argue about this, and the argument is not worth having. Pick one. Move on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7rcy4qkdgqvapule0ymv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7rcy4qkdgqvapule0ymv.webp" alt="WSJF prioritization results: ranked backlog with Business Value, Time Criticality, Risk Reduction, Job Size and WSJF score per item" width="800" height="648"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the tool actually does
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://kitmul.com/en/agile-project-management/wsjf-calculator" rel="noopener noreferrer"&gt;WSJF Calculator&lt;/a&gt; runs in your browser. Nothing leaves your machine. I'm obsessive about this for product-management tools specifically; your roadmap is the kind of thing you really don't want sitting on someone else's server.&lt;/p&gt;

&lt;p&gt;Here's what we built into it after watching teams actually use it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Templates for common domains.&lt;/strong&gt; E-commerce, SaaS, internal platform, regulated industry. Start from something that already makes sense.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What-if analysis.&lt;/strong&gt; Inflate the top task's size by 25%. Does the ranking flip? If yes, your top priority is &lt;em&gt;fragile&lt;/em&gt;; re-estimate before you commit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Robustness indicator.&lt;/strong&gt; If the top three WSJF scores are within 10% of each other, treat them as equal and let team capacity decide. The tool shows you this in real time so you stop pretending 4.20 is meaningfully bigger than 4.10.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share links.&lt;/strong&gt; Copy a URL, paste it in PI planning notes, everyone loads the same backlog state. No Jira exports, no CSVs floating around Slack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSV + PDF export.&lt;/strong&gt; Because some stakeholders only trust a PDF.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When WSJF is the wrong answer
&lt;/h2&gt;

&lt;p&gt;I'll save you a mistake I've made. WSJF is not a universal prioritization framework. It's the right tool when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have 15 or more roughly comparable backlog items.&lt;/li&gt;
&lt;li&gt;Your team is doing continuous delivery with short cycles.&lt;/li&gt;
&lt;li&gt;Cost of delay is actually variable across items (some are time-sensitive, some aren't).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's the &lt;em&gt;wrong&lt;/em&gt; tool when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have a single strategic bet and three support tickets. Just do the strategic bet.&lt;/li&gt;
&lt;li&gt;Everything is equally urgent. Then nothing is, and you have a management problem, not a prioritization problem.&lt;/li&gt;
&lt;li&gt;You're trying to decide between two products. That's a &lt;a href="https://kitmul.com/en/agile-project-management/kano-model" rel="noopener noreferrer"&gt;Kano&lt;/a&gt; or portfolio question.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For binary in/out decisions, reach for &lt;a href="https://kitmul.com/en/agile-project-management/moscow-prioritization" rel="noopener noreferrer"&gt;MoSCoW&lt;/a&gt; or the &lt;a href="https://en.wikipedia.org/wiki/Eisenhower_matrix" rel="noopener noreferrer"&gt;Eisenhower matrix&lt;/a&gt; instead. For consumer-product feature ranking where you want a simpler formula, &lt;a href="https://kitmul.com/en/agile-project-management/rice-scoring" rel="noopener noreferrer"&gt;RICE&lt;/a&gt; is friendlier. I built versions of all three; they each solve a slightly different shape of problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The meeting ritual that actually works
&lt;/h2&gt;

&lt;p&gt;WSJF breaks down when it becomes a ritual. The calculator does the math; the humans have to do the talking. Here's the loop I've seen work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Score Cost of Delay first, together.&lt;/strong&gt; Use &lt;a href="https://kitmul.com/en/agile-project-management/scrum-poker" rel="noopener noreferrer"&gt;planning poker&lt;/a&gt; to surface disagreement. If two people scored a 3 and a 13, that's interesting; dig in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score Job Size &lt;em&gt;last&lt;/em&gt;.&lt;/strong&gt; If you know the size first, it anchors the value estimate. Humans are lazy that way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the top three out loud.&lt;/strong&gt; If anyone flinches, re-score.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lock and ship.&lt;/strong&gt; Don't re-score mid-sprint. The whole point is that yesterday-you and today-you had different gut feelings but the math didn't move.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For longer-horizon commitments, pair WSJF with probabilistic forecasting. I wrote a whole piece on &lt;a href="https://kitmul.com/en/blog/monte-carlo-forecasting-agile-delivery" rel="noopener noreferrer"&gt;Monte Carlo delivery forecasting&lt;/a&gt; that pairs nicely with this; one tool tells you &lt;em&gt;what&lt;/em&gt; to do next, the other tells you &lt;em&gt;when&lt;/em&gt; you'll realistically be done.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that surprised me
&lt;/h2&gt;

&lt;p&gt;When I first shipped the calculator, I expected engineers to use it. It turned out product managers are the heaviest users, and the thing they love most isn't the score. It's the &lt;em&gt;audit trail&lt;/em&gt;. Every ranking has the three sub-scores visible; when a stakeholder asks "why isn't my feature at the top", the PM can point at a number and say "because your Time Criticality is S and your Job Size is XL." The conversation ends in a minute, not a week.&lt;/p&gt;

&lt;p&gt;That's the real point of any prioritization tool. Not to replace judgment. To make judgment &lt;em&gt;visible&lt;/em&gt; enough that the loudest person in the room can't overwrite everyone else.&lt;/p&gt;

&lt;p&gt;Try it here: &lt;a href="https://kitmul.com/en/agile-project-management/wsjf-calculator" rel="noopener noreferrer"&gt;kitmul.com/en/agile-project-management/wsjf-calculator&lt;/a&gt;. It's free, it's in your browser, and your backlog is still yours.&lt;/p&gt;

</description>
      <category>agile</category>
      <category>scrum</category>
      <category>leadership</category>
      <category>productivity</category>
    </item>
    <item>
      <title>AVIF in 2026: The Complete Guide to the Image Format That Beat JPEG, PNG, and WebP</title>
      <dc:creator>Aral Roca</dc:creator>
      <pubDate>Tue, 14 Apr 2026 19:41:39 +0000</pubDate>
      <link>https://forem.com/aralroca/avif-in-2026-the-complete-guide-to-the-image-format-that-beat-jpeg-png-and-webp-34n2</link>
      <guid>https://forem.com/aralroca/avif-in-2026-the-complete-guide-to-the-image-format-that-beat-jpeg-png-and-webp-34n2</guid>
      <description>&lt;p&gt;AVIF landed quietly in 2019 and spent years as the format nobody used. Then Chrome shipped native support. Then Firefox. Then Safari 16. By 2024, &lt;a href="https://caniuse.com/avif" rel="noopener noreferrer"&gt;Can I Use reported 93% global browser coverage&lt;/a&gt;. And suddenly the question flipped from "can I use AVIF?" to "why aren't I using AVIF?"&lt;/p&gt;

&lt;p&gt;If you're still serving JPEG and PNG on the web in 2026, you're leaving performance on the table. Here's what you need to know.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AVIF actually is
&lt;/h2&gt;

&lt;p&gt;AVIF (AV1 Image File Format) is a still-image format derived from the &lt;a href="https://en.wikipedia.org/wiki/AV1" rel="noopener noreferrer"&gt;AV1 video codec&lt;/a&gt;, developed by the &lt;a href="https://aomedia.org/" rel="noopener noreferrer"&gt;Alliance for Open Media&lt;/a&gt;; a consortium that includes Google, Apple, Microsoft, Mozilla, Netflix, and Amazon. It's royalty-free, open-source, and designed to replace JPEG, PNG, and even WebP.&lt;/p&gt;

&lt;p&gt;The key technical facts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lossy and lossless&lt;/strong&gt; compression in a single format&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;10-bit and 12-bit&lt;/strong&gt; color depth (vs JPEG's 8-bit)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HDR and wide color gamut&lt;/strong&gt; (BT.2020, PQ, HLG)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alpha transparency&lt;/strong&gt; (like PNG, unlike JPEG)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Film grain synthesis&lt;/strong&gt; (stores grain parameters instead of actual noise)&lt;/li&gt;
&lt;li&gt;Based on the &lt;strong&gt;HEIF container&lt;/strong&gt; (ISO/IEC 23008-12)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The compression comes from AV1's intra-frame coding tools: directional prediction, recursive partitioning (up to 128x128 superblocks), and the CDEF (Constrained Directional Enhancement Filter). These tools were designed for video but work remarkably well for still images.&lt;/p&gt;

&lt;h2&gt;
  
  
  The compression numbers
&lt;/h2&gt;

&lt;p&gt;Let's be specific. In independent tests by &lt;a href="https://netflixtechblog.com/avif-for-next-generation-image-coding-b1d75675fe4" rel="noopener noreferrer"&gt;Netflix&lt;/a&gt;, &lt;a href="https://blog.cloudflare.com/generate-avif-images-with-image-resizing/" rel="noopener noreferrer"&gt;Cloudflare&lt;/a&gt;, and &lt;a href="https://web.dev/articles/serve-images-avif" rel="noopener noreferrer"&gt;Google&lt;/a&gt;, AVIF consistently outperforms:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Comparison&lt;/th&gt;
&lt;th&gt;File size reduction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AVIF vs JPEG&lt;/td&gt;
&lt;td&gt;30-50% smaller at same quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AVIF vs PNG&lt;/td&gt;
&lt;td&gt;50-70% smaller (lossy mode)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AVIF vs WebP&lt;/td&gt;
&lt;td&gt;15-25% smaller at same quality&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a concrete example: a 500KB JPEG photograph typically compresses to ~250KB as AVIF with no visible quality difference. A 2MB PNG screenshot drops to ~400KB. These are not edge cases; they're typical results across diverse image types.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgbx4tseivnd8696twy5j.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgbx4tseivnd8696twy5j.webp" alt="Analytics dashboard showing web performance metrics, representing the impact of image optimization" width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AVIF matters for web performance
&lt;/h2&gt;

&lt;p&gt;Images account for &lt;a href="https://httparchive.org/reports/page-weight" rel="noopener noreferrer"&gt;roughly 50% of the average web page's total weight&lt;/a&gt;, according to HTTP Archive data. Cutting that by 30-50% has real consequences:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Core Web Vitals&lt;/strong&gt;: Smaller images directly improve LCP (Largest Contentful Paint). Google has confirmed LCP is a &lt;a href="https://developers.google.com/search/docs/appearance/core-web-vitals" rel="noopener noreferrer"&gt;ranking signal&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bandwidth costs&lt;/strong&gt;: A site serving 1 million page views/month with 500KB of images per page transfers ~500GB. Switching to AVIF cuts that to ~300GB. At CDN rates, that's real money.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mobile experience&lt;/strong&gt;: On 3G connections (still common in emerging markets), a 500KB image takes ~4 seconds to load. A 250KB AVIF takes ~2 seconds. That's the difference between a bounce and a conversion.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Carbon footprint&lt;/strong&gt;: Less data transferred means less energy consumed. The &lt;a href="https://www.thegreenwebfoundation.org/" rel="noopener noreferrer"&gt;Green Web Foundation&lt;/a&gt; estimates that data transfer accounts for ~0.06g CO2 per MB.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  AVIF vs WebP vs JPEG vs PNG: when to use each
&lt;/h2&gt;

&lt;p&gt;The format landscape isn't "use AVIF for everything." Each format still has its place:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Best format&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Photographs for the web&lt;/td&gt;
&lt;td&gt;AVIF&lt;/td&gt;
&lt;td&gt;Best compression, no visible quality loss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transparent graphics&lt;/td&gt;
&lt;td&gt;AVIF or WebP&lt;/td&gt;
&lt;td&gt;Both support alpha; AVIF is smaller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pixel-perfect screenshots&lt;/td&gt;
&lt;td&gt;PNG&lt;/td&gt;
&lt;td&gt;Lossless, universal compatibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email attachments&lt;/td&gt;
&lt;td&gt;JPEG&lt;/td&gt;
&lt;td&gt;Universal; every client supports it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Legacy system input&lt;/td&gt;
&lt;td&gt;BMP/JPEG&lt;/td&gt;
&lt;td&gt;Some systems can't decode modern formats&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Favicons&lt;/td&gt;
&lt;td&gt;ICO&lt;/td&gt;
&lt;td&gt;Required by browsers for tab icons&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalable logos&lt;/td&gt;
&lt;td&gt;SVG&lt;/td&gt;
&lt;td&gt;Vector; infinitely scalable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HDR photography&lt;/td&gt;
&lt;td&gt;AVIF&lt;/td&gt;
&lt;td&gt;Only modern format with full HDR support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The practical advice: serve AVIF as your primary web format with JPEG/PNG fallbacks using the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element or CDN-based content negotiation.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to convert images to and from AVIF
&lt;/h2&gt;

&lt;p&gt;You don't need to install command-line tools or pay for cloud services. Kitmul provides free, browser-based AVIF converters that process everything locally; your images never leave your device.&lt;/p&gt;

&lt;h3&gt;
  
  
  Converting TO AVIF (reduce file sizes)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kitmul.com/en/image-design/jpeg-to-avif" rel="noopener noreferrer"&gt;JPEG to AVIF Converter&lt;/a&gt;&lt;/strong&gt;: The most common conversion. Shrink your photo library by 30-50%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kitmul.com/en/image-design/png-to-avif" rel="noopener noreferrer"&gt;PNG to AVIF Converter&lt;/a&gt;&lt;/strong&gt;: Perfect for screenshots and graphics with transparency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kitmul.com/en/image-design/webp-to-avif" rel="noopener noreferrer"&gt;WebP to AVIF Converter&lt;/a&gt;&lt;/strong&gt;: Upgrade from WebP to the next generation; another 20% savings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kitmul.com/en/image-design/svg-to-avif" rel="noopener noreferrer"&gt;SVG to AVIF Converter&lt;/a&gt;&lt;/strong&gt;: Rasterize vector graphics into compact AVIF files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kitmul.com/en/image-design/bmp-to-avif" rel="noopener noreferrer"&gt;BMP to AVIF Converter&lt;/a&gt;&lt;/strong&gt;: Compress raw bitmaps from legacy systems; 95%+ reduction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kitmul.com/en/image-design/pdf-to-avif" rel="noopener noreferrer"&gt;PDF to AVIF Converter&lt;/a&gt;&lt;/strong&gt;: Extract PDF pages as lightweight AVIF images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kitmul.com/en/image-design/ico-to-avif" rel="noopener noreferrer"&gt;ICO to AVIF Converter&lt;/a&gt;&lt;/strong&gt;: Convert icon files for web galleries.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Converting FROM AVIF (for compatibility)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kitmul.com/en/image-design/avif-to-jpeg" rel="noopener noreferrer"&gt;AVIF to JPEG Converter&lt;/a&gt;&lt;/strong&gt;: When you need universal compatibility for email or print.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kitmul.com/en/image-design/avif-to-png" rel="noopener noreferrer"&gt;AVIF to PNG Converter&lt;/a&gt;&lt;/strong&gt;: Lossless output with transparency for editing workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kitmul.com/en/image-design/avif-to-webp" rel="noopener noreferrer"&gt;AVIF to WebP Converter&lt;/a&gt;&lt;/strong&gt;: WebP fallback for CDN pipelines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kitmul.com/en/image-design/avif-to-pdf" rel="noopener noreferrer"&gt;AVIF to PDF Converter&lt;/a&gt;&lt;/strong&gt;: Embed images in professional documents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kitmul.com/en/image-design/avif-to-gif" rel="noopener noreferrer"&gt;AVIF to GIF Converter&lt;/a&gt;&lt;/strong&gt;: For email signatures and legacy platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kitmul.com/en/image-design/avif-to-bmp" rel="noopener noreferrer"&gt;AVIF to BMP Converter&lt;/a&gt;&lt;/strong&gt;: Raw pixel data for industrial systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://kitmul.com/en/image-design/avif-to-ico" rel="noopener noreferrer"&gt;AVIF to ICO Converter&lt;/a&gt;&lt;/strong&gt;: Create favicons from AVIF source images.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo97ai3dxultdxe4ibksb.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo97ai3dxultdxe4ibksb.webp" alt="Kitmul's AVIF converter tool showing an AVIF to WebP conversion with the converted result" width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element: serving AVIF with fallbacks
&lt;/h2&gt;

&lt;p&gt;The standard pattern for progressive format delivery on the web:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;picture&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"photo.avif"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/avif"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"photo.webp"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/webp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"photo.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Description"&lt;/span&gt; &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/picture&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser picks the first format it supports. AVIF-capable browsers (93%+) get the smallest file. WebP serves as a fallback for the remaining ~7%. JPEG is the universal safety net.&lt;/p&gt;

&lt;p&gt;For responsive images, combine with &lt;code&gt;srcset&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;picture&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt;
    &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"photo-400.avif 400w, photo-800.avif 800w, photo-1200.avif 1200w"&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/avif"&lt;/span&gt;
    &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 800px) 100vw, 800px"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt;
    &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"photo-400.webp 400w, photo-800.webp 800w, photo-1200.webp 1200w"&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/webp"&lt;/span&gt;
    &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 800px) 100vw, 800px"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
    &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"photo-800.jpg"&lt;/span&gt;
    &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Description"&lt;/span&gt;
    &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;
    &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt;
    &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"600"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/picture&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CDN and build-tool support
&lt;/h2&gt;

&lt;p&gt;Most modern infrastructure handles AVIF natively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Image Resizing&lt;/strong&gt;: &lt;a href="https://developers.cloudflare.com/images/polish/" rel="noopener noreferrer"&gt;Auto-converts to AVIF&lt;/a&gt; via Polish or Image Resizing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel/Next.js&lt;/strong&gt;: &lt;code&gt;next/image&lt;/code&gt; supports AVIF via &lt;code&gt;formats: ['image/avif', 'image/webp']&lt;/code&gt; in &lt;code&gt;next.config.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Netlify&lt;/strong&gt;: Automatic AVIF via &lt;a href="https://docs.netlify.com/image-cdn/overview/" rel="noopener noreferrer"&gt;Image CDN&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sharp (Node.js)&lt;/strong&gt;: Full AVIF encode/decode since v0.29&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Squoosh&lt;/strong&gt;: Google's &lt;a href="https://squoosh.app/" rel="noopener noreferrer"&gt;browser-based encoder&lt;/a&gt; supports AVIF&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;libavif&lt;/strong&gt;: The &lt;a href="https://github.com/AOMediaCodec/libavif" rel="noopener noreferrer"&gt;reference implementation&lt;/a&gt; by the AOM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0mbg1bygfr5pwki5tfh5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0mbg1bygfr5pwki5tfh5.webp" alt="Server racks in a data center with network cables, representing CDN infrastructure for image delivery" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Known limitations
&lt;/h2&gt;

&lt;p&gt;AVIF isn't perfect. Be aware of these trade-offs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Encoding speed&lt;/strong&gt;: AVIF encodes 5-10x slower than JPEG. This matters for real-time processing but not for batch/build-time conversion.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Maximum dimensions&lt;/strong&gt;: The AV1 spec limits individual tiles to 8192x4320 pixels. Larger images require tiling, which some tools don't support.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Animation support&lt;/strong&gt;: AVIF supports animated sequences (AVIS), but tooling is immature compared to GIF/WebP animation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Older browsers&lt;/strong&gt;: IE11 and pre-2020 browsers don't support AVIF. Always include fallbacks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Email clients&lt;/strong&gt;: Most email clients don't render AVIF. Use &lt;a href="https://kitmul.com/en/image-design/avif-to-jpeg" rel="noopener noreferrer"&gt;AVIF to JPEG&lt;/a&gt; for email content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Print workflows&lt;/strong&gt;: Print shops typically expect TIFF, PDF, or high-quality JPEG. Convert with &lt;a href="https://kitmul.com/en/image-design/avif-to-pdf" rel="noopener noreferrer"&gt;AVIF to PDF&lt;/a&gt; before sending to print.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;AVIF is the best general-purpose image format available in 2026. It delivers smaller files than any alternative at equivalent quality, supports features no other format offers (HDR, wide gamut, film grain synthesis), and has near-universal browser support.&lt;/p&gt;

&lt;p&gt;The migration path is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Convert your existing images to AVIF (use our &lt;a href="https://kitmul.com/en/image-design" rel="noopener noreferrer"&gt;free converters&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Serve AVIF with &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; fallbacks&lt;/li&gt;
&lt;li&gt;Keep JPEG/PNG originals for compatibility workflows&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every kilobyte you save loads faster, ranks better, and costs less. The format war is over. AVIF won.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Your Last Coffee Isn’t the Problem; the First Three Are Still in Your System</title>
      <dc:creator>Aral Roca</dc:creator>
      <pubDate>Mon, 13 Apr 2026 19:37:09 +0000</pubDate>
      <link>https://forem.com/aralroca/your-last-coffee-isnt-the-problem-the-first-three-are-still-in-your-system-4kil</link>
      <guid>https://forem.com/aralroca/your-last-coffee-isnt-the-problem-the-first-three-are-still-in-your-system-4kil</guid>
      <description>&lt;p&gt;A friend of mine was averaging 4 cups of coffee a day. Morning espresso at 7. Drip coffee at 10. Post-lunch lungo at 14:00. And an afternoon ristretto around 16:00 "just to push through." He wasn't sleeping well, but blamed stress, screens, the usual suspects.&lt;/p&gt;

&lt;p&gt;I told him to do the math.&lt;/p&gt;

&lt;h2&gt;
  
  
  The exponential decay you're ignoring
&lt;/h2&gt;

&lt;p&gt;Caffeine follows &lt;a href="https://en.wikipedia.org/wiki/Elimination_(pharmacology)#First-order_elimination" rel="noopener noreferrer"&gt;first-order elimination kinetics&lt;/a&gt; with a half-life of approximately 5 hours in healthy adults. The formula is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dose&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elapsed_hours&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single 95mg drip coffee at 10:00 AM leaves ~47mg by 3 PM and ~24mg by 8 PM. Sounds harmless. But most of us don't drink a single cup.&lt;/p&gt;

&lt;p&gt;When you stack multiple intakes, the decay curves overlap. That 63mg espresso at 7 AM is almost gone by evening, but the 80mg lungo at 2 PM still has 40mg in your system at 9 PM. Add the 40mg ristretto at 4 PM and you're sitting at &lt;strong&gt;64mg of caffeine at bedtime&lt;/strong&gt;; well above the &lt;a href="https://www.sleepfoundation.org/nutrition/caffeine-and-sleep" rel="noopener noreferrer"&gt;50mg sleep disruption threshold&lt;/a&gt; identified in clinical research.&lt;/p&gt;

&lt;p&gt;The problem isn't any single cup. It's the superposition of all of them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frok4cruufm9fbmfl8qoz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frok4cruufm9fbmfl8qoz.webp" alt="Different types of coffee beans and brewing methods, each with a different caffeine content" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built a multi-intake caffeine tracker
&lt;/h2&gt;

&lt;p&gt;Most caffeine calculators I found online only handle one beverage. You enter "95mg at 8 AM" and get a decay curve. Useless if you're a multi-cup person; which is most of us, according to the &lt;a href="https://www.fda.gov/consumers/consumer-updates/spilling-beans-how-much-caffeine-too-much" rel="noopener noreferrer"&gt;FDA's average of 3-4 cups per day&lt;/a&gt; in the U.S.&lt;/p&gt;

&lt;p&gt;So I built one that handles the actual workflow: add as many beverages as you want, each with its own type and time, and see the &lt;strong&gt;combined&lt;/strong&gt; decay curve.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://kitmul.com/en/health/caffeine-half-life" rel="noopener noreferrer"&gt;Caffeine Half-Life Calculator&lt;/a&gt; supports 11 beverage presets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Beverage&lt;/th&gt;
&lt;th&gt;Caffeine (mg)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ristretto&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Espresso&lt;/td&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lungo&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drip Coffee&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold Brew&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matcha&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Energy Drink&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Black Tea&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Green Tea&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cola&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decaf&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can also enter custom amounts. Everything runs client-side in the browser; no data leaves your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The math behind the combined curve
&lt;/h2&gt;

&lt;p&gt;For N intakes, the total caffeine at time T is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total(T) = sum(dose_i * 0.5 ^ ((T - t_i) / 5)) for all i where T &amp;gt;= t_i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each intake only contributes to the total &lt;em&gt;after&lt;/em&gt; its consumption time. The resulting curve isn't a simple exponential; it's a sum of shifted exponentials, which can produce surprising plateaus when intakes are spaced closely.&lt;/p&gt;

&lt;p&gt;This is why eyeballing it doesn't work. A 3 PM coffee "doesn't seem like much," but combined with the residual caffeine from morning drinks, it can push your bedtime levels past the threshold.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5633u94mmpnn71eddu7t.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5633u94mmpnn71eddu7t.webp" alt="A bed with white sheets, the place where caffeine levels finally matter" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What we learned about his consumption
&lt;/h2&gt;

&lt;p&gt;After plugging in his actual daily routine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;7:00 AM Espresso (63mg)&lt;/strong&gt; ; mostly gone by evening; only 5mg left at 11 PM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;10:00 AM Drip Coffee (95mg)&lt;/strong&gt; ; still 17mg at 11 PM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2:00 PM Lungo (80mg)&lt;/strong&gt; ; 28mg at 11 PM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4:00 PM Ristretto (40mg)&lt;/strong&gt; ; 18mg at 11 PM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total at 11 PM: &lt;strong&gt;68mg&lt;/strong&gt;. That's 36% above the sleep threshold.&lt;/p&gt;

&lt;p&gt;Dropping the 4 PM ristretto brought him to 50mg; right at the edge. Moving the lungo to 1 PM dropped it to 43mg. That single hour shift made the difference.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiohwshd309i2p8bawhpn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiohwshd309i2p8bawhpn.webp" alt="Developer workspace with a coffee cup next to a laptop" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The developer who also drinks cold brew
&lt;/h2&gt;

&lt;p&gt;If you're a developer reading this; and statistically you probably are, given where I'm posting this; cold brew is particularly dangerous. At 200mg per serving, a single cold brew at 2 PM leaves &lt;strong&gt;71mg&lt;/strong&gt; in your system at 11 PM. By itself.&lt;/p&gt;

&lt;p&gt;Add that to a morning coffee and you're looking at 80+ mg at bedtime. No wonder you're staring at the ceiling at 1 AM wondering if it's the code review or the caffeine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical details
&lt;/h2&gt;

&lt;p&gt;The tool is built with React, runs entirely in the browser (no backend), and uses URL state for shareability. The decay chart uses a simple bar visualization with color coding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amber/dark&lt;/strong&gt;: high caffeine (&amp;gt;50% of peak)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amber/light&lt;/strong&gt;: moderate (below 50% of peak, above sleep threshold)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Green&lt;/strong&gt;: safe for sleep (below 50mg)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The source is part of &lt;a href="https://kitmul.com" rel="noopener noreferrer"&gt;Kitmul&lt;/a&gt;, a collection of 300+ free browser-based tools. If you're into health tracking, you might also find the &lt;a href="https://kitmul.com/en/health/bmi-calculator" rel="noopener noreferrer"&gt;BMI Calculator&lt;/a&gt;, &lt;a href="https://kitmul.com/en/health/bmr-calculator" rel="noopener noreferrer"&gt;BMR Calculator&lt;/a&gt;, or &lt;a href="https://kitmul.com/en/health/water-intake-calculator" rel="noopener noreferrer"&gt;Water Intake Calculator&lt;/a&gt; useful; staying hydrated actually helps your body &lt;a href="https://www.sleepfoundation.org/nutrition/caffeine-and-sleep" rel="noopener noreferrer"&gt;process caffeine more efficiently&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Track all your intakes, not just the last one.&lt;/strong&gt; Cumulative caffeine is what matters for sleep.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cutoff time depends on how many cups you've already had.&lt;/strong&gt; A 2 PM coffee is fine after one morning cup, but not after three.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold brew is not just "strong coffee."&lt;/strong&gt; At 200mg it's in a different category entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ristretto &amp;lt; Espresso &amp;lt; Lungo.&lt;/strong&gt; Shorter extraction = less caffeine. If you want an afternoon coffee, go short.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 50mg threshold is a population average.&lt;/strong&gt; If you're a &lt;a href="https://en.wikipedia.org/wiki/CYP1A2" rel="noopener noreferrer"&gt;slow metabolizer&lt;/a&gt; (CYP1A2 gene variant), your half-life could be 7+ hours. Adjust accordingly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Try the &lt;a href="https://kitmul.com/en/health/caffeine-half-life" rel="noopener noreferrer"&gt;Caffeine Half-Life Calculator&lt;/a&gt; with your actual daily intake. You might be surprised how much caffeine is still in your system at bedtime.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All calculations run locally in your browser. No data is sent anywhere. The tool is free, open, and has no accounts or limits.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>mentalhealth</category>
      <category>science</category>
    </item>
    <item>
      <title>6 Agile Tools I Built Because Jira Dashboards Aren't Enough</title>
      <dc:creator>Aral Roca</dc:creator>
      <pubDate>Sun, 12 Apr 2026 19:01:19 +0000</pubDate>
      <link>https://forem.com/aralroca/6-agile-tools-i-built-because-jira-dashboards-arent-enough-1m3e</link>
      <guid>https://forem.com/aralroca/6-agile-tools-i-built-because-jira-dashboards-arent-enough-1m3e</guid>
      <description>&lt;p&gt;I've been doing agile coaching and engineering management for long enough to know that most teams measure the wrong things. Or, more commonly, they measure nothing at all and just vibes-check their way through sprint planning. "How's the sprint going?" "Fine." "Are we on track?" "I think so." Cool. That's not data. That's horoscopes.&lt;/p&gt;

&lt;p&gt;The tools I'm about to walk through exist because I kept building the same spreadsheets over and over for different teams. WIP aging charts in Google Sheets. Capability matrices in Notion that nobody updated. Capacity plans in Excel that broke every time someone went on vacation. At some point I snapped and just built them properly — browser-based, no accounts, no servers, data stays on your machine.&lt;/p&gt;

&lt;p&gt;Here are six agile tools I shipped this week, what problems they solve, and why I think you might actually use them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgfqxxntmxm4o31vnklse.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgfqxxntmxm4o31vnklse.webp" alt="Team planning session with sticky notes and whiteboards" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Your WIP is lying to you
&lt;/h2&gt;

&lt;p&gt;Every kanban board shows you how many items are in progress. Three items in dev, two in review, one in testing. Great. But that number is almost useless by itself.&lt;/p&gt;

&lt;p&gt;What matters isn't how many items are in progress — it's how long they've been there. A column with three items where each one arrived yesterday is fine. A column with three items where one has been sitting there for 22 days is a disaster hiding in plain sight.&lt;/p&gt;

&lt;p&gt;This is the core insight behind WIP aging, and it comes directly from &lt;a href="https://en.wikipedia.org/wiki/Little%27s_law" rel="noopener noreferrer"&gt;Little's Law&lt;/a&gt;: average cycle time equals work in progress divided by throughput. If your WIP includes ancient items, your average cycle time is being dragged up and your flow metrics are lying to you. &lt;a href="https://actionableagile.com/" rel="noopener noreferrer"&gt;ActionableAgile&lt;/a&gt; made this concept popular in the kanban world, and it's genuinely one of the most underrated agile metrics out there.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://kitmul.com/en/agile-project-management/wip-age-chart" rel="noopener noreferrer"&gt;WIP Age Chart&lt;/a&gt; I built lets you visualize this instantly. Drop in your items, their start dates, and their current column — you get a scatter plot showing age by stage with configurable thresholds. Items aging past your 85th percentile cycle time get flagged red. No more "oh I didn't realize that ticket had been in code review for three weeks."&lt;/p&gt;

&lt;h2&gt;
  
  
  Six dimensions, one radar
&lt;/h2&gt;

&lt;p&gt;Here's a question that should be easy but never is: "Is the team healthy?"&lt;/p&gt;

&lt;p&gt;Not "are they delivering stuff" — that's one dimension. I mean the full picture. Are they delivering the &lt;em&gt;right&lt;/em&gt; stuff? Is their flow smooth or choppy? Can they make commitments and keep them? Is quality holding up or are they shipping fast and breaking things?&lt;/p&gt;

&lt;p&gt;Inspired by &lt;a href="https://en.wikipedia.org/wiki/Kanban_(development)" rel="noopener noreferrer"&gt;Daniel Vacanti&lt;/a&gt;'s work on kanban flow metrics, I built a &lt;a href="https://kitmul.com/en/agile-project-management/six-dimensions-radar" rel="noopener noreferrer"&gt;Six Dimensions Radar&lt;/a&gt; that scores teams across six axes: Quality, Responsiveness, Predictability, Productivity, Flow, and Value. You input your metrics, it renders a radar chart, and suddenly you can see that your team is crushing it on productivity but their predictability is garbage. That's a conversation starter. That's the kind of team performance visibility that turns a retrospective from "what went well / what didn't" into something with actual teeth.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bus factor spreadsheet nobody maintains
&lt;/h2&gt;

&lt;p&gt;Every engineering org I've worked with has had some version of a skills matrix. Usually it's a Google Sheet that someone created during an offsite, got populated once with enthusiasm, and has been slowly rotting ever since. Meanwhile, the actual bus factor keeps getting worse and nobody notices until Sarah goes on maternity leave and suddenly nobody knows how the payment gateway works.&lt;/p&gt;

&lt;p&gt;T-shaped skills sound great in theory. In practice, nobody tracks them. The &lt;a href="https://kitmul.com/en/agile-project-management/capability-gap-analyzer" rel="noopener noreferrer"&gt;Capability Gap Analyzer&lt;/a&gt; makes it trivially easy: list your team members, list your capabilities, rate proficiency on a simple scale, and it immediately shows you where the gaps are. Single points of failure light up. Skills that only one person has get flagged. You can export the whole thing as JSON and actually keep it updated because it takes thirty seconds instead of an afternoon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2ghwgv1y1nh4of316x7.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2ghwgv1y1nh4of316x7.webp" alt="Dashboard showing agile metrics and flow data" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop forming teams by accident
&lt;/h2&gt;

&lt;p&gt;Most teams aren't designed. They're accidents. Someone gets hired, they join whichever team has capacity, and three months later you have a team of four backend engineers and zero frontend people trying to deliver a user-facing feature. &lt;a href="https://en.wikipedia.org/wiki/Conway%27s_law" rel="noopener noreferrer"&gt;Conway's Law&lt;/a&gt; predicts exactly what happens next: the architecture mirrors the team structure, and you end up with a beautiful API that nobody can actually use because the UI is an afterthought.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://teamtopologies.com/" rel="noopener noreferrer"&gt;Team Topologies&lt;/a&gt; by Matthew Skelton and Manuel Pais made a compelling case that team design is system design. I agree completely. The &lt;a href="https://kitmul.com/en/agile-project-management/team-formation-optimizer" rel="noopener noreferrer"&gt;Team Formation Optimizer&lt;/a&gt; takes your people, their skills, and your constraints (who works well together, who needs mentoring, what capabilities each team needs) and helps you explore formations that actually make sense. It won't replace human judgment — nothing should — but it surfaces combinations you wouldn't have thought of and highlights obvious imbalances before they bite you.&lt;/p&gt;

&lt;h2&gt;
  
  
  "How much work is coming?" is a forecasting problem
&lt;/h2&gt;

&lt;p&gt;Sprint planning usually focuses on what's already in the backlog. But the backlog isn't static. New items arrive constantly — bugs, feature requests, tech debt, that thing the CEO saw at a conference and now considers urgent. If you don't model the arrival rate, your capacity planning is fiction.&lt;/p&gt;

&lt;p&gt;This is a classic time series decomposition problem. The &lt;a href="https://kitmul.com/en/agile-project-management/backlog-arrival-rate-forecaster" rel="noopener noreferrer"&gt;Backlog Arrival Rate Forecaster&lt;/a&gt; uses multiplicative seasonal decomposition to break your historical backlog arrivals into trend, seasonal, and residual components. Feed it six months of data and it'll tell you that yes, backlog arrivals spike 40% every January (post-holiday feature requests) and drop 25% in August (everyone's on vacation). The approach follows the same principles outlined in &lt;a href="https://otexts.com/fpp3/" rel="noopener noreferrer"&gt;Hyndman &amp;amp; Athanasopoulos&lt;/a&gt;'s forecasting textbook — not because I'm trying to be academic, but because the methods work and they're well-understood.&lt;/p&gt;

&lt;p&gt;Knowing your delivery forecasting numbers means nothing if you don't also know what's coming in. Inflow matters as much as throughput.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capacity planning that accounts for reality
&lt;/h2&gt;

&lt;p&gt;The most common capacity planning I see is: "We have 5 devs × 10 points each = 50 points per sprint." This is wrong in so many ways I don't even know where to start. People take vacation. People get sick. People get pulled into incidents. New hires ramp up slowly. Seasonal factors affect availability — good luck staffing a full team between Christmas and New Year's.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://kitmul.com/en/agile-project-management/seasonal-capacity-planner" rel="noopener noreferrer"&gt;Seasonal Capacity Planner&lt;/a&gt; accounts for all of this. You input your team composition, planned absences, hiring timeline, and seasonal adjustment factors. It produces a realistic capacity forecast that doesn't pretend everyone is available 100% of the time. This is basically the &lt;a href="https://en.wikipedia.org/wiki/Theory_of_constraints" rel="noopener noreferrer"&gt;Theory of Constraints&lt;/a&gt; applied to team capacity — your actual throughput is determined by your bottleneck, not your theoretical maximum. Sprint planning with realistic capacity numbers is a different experience entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  All of this runs in your browser
&lt;/h2&gt;

&lt;p&gt;I'm going to keep hammering this point because it matters: none of these free agile tools send your data anywhere. No servers. No accounts. No analytics tracking your team's performance data. Everything runs client-side in JavaScript. Your data lives in localStorage. You can export and import JSON files to move data between machines or share configurations with teammates.&lt;/p&gt;

&lt;p&gt;Most of the tools also support URL state — meaning you can configure a view and share the URL directly. Someone clicks it and sees exactly what you see. No login required.&lt;/p&gt;

&lt;p&gt;These are browser-based tools built for people who want to understand their teams better without handing their data to yet another SaaS vendor. If you're doing kanban flow analysis or trying to improve your agile metrics visibility, I think they'll save you time.&lt;/p&gt;

&lt;p&gt;I ship tools weekly. If there's something missing or broken, &lt;a href="https://github.com/aralroca/awesome-kitmul/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; or just tell me on Twitter. I read everything.&lt;/p&gt;

</description>
      <category>agile</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to Detect AI-Generated Content Using Perplexity and Burstiness</title>
      <dc:creator>Aral Roca</dc:creator>
      <pubDate>Thu, 09 Apr 2026 13:24:42 +0000</pubDate>
      <link>https://forem.com/aralroca/how-to-detect-ai-generated-content-using-perplexity-and-burstiness-gio</link>
      <guid>https://forem.com/aralroca/how-to-detect-ai-generated-content-using-perplexity-and-burstiness-gio</guid>
      <description>&lt;p&gt;A friend of mine who runs a content agency told me over coffee last week: "we've tried every AI detector out there and they're all snake oil." I told him I thought I could build a better one. He laughed. Fair enough.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://kitmul.com/en/ai/ai-content-detector" rel="noopener noreferrer"&gt;AI Content Detector&lt;/a&gt; I built runs entirely in the browser. No uploads, no subscriptions, no cloud API charging you per scan. It uses ten statistical metrics and eighteen sentence-level signals to figure out whether text was written by a human or generated by ChatGPT, Claude, Gemini, or whatever LLM people are using this week. I want to explain how it actually works, because most "AI detector" marketing pages are deliberately vague about their methodology.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbnx8sp1snqnpposhe1cq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbnx8sp1snqnpposhe1cq.webp" alt="AI Content Detector analyzing a webpage with ten metrics" width="800" height="909"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why perplexity and burstiness alone don't cut it
&lt;/h2&gt;

&lt;p&gt;Every blog post about AI detection mentions perplexity and burstiness. They're real metrics, they do measure something useful, but here's the uncomfortable truth I discovered after weeks of testing: modern AI models like GPT-4 and Claude produce text with high perplexity and high burstiness. They've been trained to sound human. Relying on these two metrics alone is like trying to catch a burglar by checking if they used the front door.&lt;/p&gt;

&lt;p&gt;Perplexity measures how predictable word sequences are (low = robotic, high = creative). Burstiness measures sentence length variation (low = uniform, high = varied). Old-school AI from 2022 failed both tests spectacularly. But 2025-2026 models? They pass with flying colors.&lt;/p&gt;

&lt;p&gt;So what actually works?&lt;/p&gt;

&lt;h2&gt;
  
  
  The ten metrics that matter
&lt;/h2&gt;

&lt;p&gt;After benchmarking against articles I knew were AI-generated and articles I knew were human (I used a set of ten real URLs ranging from MIT Technology Review to generic SEO coffee blogs), I found that these signals, combined, produce results that are actually useful:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zipf's Law conformity&lt;/strong&gt; turned out to be the single most reliable metric. Every natural language follows Zipf's law: the second most common word appears half as often as the first, the third appears a third as often, and so on. Human text deviates from this curve because we get fixated on certain words, go on tangents, make weird word choices. AI text follows Zipf's law almost perfectly because it's sampling from probability distributions that inherently produce Zipfian outputs. I compute R-squared of log-rank vs log-frequency and anything above 0.96 is suspicious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repeated sentence starters&lt;/strong&gt; is embarrassingly simple but catches a ton of AI. Count what percentage of sentences start with the same word. AI loves starting sentences with "The", "This", "It", "In". I've seen AI blog posts where 70%+ of sentences start with one of four words. Humans are messier about it without even trying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Punctuation entropy&lt;/strong&gt; measures the Shannon entropy of distances between punctuation marks. AI places commas and periods at remarkably regular intervals. Humans are chaotic; sometimes we write three short sentences in a row, then a long one with five commas, then a fragment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sentence length skewness&lt;/strong&gt; captures the shape of the sentence length distribution. AI produces near-symmetrical distributions (bell curve). Humans write with positive skew: many short sentences, some medium ones, and the occasional monster sentence that runs away from you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hapax legomena ratio&lt;/strong&gt; counts what percentage of words appear only once in the text. Human text has more one-time words because we use specific, contextual vocabulary. AI reuses words more evenly across the text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Paragraph uniformity&lt;/strong&gt; is the coefficient of variation of paragraph lengths. AI produces remarkably uniform paragraphs. Humans write a two-sentence paragraph followed by a twelve-sentence one without thinking about it.&lt;/p&gt;

&lt;p&gt;The remaining four metrics (perplexity, burstiness, vocabulary richness, word length standard deviation) contribute smaller weights. They help break ties but they're not the heavy hitters anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real trick: multiplicative signal scoring
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. Individual signals overlap between AI and human text all the time. A human might use dashes (AI signal) or have uniform paragraphs (AI signal). But humans almost never have dashes AND uniform paragraphs AND transition words AND no contractions AND formulaic structure AND repeated starters all in the same sentence.&lt;/p&gt;

&lt;p&gt;AI text has clusters of co-occurring signals. When three or more AI signals appear in the same sentence, the score doesn't just add up; it multiplies. A sentence with two AI signals scores normally. Three signals? Score multiplied by 1.5x. Four or more? Multiplied by 2x. This multiplicative approach captures something that linear scoring misses: the difference between "occasionally looks AI-ish" and "this is obviously a pattern."&lt;/p&gt;

&lt;p&gt;The sentence-level classifier tracks eighteen separate signals per sentence: length uniformity, dash usage, transition words, filler phrases ("it is important to note", "plays a crucial role"), overused vocabulary ("leverage", "comprehensive", "facilitate"), bold-then-explain patterns, "Here's what/why/how" hooks, proper noun density, contractions, parenthetical asides, questions, informal language, passive voice, starter repetition, colon endings, semicolons, numbered lists, and conclusion patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  URL mode and content extraction
&lt;/h2&gt;

&lt;p&gt;You can paste text directly or enter a URL. In URL mode, the tool fetches the HTML, strips out navigation, sidebars, footers, images, scripts, and all non-text elements, then converts the remaining content to Markdown using Turndown. You can expand the extracted content below the results to verify what the tool actually analyzed. Some sites load content via JavaScript (client-side rendering), which the fetcher can't capture; for those, the text tab works better.&lt;/p&gt;

&lt;p&gt;The URL fetch tries your browser first (no server involved). If CORS blocks it, a lightweight Edge proxy kicks in with a rate limit of five requests per minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it falls short
&lt;/h2&gt;

&lt;p&gt;I'm not going to pretend this is perfect.&lt;/p&gt;

&lt;p&gt;The biggest weakness: well-written AI text that has been lightly edited by a human. If someone generates a draft with ChatGPT and then rewrites a third of the sentences, adds a personal anecdote, and removes the transition words, our detector (and every other detector) will struggle. That's a fundamental limitation of statistical approaches.&lt;/p&gt;

&lt;p&gt;The second weakness: some human writing is genuinely formulaic. Corporate press releases, legal documents, academic abstracts. These trigger AI signals because they lack the messiness that statistical detectors look for. This isn't a bug exactly, but it does produce false positives on a category of text that nobody would call creative writing.&lt;/p&gt;

&lt;p&gt;The third weakness: very short text. Below about 200 words, there isn't enough statistical signal for any of the metrics to be reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compared to GPTZero, Originality.ai, Copyleaks
&lt;/h2&gt;

&lt;p&gt;Those services use trained ML classifiers (neural networks trained on millions of labeled AI/human samples). In theory, they should be more accurate than statistical heuristics like mine. In practice, the gap is smaller than you'd think, especially on longer texts. Their models were trained on specific AI outputs and struggle when new models appear; statistical patterns are more model-agnostic.&lt;/p&gt;

&lt;p&gt;The real advantage of the browser-based approach: your text never leaves your device, it's free, and it's instant. If you're scanning a hundred blog posts for a content audit, that matters more than a few percentage points of accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://kitmul.com/en/ai/ai-content-detector" rel="noopener noreferrer"&gt;AI Content Detector&lt;/a&gt; is on Kitmul. Free, no signup, runs in your browser. Test it on something you know is AI-generated, test it on something you wrote yourself, and see if the results match your intuition.&lt;/p&gt;

&lt;p&gt;Next up: a sitemap scanner that crawls every URL in your sitemap.xml and produces a report of which pages look AI-generated. That one should be fun.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Related tools: &lt;a href="https://kitmul.com/en/ai/text-readability-scorer" rel="noopener noreferrer"&gt;Text Readability Scorer&lt;/a&gt; · &lt;a href="https://kitmul.com/en/ai/sentiment-analyzer" rel="noopener noreferrer"&gt;Sentiment Analyzer&lt;/a&gt; · &lt;a href="https://kitmul.com/en/ai/keyword-extractor" rel="noopener noreferrer"&gt;Keyword Extractor&lt;/a&gt; · &lt;a href="https://kitmul.com/en/ai/text-tone-analyzer" rel="noopener noreferrer"&gt;Text Tone Analyzer&lt;/a&gt; · &lt;a href="https://kitmul.com/en/ai/syllable-counter" rel="noopener noreferrer"&gt;Syllable Counter&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Perplexity" rel="noopener noreferrer"&gt;Perplexity (information theory)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Burstiness" rel="noopener noreferrer"&gt;Burstiness&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Zipf%27s_law" rel="noopener noreferrer"&gt;Zipf's law&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2301.11305" rel="noopener noreferrer"&gt;DetectGPT: Zero-Shot Machine-Generated Text Detection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2304.04736" rel="noopener noreferrer"&gt;On the Possibilities of AI-Generated Text Detection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>writing</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Resting heart rate 68 to 56: what changed in my dev work</title>
      <dc:creator>Aral Roca</dc:creator>
      <pubDate>Sat, 04 Apr 2026 15:39:11 +0000</pubDate>
      <link>https://forem.com/aralroca/resting-heart-rate-68-to-56-what-changed-in-my-dev-work-i13</link>
      <guid>https://forem.com/aralroca/resting-heart-rate-68-to-56-what-changed-in-my-dev-work-i13</guid>
      <description>&lt;p&gt;I normally write about web frameworks, WebAssembly, and JavaScript internals. And I normally don't publish on a Saturday. But I've been wanting to write this for a while and today I woke up inspired, so here it is.&lt;/p&gt;

&lt;p&gt;This isn't a technical article. It's personal. But I genuinely believe it can help a lot of people, especially developers and founders who spend long hours in front of a screen and feel like their brain gives up before their schedule does.&lt;/p&gt;

&lt;p&gt;I want to talk about the things that changed my daily performance more than any tool, framework, or productivity app ever did. Not hypothetical stuff I read on a blog. Things I've been doing for months (some for years) that produced measurable, repeatable differences in how I work.&lt;/p&gt;

&lt;p&gt;Here are the numbers first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resting heart rate: 68 → 56 bpm&lt;/li&gt;
&lt;li&gt;Breathing rate: 14-16 → 8-10 breaths/min&lt;/li&gt;
&lt;li&gt;Deep work blocks: ~90 min max → 3-4 hours consistently&lt;/li&gt;
&lt;li&gt;Post-meeting recovery: 20-30 min → basically instant&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this came from a supplement, an app, or a course. It came from applying things I learned through competitive athletics to how I work as a developer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some context about me&lt;/li&gt;
&lt;li&gt;The parkour connection&lt;/li&gt;
&lt;li&gt;
Hack #1: Intermittent hypoxia

&lt;ul&gt;
&lt;li&gt;What it actually is&lt;/li&gt;
&lt;li&gt;What changed for me&lt;/li&gt;
&lt;li&gt;The science (briefly)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Hack #2: The Pomodoro technique, done right

&lt;ul&gt;
&lt;li&gt;Why most people do it wrong&lt;/li&gt;
&lt;li&gt;The parkour parallel&lt;/li&gt;
&lt;li&gt;How I actually use it&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Hack #3: Walking meetings&lt;/li&gt;

&lt;li&gt;Hack #4: Strategic caffeine&lt;/li&gt;

&lt;li&gt;Hack #5: Cold exposure&lt;/li&gt;

&lt;li&gt;Hack #6: Power naps&lt;/li&gt;

&lt;li&gt;What didn't work&lt;/li&gt;

&lt;li&gt;The thing nobody talks about: Silicon Valley already does this&lt;/li&gt;

&lt;li&gt;The physiological argument for founders&lt;/li&gt;

&lt;li&gt;How to start&lt;/li&gt;

&lt;li&gt;References&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Some context about me
&lt;/h2&gt;

&lt;p&gt;I've been in software development for over ten years. Most of that time I've been deep in open source. I created &lt;a href="https://github.com/aralroca/next-translate" rel="noopener noreferrer"&gt;NextTranslate&lt;/a&gt;, &lt;a href="https://github.com/teafuljs/teaful" rel="noopener noreferrer"&gt;Teaful&lt;/a&gt;, and more recently &lt;a href="https://brisa.build" rel="noopener noreferrer"&gt;Brisa&lt;/a&gt;, a web framework built on web components. I also recently built &lt;a href="https://kitmul.com/en" rel="noopener noreferrer"&gt;Kitmul&lt;/a&gt;, which went from a testing ground for my libraries to 300+ browser-based tools in three weeks (I wrote about that &lt;a href="https://aralroca.com/blog/ai-agents-should-be-the-app" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That's the professional side. But there's another side that's been part of my life for just as long: parkour.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parkour connection
&lt;/h2&gt;

&lt;p&gt;I started doing parkour in the mid-2000s. By 2011 I was competing in Red Bull events. Not casually. This was serious training, serious risk, serious discipline. I trained alongside people who are now elite athletes preparing for the Olympics. Parkour will officially be part of the Games, and some of the people I used to train with are on that path.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fbiohack-aral-redbull.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fbiohack-aral-redbull.webp" alt="Aral competing at a Red Bull parkour event in Santorini" width="640" height="640"&gt;&lt;/a&gt;&lt;br&gt;&lt;small&gt;Red Bull Art of Motion, Santorini&lt;/small&gt;
  &lt;/p&gt;

&lt;p&gt;In 2021 I had the worst accident of my life. I misjudged a jump from a rooftop to a fence, fell badly, and ended up in a coma for three days. When I woke up, my vestibular system was damaged. The apparatus that controls balance. For weeks I couldn't walk straight without the world spinning.&lt;/p&gt;

&lt;p&gt;Most people would have quit parkour after that. I did the opposite. I used parkour as rehabilitation. I went back to basics. Simple movements, low risk, building balance from scratch. The mindset was the same one I apply to debugging: the jump wasn't the problem. My execution was. I analyzed what I did wrong, and I trained specifically to fix it.&lt;/p&gt;

&lt;p&gt;It took years of patient work. But this year, I went back and completed that exact same jump. Rooftop to fence. Clean.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/cGpxGp7wHG8"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I'm telling this story because it's directly relevant to everything that follows. The accident taught me something I already knew intellectually but had never felt in my bones: your body is the foundation of everything you do. When it breaks, nothing else matters. Not your code, not your startup, not your deadlines. And when you rebuild it deliberately, with discipline, everything else gets better too.&lt;/p&gt;

&lt;p&gt;These days I do parkour as a hobby to stay in shape. But the years of competitive training, the accident, the recovery, gave me something that no programming book or productivity course ever could: an intuitive understanding of how the body and brain actually perform under pressure. And how fragile that performance is if you don't take care of the hardware.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fbiohack-aral-climbing.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fbiohack-aral-climbing.webp" alt="Aral climbing natural rock formations" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;&lt;small&gt;Training outdoors&lt;/small&gt;
  &lt;/p&gt;

&lt;p&gt;Athletes know things about performance that knowledge workers generally ignore. How rest periods affect output quality. How breathing patterns change your nervous system state. How adrenaline management is a trainable skill, not a personality trait. How the difference between a good day and a bad day often comes down to physiology, not motivation. And how you recover from failure matters more than the failure itself.&lt;/p&gt;

&lt;p&gt;Once I started applying these principles to my work as a developer, everything changed.&lt;/p&gt;

&lt;p&gt;And exercise itself, just moving your body regularly, is the most underrated performance hack that exists. It increases BDNF (the protein that helps your brain form new connections), regulates cortisol, improves sleep, and directly enhances executive function. Everything else I'm about to describe works better on top of a foundation of consistent physical activity. Parkour is my thing, but any movement practice works.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Fz7hOUp0PXk"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Hack #1: Intermittent hypoxia
&lt;/h2&gt;

&lt;p&gt;This is the one that surprised me the most.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it actually is
&lt;/h3&gt;

&lt;p&gt;Intermittent hypoxia training (IHT) is simple: you alternate short periods of reduced oxygen breathing with normal recovery. It's the same principle behind altitude training, the thing that endurance athletes have been doing for decades to improve oxygen efficiency, except you can do it at sea level, sitting at your desk, in 15-30 minutes.&lt;/p&gt;

&lt;p&gt;The protocol: controlled breathing cycles that temporarily reduce your blood oxygen saturation, followed by recovery breathing. Repeat for several rounds. That's it.&lt;/p&gt;

&lt;p&gt;I started doing this to improve my breath-hold times and cardiovascular performance for parkour. The results in training were immediate. Longer runs, faster recovery between sequences, and much better composure during high-risk movements. When you're mid-air in a precision jump, the ability to manage your adrenaline response is not optional. IHT gave me noticeably more control over that.&lt;/p&gt;

&lt;h3&gt;
  
  
  What changed for me
&lt;/h3&gt;

&lt;p&gt;But the real surprise came outside of training sessions. And this is the part that I think matters for anyone who works with their brain.&lt;/p&gt;

&lt;p&gt;After about two weeks of consistent IHT practice, I noticed something: &lt;strong&gt;I was breathing more slowly throughout the entire day&lt;/strong&gt;. Not because I was trying to. My body had simply recalibrated. Where I used to take 14-16 breaths per minute sitting at my desk, I dropped to 8-10.&lt;/p&gt;

&lt;p&gt;This sounds minor. It's not.&lt;/p&gt;

&lt;p&gt;Patrick McKeown, author of &lt;em&gt;The Oxygen Advantage&lt;/em&gt;, has documented extensively how slower, lighter nasal breathing improves CO2 tolerance. Higher CO2 tolerance means better oxygen delivery to tissues, including your brain. It's counterintuitive (more CO2 = better oxygenation?), but the physiology is well-established: CO2 is what triggers hemoglobin to release oxygen to cells (the Bohr effect). If you breathe fast and shallow, you actually reduce oxygen delivery despite taking in more air.&lt;/p&gt;

&lt;p&gt;Here's what I noticed in my daily work after my breathing shifted:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sustained focus got dramatically easier.&lt;/strong&gt; I used to hit a wall at about 90 minutes of deep work. That wall moved to 3-4 hours. Not through willpower or discipline. I just didn't get tired as quickly. Better brain oxygenation, fewer stress hormones circulating, more stable energy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adrenaline management at work.&lt;/strong&gt; This was the one I didn't expect. Running a startup (or even just being a senior developer) means constant high-stakes moments. A production incident at 2am. A hard conversation with a coworker. An investor call where you have 20 minutes to make your case. Before IHT, these situations would leave me wired for hours afterward. Now the recovery is almost instant. The same nervous system control that helps me stay calm mid-air during parkour helps me stay clear-headed during a crisis at work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Faster cognitive recovery.&lt;/strong&gt; After an intense 3-hour coding session or a difficult meeting, I used to need a significant break before I could do anything useful again. Now I bounce back in minutes. It's like my brain's recovery time dropped from "restart the computer" to "close the tab and open a new one."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fbiohack-meditation.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fbiohack-meditation.webp" alt="Controlled breathing practice at sunrise" width="800" height="930"&gt;&lt;/a&gt;&lt;br&gt;&lt;small&gt;The simplest performance intervention: breathe less&lt;/small&gt;
  &lt;/p&gt;

&lt;h3&gt;
  
  
  The science (briefly)
&lt;/h3&gt;

&lt;p&gt;I'll keep this short because I'm a developer, not a scientist, but I did go down the rabbit hole on the research:&lt;/p&gt;

&lt;p&gt;A 2022 study in &lt;em&gt;Frontiers in Neuroscience&lt;/em&gt; found that IHT causes "proadaptive modifications" in the brain, stimulating BDNF (brain-derived neurotrophic factor) and increasing what they called "the adaptive potential, endurance, and working capacity of the brain" [1]. Another study showed significant gains in memory recall and attention in participants following IHT protocols [2].&lt;/p&gt;

&lt;p&gt;The mechanism is called &lt;strong&gt;hormesis&lt;/strong&gt;. Controlled, small doses of stress that make the body more resilient. Same principle as cold exposure, sauna, or fasting. The difference is that hypoxia specifically targets oxygen efficiency and neural adaptation.&lt;/p&gt;

&lt;p&gt;A study published in &lt;em&gt;Physiology&lt;/em&gt; documented that intermittent hypoxia upregulates three growth factors (EPO, BDNF, and VEGF) all directly linked to enhanced neural function and cellular resilience [10]. This isn't about feeling zen. It's about building a more capable brain at the physiological level.&lt;/p&gt;

&lt;p&gt;Andrew Huberman, a Stanford neuroscientist, has documented how breathing protocols shift autonomic nervous system state, reducing cortisol, increasing parasympathetic tone, and enhancing executive function [4]. IHT is essentially a more intense version of breathwork: it doesn't just teach you to breathe better in the moment. It permanently upgrades your baseline respiratory efficiency.&lt;/p&gt;

&lt;p&gt;My resting heart rate went from 68 to 56 bpm. That's not a small change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hack #2: The Pomodoro technique, done right
&lt;/h2&gt;

&lt;p&gt;I know, I know. Everyone's heard of Pomodoro. "25 minutes of work, 5 minutes of break, revolutionary." Most people try it for a week and drop it because it feels artificial.&lt;/p&gt;

&lt;p&gt;I did the same thing years ago. Then parkour taught me something that made me come back to it with a completely different understanding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why most people do it wrong
&lt;/h3&gt;

&lt;p&gt;The issue isn't the timer. It's what people do during the "break."&lt;/p&gt;

&lt;p&gt;Most developers hit the 5-minute break and immediately check Slack, scroll Twitter, read email, or look at their phone. That's not rest. That's a different kind of cognitive load. Your prefrontal cortex doesn't get to discharge. It just switches to a different task. When the timer starts again, you're not fresh. You're fragmented.&lt;/p&gt;

&lt;h3&gt;
  
  
  The parkour parallel
&lt;/h3&gt;

&lt;p&gt;Here's what parkour taught me about rest, and it took me years to really internalize this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need rest to improve. The gains happen during recovery, not during effort.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In a parkour training session, you give 100% on a sequence. A run, a series of jumps, a technical combination. Then you walk back. You breathe. You shake out your arms. You stand there for a minute doing literally nothing. And then you go again at 100%.&lt;/p&gt;

&lt;p&gt;If you skip that recovery and try to chain explosive movements back-to-back, two things happen: your performance degrades fast, and you get injured. Every serious practitioner learns this the hard way.&lt;/p&gt;

&lt;p&gt;Your brain works exactly the same.&lt;/p&gt;

&lt;p&gt;When you try to code for four straight hours without structured breaks, the last two hours are objectively worse. Your attention drifts. Your error rate climbs. You make architectural decisions you'll regret tomorrow. But you don't notice it in the moment because cognitive degradation is invisible from the inside.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I actually use it
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fbiohack-focus.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fbiohack-focus.webp" alt="Focused deep work session at a desk" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;&lt;small&gt;25 minutes of this, then 5 minutes of not-this&lt;/small&gt;
  &lt;/p&gt;

&lt;p&gt;Once I understood that the 5-minute break is not a concession to weakness but the actual mechanism that makes high performance sustainable, Pomodoro clicked for me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;25 minutes of full-intensity focus.&lt;/strong&gt; One task. No Slack. No email. No "quick check" on anything. If something pops into my head, I write it on a sticky note and go back to what I'm doing. The goal is to create a state of total immersion, the same mental state I'm in during a parkour sequence where I literally can't afford to think about anything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5 minutes of real rest.&lt;/strong&gt; Stand up. Look out the window. Walk to the kitchen. Breathe. The key word is "real." Your eyes need to leave the screen. Your working memory needs to flush. If you don't do this, you're just doing 4 hours of gradually degrading work with arbitrary timer interruptions.&lt;/p&gt;

&lt;p&gt;The parallel to parkour is direct: train hard in bursts, rest completely between sets. The rest is what lets you sustain 100% intensity. Without it, by the third round you're operating at 60% and making mistakes. Except at your desk, the "injury" is a bug you'll spend two days debugging.&lt;/p&gt;

&lt;p&gt;Here's a trick I use that combines both hacks: &lt;strong&gt;during some Pomodoro breaks, I do a quick round of breath-hold exercises.&lt;/strong&gt; Five minutes of controlled breathing resets my autonomic nervous system and primes me for the next focus block. It's like a mini-reboot. When the timer starts again, I'm genuinely fresh, not just "took a break" fresh.&lt;/p&gt;

&lt;p&gt;After four Pomodoro cycles, I take a longer break of 15-20 minutes. Go outside if possible. Move. The compounding effect across a full day is dramatic. Hour six feels like hour one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hack #3: Walking meetings
&lt;/h2&gt;

&lt;p&gt;This one is simple but I almost never see people talk about it.&lt;/p&gt;

&lt;p&gt;Many meetings don't require you to be sitting at a desk staring at slides. Status updates, brainstorming sessions, 1-on-1s, planning discussions. For all of these, you can be walking.&lt;/p&gt;

&lt;p&gt;I use a walking treadmill at my desk during meetings. Slow pace, 3-4 km/h, just enough to keep blood flowing. The effects are immediate: better attention, less fidgeting, fewer distractions. There's something about low-intensity movement that keeps the brain engaged without consuming cognitive resources. Research backs this up: walking improves creative thinking and sustained attention.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fbiohack-aral-treadmill.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fbiohack-aral-treadmill.webp" alt="Aral walking on a treadmill desk while working" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;&lt;small&gt;My walking treadmill setup. Most meetings don't require sitting.&lt;/small&gt;
  &lt;/p&gt;

&lt;p&gt;But here's the part nobody mentions: &lt;strong&gt;what happens after the meeting.&lt;/strong&gt; When you've been sitting for an hour-long call, you finish drained. You need a transition period before you can do real work again. When you've been walking during that same call, you finish energized. You sit down at your desk and you're immediately ready to work. The meeting didn't drain your battery. It charged it.&lt;/p&gt;

&lt;p&gt;This has been particularly useful for me during weeks with heavy meeting loads. The meetings themselves become light exercise, and the transitions between meetings and deep work become seamless.&lt;/p&gt;

&lt;p&gt;Obviously this doesn't work for every meeting. If you're pair programming, sharing your screen, or doing a code review where you need to type, sit down. But for the 60-70% of meetings that are primarily conversation, walking is strictly better than sitting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hack #4: Strategic caffeine
&lt;/h2&gt;

&lt;p&gt;I've never been a big coffee drinker. But I noticed that the few times I did have coffee, the effect was massive. That got me thinking about why.&lt;/p&gt;

&lt;p&gt;The answer is simple: most people drink coffee every day, and daily caffeine consumption builds tolerance fast. After a couple of weeks, your morning coffee doesn't enhance your performance. It gets you back to your baseline. You're not getting a boost. You're paying a tax (dependency, sleep disruption, afternoon crashes) to feel normal. That's a bad trade.&lt;/p&gt;

&lt;p&gt;Because I don't have that daily habit, I can treat caffeine purely as a tool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fbiohack-coffee.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fbiohack-coffee.webp" alt="Coffee cups" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;&lt;small&gt;What if the daily coffee ritual is actually costing you more than it gives?&lt;/small&gt;
  &lt;/p&gt;

&lt;p&gt;Most days I drink water. Just water. This means my caffeine tolerance stays low. Then, when I actually need it (I slept poorly and have a big day ahead, there's a product launch, I'm doing a full day of complex pair programming) a single coffee delivers a genuine cognitive boost. Alertness, processing speed, working memory, all measurably better because my body isn't habituated.&lt;/p&gt;

&lt;p&gt;Think of it like a power-up in a game. If you use it on every level, it stops being special. If you save it for the boss fight, it actually matters.&lt;/p&gt;

&lt;p&gt;The transition period when you stop daily coffee is about a week of mild headaches and low energy. After that, you reach a new normal where you feel fine without it, and caffeine becomes something you choose to deploy strategically rather than something you depend on to function.&lt;/p&gt;

&lt;p&gt;I know this one is controversial. Coffee culture is deeply embedded in developer identity. All I can say is: try it for two weeks and see what happens. The worst that can happen is you go back to your regular habit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hack #5: Cold exposure
&lt;/h2&gt;

&lt;p&gt;This is a quick one. A cold shower at the end of my normal shower, 60-90 seconds of cold water, produces a norepinephrine spike that improves alertness and mood for hours. This is well-documented by Huberman and others [4].&lt;/p&gt;

&lt;p&gt;I'm not doing ice baths or any extreme Wim Hof protocol. Just cold water at the end. The cost is minimal (it's uncomfortable for about 30 seconds, then you adapt). The effect on morning focus is noticeable and consistent.&lt;/p&gt;

&lt;p&gt;Wim Hof's method (breathing + cold + meditation) was actually validated in a Radboud University study that showed trained participants could voluntarily influence their sympathetic nervous system and innate immune response [8]. That's remarkable. But you don't need the full method to get the basic alertness benefit. Just turn the handle to cold for a minute at the end of your shower.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hack #6: Power naps
&lt;/h2&gt;

&lt;p&gt;When I hit a wall after lunch or after a heavy morning, a 20-30 minute nap resets me completely. Not a "nice to have." A genuine cognitive reboot.&lt;/p&gt;

&lt;p&gt;The key is keeping it under 30 minutes. Go longer and you enter deep sleep, which means you wake up groggy and worse than before. But a short nap, 20 minutes is the sweet spot, clears the mental fog and gives you what feels like a second morning. I've had some of my most productive afternoons right after a power nap.&lt;/p&gt;

&lt;p&gt;I don't schedule them. I just listen to my body. If I'm not tired, napping is pointless. But when the signal is there, fighting it with coffee or willpower is a losing strategy. The nap is faster, free, and has no side effects.&lt;/p&gt;

&lt;h2&gt;
  
  
  What didn't work
&lt;/h2&gt;

&lt;p&gt;I think it's important to mention what I tried that either didn't stick or didn't produce noticeable results. Not everything works for everyone, and I don't want to give the impression that I just tried five things and all five were magic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Meditation apps.&lt;/strong&gt; I tried Headspace and Calm for a few months each. They're well-made products, but guided meditation didn't produce the same physiological changes as breathwork and IHT. My suspicion is that meditation works better as a long-term practice (years, not months) while breathing protocols produce measurable changes in weeks. Your mileage may vary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strict time-blocking the entire day.&lt;/strong&gt; I tried scheduling every 30-minute slot for a few weeks. It created more anxiety than productivity. Pomodoro works for me because it structures &lt;em&gt;intensity and rest&lt;/em&gt;, not &lt;em&gt;content&lt;/em&gt;. I decide what to work on, then I decide how intensely to focus on it. Scheduling every minute took away the flexibility that makes creative work possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing nobody talks about: Silicon Valley already does this
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fbiohack-sv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fbiohack-sv.webp" alt="Modern tech office corridor" width="800" height="534"&gt;&lt;/a&gt;&lt;br&gt;&lt;small&gt;Behind the clean offices, a quiet obsession with physiological optimization&lt;/small&gt;
  &lt;/p&gt;

&lt;p&gt;Here's what I find interesting. These aren't fringe biohacking experiments. Breathwork and oxygen manipulation have been quietly adopted across the tech industry for years. Not as a trend, but as a serious performance tool.&lt;/p&gt;

&lt;p&gt;Bryan Johnson, the founder of Braintree (sold to PayPal for $800M), spends roughly $2M per year on biohacking protocols. The man literally moved his office into a hyperbaric chamber [5]. Now, $2M/year is absurd and not what I'm suggesting. But the underlying logic, that physiological optimization directly translates to cognitive performance, is sound.&lt;/p&gt;

&lt;p&gt;Jack Dorsey, co-founder of Twitter and Block, has practiced meditation and controlled breathing for over 20 years [6]. Tim Ferriss has dedicated entire podcast episodes to breathing protocols for performance, hosting James Nestor (author of the NYT bestseller &lt;em&gt;Breath: The New Science of a Lost Art&lt;/em&gt;) to discuss how subtle breathing adjustments transform cognitive output [7].&lt;/p&gt;

&lt;p&gt;Dave Asprey, founder of Bulletproof, has worked extensively with Wim Hof. Patrick McKeown's Oxygen Advantage method, which is essentially breath-hold training that simulates altitude, has been adopted by athletes, military operators, and increasingly, tech executives [3].&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Longevity Technology&lt;/em&gt; reported that biohacking has become "a major trend among Silicon Valley executives seeking to optimize health and performance," with breathwork, cryotherapy, and oxygen manipulation among the most commonly adopted protocols [9].&lt;/p&gt;

&lt;p&gt;What's telling is &lt;em&gt;why&lt;/em&gt; these people gravitate toward physiological hacks rather than (or in addition to) productivity software. When you're building something that demands 12-16 hours of sustained cognitive output per day, across months and years, the bottleneck is not your task manager. It's not your team structure. It's not your tech stack. &lt;strong&gt;The bottleneck is your brain's ability to maintain quality output under sustained pressure.&lt;/strong&gt; That's a physiology problem, not a tooling problem.&lt;/p&gt;

&lt;p&gt;And marginal gains in oxygen efficiency and stress resilience compound dramatically over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The physiological argument for founders
&lt;/h2&gt;

&lt;p&gt;The startup world glorifies hustle but ignores the hardware it runs on. You can optimize your deployment pipeline, your sprint process, your hiring funnel. But if your brain is running on shallow breathing and chronic cortisol, none of it matters. You're making your worst decisions at the end of the day, exactly when they matter most.&lt;/p&gt;

&lt;p&gt;Consider what changes when your baseline physiology is different:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your resting heart rate drops.&lt;/strong&gt; Mine went from 68 to 56 bpm. A lower resting heart rate correlates with better cardiovascular efficiency, better stress recovery, and better autonomic regulation. You're literally running calmer all day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your breathing rate drops.&lt;/strong&gt; 8 breaths per minute instead of 15. Fewer breaths means less sympathetic nervous system activation throughout the day. Less fight-or-flight. Less background anxiety. Less cortisol.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your work is structured around recovery.&lt;/strong&gt; Pomodoro ensures that hour six of your day is as sharp as hour one. You don't gradually degrade. You stay at peak performance in short bursts and actively recover between them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your oxygen utilization improves.&lt;/strong&gt; The same blood volume delivers more oxygen to your prefrontal cortex, the part of your brain that handles planning, decision-making, and impulse control. The exact functions you need most as a founder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your stress recovery accelerates.&lt;/strong&gt; The interval between "something went wrong" and "here's what we're going to do about it" shrinks from minutes to seconds. This is probably the most practically valuable change. In a startup, the speed at which you can move from panic to plan is everything.&lt;/p&gt;

&lt;p&gt;None of this is woo. James Nestor's research, documented across multiple peer-reviewed collaborations, shows that breathing efficiency is one of the single highest-leverage interventions for cognitive performance [7]. And a study published in &lt;em&gt;Physiology&lt;/em&gt; documented that intermittent hypoxia upregulates EPO, BDNF, and VEGF, three growth factors directly linked to neural function and cellular resilience [10].&lt;/p&gt;

&lt;h2&gt;
  
  
  How to start
&lt;/h2&gt;

&lt;p&gt;None of this requires expensive equipment or complicated protocols.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Intermittent hypoxia, the 5-minute version:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Breathe normally through your nose for 2 minutes. Just calm, quiet nasal breathing.&lt;/li&gt;
&lt;li&gt;Take a normal breath in, exhale gently, and hold your breath.&lt;/li&gt;
&lt;li&gt;Walk slowly while holding. When you feel a moderate urge to breathe, stop.&lt;/li&gt;
&lt;li&gt;Resume nasal breathing and recover for 1-2 minutes.&lt;/li&gt;
&lt;li&gt;Repeat for 6-8 rounds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a simplified version of Patrick McKeown's Oxygen Advantage method [3]. It works by increasing your CO2 tolerance and training your body to do more with less oxygen, exactly what altitude training does, but at sea level.&lt;/p&gt;

&lt;p&gt;For more structured sessions with timed rounds and progressive difficulty, I built an &lt;a href="https://kitmul.com/en/sport-performance/hypoxia-breathing-timer" rel="noopener noreferrer"&gt;intermittent hypoxia breathing timer&lt;/a&gt; as one of the tools on Kitmul.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pomodoro, the non-negotiable version:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick one task. Not "a few things." One.&lt;/li&gt;
&lt;li&gt;Set a timer for 25 minutes.&lt;/li&gt;
&lt;li&gt;Work at full focus. No Slack, no email, no phone.&lt;/li&gt;
&lt;li&gt;When the timer rings, stop. Stand up. Walk away from your screen for 5 minutes.&lt;/li&gt;
&lt;li&gt;Repeat. After 4 cycles, take a 15-20 minute break.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The discipline is in the rest, not the work. Anyone can focus for 25 minutes. The hard part is actually stopping and actually resting. If you want a good timer, there's a &lt;a href="https://kitmul.com/en/agile-project-management/pomodoro-agile" rel="noopener noreferrer"&gt;Pomodoro timer on Kitmul&lt;/a&gt; too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caffeine reset:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stop drinking coffee for one week. Push through the headaches (they peak at day 2-3 and disappear by day 5). Then only use caffeine on days when you genuinely need a boost. You'll be amazed at how much more effective a single cup becomes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold exposure:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At the end of your normal shower, turn the water to cold for 60-90 seconds. That's it. The first week is uncomfortable. After that, it becomes almost pleasant, and the alertness benefit is consistent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Walking meetings:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Buy a cheap walking treadmill. Use it during any meeting where you don't need to type. You'll finish meetings energized instead of drained.&lt;/p&gt;




&lt;p&gt;I started all of this because of parkour. The breathing work, the structured rest, the body awareness. It all came from athletic training, not from reading productivity blogs. But the transfer to knowledge work was immediate and dramatic.&lt;/p&gt;

&lt;p&gt;The best productivity hack I've found in over ten years of professional software development isn't a tool, a framework, or an AI agent. It's optimizing the machine that runs all the other machines: your body.&lt;/p&gt;

&lt;p&gt;Oxygen and well-timed rest. That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Rybnikova, E. &amp;amp; Nalivaeva, N. (2022). "Intermittent Hypoxic Training as an Effective Tool for Increasing the Adaptive Potential, Endurance and Working Capacity of the Brain." &lt;em&gt;Frontiers in Neuroscience&lt;/em&gt;. &lt;a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC9254677/" rel="noopener noreferrer"&gt;Read study&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Schega, L. et al. (2013). "Effects of Intermittent Hypoxia on Cognitive Performance and Quality of Life in Elderly Adults." &lt;em&gt;Gerontology&lt;/em&gt;. &lt;a href="https://pubmed.ncbi.nlm.nih.gov/23689305/" rel="noopener noreferrer"&gt;Read on PubMed&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;McKeown, P. &lt;em&gt;The Oxygen Advantage&lt;/em&gt;. Breath-hold exercises that simulate altitude training at sea level. &lt;a href="https://oxygenadvantage.com/pages/patrick-mckeown-m-a-tcd" rel="noopener noreferrer"&gt;Official site&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Huberman, A. "Breathwork Protocols for Health, Focus &amp;amp; Stress." &lt;em&gt;Huberman Lab&lt;/em&gt;. &lt;a href="https://www.hubermanlab.com/newsletter/breathwork-protocols-for-health-focus-stress" rel="noopener noreferrer"&gt;Read article&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Johnson, B. Braintree founder, $2M/year biohacking protocol including hyperbaric oxygen. &lt;a href="https://longevity.technology/news/silicon-valleys-biohacking-obsession-why-tech-executives-are-hooked/" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dorsey, J. 20+ years of meditation and breathwork practice. &lt;a href="https://longevity.technology/news/silicon-valleys-biohacking-obsession-why-tech-executives-are-hooked/" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Nestor, J. &lt;em&gt;Breath: The New Science of a Lost Art&lt;/em&gt;. NYT bestseller on breathing science. &lt;a href="https://www.mrjamesnestor.com/" rel="noopener noreferrer"&gt;Official site&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wim Hof Method. Radboud University study on voluntary influence over sympathetic nervous system. &lt;a href="https://www.wimhofmethod.com/biohacking" rel="noopener noreferrer"&gt;Read more&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"Silicon Valley's biohacking obsession: Why tech executives are hooked." &lt;em&gt;Longevity Technology&lt;/em&gt;. &lt;a href="https://longevity.technology/news/silicon-valleys-biohacking-obsession-why-tech-executives-are-hooked/" rel="noopener noreferrer"&gt;Read article&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dale, E. et al. (2014). "Unexpected Benefits of Intermittent Hypoxia: Enhanced Respiratory and Nonrespiratory Motor Function." &lt;em&gt;Physiology&lt;/em&gt;. &lt;a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC4073945/" rel="noopener noreferrer"&gt;Read study&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://kitmul.com/en/sport-performance/hypoxia-breathing-timer" rel="noopener noreferrer"&gt;Intermittent Hypoxia Breathing Timer&lt;/a&gt;. Free guided tool for structured IHT sessions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://kitmul.com/en/agile-project-management/pomodoro-agile" rel="noopener noreferrer"&gt;Pomodoro Timer&lt;/a&gt;. Free focus timer with structured work/rest cycles.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>productivity</category>
      <category>career</category>
      <category>mentalhealth</category>
      <category>beginners</category>
    </item>
    <item>
      <title>AI agents shouldn't control your apps; they should be the app</title>
      <dc:creator>Aral Roca</dc:creator>
      <pubDate>Tue, 31 Mar 2026 14:44:02 +0000</pubDate>
      <link>https://forem.com/aralroca/from-maintaining-open-source-libraries-to-building-an-ai-powered-tools-os-with-rust-and-webassembly-3eg4</link>
      <guid>https://forem.com/aralroca/from-maintaining-open-source-libraries-to-building-an-ai-powered-tools-os-with-rust-and-webassembly-3eg4</guid>
      <description>&lt;p&gt;&lt;a href="https://kitmul.com/en" rel="noopener noreferrer"&gt;Kitmul&lt;/a&gt; started as something far more modest than what it is today. I maintain two open source libraries: &lt;a href="https://github.com/aralroca/next-translate" rel="noopener noreferrer"&gt;NextTranslate&lt;/a&gt; and &lt;a href="https://github.com/teafuljs/teaful" rel="noopener noreferrer"&gt;Teaful&lt;/a&gt;. I needed a real Next.js project where I could iterate on them. Not artificial demos or example repositories; a live product where bugs surface naturally and limitations become obvious.&lt;/p&gt;

&lt;p&gt;That was the only goal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusnyhcygv1jtsoafzwlp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusnyhcygv1jtsoafzwlp.webp" alt="Kitmul homepage" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI multiplier effect
&lt;/h2&gt;

&lt;p&gt;To speed up development, I started using AI coding agents: Claude Code, Gemini, and Codex. Not just for productivity. I wanted to understand firsthand how these agents behave in a real development workflow. What they're good at, where they break, and how they change the way you think about building software.&lt;/p&gt;

&lt;p&gt;What I didn't expect was the effect on scope. When you can implement an idea in minutes instead of hours, you stop triaging ideas. You just build them. I went from "let me maintain these two libraries" to "let me build 300+ tools" in just 3 weeks.&lt;/p&gt;

&lt;p&gt;Currently I'm on Claude Code 20x. The combination of an agent that understands your codebase deeply and can execute multi-step tasks autonomously has been the biggest development velocity multiplier I've experienced.&lt;/p&gt;

&lt;h2&gt;
  
  
  From dev tools to tools for everyone
&lt;/h2&gt;

&lt;p&gt;As an open source developer, I've always built for other developers. Libraries, CLI tools, build utilities. Limited audience, limited impact.&lt;/p&gt;

&lt;p&gt;With Kitmul I flipped the question. Instead of "what tool does a dev need," I asked: "what tool do people search for on Google and end up on a website that charges them or uploads their files to a server."&lt;/p&gt;

&lt;p&gt;The answer: hundreds of tools. Remove image backgrounds, separate audio tracks, convert formats, compress PDFs, generate QR codes. Tools people use daily, and for which many sites charge €10-20/month.&lt;/p&gt;

&lt;p&gt;Today Kitmul has over 300. And they're not trivial wrappers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture: everything on the user's device
&lt;/h2&gt;

&lt;p&gt;Kitmul's fundamental technical decision is that everything runs on the client. No exceptions whenever possible.&lt;/p&gt;

&lt;p&gt;The stack is straightforward: if native JavaScript is enough, use JavaScript. If the operation is intensive (audio processing, heavy image manipulation, track separation) compile to WebAssembly. For performance-critical parts, Rust compiled to WASM.&lt;/p&gt;

&lt;p&gt;For example, here's how we merge PDFs entirely in the browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PDFDocument&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pdf-lib&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mergePDFs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;PDFDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;PDFDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copyPages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPageIndices&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Returns Uint8Array, never leaves the browser&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero network calls. The file goes from &lt;code&gt;File API → pdf-lib → download&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Rust + WASM: the prime number checker
&lt;/h2&gt;

&lt;p&gt;For heavier computation, JavaScript hits a wall. A real example from Kitmul: our &lt;a href="https://kitmul.com/en/math/prime-number-checker" rel="noopener noreferrer"&gt;Prime Number Checker&lt;/a&gt;. JavaScript can check primality for small numbers, but try testing a number with more than 1,200 digits and the browser will choke. BigInt simply can't handle it.&lt;/p&gt;

&lt;p&gt;Here's the JavaScript approach, which works fine for numbers up to ~13 digits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isPrime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Works for small numbers, but for a 1200+ digit number?&lt;/span&gt;
&lt;span class="c1"&gt;// BigInt arithmetic becomes so slow the tab freezes.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The solution: we compiled a Rust crate that uses &lt;code&gt;num-bigint&lt;/code&gt; with a Miller-Rabin primality test to WASM. The Rust side receives the number as a string (because it can be thousands of digits long) and returns whether it's prime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;num_bigint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BigUint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;num_traits&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;One&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nd"&gt;#[no_mangle]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;is_number_prime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_raw_parts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;num_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;str&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_utf8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;num_str&lt;/span&gt;&lt;span class="py"&gt;.parse&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BigUint&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;BigUint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nn"&gt;BigUint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;one&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;miller_rabin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then from JavaScript, we load the WASM module and pass the number as bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Load WASM and check a 1200+ digit number&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instantiate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wasmBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;12345...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1200+ digit number&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numStr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wasm_alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_number_prime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wasm_dealloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// result: 1 = prime, 0 = not prime&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JavaScript with BigInt can't handle numbers above ~1,200 digits. The Rust WASM module handles numbers of &lt;strong&gt;any size&lt;/strong&gt;; we've tested it with numbers over 3,000 digits. Same browser, same device, but Rust's &lt;code&gt;num-bigint&lt;/code&gt; crate uses optimized limb arithmetic that JavaScript simply can't match.&lt;/p&gt;

&lt;p&gt;The implications of this architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Near-zero infrastructure cost&lt;/strong&gt;: no servers processing files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real privacy, not promised&lt;/strong&gt;: data literally never leaves the device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability without provisioning&lt;/strong&gt;: each user brings their own compute.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The thesis: AI agents shouldn't control your apps. They should BE the app.
&lt;/h2&gt;

&lt;p&gt;This is where I think the current industry is getting it wrong.&lt;/p&gt;

&lt;p&gt;OpenAI with Operator, Anthropic with Computer Use, Google with Project Mariner: the big players are all building AI agents that &lt;strong&gt;control existing applications&lt;/strong&gt;. They take screenshots of your screen, move your mouse, click buttons, fill forms. Essentially they're building really sophisticated RPA bots.&lt;/p&gt;

&lt;p&gt;I think this approach is fundamentally flawed. Here's why:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. You're building on top of interfaces designed for humans, not machines.&lt;/strong&gt; When an AI agent navigates a website, it's fighting against dropdowns, modals, cookie banners, CAPTCHAs, and layout changes. Every website redesign can break the agent. This is fragile by design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. You still depend on third-party services.&lt;/strong&gt; The AI agent might be smart, but it's still uploading your PDF to iLovePDF, still sending your images to Canva's servers, still giving your data to someone else. The privacy problem doesn't go away just because a robot is clicking the buttons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It's slow.&lt;/strong&gt; Screenshot → analyze → click → wait for page load → screenshot again. This loop takes seconds per action. Meanwhile, a direct function call takes milliseconds.&lt;/p&gt;

&lt;p&gt;The alternative: what I'm building with Kitmul is radically different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI agent doesn't control apps. The AI agent IS the app.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of navigating to SmallPDF to merge files, the agent calls &lt;code&gt;mergePDFs()&lt;/code&gt; directly. Instead of opening Canva to remove a background, it runs a WASM model in-browser. No screenshots, no mouse movements, no waiting for page loads. Direct function calls, direct results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The "screenshot and click" approach (Operator, Computer Use):&lt;/span&gt;
&lt;span class="c1"&gt;// 1. Navigate to smallpdf.com          (~3s)&lt;/span&gt;
&lt;span class="c1"&gt;// 2. Find upload button                 (~1s)&lt;/span&gt;
&lt;span class="c1"&gt;// 3. Upload file                        (~5s)&lt;/span&gt;
&lt;span class="c1"&gt;// 4. Wait for processing                (~3s)&lt;/span&gt;
&lt;span class="c1"&gt;// 5. Click download                     (~2s)&lt;/span&gt;
&lt;span class="c1"&gt;// 6. Wait for download                  (~3s)&lt;/span&gt;
&lt;span class="c1"&gt;// Total: ~17 seconds + your files on someone else's server&lt;/span&gt;

&lt;span class="c1"&gt;// The Kitmul approach:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;mergePDFs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// ~300ms, never leaves your device&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a &lt;strong&gt;30-50x speed difference&lt;/strong&gt;, with the added benefit that your files never touch a remote server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Orchestration in practice
&lt;/h2&gt;

&lt;p&gt;Kitmul already has a built-in chat where the AI acts as an orchestrator. The user describes what they want in natural language. The AI selects tools from the catalog, executes them in sequence, and chains outputs.&lt;/p&gt;

&lt;p&gt;A concrete example: you upload an audio file. The AI separates it into tracks using a WASM source separation model. Then applies noise reduction to the vocal track. Converts the result to MP3. All chained, all in the browser, all without the file leaving your device.&lt;/p&gt;

&lt;p&gt;The orchestrator uses a tool registry that maps natural language intents to concrete functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified version of how the orchestrator works&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolRegistry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;merge_pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mergePDFs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file[]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;split_audio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;splitAudio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file[]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compress_image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;compressImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ... 300+ more tools&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// The AI decides the execution plan, then runs it:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Split this song and remove noise from vocals&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// → [{ tool: 'split_audio', input: userFile },&lt;/span&gt;
&lt;span class="c1"&gt;//    { tool: 'noise_reduction', input: '$prev.vocals' },&lt;/span&gt;
&lt;span class="c1"&gt;//    { tool: 'convert_to_mp3', input: '$prev' }]&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;toolRegistry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's not a concept. It works today.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that gets really interesting: self-building tools
&lt;/h2&gt;

&lt;p&gt;Here's where Kitmul diverges from everything else.&lt;/p&gt;

&lt;p&gt;With 300+ tools, we cover a lot of use cases. But when a user asks for something we don't have, instead of saying "sorry, we can't do that," the system should be able to &lt;strong&gt;create the tool on the fly with AI&lt;/strong&gt;; and then, critically, have a &lt;strong&gt;human-in-the-loop&lt;/strong&gt; verify that the generated tool actually works correctly before it becomes part of the permanent catalog.&lt;/p&gt;

&lt;p&gt;Think about it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User asks: "I need a tool that converts MIDI files to sheet music."&lt;/li&gt;
&lt;li&gt;The AI generates the tool: a client-side implementation using WebAssembly.&lt;/li&gt;
&lt;li&gt;A human reviewer (me, or eventually a community of contributors) verifies the tool works, handles edge cases, and meets Kitmul's quality standards.&lt;/li&gt;
&lt;li&gt;Once approved, the tool is permanently added to the catalog.&lt;/li&gt;
&lt;li&gt;The next user who asks for the same thing gets the verified, production-quality tool instantly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The system literally builds itself based on what users need.&lt;/strong&gt; Every unanswered request becomes a signal. Every verified tool makes the platform more capable. It's a flywheel where AI generates, humans verify, and the catalog grows organically.&lt;/p&gt;

&lt;p&gt;This is fundamentally different from the "generate code at runtime" approach that some AI companies are pursuing. Generated code running without verification is a liability: it might have bugs, security holes, or simply not work for edge cases. The human-in-the-loop step is not a limitation; it's a &lt;strong&gt;feature&lt;/strong&gt;. It ensures every tool in the catalog is production-quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  See it in action
&lt;/h2&gt;

&lt;p&gt;Here's a quick demo of how Kitmul works:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/_CW7LiagTi8"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The goal: 4,000 tools in a year
&lt;/h2&gt;

&lt;p&gt;I have a backlog with over 4,000 tool ideas. It's not an arbitrary number: it's the actual size of the list after analyzing what people search for and what currently requires uploading files to a server or paying a subscription.&lt;/p&gt;

&lt;p&gt;With Claude Code 20x and the self-building approach, this is not a moonshot. Many tools are built in a single session. The more complex ones (those requiring custom WASM or signal processing) take longer, but they're still orders of magnitude faster than without AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The orchestration works for simple flows, but complex workflows with branching, conditionals, and feedback loops still need work. The self-building pipeline is being designed right now.&lt;/p&gt;

&lt;p&gt;The long-term vision: a system where any task you do today with fragmented software (uploading files here, paying a subscription there, installing an app for something else) can be resolved within a single interface, executed locally, orchestrated by AI, and constantly expanding based on what users actually need.&lt;/p&gt;

&lt;p&gt;The question I want to leave you with: &lt;strong&gt;Do we really need AI agents that puppet our existing apps? Or do we need to rethink what the app itself should be?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I think the answer is the latter. And I think the browser is the right runtime to prove it.&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>ai</category>
      <category>rust</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I Built a 100% Private, On-Device AI Audio Stem Splitter (No Servers!)</title>
      <dc:creator>Aral Roca</dc:creator>
      <pubDate>Tue, 17 Mar 2026 19:02:18 +0000</pubDate>
      <link>https://forem.com/aralroca/i-built-a-100-private-on-device-ai-audio-stem-splitter-no-servers-5016</link>
      <guid>https://forem.com/aralroca/i-built-a-100-private-on-device-ai-audio-stem-splitter-no-servers-5016</guid>
      <description>&lt;p&gt;Most "AI" tools these days are just wrappers around an API. You upload your&lt;br&gt;
file, wait for a server to process it, and hope your data isn't being used to&lt;br&gt;
train the next big model.&lt;/p&gt;

&lt;p&gt;When I decided to add an &lt;strong&gt;Audio Stem Splitter&lt;/strong&gt; to&lt;br&gt;
&lt;a href="https://kitmul.com/en/music/audio-stem-splitter" rel="noopener noreferrer"&gt;Kitmul&lt;/a&gt;, I had one&lt;br&gt;
non-negotiable rule: &lt;strong&gt;Zero server uploads&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The result is a tool that can take any song and split it into &lt;strong&gt;Vocals, Drums,&lt;br&gt;
Bass, and Instruments&lt;/strong&gt; entirely within your browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fstems.png" class="article-body-image-wrapper"&gt;&lt;img alt="Stems" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fstems.png" width="800" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Traditional Audio Splitting
&lt;/h2&gt;

&lt;p&gt;If you've ever used tools like PhonicMind or LALAL.AI, you know the drill:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload your MP3.&lt;/li&gt;
&lt;li&gt;Wait in a queue.&lt;/li&gt;
&lt;li&gt;Pay for "credits" or high-quality downloads.&lt;/li&gt;
&lt;li&gt;Your file sits on someone else's server.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For musicians, producers, or just karaoke fans, this is slow and&lt;br&gt;
privacy-invasive. I wanted to see if we could bring the power of models like&lt;br&gt;
&lt;a href="https://huggingface.co/smank/htdemucs-onnx/resolve/main/htdemucs.onnx" rel="noopener noreferrer"&gt;&lt;strong&gt;Demucs&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
directly to the user's hardware using &lt;strong&gt;WebAssembly&lt;/strong&gt; and &lt;strong&gt;Web Workers&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it Works: AI in the Browser
&lt;/h2&gt;

&lt;p&gt;The magic happens thanks to a few modern web technologies:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;WebAssembly (WASM):&lt;/strong&gt; We run the heavy lifting—the actual neural network
inference—using a specialized AI model optimized for the browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Workers:&lt;/strong&gt; Splitting audio is CPU-intensive. By offloading the process
to a background thread, the UI remains snappy. You can still navigate the
site while the "AI chef" is in the kitchen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local Processing:&lt;/strong&gt; When you drag a file into the splitter, the browser
reads the raw bytes, processes them locally, and generates the stems. &lt;strong&gt;Your
audio never leaves your computer.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fkitmul-stem-splitter.png" class="article-body-image-wrapper"&gt;&lt;img alt="Stem Splitter process" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fkitmul-stem-splitter.png" width="500" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use an On-Device Splitter?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy First:&lt;/strong&gt; Your unfinished demos or private recordings stay private.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Subscriptions:&lt;/strong&gt; Since it uses &lt;em&gt;your&lt;/em&gt; CPU/GPU, there's no server cost for
me to pass on to you. It's free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High Fidelity:&lt;/strong&gt; We export the results in high-quality &lt;strong&gt;WAV&lt;/strong&gt; format, not
compressed MP3s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Limits:&lt;/strong&gt; Split as many songs as you want without worrying about "minutes
remaining."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Beyond Karaoke: Practical Use Cases
&lt;/h2&gt;

&lt;p&gt;While removing vocals for karaoke is the most obvious use, I've seen some great&lt;br&gt;
creative ways to use it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sampling for Producers:&lt;/strong&gt; Isolate a clean drum break or a bassline for your
own tracks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instrument Practice:&lt;/strong&gt; Remove the guitar track so you can be the lead
guitarist for your favorite band.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixing Reference:&lt;/strong&gt; Listen only to the vocal harmonies to study how a
professional track was layered.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it Out
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Audio Stem Splitter&lt;/strong&gt; is now live on Kitmul. It's best used on desktop&lt;br&gt;
(Chrome or Edge handle the AI models particularly well).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fkitmul-processing.png" class="article-body-image-wrapper"&gt;&lt;img alt="Kitmul processing" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fkitmul-processing.png" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://kitmul.com/en/music/audio-stem-splitter" rel="noopener noreferrer"&gt;👉 Try the Audio Stem Splitter on Kitmul&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm constantly adding more tools to Kitmul (we're at over 150 now!), but this&lt;br&gt;
one feels special because it pushes the boundaries of what the browser can do&lt;br&gt;
without relying on the cloud.&lt;/p&gt;

&lt;p&gt;If you are a developer interested in on-device AI or a musician looking for a&lt;br&gt;
private way to split tracks, let me know what you think in the comments!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>privacy</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Don't Fall Into the CDN Trap! 🪤</title>
      <dc:creator>Aral Roca</dc:creator>
      <pubDate>Thu, 19 Dec 2024 00:43:24 +0000</pubDate>
      <link>https://forem.com/aralroca/dont-fall-into-the-cdn-trap-4keg</link>
      <guid>https://forem.com/aralroca/dont-fall-into-the-cdn-trap-4keg</guid>
      <description>&lt;p&gt;This blog post’ll explore how &lt;strong&gt;Brisa's HTML Streaming&lt;/strong&gt; feature can help you avoid the CDN trap and improve your app’s performance. We’ll also compare &lt;a href="https://brisa.build" rel="noopener noreferrer"&gt;Brisa&lt;/a&gt; with other popular tools like &lt;strong&gt;HTMX&lt;/strong&gt; and &lt;strong&gt;React&lt;/strong&gt; to highlight the benefits of using Brisa for server-side rendering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why HTML Streaming is Important
&lt;/h2&gt;

&lt;p&gt;HTML Streaming is a powerful technique that allows you to send &lt;strong&gt;HTML incrementally&lt;/strong&gt; from the server to the client, improving performance and reducing latency. With &lt;a href="https://brisa.build" rel="noopener noreferrer"&gt;Brisa&lt;/a&gt;, you can stream HTML content directly to the client, not only for the initial render but also for subsequent updates and server actions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Frender.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Frender.png" alt="HTML Streaming vs CDN + fetching data" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your app needs just &lt;strong&gt;1 request&lt;/strong&gt; to fetch server data, you’re stuck in the &lt;strong&gt;CDN trap&lt;/strong&gt;. Add more requests, and you’re in a cascading nightmare. CDNs are fine for assets—nothing more. Your website is an asset only if it doesn’t rely on server data. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rendering&lt;/strong&gt; the Components on the &lt;strong&gt;server&lt;/strong&gt; with &lt;strong&gt;streaming&lt;/strong&gt; is the best way to &lt;strong&gt;avoid the CDN trap&lt;/strong&gt;. Brisa not only uses &lt;strong&gt;HTML Streaming&lt;/strong&gt; for the first render but also as a response to &lt;strong&gt;Server Actions&lt;/strong&gt; after rendering a component on the server.&lt;/p&gt;

&lt;p&gt;You may wonder, but many components are static and I don't want to render them always on the server. Well, Brisa allows you to &lt;a href="https://brisa.build/api-reference/extended-props/renderOn#renderon" rel="noopener noreferrer"&gt;&lt;strong&gt;pre-render them at build-time&lt;/strong&gt;&lt;/a&gt; with just one attribute, so that &lt;strong&gt;only dynamic components are rendered&lt;/strong&gt; on the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StaticComponent&lt;/span&gt; &lt;span class="na"&gt;renderOn&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"build"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Do dynamic imports solve the CDN trap?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No&lt;/strong&gt;. Using HTML Streaming on Server Actions have exactly the same benefits as using it on the first render.&lt;/p&gt;

&lt;p&gt;Imagine managing the &lt;strong&gt;opening&lt;/strong&gt; and &lt;strong&gt;rendering&lt;/strong&gt; of a &lt;strong&gt;dialog&lt;/strong&gt; with a &lt;strong&gt;dynamic import&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fdialog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fdialog.png" alt="Server-side Dialog vs Client-side Dialog" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Only if the dialog needs server data, we are in the same problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code example: Quiz with HTML Streaming
&lt;/h2&gt;

&lt;p&gt;As an example, we are going to make a quiz of questions in Brisa using HTML Streaming through Server Actions, and you will see how easy it is to do it, apart from its benefits.&lt;/p&gt;

&lt;p&gt;In order to make the quiz, we are going to create a component that will be rendered on the server and will be responsible for managing the questions and answers. The questions will be sent to the client through HTML Streaming, and the answers will be processed on the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;renderComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;brisa/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Define the type for questions to ensure type safety.&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// The correct answer for the question ('yes' or 'no').&lt;/span&gt;
  &lt;span class="nl"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// The text of the question to be displayed.&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Unique identifier for each question.&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// All this code is server-code. It ensures that the user cannot infer the correct&lt;/span&gt;
&lt;span class="c1"&gt;// answers by inspecting the page source or the network.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Is the Earth flat? &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Is the Earth round? &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Can giraffes lay eggs?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Can penguins fly?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Can a cow jump over the moon?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Is water wet by definition?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Is the sky blue?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Do fish sleep with their eyes open?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Can a cow fly?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// Function to open the modal with a random question.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;openModal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;randomIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;renderComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;randomIndex&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
    &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Specify the DOM element where the modal should render.&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Function to process the user’s answer and display the result.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processAnswer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ClickEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// We have the event on the server, so we can access the target and the dataset!&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLButtonElement&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Check if the user's answer matches the correct answer.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isCorrect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Render feedback based on the correctness of the answer.&lt;/span&gt;
  &lt;span class="nf"&gt;renderComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isCorrect&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"correct"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Correct!
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"incorrect"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Incorrect!
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dialog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Main homepage component.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Homepage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"h1_addition"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Welcome to &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Brisa
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"edit-note"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;✏️ SSR Modal example&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;src/pages/index.tsx&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;openModal&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Open modal&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Container for rendering dynamic content */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Modal component to display a question and answer options.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;dialog&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"dialog"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Button for "Yes" answer */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;data-id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;processAnswer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          Yes
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Button for "No" answer */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;data-id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;processAnswer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          No
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/brisa-build/brisa/tree/canary/examples/with-ssr-modal" rel="noopener noreferrer"&gt;🔗 Code example here&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of the advantages of using server-side logic for rendering and controlling a dialog is the enhanced security and simplicity it offers. With Brisa, you can offload both the rendering and logic processing to the server, ensuring that critical data remains inaccessible to the client and maintaining a clean separation of concerns.&lt;/p&gt;

&lt;p&gt;In this example, a modal dialog is used to present random quiz questions to the user. The logic for selecting the question, validating the answer, and rendering the UI is entirely managed on the server. This approach eliminates the risk of exposing sensitive data, such as the correct answer, to the client.&lt;/p&gt;

&lt;p&gt;Normally we load modals on the client with a dynamic import to avoid loading them at the start, requesting the CDN, and then if the modal needs server data, once rendered it has to make a cascade of calls to the server. With Brisa, we can load the modal directly from the server, avoiding the need to make additional calls and keeping the modal logic on the server.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/7kwT1oshUJA"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;With this approach, we get the following benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Security:&lt;/strong&gt; Sensitive data, such as correct answers, remain on the server, preventing unauthorized access or manipulation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve UX&lt;/strong&gt;: By rendering the modal directly on the server with streaming, we can avoid additional network requests and improve the user experience thanks to streaming.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplified State Management:&lt;/strong&gt;  By centralizing logic on the server, the client remains lightweight and focuses only on rendering and user interaction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced Client-Side Complexity and Size:&lt;/strong&gt; No need for complex state management libraries or additional client-side logic to handle the modal. The server manages everything and you can do an SPA without increasing the client bundle size.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  SPA without Client-Side JavaScript?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes&lt;/strong&gt;, you can! Although we support writing &lt;a href="https://brisa.build/building-your-application/components-details/web-components" rel="noopener noreferrer"&gt;Web Components&lt;/a&gt; with &lt;strong&gt;JSX&lt;/strong&gt; and &lt;strong&gt;Signals&lt;/strong&gt; with very little code (3kb), we want you to use them &lt;strong&gt;only&lt;/strong&gt; for pure &lt;strong&gt;client interactions&lt;/strong&gt;. Our &lt;strong&gt;goal&lt;/strong&gt; is to make it possible to create &lt;strong&gt;SPAs without&lt;/strong&gt; the need for &lt;strong&gt;client-side JavaScript&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine that your &lt;strong&gt;e-commerce&lt;/strong&gt; site, instead of having a &lt;strong&gt;cascade&lt;/strong&gt; of &lt;strong&gt;requests&lt;/strong&gt; and a lot of &lt;strong&gt;client-side JavaScript code&lt;/strong&gt; that harms &lt;strong&gt;performance&lt;/strong&gt; and &lt;strong&gt;user experience&lt;/strong&gt;, becomes a &lt;strong&gt;SPA&lt;/strong&gt; without client-side JavaScript, since most interactions that require server data can be &lt;strong&gt;managed directly on the server&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Rendering on the server is very &lt;strong&gt;cheap&lt;/strong&gt; and &lt;strong&gt;fast&lt;/strong&gt; (~10ms)! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fcheap.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faralroca.com%2Fimages%2Fblog-images%2Fcheap.gif" alt="Cheap and fast" width="800" height="711"&gt;&lt;/a&gt;&lt;br&gt;&lt;small&gt;Cheap and fast&lt;/small&gt;
  &lt;/p&gt;

&lt;h2&gt;
  
  
  Control stream chunks with Async Generators
&lt;/h2&gt;

&lt;p&gt;Brisa allows you to control the &lt;strong&gt;stream chunks&lt;/strong&gt; with &lt;strong&gt;Async Generators&lt;/strong&gt;. This way, you can &lt;strong&gt;stream&lt;/strong&gt; the &lt;strong&gt;HTML&lt;/strong&gt; in &lt;strong&gt;chunks&lt;/strong&gt; and &lt;strong&gt;control&lt;/strong&gt; the &lt;strong&gt;flow&lt;/strong&gt; of the &lt;strong&gt;stream&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Database&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bun:sqlite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;renderComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brisa/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;LoadMovies&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;streamMovies&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;renderComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MovieItems&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ul&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;placement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;append&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"movies"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;streamMovies&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Click here to stream movies from a Server Action
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;db.sqlite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nc"&gt;MovieItems&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;movie&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT title, year FROM movies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;yield &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; (&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;)
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we are using an &lt;a href="https://brisa.build/building-your-application/data-management/fetching#async-generators" rel="noopener noreferrer"&gt;&lt;strong&gt;Async Generator&lt;/strong&gt;&lt;/a&gt; to &lt;strong&gt;stream&lt;/strong&gt; the movies from a &lt;strong&gt;SQLite database&lt;/strong&gt;. Any &lt;code&gt;yield&lt;/code&gt; is a chunk of the stream that will be sent to the client.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/brisa-build/brisa/tree/canary/examples/with-sqlite-with-server-action" rel="noopener noreferrer"&gt;🔗 Code example Streaming HTML from SQLite&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is &lt;code&gt;renderComponent&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;Brisa’s &lt;code&gt;renderComponent&lt;/code&gt; allows developers to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Re-render components dynamically on server actions.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render specific components to specific locations&lt;/strong&gt; in the DOM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose how and where to place them&lt;/strong&gt; (e.g., replace, append, prepend, after &amp;amp; before).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhance transitions&lt;/strong&gt; with the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition" rel="noopener noreferrer"&gt;View Transition API&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stream JSX components&lt;/strong&gt; incrementally from the server.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s a quick look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;foo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Re-render the same component&lt;/span&gt;
    &lt;span class="nf"&gt;renderComponent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Re-render with new props&lt;/span&gt;
    &lt;span class="nf"&gt;renderComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bar"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Render another Component to a specific location&lt;/span&gt;
    &lt;span class="nf"&gt;renderComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AnotherComponent&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#target-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;placement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;append&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;withTransition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Enhance transitions with the View Transition API&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All this code is server-side code. In Brisa, all the events from server components are Server Actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is This a Game-Changer?
&lt;/h2&gt;

&lt;p&gt;Brisa’s approach to server actions is inspired by React’s model and HTMX concepts but is designed to be simpler and inherently safer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Brisa vs. HTMX
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;HTMX&lt;/strong&gt; allows developers to dynamically update portions of the DOM via server responses. But it lacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Component-level granularity:&lt;/strong&gt; HTMX relies on server-generated HTML partials without the concept of components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming support:&lt;/strong&gt; HTMX does not natively support streaming updates to the client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundle size&lt;/strong&gt;: HTMX is a 14KB library, while Brisa is only 2KB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With &lt;code&gt;renderComponent&lt;/code&gt;, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Component reuse:&lt;/strong&gt; Brisa components are re-rendered seamlessly, leveraging JSX and React-like composition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic placement:&lt;/strong&gt; Update or append components where in the DOM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming support:&lt;/strong&gt; Send and render data incrementally using server-side streams.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Brisa vs. React
&lt;/h3&gt;

&lt;p&gt;In &lt;strong&gt;React&lt;/strong&gt;, implementing server actions often involves using &lt;code&gt;"use server"&lt;/code&gt; and &lt;code&gt;"use client"&lt;/code&gt; directives. This dual model introduces the potential for human error and can unintentionally expose components to the client. &lt;/p&gt;

&lt;p&gt;Key differentiators include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Streaming HTML support:&lt;/strong&gt; React communicates between the server and client by sending JavaScript, which can add significant overhead. Conversely, Brisa streams HTML directly to the client, reducing complexity and improving performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signals - fine-grained reactivity:&lt;/strong&gt; Brisa’s client-side signals automatically react to server-side changes by updating Web Components, avoiding the need for a complete re-render.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundle size:&lt;/strong&gt; React-DOM v.19 weighs around 200KB, while Brisa maintains an ultra-lightweight footprint of just 2KB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selective updates:&lt;/strong&gt; Brisa allows you to update specific components on the server, reducing the need for full-page reloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Brisa’s HTML Streaming avoids the CDN trap and improves your app’s performance. You can stream HTML content directly to the client for the initial render, subsequent updates, and Server Actions. This approach improves security, and user experience, and simplifies state management, making it an ideal choice for server-side rendering.&lt;/p&gt;

&lt;p&gt;If you’re looking to build fast, secure, and scalable web applications, give &lt;a href="https://brisa.build" rel="noopener noreferrer"&gt;Brisa&lt;/a&gt; a try today!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Support Us:&lt;/strong&gt; &lt;a href="https://brisadotbuild.myspreadshop.es/" rel="noopener noreferrer"&gt;Visit our shop&lt;/a&gt; for Brisa swag! 🛍️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://brisadotbuild.myspreadshop.es/" alt="Brisa Shop" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;img width="800" height="851" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbrisa.build%2Fimages%2Fblog-images%2Fshop.webp" alt="Brisa Shop"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>htmx</category>
      <category>react</category>
      <category>brisa</category>
    </item>
    <item>
      <title>Brisa 0.2.0 Release Notes</title>
      <dc:creator>Aral Roca</dc:creator>
      <pubDate>Sun, 08 Dec 2024 22:03:06 +0000</pubDate>
      <link>https://forem.com/aralroca/brisa-020-release-notes-37jj</link>
      <guid>https://forem.com/aralroca/brisa-020-release-notes-37jj</guid>
      <description>&lt;p&gt;We’re thrilled to announce &lt;strong&gt;Brisa 0.2.0&lt;/strong&gt;, a minor milestone in Brisa's journey. This release introduces &lt;strong&gt;official support for Deno&lt;/strong&gt;, alongside multiple performance optimizations and important fixes. Let's dive into the highlights!&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/-OVJ7O4unVk"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  🦕 Deno Support
&lt;/h2&gt;

&lt;p&gt;Brisa now officially supports &lt;a href="https://brisa.build/building-your-application/building/deno-server" rel="noopener noreferrer"&gt;&lt;strong&gt;Deno&lt;/strong&gt;&lt;/a&gt; as an output target. Here’s what’s new:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Output Configuration:&lt;/strong&gt; Use &lt;code&gt;output: 'deno'&lt;/code&gt; in your &lt;code&gt;brisa.config.ts&lt;/code&gt; to build for Deno.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deno-Specific Configurations:&lt;/strong&gt; Brisa leverages &lt;code&gt;deno.json&lt;/code&gt; for custom setups, automatically placed in your build directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI Integration:&lt;/strong&gt; Running &lt;code&gt;brisa start&lt;/code&gt; detects the Deno output and uses &lt;code&gt;Deno.serve&lt;/code&gt; for serving your application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhenruf1m1gt8yy1j4qns.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhenruf1m1gt8yy1j4qns.png" alt=" " width="200" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Brisa every time is more JS runtime agnostic, and we are excited to see how you will use it with Deno!&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Key Fixes and Improvements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance Optimizations:&lt;/strong&gt; Client builds have been further optimized, enhancing build times and overall performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbrisa.build%2Fimages%2Fblog-images%2Fbuild-time-performance.webp" class="article-body-image-wrapper"&gt;&lt;img width="800" height="476" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbrisa.build%2Fimages%2Fblog-images%2Fbuild-time-performance.webp" alt="Brisa Build Time improvement"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved Compatibility:&lt;/strong&gt; 

&lt;ul&gt;
&lt;li&gt;Added a polyfill for &lt;code&gt;Promise.try&lt;/code&gt; to ensure working in Node.js and Deno.&lt;/li&gt;
&lt;li&gt;Fixed HTTP response issues with uncoded &lt;code&gt;ReadableStream&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Resolved locale-changing issues in navigation for i18n.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;CLI Improvements:&lt;/strong&gt; 

&lt;ul&gt;
&lt;li&gt;Fixed &lt;code&gt;brisa start&lt;/code&gt; to correctly load &lt;code&gt;brisa.config.ts&lt;/code&gt; for all outputs.&lt;/li&gt;
&lt;li&gt;Enhanced build consistency across output types.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  🛠️ Recap: Key Features from 0.1.x Releases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Development Tools&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PandaCSS Integration&lt;/strong&gt; (0.1.1): Seamlessly use PandaCSS within Brisa for robust styling options.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hot Reload Enhancements&lt;/strong&gt; (0.1.6, 0.1.7): Improved hot-reload for multi-save scenarios, making your development workflows faster and more reliable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;idleTimeout&lt;/code&gt; Configuration&lt;/strong&gt; (0.1.2): Fine-tune connection handling with the &lt;code&gt;idleTimeout&lt;/code&gt; property for more predictable server behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Routing &amp;amp; Middleware&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Route Rewrite Support&lt;/strong&gt; (0.1.3): Dynamically rewrite routes with middleware, enabling flexible routing scenarios.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved SPA Middleware&lt;/strong&gt; (0.1.3): Added support for transforming hard redirects into soft redirects in SPA navigation, streamlining client-side transitions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Server-Side Rendering&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SSR Support for Web Components&lt;/strong&gt; (0.1.5): Extended support for &lt;code&gt;self&lt;/code&gt; in Web Components during SSR, enabling advanced interactions with attributes, styles, and events at the server level.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;API Utilities&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;getServer&lt;/code&gt; API&lt;/strong&gt; (0.1.3): Access the Brisa server instance directly to extend its functionality or introspect runtime details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming HTML from Async Generators&lt;/strong&gt; (0.1.2): Combine SQLite queries with async generators for streaming HTML content directly to the client, unlocking performance benefits for large datasets.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Changed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;fix(example)&lt;/strong&gt;: update wc external dep to fix warning – &lt;a href="https://github.com/aralroca" rel="noopener noreferrer"&gt;@aralroca&lt;/a&gt; in &lt;a href="https://github.com/brisa-build/brisa/pull/653" rel="noopener noreferrer"&gt;#653&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;perf&lt;/strong&gt;: optimize client builds – &lt;a href="https://github.com/aralroca" rel="noopener noreferrer"&gt;@aralroca&lt;/a&gt; in &lt;a href="https://github.com/brisa-build/brisa/pull/643" rel="noopener noreferrer"&gt;#643&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fix(node)&lt;/strong&gt;: add &lt;code&gt;Promise.try&lt;/code&gt; polyfill to fix error with Node – &lt;a href="https://github.com/aralroca" rel="noopener noreferrer"&gt;@aralroca&lt;/a&gt; in &lt;a href="https://github.com/brisa-build/brisa/pull/656" rel="noopener noreferrer"&gt;#656&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;feat&lt;/strong&gt;: add &lt;code&gt;output: 'deno'&lt;/code&gt; – &lt;a href="https://github.com/aralroca" rel="noopener noreferrer"&gt;@aralroca&lt;/a&gt; in &lt;a href="https://github.com/brisa-build/brisa/pull/657" rel="noopener noreferrer"&gt;#657&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;feat&lt;/strong&gt;: use &lt;code&gt;deno.json&lt;/code&gt; and move it inside build – &lt;a href="https://github.com/aralroca" rel="noopener noreferrer"&gt;@aralroca&lt;/a&gt; in &lt;a href="https://github.com/brisa-build/brisa/pull/658" rel="noopener noreferrer"&gt;#658&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;feat(cli)&lt;/strong&gt;: run Deno on &lt;code&gt;brisa start&lt;/code&gt; when output is 'deno' – &lt;a href="https://github.com/aralroca" rel="noopener noreferrer"&gt;@aralroca&lt;/a&gt; in &lt;a href="https://github.com/brisa-build/brisa/pull/660" rel="noopener noreferrer"&gt;#660&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fix(cli)&lt;/strong&gt;: fix &lt;code&gt;brisa start&lt;/code&gt; to load correct &lt;code&gt;brisa.config&lt;/code&gt; – &lt;a href="https://github.com/aralroca" rel="noopener noreferrer"&gt;@aralroca&lt;/a&gt; in &lt;a href="https://github.com/brisa-build/brisa/pull/662" rel="noopener noreferrer"&gt;#662&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;docs(www)&lt;/strong&gt;: add Deno to the home – &lt;a href="https://github.com/aralroca" rel="noopener noreferrer"&gt;@aralroca&lt;/a&gt; in &lt;a href="https://github.com/brisa-build/brisa/pull/663" rel="noopener noreferrer"&gt;#663&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fix(build)&lt;/strong&gt;: co-relate details with the correct output – &lt;a href="https://github.com/aralroca" rel="noopener noreferrer"&gt;@aralroca&lt;/a&gt; in &lt;a href="https://github.com/brisa-build/brisa/pull/666" rel="noopener noreferrer"&gt;#666&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fix(render)&lt;/strong&gt;: fix uncoded &lt;code&gt;ReadableStream&lt;/code&gt; in HTTP responses – &lt;a href="https://github.com/aralroca" rel="noopener noreferrer"&gt;@aralroca&lt;/a&gt; in &lt;a href="https://github.com/brisa-build/brisa/pull/668" rel="noopener noreferrer"&gt;#668&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fix(i18n)&lt;/strong&gt;: fix change locale from navigate – &lt;a href="https://github.com/aralroca" rel="noopener noreferrer"&gt;@aralroca&lt;/a&gt; in &lt;a href="https://github.com/brisa-build/brisa/pull/669" rel="noopener noreferrer"&gt;#669&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;feat(deno)&lt;/strong&gt;: use &lt;code&gt;Deno.serve&lt;/code&gt; – &lt;a href="https://github.com/aralroca" rel="noopener noreferrer"&gt;@aralroca&lt;/a&gt; in &lt;a href="https://github.com/brisa-build/brisa/pull/659" rel="noopener noreferrer"&gt;#659&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href="https://github.com/brisa-build/brisa/compare/0.1.7...0.2.0" rel="noopener noreferrer"&gt;https://github.com/brisa-build/brisa/compare/0.1.7...0.2.0&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://brisadotbuild.myspreadshop.es/" rel="noopener noreferrer"&gt;Visit our shop&lt;/a&gt; to get your Brisa swag and show your support!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://brisadotbuild.myspreadshop.es/" alt="Brisa Shop" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;img width="800" height="851" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbrisa.build%2Fimages%2Fblog-images%2Fshop.webp" alt="Brisa Shop"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>react</category>
    </item>
  </channel>
</rss>
